summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorWander Hillen <wjw.hillen@gmail.com>2019-10-25 10:18:26 +0200
committerGitHub <noreply@github.com>2019-10-25 10:18:26 +0200
commitfb1f4f4e7e4a7a1fd5366afe5581ac6613047faf (patch)
tree7a8736f75b6c5c96d53d256161e86f0645d8840f
parentdda8cc182117cd5b164a524a1246e8be33d08f7e (diff)
parent6e98214f740eb04c9761ccb983ba51988ee35bc2 (diff)
downloadredis-fb1f4f4e7e4a7a1fd5366afe5581ac6613047faf.tar.gz
Merge branch 'unstable' into minor-typos
-rw-r--r--CONTRIBUTING26
-rw-r--r--MANIFESTO53
-rw-r--r--README.md24
-rw-r--r--TLS.md106
-rw-r--r--deps/Makefile8
-rw-r--r--deps/README.md38
-rw-r--r--deps/hiredis/.gitignore1
-rw-r--r--deps/hiredis/.travis.yml80
-rw-r--r--deps/hiredis/CHANGELOG.md62
-rw-r--r--deps/hiredis/CMakeLists.txt90
-rw-r--r--deps/hiredis/Makefile128
-rw-r--r--deps/hiredis/README.md3
-rw-r--r--deps/hiredis/adapters/libevent.h112
-rw-r--r--deps/hiredis/adapters/libuv.h9
-rw-r--r--deps/hiredis/appveyor.yml24
-rw-r--r--deps/hiredis/async.c233
-rw-r--r--deps/hiredis/async.h13
-rw-r--r--deps/hiredis/async_private.h72
-rw-r--r--deps/hiredis/examples/CMakeLists.txt46
-rw-r--r--deps/hiredis/examples/example-libevent-ssl.c73
-rw-r--r--deps/hiredis/examples/example-libevent.c15
-rw-r--r--deps/hiredis/examples/example-ssl.c97
-rw-r--r--deps/hiredis/examples/example.c17
-rw-r--r--deps/hiredis/fmacros.h19
-rw-r--r--deps/hiredis/hiredis.c373
-rw-r--r--deps/hiredis/hiredis.h140
-rw-r--r--deps/hiredis/hiredis.pc.in11
-rw-r--r--deps/hiredis/hiredis_ssl.h53
-rw-r--r--deps/hiredis/hiredis_ssl.pc.in12
-rw-r--r--deps/hiredis/net.c189
-rw-r--r--deps/hiredis/net.h7
-rw-r--r--deps/hiredis/read.c256
-rw-r--r--deps/hiredis/read.h13
-rw-r--r--deps/hiredis/sds.c29
-rw-r--r--deps/hiredis/sds.h31
-rw-r--r--deps/hiredis/sockcompat.c248
-rw-r--r--deps/hiredis/sockcompat.h91
-rw-r--r--deps/hiredis/ssl.c448
-rw-r--r--deps/hiredis/test.c237
-rwxr-xr-xdeps/hiredis/test.sh70
-rw-r--r--deps/hiredis/win32.h18
-rw-r--r--deps/jemalloc/.appveyor.yml42
-rw-r--r--deps/jemalloc/.gitignore46
-rw-r--r--deps/jemalloc/.travis.yml156
-rw-r--r--deps/jemalloc/COPYING4
-rw-r--r--deps/jemalloc/ChangeLog596
-rw-r--r--deps/jemalloc/INSTALL.md (renamed from deps/jemalloc/INSTALL)335
-rw-r--r--deps/jemalloc/Makefile.in329
-rw-r--r--deps/jemalloc/README14
-rw-r--r--deps/jemalloc/TUNING.md129
-rw-r--r--deps/jemalloc/VERSION2
-rw-r--r--deps/jemalloc/bin/jemalloc-config.in4
-rw-r--r--deps/jemalloc/bin/jeprof.in172
-rwxr-xr-xdeps/jemalloc/build-aux/config.guess (renamed from deps/jemalloc/config.guess)174
-rwxr-xr-xdeps/jemalloc/build-aux/config.sub (renamed from deps/jemalloc/config.sub)76
-rwxr-xr-xdeps/jemalloc/build-aux/install-sh (renamed from deps/jemalloc/install-sh)0
-rwxr-xr-xdeps/jemalloc/configure5798
-rw-r--r--deps/jemalloc/configure.ac1371
-rwxr-xr-xdeps/jemalloc/coverage.sh16
-rw-r--r--deps/jemalloc/doc/html.xsl.in1
-rw-r--r--deps/jemalloc/doc/jemalloc.31961
-rw-r--r--deps/jemalloc/doc/jemalloc.html1825
-rw-r--r--deps/jemalloc/doc/jemalloc.xml.in1899
-rw-r--r--deps/jemalloc/doc/stylesheet.xsl7
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena.h1347
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_externs.h94
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_inlines_a.h57
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_inlines_b.h354
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_stats.h237
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_structs_a.h11
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_structs_b.h229
-rw-r--r--deps/jemalloc/include/jemalloc/internal/arena_types.h43
-rw-r--r--deps/jemalloc/include/jemalloc/internal/assert.h56
-rw-r--r--deps/jemalloc/include/jemalloc/internal/atomic.h692
-rw-r--r--deps/jemalloc/include/jemalloc/internal/atomic_c11.h97
-rw-r--r--deps/jemalloc/include/jemalloc/internal/atomic_gcc_atomic.h127
-rw-r--r--deps/jemalloc/include/jemalloc/internal/atomic_gcc_sync.h191
-rw-r--r--deps/jemalloc/include/jemalloc/internal/atomic_msvc.h158
-rw-r--r--deps/jemalloc/include/jemalloc/internal/background_thread_externs.h33
-rw-r--r--deps/jemalloc/include/jemalloc/internal/background_thread_inlines.h57
-rw-r--r--deps/jemalloc/include/jemalloc/internal/background_thread_structs.h53
-rw-r--r--deps/jemalloc/include/jemalloc/internal/base.h24
-rw-r--r--deps/jemalloc/include/jemalloc/internal/base_externs.h22
-rw-r--r--deps/jemalloc/include/jemalloc/internal/base_inlines.h13
-rw-r--r--deps/jemalloc/include/jemalloc/internal/base_structs.h59
-rw-r--r--deps/jemalloc/include/jemalloc/internal/base_types.h33
-rw-r--r--deps/jemalloc/include/jemalloc/internal/bin.h106
-rw-r--r--deps/jemalloc/include/jemalloc/internal/bin_stats.h51
-rw-r--r--deps/jemalloc/include/jemalloc/internal/bit_util.h165
-rw-r--r--deps/jemalloc/include/jemalloc/internal/bitmap.h319
-rw-r--r--deps/jemalloc/include/jemalloc/internal/cache_bin.h114
-rw-r--r--deps/jemalloc/include/jemalloc/internal/chunk.h99
-rw-r--r--deps/jemalloc/include/jemalloc/internal/chunk_dss.h39
-rw-r--r--deps/jemalloc/include/jemalloc/internal/chunk_mmap.h21
-rw-r--r--deps/jemalloc/include/jemalloc/internal/ckh.h115
-rw-r--r--deps/jemalloc/include/jemalloc/internal/ctl.h176
-rw-r--r--deps/jemalloc/include/jemalloc/internal/div.h41
-rw-r--r--deps/jemalloc/include/jemalloc/internal/emitter.h435
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent.h239
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_dss.h26
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_externs.h73
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_inlines.h433
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_mmap.h10
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_structs.h219
-rw-r--r--deps/jemalloc/include/jemalloc/internal/extent_types.h17
-rw-r--r--deps/jemalloc/include/jemalloc/internal/hash.h156
-rw-r--r--deps/jemalloc/include/jemalloc/internal/hooks.h19
-rw-r--r--deps/jemalloc/include/jemalloc/internal/huge.h36
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal.h.in1134
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h37
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in204
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_externs.h53
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_includes.h94
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_a.h172
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_b.h86
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h246
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h76
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_internal_types.h185
-rw-r--r--deps/jemalloc/include/jemalloc/internal/jemalloc_preamble.h.in194
-rw-r--r--deps/jemalloc/include/jemalloc/internal/large_externs.h26
-rw-r--r--deps/jemalloc/include/jemalloc/internal/log.h115
-rw-r--r--deps/jemalloc/include/jemalloc/internal/malloc_io.h102
-rw-r--r--deps/jemalloc/include/jemalloc/internal/mb.h115
-rw-r--r--deps/jemalloc/include/jemalloc/internal/mutex.h283
-rw-r--r--deps/jemalloc/include/jemalloc/internal/mutex_pool.h94
-rw-r--r--deps/jemalloc/include/jemalloc/internal/mutex_prof.h99
-rw-r--r--deps/jemalloc/include/jemalloc/internal/nstime.h34
-rw-r--r--deps/jemalloc/include/jemalloc/internal/pages.h102
-rw-r--r--deps/jemalloc/include/jemalloc/internal/ph.h391
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/private_namespace.sh4
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/private_symbols.sh51
-rw-r--r--deps/jemalloc/include/jemalloc/internal/private_symbols.txt499
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/private_unnamespace.sh5
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prng.h197
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof.h545
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof_externs.h92
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof_inlines_a.h83
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof_inlines_b.h206
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof_structs.h201
-rw-r--r--deps/jemalloc/include/jemalloc/internal/prof_types.h56
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/public_namespace.sh2
-rw-r--r--deps/jemalloc/include/jemalloc/internal/ql.h43
-rw-r--r--deps/jemalloc/include/jemalloc/internal/qr.h35
-rw-r--r--deps/jemalloc/include/jemalloc/internal/quarantine.h60
-rw-r--r--deps/jemalloc/include/jemalloc/internal/rb.h325
-rw-r--r--deps/jemalloc/include/jemalloc/internal/rtree.h660
-rw-r--r--deps/jemalloc/include/jemalloc/internal/rtree_tsd.h50
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/size_classes.sh173
-rw-r--r--deps/jemalloc/include/jemalloc/internal/smoothstep.h232
-rwxr-xr-xdeps/jemalloc/include/jemalloc/internal/smoothstep.sh101
-rw-r--r--deps/jemalloc/include/jemalloc/internal/spin.h40
-rw-r--r--deps/jemalloc/include/jemalloc/internal/stats.h203
-rw-r--r--deps/jemalloc/include/jemalloc/internal/sz.h317
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tcache.h426
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tcache_externs.h55
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tcache_inlines.h223
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tcache_structs.h61
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tcache_types.h56
-rw-r--r--deps/jemalloc/include/jemalloc/internal/ticker.h78
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd.h905
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd_generic.h157
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd_malloc_thread_cleanup.h60
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd_tls.h59
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd_types.h10
-rw-r--r--deps/jemalloc/include/jemalloc/internal/tsd_win.h139
-rw-r--r--deps/jemalloc/include/jemalloc/internal/util.h311
-rw-r--r--deps/jemalloc/include/jemalloc/internal/valgrind.h112
-rw-r--r--deps/jemalloc/include/jemalloc/internal/witness.h346
-rwxr-xr-xdeps/jemalloc/include/jemalloc/jemalloc.sh3
-rw-r--r--deps/jemalloc/include/jemalloc/jemalloc_defs.h.in8
-rw-r--r--deps/jemalloc/include/jemalloc/jemalloc_macros.h.in106
-rwxr-xr-xdeps/jemalloc/include/jemalloc/jemalloc_mangle.sh2
-rw-r--r--deps/jemalloc/include/jemalloc/jemalloc_typedefs.h.in80
-rw-r--r--deps/jemalloc/include/msvc_compat/strings.h45
-rw-r--r--deps/jemalloc/include/msvc_compat/windows_extra.h24
-rw-r--r--deps/jemalloc/jemalloc.pc.in4
-rw-r--r--deps/jemalloc/m4/ax_cxx_compile_stdcxx.m4562
-rw-r--r--deps/jemalloc/msvc/ReadMe.txt23
-rw-r--r--deps/jemalloc/msvc/jemalloc_vc2015.sln63
-rw-r--r--deps/jemalloc/msvc/jemalloc_vc2017.sln63
-rw-r--r--deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj348
-rw-r--r--deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj.filters101
-rw-r--r--deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj327
-rw-r--r--deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj.filters26
-rw-r--r--deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj347
-rw-r--r--deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj.filters101
-rw-r--r--deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj326
-rw-r--r--deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj.filters26
-rw-r--r--deps/jemalloc/msvc/test_threads/test_threads.cpp88
-rw-r--r--deps/jemalloc/msvc/test_threads/test_threads.h3
-rw-r--r--deps/jemalloc/msvc/test_threads/test_threads_main.cpp11
-rwxr-xr-xdeps/jemalloc/run_tests.sh1
-rwxr-xr-xdeps/jemalloc/scripts/gen_run_tests.py112
-rwxr-xr-xdeps/jemalloc/scripts/gen_travis.py107
-rw-r--r--deps/jemalloc/src/arena.c4329
-rw-r--r--deps/jemalloc/src/atomic.c2
-rw-r--r--deps/jemalloc/src/background_thread.c915
-rw-r--r--deps/jemalloc/src/base.c586
-rw-r--r--deps/jemalloc/src/bin.c50
-rw-r--r--deps/jemalloc/src/bitmap.c99
-rw-r--r--deps/jemalloc/src/chunk.c761
-rw-r--r--deps/jemalloc/src/chunk_dss.c214
-rw-r--r--deps/jemalloc/src/chunk_mmap.c80
-rw-r--r--deps/jemalloc/src/ckh.c233
-rw-r--r--deps/jemalloc/src/ctl.c2594
-rw-r--r--deps/jemalloc/src/div.c55
-rw-r--r--deps/jemalloc/src/extent.c2190
-rw-r--r--deps/jemalloc/src/extent_dss.c270
-rw-r--r--deps/jemalloc/src/extent_mmap.c42
-rw-r--r--deps/jemalloc/src/hash.c5
-rw-r--r--deps/jemalloc/src/hooks.c12
-rw-r--r--deps/jemalloc/src/huge.c435
-rw-r--r--deps/jemalloc/src/jemalloc.c3472
-rw-r--r--deps/jemalloc/src/jemalloc_cpp.cpp141
-rw-r--r--deps/jemalloc/src/large.c371
-rw-r--r--deps/jemalloc/src/log.c78
-rw-r--r--deps/jemalloc/src/malloc_io.c (renamed from deps/jemalloc/src/util.c)224
-rw-r--r--deps/jemalloc/src/mb.c2
-rw-r--r--deps/jemalloc/src/mutex.c201
-rw-r--r--deps/jemalloc/src/mutex_pool.c18
-rw-r--r--deps/jemalloc/src/nstime.c170
-rw-r--r--deps/jemalloc/src/pages.c617
-rw-r--r--deps/jemalloc/src/prng.c3
-rw-r--r--deps/jemalloc/src/prof.c1610
-rw-r--r--deps/jemalloc/src/quarantine.c183
-rw-r--r--deps/jemalloc/src/rtree.c385
-rw-r--r--deps/jemalloc/src/stats.c1690
-rw-r--r--deps/jemalloc/src/sz.c107
-rw-r--r--deps/jemalloc/src/tcache.c696
-rw-r--r--deps/jemalloc/src/ticker.c3
-rw-r--r--deps/jemalloc/src/tsd.c288
-rw-r--r--deps/jemalloc/src/valgrind.c34
-rw-r--r--deps/jemalloc/src/witness.c100
-rw-r--r--deps/jemalloc/src/zone.c457
-rw-r--r--deps/jemalloc/test/include/test/SFMT-alti.h12
-rw-r--r--deps/jemalloc/test/include/test/SFMT-sse2.h12
-rw-r--r--deps/jemalloc/test/include/test/SFMT.h49
-rw-r--r--deps/jemalloc/test/include/test/btalloc.h13
-rw-r--r--deps/jemalloc/test/include/test/extent_hooks.h289
-rw-r--r--deps/jemalloc/test/include/test/jemalloc_test.h.in114
-rw-r--r--deps/jemalloc/test/include/test/math.h95
-rw-r--r--deps/jemalloc/test/include/test/mq.h44
-rw-r--r--deps/jemalloc/test/include/test/mtx.h2
-rw-r--r--deps/jemalloc/test/include/test/test.h251
-rw-r--r--deps/jemalloc/test/include/test/timer.h19
-rw-r--r--deps/jemalloc/test/integration/MALLOCX_ARENA.c25
-rw-r--r--deps/jemalloc/test/integration/aligned_alloc.c42
-rw-r--r--deps/jemalloc/test/integration/allocated.c51
-rw-r--r--deps/jemalloc/test/integration/chunk.c276
-rw-r--r--deps/jemalloc/test/integration/cpp/basic.cpp25
-rw-r--r--deps/jemalloc/test/integration/extent.c248
-rw-r--r--deps/jemalloc/test/integration/extent.sh5
-rw-r--r--deps/jemalloc/test/integration/mallocx.c154
-rw-r--r--deps/jemalloc/test/integration/mallocx.sh5
-rw-r--r--deps/jemalloc/test/integration/overflow.c25
-rw-r--r--deps/jemalloc/test/integration/posix_memalign.c42
-rw-r--r--deps/jemalloc/test/integration/rallocx.c118
-rw-r--r--deps/jemalloc/test/integration/sdallocx.c24
-rw-r--r--deps/jemalloc/test/integration/thread_arena.c49
-rw-r--r--deps/jemalloc/test/integration/thread_tcache_enabled.c82
-rw-r--r--deps/jemalloc/test/integration/xallocx.c311
-rw-r--r--deps/jemalloc/test/integration/xallocx.sh5
-rw-r--r--deps/jemalloc/test/src/SFMT.c76
-rw-r--r--deps/jemalloc/test/src/btalloc.c6
-rw-r--r--deps/jemalloc/test/src/math.c2
-rw-r--r--deps/jemalloc/test/src/mq.c4
-rw-r--r--deps/jemalloc/test/src/mtx.c40
-rw-r--r--deps/jemalloc/test/src/test.c180
-rw-r--r--deps/jemalloc/test/src/thd.c21
-rw-r--r--deps/jemalloc/test/src/timer.c65
-rw-r--r--deps/jemalloc/test/stress/microbench.c70
-rw-r--r--deps/jemalloc/test/test.sh.in31
-rw-r--r--deps/jemalloc/test/unit/SFMT.c28
-rw-r--r--deps/jemalloc/test/unit/a0.c16
-rw-r--r--deps/jemalloc/test/unit/arena_reset.c344
-rw-r--r--deps/jemalloc/test/unit/arena_reset_prof.c4
-rw-r--r--deps/jemalloc/test/unit/arena_reset_prof.sh3
-rw-r--r--deps/jemalloc/test/unit/atomic.c273
-rw-r--r--deps/jemalloc/test/unit/background_thread.c119
-rw-r--r--deps/jemalloc/test/unit/background_thread_enable.c83
-rw-r--r--deps/jemalloc/test/unit/base.c234
-rw-r--r--deps/jemalloc/test/unit/bit_util.c57
-rw-r--r--deps/jemalloc/test/unit/bitmap.c502
-rw-r--r--deps/jemalloc/test/unit/ckh.c33
-rw-r--r--deps/jemalloc/test/unit/decay.c599
-rw-r--r--deps/jemalloc/test/unit/decay.sh3
-rw-r--r--deps/jemalloc/test/unit/div.c29
-rw-r--r--deps/jemalloc/test/unit/emitter.c413
-rw-r--r--deps/jemalloc/test/unit/extent_quantize.c141
-rw-r--r--deps/jemalloc/test/unit/fork.c141
-rw-r--r--deps/jemalloc/test/unit/hash.c76
-rw-r--r--deps/jemalloc/test/unit/hooks.c38
-rw-r--r--deps/jemalloc/test/unit/junk.c209
-rw-r--r--deps/jemalloc/test/unit/junk.sh5
-rw-r--r--deps/jemalloc/test/unit/junk_alloc.c2
-rw-r--r--deps/jemalloc/test/unit/junk_alloc.sh5
-rw-r--r--deps/jemalloc/test/unit/junk_free.c2
-rw-r--r--deps/jemalloc/test/unit/junk_free.sh5
-rw-r--r--deps/jemalloc/test/unit/lg_chunk.c26
-rw-r--r--deps/jemalloc/test/unit/log.c193
-rw-r--r--deps/jemalloc/test/unit/mallctl.c644
-rw-r--r--deps/jemalloc/test/unit/malloc_io.c (renamed from deps/jemalloc/test/unit/util.c)80
-rw-r--r--deps/jemalloc/test/unit/math.c52
-rw-r--r--deps/jemalloc/test/unit/mq.c34
-rw-r--r--deps/jemalloc/test/unit/mtx.c29
-rw-r--r--deps/jemalloc/test/unit/nstime.c249
-rw-r--r--deps/jemalloc/test/unit/pack.c166
-rw-r--r--deps/jemalloc/test/unit/pack.sh4
-rw-r--r--deps/jemalloc/test/unit/pages.c29
-rw-r--r--deps/jemalloc/test/unit/ph.c318
-rw-r--r--deps/jemalloc/test/unit/prng.c237
-rw-r--r--deps/jemalloc/test/unit/prof_accum.c48
-rw-r--r--deps/jemalloc/test/unit/prof_accum.sh5
-rw-r--r--deps/jemalloc/test/unit/prof_active.c57
-rw-r--r--deps/jemalloc/test/unit/prof_active.sh5
-rw-r--r--deps/jemalloc/test/unit/prof_gdump.c41
-rw-r--r--deps/jemalloc/test/unit/prof_gdump.sh6
-rw-r--r--deps/jemalloc/test/unit/prof_idump.c27
-rw-r--r--deps/jemalloc/test/unit/prof_idump.sh8
-rw-r--r--deps/jemalloc/test/unit/prof_reset.c82
-rw-r--r--deps/jemalloc/test/unit/prof_reset.sh5
-rw-r--r--deps/jemalloc/test/unit/prof_tctx.c46
-rw-r--r--deps/jemalloc/test/unit/prof_tctx.sh5
-rw-r--r--deps/jemalloc/test/unit/prof_thread_name.c63
-rw-r--r--deps/jemalloc/test/unit/prof_thread_name.sh5
-rw-r--r--deps/jemalloc/test/unit/ql.c51
-rw-r--r--deps/jemalloc/test/unit/qr.c65
-rw-r--r--deps/jemalloc/test/unit/quarantine.c108
-rw-r--r--deps/jemalloc/test/unit/rb.c157
-rw-r--r--deps/jemalloc/test/unit/retained.c181
-rw-r--r--deps/jemalloc/test/unit/rtree.c300
-rw-r--r--deps/jemalloc/test/unit/size_classes.c196
-rw-r--r--deps/jemalloc/test/unit/slab.c32
-rw-r--r--deps/jemalloc/test/unit/smoothstep.c102
-rw-r--r--deps/jemalloc/test/unit/spin.c18
-rw-r--r--deps/jemalloc/test/unit/stats.c423
-rw-r--r--deps/jemalloc/test/unit/stats_print.c999
-rw-r--r--deps/jemalloc/test/unit/ticker.c73
-rw-r--r--deps/jemalloc/test/unit/tsd.c128
-rw-r--r--deps/jemalloc/test/unit/witness.c280
-rw-r--r--deps/jemalloc/test/unit/zero.c51
-rw-r--r--deps/jemalloc/test/unit/zero.sh5
-rw-r--r--deps/lua/src/lua_cmsgpack.c10
-rw-r--r--deps/lua/src/lua_struct.c54
-rw-r--r--redis.conf665
-rwxr-xr-xruntest2
-rwxr-xr-xruntest-moduleapi16
-rw-r--r--sentinel.conf82
-rw-r--r--src/Makefile45
-rw-r--r--src/acl.c1731
-rw-r--r--src/ae.c35
-rw-r--r--src/ae.h3
-rw-r--r--src/ae_epoll.c4
-rw-r--r--src/anet.c38
-rw-r--r--src/anet.h13
-rw-r--r--src/aof.c292
-rw-r--r--src/atomicvar.h4
-rw-r--r--src/bio.c10
-rw-r--r--src/bitops.c22
-rw-r--r--src/blocked.c413
-rw-r--r--src/childinfo.c2
-rw-r--r--src/cluster.c557
-rw-r--r--src/cluster.h31
-rw-r--r--src/config.c894
-rw-r--r--src/config.h10
-rw-r--r--src/connection.c407
-rw-r--r--src/connection.h220
-rw-r--r--src/connhelpers.h85
-rw-r--r--src/crc16_slottable.h835
-rw-r--r--src/db.c282
-rw-r--r--src/debug.c715
-rw-r--r--src/defrag.c851
-rw-r--r--src/dict.c63
-rw-r--r--src/dict.h1
-rw-r--r--src/endianconv.h8
-rw-r--r--src/evict.c116
-rw-r--r--src/expire.c3
-rw-r--r--src/geo.c44
-rw-r--r--src/geohash.c8
-rw-r--r--src/gopher.c97
-rw-r--r--src/help.h177
-rw-r--r--src/hyperloglog.c284
-rw-r--r--src/intset.c2
-rw-r--r--src/latency.c39
-rw-r--r--src/lazyfree.c15
-rw-r--r--src/listpack.c26
-rw-r--r--src/localtime.c123
-rw-r--r--src/lolwut.c188
-rw-r--r--src/lolwut.h49
-rw-r--r--src/lolwut5.c177
-rw-r--r--src/lolwut6.c200
-rw-r--r--src/lzf_d.c28
-rwxr-xr-xsrc/mkreleasehdr.sh3
-rw-r--r--src/module.c2639
-rw-r--r--src/modules/Makefile22
-rw-r--r--src/modules/gendoc.rb2
-rw-r--r--src/modules/helloblock.c25
-rw-r--r--src/modules/hellocluster.c118
-rw-r--r--src/modules/hellodict.c132
-rw-r--r--src/modules/hellohook.c93
-rw-r--r--src/modules/hellotimer.c76
-rw-r--r--src/modules/testmodule.c29
-rw-r--r--src/multi.c35
-rw-r--r--src/networking.c1737
-rw-r--r--src/notify.c10
-rw-r--r--src/object.c382
-rw-r--r--src/pubsub.c162
-rw-r--r--src/quicklist.c4
-rw-r--r--src/quicklist.h2
-rw-r--r--src/rax.c350
-rw-r--r--src/rax.h61
-rw-r--r--src/rdb.c945
-rw-r--r--src/rdb.h28
-rw-r--r--src/redis-benchmark.c1015
-rw-r--r--src/redis-check-aof.c6
-rw-r--r--src/redis-check-rdb.c46
-rw-r--r--src/redis-cli.c5396
-rwxr-xr-xsrc/redis-trib.rb1907
-rw-r--r--src/redisassert.h2
-rw-r--r--src/redismodule.h377
-rw-r--r--src/release.c14
-rw-r--r--src/replication.c996
-rw-r--r--src/rio.c260
-rw-r--r--src/rio.h58
-rw-r--r--src/scripting.c433
-rw-r--r--src/sds.c16
-rw-r--r--src/sentinel.c384
-rw-r--r--src/server.c2187
-rw-r--r--src/server.h553
-rw-r--r--src/setproctitle.c2
-rw-r--r--src/sha256.c158
-rw-r--r--src/sha256.h35
-rw-r--r--src/siphash.c27
-rw-r--r--src/slowlog.c16
-rw-r--r--src/sort.c69
-rw-r--r--src/stream.h59
-rw-r--r--src/t_hash.c35
-rw-r--r--src/t_list.c46
-rw-r--r--src/t_set.c47
-rw-r--r--src/t_stream.c1707
-rw-r--r--src/t_string.c24
-rw-r--r--src/t_zset.c371
-rw-r--r--src/tls.c808
-rw-r--r--src/tracking.c296
-rw-r--r--src/util.c154
-rw-r--r--src/util.h2
-rw-r--r--src/ziplist.c10
-rw-r--r--src/zmalloc.c118
-rw-r--r--src/zmalloc.h16
-rw-r--r--tests/cluster/run.tcl1
-rw-r--r--tests/cluster/tests/04-resharding.tcl12
-rw-r--r--tests/cluster/tests/05-slave-selection.tcl77
-rw-r--r--tests/cluster/tests/12-replica-migration-2.tcl18
-rw-r--r--tests/helpers/bg_block_op.tcl54
-rw-r--r--tests/helpers/bg_complex_data.tcl8
-rw-r--r--tests/helpers/gen_write_load.tcl8
-rw-r--r--tests/instances.tcl26
-rw-r--r--tests/integration/aof-race.tcl9
-rw-r--r--tests/integration/aof.tcl43
-rw-r--r--tests/integration/block-repl.tcl58
-rw-r--r--tests/integration/psync2-reg.tcl9
-rw-r--r--tests/integration/psync2.tcl29
-rw-r--r--tests/integration/rdb.tcl33
-rw-r--r--tests/integration/redis-cli.tcl9
-rw-r--r--tests/integration/replication-2.tcl8
-rw-r--r--tests/integration/replication-3.tcl4
-rw-r--r--tests/integration/replication-4.tcl11
-rw-r--r--tests/integration/replication-psync.tcl72
-rw-r--r--tests/integration/replication.tcl494
-rw-r--r--tests/modules/Makefile44
-rw-r--r--tests/modules/commandfilter.c149
-rw-r--r--tests/modules/fork.c84
-rw-r--r--tests/modules/hooks.c79
-rw-r--r--tests/modules/infotest.c41
-rw-r--r--tests/modules/propagate.c104
-rw-r--r--tests/modules/testrdb.c240
-rw-r--r--tests/sentinel/run.tcl1
-rw-r--r--tests/sentinel/tests/00-base.tcl4
-rw-r--r--tests/sentinel/tests/01-conf-update.tcl4
-rw-r--r--tests/sentinel/tests/02-slaves-reconf.tcl2
-rw-r--r--tests/sentinel/tests/05-manual.tcl2
-rw-r--r--tests/sentinel/tests/07-down-conditions.tcl13
-rw-r--r--tests/support/cli.tcl19
-rw-r--r--tests/support/cluster.tcl4
-rw-r--r--tests/support/redis.tcl21
-rw-r--r--tests/support/server.tcl40
-rw-r--r--tests/support/test.tcl26
-rw-r--r--tests/support/util.tcl57
-rw-r--r--tests/test_helper.tcl120
-rw-r--r--tests/unit/acl.tcl144
-rw-r--r--tests/unit/aofrw.tcl2
-rw-r--r--tests/unit/auth.tcl4
-rw-r--r--tests/unit/dump.tcl37
-rw-r--r--tests/unit/expire.tcl2
-rw-r--r--tests/unit/geo.tcl16
-rw-r--r--tests/unit/hyperloglog.tcl28
-rw-r--r--tests/unit/introspection-2.tcl57
-rw-r--r--tests/unit/introspection.tcl2
-rw-r--r--tests/unit/limits.tcl7
-rw-r--r--tests/unit/maxmemory.tcl99
-rw-r--r--tests/unit/memefficiency.tcl208
-rw-r--r--tests/unit/moduleapi/commandfilter.tcl84
-rw-r--r--tests/unit/moduleapi/fork.tcl32
-rw-r--r--tests/unit/moduleapi/hooks.tcl28
-rw-r--r--tests/unit/moduleapi/infotest.tcl63
-rw-r--r--tests/unit/moduleapi/propagate.tcl30
-rw-r--r--tests/unit/moduleapi/testrdb.tcl122
-rw-r--r--tests/unit/multi.tcl14
-rw-r--r--tests/unit/obuf-limits.tcl2
-rw-r--r--tests/unit/other.tcl8
-rw-r--r--tests/unit/pendingquerybuf.tcl35
-rw-r--r--tests/unit/protocol.tcl6
-rw-r--r--tests/unit/scan.tcl91
-rw-r--r--tests/unit/scripting.tcl32
-rw-r--r--tests/unit/slowlog.tcl12
-rw-r--r--tests/unit/tls.tcl105
-rw-r--r--tests/unit/type/list.tcl7
-rw-r--r--tests/unit/type/stream-cgroups.tcl291
-rw-r--r--tests/unit/type/stream.tcl165
-rw-r--r--tests/unit/type/zset.tcl200
-rw-r--r--tests/unit/wait.tcl5
-rw-r--r--utils/corrupt_rdb.c3
-rw-r--r--utils/create-cluster/README4
-rwxr-xr-xutils/create-cluster/create-cluster10
-rwxr-xr-xutils/gen-test-certs.sh23
-rwxr-xr-xutils/generate-command-help.rb3
-rw-r--r--utils/hashtable/README2
-rw-r--r--utils/hyperloglog/hll-err.rb2
-rwxr-xr-xutils/install_server.sh3
-rwxr-xr-xutils/redis_init_script8
-rwxr-xr-xutils/releasetools/changelog.tcl11
-rw-r--r--utils/srandmember/README.md14
-rw-r--r--utils/srandmember/showdist.rb33
-rw-r--r--utils/srandmember/showfreq.rb23
-rw-r--r--utils/tracking_collisions.c76
535 files changed, 80610 insertions, 33017 deletions
diff --git a/CONTRIBUTING b/CONTRIBUTING
index f57de3fd9..000edbeaf 100644
--- a/CONTRIBUTING
+++ b/CONTRIBUTING
@@ -8,13 +8,13 @@ each source file that you contribute.
# IMPORTANT: HOW TO USE REDIS GITHUB ISSUES
* Github issues SHOULD ONLY BE USED to report bugs, and for DETAILED feature
- requests. Everything else belongs to the Redis Google Group.
+ requests. Everything else belongs to the Redis Google Group:
+
+ https://groups.google.com/forum/m/#!forum/Redis-db
PLEASE DO NOT POST GENERAL QUESTIONS that are not about bugs or suspected
bugs in the Github issues system. We'll be very happy to help you and provide
- all the support at the Reddit sub:
-
- http://reddit.com/r/redis
+ all the support in the mailing list.
There is also an active community of Redis users at Stack Overflow:
@@ -22,7 +22,12 @@ each source file that you contribute.
# How to provide a patch for a new feature
-1. If it is a major feature or a semantical change, please post it as a new submission in r/redis on Reddit at http://reddit.com/r/redis. Try to be passionate about why the feature is needed, make users upvote your proposal to gain traction and so forth. Read feedbacks about the community. But in this first step **please don't write code yet**.
+1. If it is a major feature or a semantical change, please don't start coding
+straight away: if your feature is not a conceptual fit you'll lose a lot of
+time writing the code without any reason. Start by posting in the mailing list
+and creating an issue at Github with the description of, exactly, what you want
+to accomplish and why. Use cases are important for features to be accepted.
+Here you'll see if there is consensus about your idea.
2. If in step 1 you get an acknowledgment from the project leaders, use the
following procedure to submit a patch:
@@ -30,9 +35,16 @@ each source file that you contribute.
a. Fork Redis on github ( http://help.github.com/fork-a-repo/ )
b. Create a topic branch (git checkout -b my_branch)
c. Push to your branch (git push origin my_branch)
- d. Initiate a pull request on github ( http://help.github.com/send-pull-requests/ )
+ d. Initiate a pull request on github ( https://help.github.com/articles/creating-a-pull-request/ )
e. Done :)
-For minor fixes just open a pull request on Github.
+3. Keep in mind that we are very overloaded, so issues and PRs sometimes wait
+for a *very* long time. However this is not lack of interest, as the project
+gets more and more users, we find ourselves in a constant need to prioritize
+certain issues/PRs over others. If you think your issue/PR is very important
+try to popularize it, have other users commenting and sharing their point of
+view and so forth. This helps.
+
+4. For minor fixes just open a pull request on Github.
Thanks!
diff --git a/MANIFESTO b/MANIFESTO
index 2b719057e..372789462 100644
--- a/MANIFESTO
+++ b/MANIFESTO
@@ -34,7 +34,21 @@ Redis Manifesto
so that the complexity is obvious and more complex operations can be
performed as the sum of the basic operations.
-4 - Code is like a poem; it's not just something we write to reach some
+4 - We believe in code efficiency. Computers get faster and faster, yet we
+ believe that abusing computing capabilities is not wise: the amount of
+ operations you can do for a given amount of energy remains anyway a
+ significant parameter: it allows to do more with less computers and, at
+ the same time, having a smaller environmental impact. Similarly Redis is
+ able to "scale down" to smaller devices. It is perfectly usable in a
+ Raspberry Pi and other small ARM based computers. Faster code having
+ just the layers of abstractions that are really needed will also result,
+ often, in more predictable performances. We think likewise about memory
+ usage, one of the fundamental goals of the Redis project is to
+ incrementally build more and more memory efficient data structures, so that
+ problems that were not approachable in RAM in the past will be perfectly
+ fine to handle in the future.
+
+5 - Code is like a poem; it's not just something we write to reach some
practical result. Sometimes people that are far from the Redis philosophy
suggest using other code written by other authors (frequently in other
languages) in order to implement something Redis currently lacks. But to us
@@ -45,23 +59,48 @@ Redis Manifesto
when needed. At the same time, when writing the Redis story we're trying to
write smaller stories that will fit in to other code.
-5 - We're against complexity. We believe designing systems is a fight against
+6 - We're against complexity. We believe designing systems is a fight against
complexity. We'll accept to fight the complexity when it's worthwhile but
we'll try hard to recognize when a small feature is not worth 1000s of lines
of code. Most of the time the best way to fight complexity is by not
- creating it at all.
+ creating it at all. Complexity is also a form of lock-in: code that is
+ very hard to understand cannot be modified by users in an independent way
+ regardless of the license. One of the main Redis goals is to remain
+ understandable, enough for a single programmer to have a clear idea of how
+ it works in detail just reading the source code for a couple of weeks.
+
+7 - Threading is not a silver bullet. Instead of making Redis threaded we
+ believe on the idea of an efficient (mostly) single threaded Redis core.
+ Multiple of such cores, that may run in the same computer or may run
+ in multiple computers, are abstracted away as a single big system by
+ higher order protocols and features: Redis Cluster and the upcoming
+ Redis Proxy are our main goals. A shared nothing approach is not just
+ much simpler (see the previous point in this document), is also optimal
+ in NUMA systems. In the specific case of Redis it allows for each instance
+ to have a more limited amount of data, making the Redis persist-by-fork
+ approach more sounding. In the future we may explore parallelism only for
+ I/O, which is the low hanging fruit: minimal complexity could provide an
+ improved single process experience.
-6 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
+8 - Two levels of API. The Redis API has two levels: 1) a subset of the API fits
naturally into a distributed version of Redis and 2) a more complex API that
supports multi-key operations. Both are useful if used judiciously but
there's no way to make the more complex multi-keys API distributed in an
opaque way without violating our other principles. We don't want to provide
the illusion of something that will work magically when actually it can't in
all cases. Instead we'll provide commands to quickly migrate keys from one
- instance to another to perform multi-key operations and expose the tradeoffs
- to the user.
+ instance to another to perform multi-key operations and expose the
+ trade-offs to the user.
-7 - We optimize for joy. We believe writing code is a lot of hard work, and the
+9 - We optimize for joy. We believe writing code is a lot of hard work, and the
only way it can be worth is by enjoying it. When there is no longer joy in
writing code, the best thing to do is stop. To prevent this, we'll avoid
taking paths that will make Redis less of a joy to develop.
+
+10 - All the above points are put together in what we call opportunistic
+ programming: trying to get the most for the user with minimal increases
+ in complexity (hanging fruits). Solve 95% of the problem with 5% of the
+ code when it is acceptable. Avoid a fixed schedule but follow the flow of
+ user requests, inspiration, Redis internal readiness for certain features
+ (sometimes many past changes reach a critical point making a previously
+ complex feature very easy to obtain).
diff --git a/README.md b/README.md
index 42ab47853..3442659e6 100644
--- a/README.md
+++ b/README.md
@@ -119,7 +119,7 @@ parameter (the path of the configuration file):
It is possible to alter the Redis configuration by passing parameters directly
as options using the command line. Examples:
- % ./redis-server --port 9999 --slaveof 127.0.0.1 6379
+ % ./redis-server --port 9999 --replicaof 127.0.0.1 6379
% ./redis-server /etc/redis/6379.conf --loglevel debug
All the options in redis.conf are also supported as options using the command
@@ -166,6 +166,8 @@ for Ubuntu and Debian systems:
% cd utils
% ./install_server.sh
+_Note_: `install_server.sh` will not work on Mac OSX; it is built for Linux only.
+
The script will ask you a few questions and will setup everything you need
to run Redis properly as a background daemon that will start again on
system reboots.
@@ -216,7 +218,7 @@ Inside the root are the following important directories:
* `src`: contains the Redis implementation, written in C.
* `tests`: contains the unit tests, implemented in Tcl.
-* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `anitrez/redis`. An exception to this rule is `deps/geohash-int` which is the low level geocoding library used by Redis: it originated from a different project, but at this point it diverged so much that it is developed as a separated entity directly inside the Redis repository.
+* `deps`: contains libraries Redis uses. Everything needed to compile Redis is inside this directory; your system just needs to provide `libc`, a POSIX compatible interface and a C compiler. Notably `deps` contains a copy of `jemalloc`, which is the default allocator of Redis under Linux. Note that under `deps` there are also things which started with the Redis project, but for which the main repository is not `antirez/redis`.
There are a few more directories but they are not very important for our goals
here. We'll focus mostly on `src`, where the Redis implementation is contained,
@@ -227,7 +229,7 @@ of complexity incrementally.
Note: lately Redis was refactored quite a bit. Function names and file
names have been changed, so you may find that this documentation reflects the
`unstable` branch more closely. For instance in Redis 3.0 the `server.c`
-and `server.h` files were named to `redis.c` and `redis.h`. However the overall
+and `server.h` files were named `redis.c` and `redis.h`. However the overall
structure is the same. Keep in mind that all the new developments and pull
requests should be performed against the `unstable` branch.
@@ -245,7 +247,7 @@ A few important fields in this structure are:
* `server.db` is an array of Redis databases, where data is stored.
* `server.commands` is the command table.
* `server.clients` is a linked list of clients connected to the server.
-* `server.master` is a special client, the master, if the instance is a slave.
+* `server.master` is a special client, the master, if the instance is a replica.
There are tons of other fields. Most fields are commented directly inside
the structure definition.
@@ -323,7 +325,7 @@ Inside server.c you can find code that handles other vital things of the Redis s
networking.c
---
-This file defines all the I/O functions with clients, masters and slaves
+This file defines all the I/O functions with clients, masters and replicas
(which in Redis are just special clients):
* `createClient()` allocates and initializes a new client.
@@ -390,21 +392,21 @@ replication.c
This is one of the most complex files inside Redis, it is recommended to
approach it only after getting a bit familiar with the rest of the code base.
-In this file there is the implementation of both the master and slave role
+In this file there is the implementation of both the master and replica role
of Redis.
-One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing slave instances connected
-to our master, so that the slaves can get the writes performed by the clients:
+One of the most important functions inside this file is `replicationFeedSlaves()` that writes commands to the clients representing replica instances connected
+to our master, so that the replicas can get the writes performed by the clients:
this way their data set will remain synchronized with the one in the master.
This file also implements both the `SYNC` and `PSYNC` commands that are
used in order to perform the first synchronization between masters and
-slaves, or to continue the replication after a disconnection.
+replicas, or to continue the replication after a disconnection.
Other C files
---
-* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c` and `t_zset.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
+* `t_hash.c`, `t_list.c`, `t_set.c`, `t_string.c`, `t_zset.c` and `t_stream.c` contains the implementation of the Redis data types. They implement both an API to access a given data type, and the client commands implementations for these data types.
* `ae.c` implements the Redis event loop, it's a self contained library which is simple to read and understand.
* `sds.c` is the Redis string library, check http://github.com/antirez/sds for more information.
* `anet.c` is a library to use POSIX networking in a simpler way compared to the raw interface exposed by the kernel.
@@ -435,7 +437,7 @@ top comment inside `server.c`.
After the command operates in some way, it returns a reply to the client,
usually using `addReply()` or a similar function defined inside `networking.c`.
-There are tons of commands implementations inside th Redis source code
+There are tons of commands implementations inside the Redis source code
that can serve as examples of actual commands implementations. To write
a few toy commands can be a good exercise to familiarize with the code base.
diff --git a/TLS.md b/TLS.md
new file mode 100644
index 000000000..76fe0be2e
--- /dev/null
+++ b/TLS.md
@@ -0,0 +1,106 @@
+TLS Support -- Work In Progress
+===============================
+
+This is a brief note to capture current thoughts/ideas and track pending action
+items.
+
+Getting Started
+---------------
+
+### Building
+
+To build with TLS support you'll need OpenSSL development libraries (e.g.
+libssl-dev on Debian/Ubuntu).
+
+Run `make BUILD_TLS=yes`.
+
+### Tests
+
+To run Redis test suite with TLS, you'll need TLS support for TCL (i.e.
+`tcl-tls` package on Debian/Ubuntu).
+
+1. Run `./utils/gen-test-certs.sh` to generate a root CA and a server
+ certificate.
+
+2. Run `./runtest --tls` or `./runtest-cluster --tls` to run Redis and Redis
+ Cluster tests in TLS mode.
+
+### Running manually
+
+To manually run a Redis server with TLS mode (assuming `gen-test-certs.sh` was
+invoked so sample certificates/keys are available):
+
+ ./src/redis-server --tls-port 6379 --port 0 \
+ --tls-cert-file ./tests/tls/redis.crt \
+ --tls-key-file ./tests/tls/redis.key \
+ --tls-ca-cert-file ./tests/tls/ca.crt
+
+To connect to this Redis server with `redis-cli`:
+
+ ./src/redis-cli --tls \
+ --cert ./tests/tls/redis.crt \
+ --key ./tests/tls/redis.key \
+ --cacert ./tests/tls/ca.crt
+
+This will disable TCP and enable TLS on port 6379. It's also possible to have
+both TCP and TLS available, but you'll need to assign different ports.
+
+To make a Replica connect to the master using TLS, use `--tls-replication yes`,
+and to make Redis Cluster use TLS across nodes use `--tls-cluster yes`.
+
+Connections
+-----------
+
+All socket operations now go through a connection abstraction layer that hides
+I/O and read/write event handling from the caller.
+
+**Multi-threading I/O is not currently supported for TLS**, as a TLS connection
+needs to do its own manipulation of AE events which is not thread safe. The
+solution is probably to manage independent AE loops for I/O threads and longer
+term association of connections with threads. This may potentially improve
+overall performance as well.
+
+Sync IO for TLS is currently implemented in a hackish way, i.e. making the
+socket blocking and configuring socket-level timeout. This means the timeout
+value may not be so accurate, and there would be a lot of syscall overhead.
+However I believe that getting rid of syncio completely in favor of pure async
+work is probably a better move than trying to fix that. For replication it would
+probably not be so hard. For cluster keys migration it might be more difficult,
+but there are probably other good reasons to improve that part anyway.
+
+To-Do List
+==========
+
+Additional TLS Features
+-----------------------
+
+1. Add metrics to INFO?
+2. Add session caching support. Check if/how it's handled by clients to assess
+ how useful/important it is.
+
+redis-benchmark
+---------------
+
+The current implementation is a mix of using hiredis for parsing and basic
+networking (establishing connections), but directly manipulating sockets for
+most actions.
+
+This will need to be cleaned up for proper TLS support. The best approach is
+probably to migrate to hiredis async mode.
+
+redis-cli
+---------
+
+1. Add support for TLS in --slave and --rdb modes.
+
+Others
+------
+
+Consider the implications of allowing TLS to be configured on a separate port,
+making Redis listening on multiple ports.
+
+This impacts many things, like
+1. Startup banner port notification
+2. Proctitle
+3. How slaves announce themselves
+4. Cluster bus port calculation
diff --git a/deps/Makefile b/deps/Makefile
index e148a331c..700867f3b 100644
--- a/deps/Makefile
+++ b/deps/Makefile
@@ -41,9 +41,13 @@ distclean:
.PHONY: distclean
+ifeq ($(BUILD_TLS),yes)
+ HIREDIS_MAKE_FLAGS = USE_SSL=1
+endif
+
hiredis: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
- cd hiredis && $(MAKE) static
+ cd hiredis && $(MAKE) static $(HIREDIS_MAKE_FLAGS)
.PHONY: hiredis
@@ -77,7 +81,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
jemalloc: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
- cd jemalloc && ./configure --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
+ cd jemalloc && ./configure --with-version=5.1.0-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
.PHONY: jemalloc
diff --git a/deps/README.md b/deps/README.md
index 0ce480046..685dbb40d 100644
--- a/deps/README.md
+++ b/deps/README.md
@@ -2,7 +2,6 @@ This directory contains all Redis dependencies, except for the libc that
should be provided by the operating system.
* **Jemalloc** is our memory allocator, used as replacement for libc malloc on Linux by default. It has good performances and excellent fragmentation behavior. This component is upgraded from time to time.
-* **geohash-int** is inside the dependencies directory but is actually part of the Redis project, since it is our private fork (heavily modified) of a library initially developed for Ardb, which is in turn a fork of Redis.
* **hiredis** is the official C client library for Redis. It is used by redis-cli, redis-benchmark and Redis Sentinel. It is part of the Redis official ecosystem but is developed externally from the Redis repository, so we just upgrade it as needed.
* **linenoise** is a readline replacement. It is developed by the same authors of Redis but is managed as a separated project and updated as needed.
* **lua** is Lua 5.1 with minor changes for security and additional libraries.
@@ -13,22 +12,39 @@ How to upgrade the above dependencies
Jemalloc
---
-Jemalloc is unmodified. We only change settings via the `configure` script of Jemalloc using the `--with-lg-quantum` option, setting it to the value of 3 instead of 4. This provides us with more size classes that better suit the Redis data structures, in order to gain memory efficiency.
-
-So in order to upgrade jemalloc:
+Jemalloc is modified with changes that allow us to implement the Redis
+active defragmentation logic. However this feature of Redis is not mandatory
+and Redis is able to understand if the Jemalloc version it is compiled
+against supports such Redis-specific modifications. So in theory, if you
+are not interested in the active defragmentation, you can replace Jemalloc
+just following tose steps:
1. Remove the jemalloc directory.
2. Substitute it with the new jemalloc source tree.
-
-Geohash
----
-
-This is never upgraded since it's part of the Redis project. If there are changes to merge from Ardb there is the need to manually check differences, but at this point the source code is pretty different.
+3. Edit the Makefile localted in the same directory as the README you are
+ reading, and change the --with-version in the Jemalloc configure script
+ options with the version you are using. This is required because otherwise
+ Jemalloc configuration script is broken and will not work nested in another
+ git repository.
+
+However note that we change Jemalloc settings via the `configure` script of Jemalloc using the `--with-lg-quantum` option, setting it to the value of 3 instead of 4. This provides us with more size classes that better suit the Redis data structures, in order to gain memory efficiency.
+
+If you want to upgrade Jemalloc while also providing support for
+active defragmentation, in addition to the above steps you need to perform
+the following additional steps:
+
+5. In Jemalloc three, file `include/jemalloc/jemalloc_macros.h.in`, make sure
+ to add `#define JEMALLOC_FRAG_HINT`.
+6. Implement the function `je_get_defrag_hint()` inside `src/jemalloc.c`. You
+ can see how it is implemented in the current Jemalloc source tree shipped
+ with Redis, and rewrite it according to the new Jemalloc internals, if they
+ changed, otherwise you could just copy the old implementation if you are
+ upgrading just to a similar version of Jemalloc.
Hiredis
---
-Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is adviced to take a lot of care:
+Hiredis uses the SDS string library, that must be the same version used inside Redis itself. Hiredis is also very critical for Sentinel. Historically Redis often used forked versions of hiredis in a way or the other. In order to upgrade it is advised to take a lot of care:
1. Check with diff if hiredis API changed and what impact it could have in Redis.
2. Make sure thet the SDS library inside Hiredis and inside Redis are compatible.
@@ -61,6 +77,6 @@ and our version:
1. Makefile is modified to allow a different compiler than GCC.
2. We have the implementation source code, and directly link to the following external libraries: `lua_cjson.o`, `lua_struct.o`, `lua_cmsgpack.o` and `lua_bit.o`.
-3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode exectuion.
+3. There is a security fix in `ldo.c`, line 498: The check for `LUA_SIGNATURE[0]` is removed in order toa void direct bytecode execution.
diff --git a/deps/hiredis/.gitignore b/deps/hiredis/.gitignore
index c44b5c537..8e50b5434 100644
--- a/deps/hiredis/.gitignore
+++ b/deps/hiredis/.gitignore
@@ -5,3 +5,4 @@
/*.dylib
/*.a
/*.pc
+*.dSYM
diff --git a/deps/hiredis/.travis.yml b/deps/hiredis/.travis.yml
index ad08076d8..dd8e0e73d 100644
--- a/deps/hiredis/.travis.yml
+++ b/deps/hiredis/.travis.yml
@@ -8,6 +8,12 @@ os:
- linux
- osx
+branches:
+ only:
+ - staging
+ - trying
+ - master
+
before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi
@@ -20,20 +26,72 @@ addons:
- libc6-dev-i386
- libc6-dbg:i386
- gcc-multilib
+ - g++-multilib
- valgrind
env:
- - CFLAGS="-Werror"
- - PRE="valgrind --track-origins=yes --leak-check=full"
- - TARGET="32bit" TARGET_VARS="32bit-vars" CFLAGS="-Werror"
- - TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+ - BITS="32"
+ - BITS="64"
-matrix:
- exclude:
- - os: osx
- env: PRE="valgrind --track-origins=yes --leak-check=full"
+script:
+ - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON";
+ if [ "$TRAVIS_OS_NAME" == "osx" ]; then
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ else
+ TEST_PREFIX="valgrind --track-origins=yes --leak-check=full";
+ if [ "$BITS" == "32" ]; then
+ CFLAGS="-m32 -Werror";
+ CXXFLAGS="-m32 -Werror";
+ LDFLAGS="-m32";
+ EXTRA_CMAKE_OPTS=;
+ else
+ CFLAGS="-Werror";
+ CXXFLAGS="-Werror";
+ fi;
+ fi;
+ export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
+ - mkdir build/ && cd build/
+ - cmake .. ${EXTRA_CMAKE_OPTS}
+ - make VERBOSE=1
+ - ctest -V
- - os: osx
- env: TARGET="32bit" TARGET_VARS="32bit-vars" PRE="valgrind --track-origins=yes --leak-check=full"
+matrix:
+ include:
+ # Windows MinGW cross compile on Linux
+ - os: linux
+ dist: xenial
+ compiler: mingw
+ addons:
+ apt:
+ packages:
+ - ninja-build
+ - gcc-mingw-w64-x86-64
+ - g++-mingw-w64-x86-64
+ script:
+ - mkdir build && cd build
+ - CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_BUILD_WITH_INSTALL_RPATH=on
+ - ninja -v
-script: make $TARGET CFLAGS="$CFLAGS" && make check PRE="$PRE" && make $TARGET_VARS hiredis-example
+ # Windows MSVC 2017
+ - os: windows
+ compiler: msvc
+ env:
+ - MATRIX_EVAL="CC=cl.exe && CXX=cl.exe"
+ before_install:
+ - eval "${MATRIX_EVAL}"
+ install:
+ - choco install ninja
+ script:
+ - mkdir build && cd build
+ - cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 &&
+ cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release &&
+ ninja -v'
+ - ctest -V
diff --git a/deps/hiredis/CHANGELOG.md b/deps/hiredis/CHANGELOG.md
index f92bcb3c9..d1d37e515 100644
--- a/deps/hiredis/CHANGELOG.md
+++ b/deps/hiredis/CHANGELOG.md
@@ -1,12 +1,18 @@
### 1.0.0 (unreleased)
-**Fixes**:
+**BREAKING CHANGES**:
-* Catch a buffer overflow when formatting the error message
-* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
-* Fix warnings, when compiled with -Wshadow
-* Make hiredis compile in Cygwin on Windows, now CI-tested
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+ protocol errors. This is consistent with the RESP specification. On 32-bit
+ platforms, the upper bound is lowered to `SIZE_MAX`.
+
+* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
+ User code should compare this to `size_t` values as well. If it was used to
+ compare to other values, casting might be necessary or can be removed, if
+ casting was applied before.
+
+### 0.x.x (unreleased)
**BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string
@@ -14,6 +20,50 @@
User code should compare this to `size_t` values as well.
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
+* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
+
+### 0.14.0 (2018-09-25)
+
+* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
+* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
+* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
+* Makefile - OSX compilation fixes (Ryan Schmidt [881fcb, 0e9af8])
+* Remove redundant NULL checks (Justin Brewer [54acc8, 58e6b8])
+* Fix bulk and multi-bulk length truncation (Justin Brewer [109197])
+* Fix SIGSEGV in OpenBSD by checking for NULL before calling freeaddrinfo (Justin Brewer [546d94])
+* Several POSIX compatibility fixes (Justin Brewer [bbeab8, 49bbaa, d1c1b6])
+* Makefile - Compatibility fixes (Dimitri Vorobiev [3238cf, 12a9d1])
+* Makefile - Fix make install on FreeBSD (Zach Shipko [a2ef2b])
+* Makefile - don't assume $(INSTALL) is cp (Igor Gnatenko [725a96])
+* Separate side-effect causing function from assert and small cleanup (amallia [b46413, 3c3234])
+* Don't send negative values to `__redisAsyncCommand` (Frederik Deweerdt [706129])
+* Fix leak if setsockopt fails (Frederik Deweerdt [e21c9c])
+* Fix libevent leak (zfz [515228])
+* Clean up GCC warning (Ichito Nagata [2ec774])
+* Keep track of errno in `__redisSetErrorFromErrno()` as snprintf may use it (Jin Qing [25cd88])
+* Solaris compilation fix (Donald Whyte [41b07d])
+* Reorder linker arguments when building examples (Tustfarm-heart [06eedd])
+* Keep track of subscriptions in case of rapid subscribe/unsubscribe (Hyungjin Kim [073dc8, be76c5, d46999])
+* libuv use after free fix (Paul Scott [cbb956])
+* Properly close socket fd on reconnect attempt (WSL [64d1ec])
+* Skip valgrind in OSX tests (Jan-Erik Rediger [9deb78])
+* Various updates for Travis testing OSX (Ted Nyman [fa3774, 16a459, bc0ea5])
+* Update libevent (Chris Xin [386802])
+* Change sds.h for building in C++ projects (Ali Volkan ATLI [f5b32e])
+* Use proper format specifier in redisFormatSdsCommandArgv (Paulino Huerta, Jan-Erik Rediger [360a06, 8655a6])
+* Better handling of NULL reply in example code (Jan-Erik Rediger [1b8ed3])
+* Prevent overflow when formatting an error (Jan-Erik Rediger [0335cb])
+* Compatibility fix for strerror_r (Tom Lee [bb1747])
+* Properly detect integer parse/overflow errors (Justin Brewer [93421f])
+* Adds CI for Windows and cygwin fixes (owent, [6c53d6, 6c3e40])
+* Catch a buffer overflow when formatting the error message
+* Import latest upstream sds. This breaks applications that are linked against the old hiredis v0.13
+* Fix warnings, when compiled with -Wshadow
+* Make hiredis compile in Cygwin on Windows, now CI-tested
+* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
+ protocol errors. This is consistent with the RESP specification. On 32-bit
+ platforms, the upper bound is lowered to `SIZE_MAX`.
+
* Remove backwards compatibility macro's
This removes the following old function aliases, use the new name now:
@@ -94,7 +144,7 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
* Add IPv6 support
-* Remove possiblity of multiple close on same fd
+* Remove possibility of multiple close on same fd
* Add ability to bind source address on connect
diff --git a/deps/hiredis/CMakeLists.txt b/deps/hiredis/CMakeLists.txt
new file mode 100644
index 000000000..9e78894f3
--- /dev/null
+++ b/deps/hiredis/CMakeLists.txt
@@ -0,0 +1,90 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 3.4.0)
+INCLUDE(GNUInstallDirs)
+PROJECT(hiredis)
+
+OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
+
+MACRO(getVersionBit name)
+ SET(VERSION_REGEX "^#define ${name} (.+)$")
+ FILE(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/hiredis.h"
+ VERSION_BIT REGEX ${VERSION_REGEX})
+ STRING(REGEX REPLACE ${VERSION_REGEX} "\\1" ${name} "${VERSION_BIT}")
+ENDMACRO(getVersionBit)
+
+getVersionBit(HIREDIS_MAJOR)
+getVersionBit(HIREDIS_MINOR)
+getVersionBit(HIREDIS_PATCH)
+getVersionBit(HIREDIS_SONAME)
+SET(VERSION "${HIREDIS_MAJOR}.${HIREDIS_MINOR}.${HIREDIS_PATCH}")
+MESSAGE("Detected version: ${VERSION}")
+
+PROJECT(hiredis VERSION "${VERSION}")
+
+SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
+
+ADD_LIBRARY(hiredis SHARED
+ async.c
+ dict.c
+ hiredis.c
+ net.c
+ read.c
+ sds.c
+ sockcompat.c)
+
+SET_TARGET_PROPERTIES(hiredis
+ PROPERTIES
+ VERSION "${HIREDIS_SONAME}")
+IF(WIN32 OR MINGW)
+ TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
+ENDIF()
+TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
+
+CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
+
+INSTALL(TARGETS hiredis
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+
+INSTALL(FILES hiredis.h read.h sds.h async.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+INSTALL(DIRECTORY adapters
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+
+IF(ENABLE_SSL)
+ IF (NOT OPENSSL_ROOT_DIR)
+ IF (APPLE)
+ SET(OPENSSL_ROOT_DIR "/usr/local/opt/openssl")
+ ENDIF()
+ ENDIF()
+ FIND_PACKAGE(OpenSSL REQUIRED)
+ ADD_LIBRARY(hiredis_ssl SHARED
+ ssl.c)
+ TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
+ TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
+ CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
+
+ INSTALL(TARGETS hiredis_ssl
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}")
+
+ INSTALL(FILES hiredis_ssl.h
+ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
+
+ INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+ENDIF()
+
+IF(NOT (WIN32 OR MINGW))
+ ENABLE_TESTING()
+ ADD_EXECUTABLE(hiredis-test test.c)
+ TARGET_LINK_LIBRARIES(hiredis-test hiredis)
+ ADD_TEST(NAME hiredis-test
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
+ENDIF()
+
+# Add examples
+IF(ENABLE_EXAMPLES)
+ ADD_SUBDIRECTORY(examples)
+ENDIF(ENABLE_EXAMPLES)
diff --git a/deps/hiredis/Makefile b/deps/hiredis/Makefile
index 9a4de8360..25ac15464 100644
--- a/deps/hiredis/Makefile
+++ b/deps/hiredis/Makefile
@@ -3,11 +3,17 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file
-OBJ=net.o hiredis.o sds.o async.o read.o
+OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o
+SSL_OBJ=ssl.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib
+ifeq ($(USE_SSL),1)
+EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
+endif
TESTS=hiredis-test
LIBNAME=libhiredis
+SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc
+SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
HIREDIS_MINOR=$(shell grep HIREDIS_MINOR hiredis.h | awk '{print $$3}')
@@ -36,71 +42,109 @@ endef
export REDIS_TEST_CONFIG
# Fallback to gcc when $CC is not in $PATH.
-CC:=$(shell sh -c 'type $(CC) >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
-CXX:=$(shell sh -c 'type $(CXX) >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
+CC:=$(shell sh -c 'type $${CC%% *} >/dev/null 2>/dev/null && echo $(CC) || echo gcc')
+CXX:=$(shell sh -c 'type $${CXX%% *} >/dev/null 2>/dev/null && echo $(CXX) || echo g++')
OPTIMIZATION?=-O3
-WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings
+WARNINGS=-Wall -W -Wstrict-prototypes -Wwrite-strings -Wno-missing-field-initializers
DEBUG_FLAGS?= -g -ggdb
-REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS) $(ARCH)
-REAL_LDFLAGS=$(LDFLAGS) $(ARCH)
+REAL_CFLAGS=$(OPTIMIZATION) -fPIC $(CPPFLAGS) $(CFLAGS) $(WARNINGS) $(DEBUG_FLAGS)
+REAL_LDFLAGS=$(LDFLAGS)
DYLIBSUFFIX=so
STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
-DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
+DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
-STLIB_MAKE_CMD=ar rcs $(STLIBNAME)
+SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
+STLIB_MAKE_CMD=$(AR) rcs
# Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+USE_SSL?=0
+
+# This is required for test.c only
+ifeq ($(USE_SSL),1)
+ CFLAGS+=-DHIREDIS_TEST_SSL
+endif
+
+ifeq ($(uname_S),Linux)
+ SSL_LDFLAGS=-lssl -lcrypto
+else
+ OPENSSL_PREFIX?=/usr/local/opt/openssl
+ CFLAGS+=-I$(OPENSSL_PREFIX)/include
+ SSL_LDFLAGS+=-L$(OPENSSL_PREFIX)/lib -lssl -lcrypto
+endif
+
ifeq ($(uname_S),SunOS)
REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
- INSTALL= cp -r
endif
ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
- DYLIB_MAKE_CMD=$(CC) -shared -Wl,-install_name,$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
+ DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
+ifeq ($(USE_SSL),1)
+all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
+endif
# Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h
dict.o: dict.c fmacros.h dict.h
-hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h
-net.o: net.c fmacros.h net.h hiredis.h read.h sds.h
+hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h
+net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h
read.o: read.c fmacros.h read.h sds.h
sds.o: sds.c sds.h
+sockcompat.o: sockcompat.c sockcompat.h
+ssl.o: ssl.c hiredis.h
test.o: test.c fmacros.h hiredis.h read.h sds.h
$(DYLIBNAME): $(OBJ)
- $(DYLIB_MAKE_CMD) $(OBJ)
+ $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
$(STLIBNAME): $(OBJ)
- $(STLIB_MAKE_CMD) $(OBJ)
+ $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
+
+$(SSL_DYLIBNAME): $(SSL_OBJ)
+ $(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
+
+$(SSL_STLIBNAME): $(SSL_OBJ)
+ $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
dynamic: $(DYLIBNAME)
static: $(STLIBNAME)
+ifeq ($(USE_SSL),1)
+dynamic: $(SSL_DYLIBNAME)
+static: $(SSL_STLIBNAME)
+endif
# Binaries:
hiredis-example-libevent: examples/example-libevent.c adapters/libevent.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -levent $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-libevent-ssl: examples/example-libevent-ssl.c adapters/libevent.h $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -levent $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
hiredis-example-libev: examples/example-libev.c adapters/libev.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -lev $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -lev $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-glib: examples/example-glib.c adapters/glib.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) $(shell pkg-config --cflags --libs glib-2.0) -I. $< $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(shell pkg-config --cflags --libs glib-2.0) $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-ivykis: examples/example-ivykis.c adapters/ivykis.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -livykis $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -livykis $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< -framework CoreFoundation $(STLIBNAME) $(REAL_LDFLAGS)
+
+hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR
hiredis-example-ae:
@@ -117,7 +161,7 @@ hiredis-example-libuv:
@false
else
hiredis-example-libuv: examples/example-libuv.c adapters/libuv.h $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. -I$(LIBUV_DIR)/include $< $(LIBUV_DIR)/.libs/libuv.a -lpthread -lrt $(STLIBNAME) $(REAL_LDFLAGS)
endif
ifeq ($(and $(QT_MOC),$(QT_INCLUDE_DIR),$(QT_LIBRARY_DIR)),)
@@ -134,38 +178,35 @@ hiredis-example-qt: examples/example-qt.cpp adapters/qt.h $(STLIBNAME)
endif
hiredis-example: examples/example.c $(STLIBNAME)
- $(CC) -o examples/$@ $(REAL_CFLAGS) $(REAL_LDFLAGS) -I. $< $(STLIBNAME)
+ $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES)
-hiredis-test: test.o $(STLIBNAME)
+TEST_LIBS = $(STLIBNAME)
+ifeq ($(USE_SSL),1)
+ TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread
+endif
+hiredis-test: test.o $(TEST_LIBS)
hiredis-%: %.o $(STLIBNAME)
- $(CC) $(REAL_CFLAGS) -o $@ $(REAL_LDFLAGS) $< $(STLIBNAME)
+ $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
test: hiredis-test
./hiredis-test
check: hiredis-test
- @echo "$$REDIS_TEST_CONFIG" | $(REDIS_SERVER) -
- $(PRE) ./hiredis-test -h 127.0.0.1 -p $(REDIS_PORT) -s /tmp/hiredis-test-redis.sock || \
- ( kill `cat /tmp/hiredis-test-redis.pid` && false )
- kill `cat /tmp/hiredis-test-redis.pid`
+ TEST_SSL=$(USE_SSL) ./test.sh
.c.o:
$(CC) -std=c99 -pedantic -c $(REAL_CFLAGS) $<
clean:
- rm -rf $(DYLIBNAME) $(STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
+ rm -rf $(DYLIBNAME) $(STLIBNAME) $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(TESTS) $(PKGCONFNAME) examples/hiredis-example* *.o *.gcda *.gcno *.gcov
dep:
- $(CC) -MM *.c
+ $(CC) $(CPPFLAGS) $(CFLAGS) -MM *.c
-ifeq ($(uname_S),SunOS)
- INSTALL?= cp -r
-endif
-
-INSTALL?= cp -a
+INSTALL?= cp -pPR
$(PKGCONFNAME): hiredis.h
@echo "Generating $@ for pkgconfig..."
@@ -180,9 +221,24 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
+$(SSL_PKGCONFNAME): hiredis.h
+ @echo "Generating $@ for pkgconfig..."
+ @echo prefix=$(PREFIX) > $@
+ @echo exec_prefix=\$${prefix} >> $@
+ @echo libdir=$(PREFIX)/$(LIBRARY_PATH) >> $@
+ @echo includedir=$(PREFIX)/$(INCLUDE_PATH) >> $@
+ @echo >> $@
+ @echo Name: hiredis_ssl >> $@
+ @echo Description: SSL Support for hiredis. >> $@
+ @echo Version: $(HIREDIS_MAJOR).$(HIREDIS_MINOR).$(HIREDIS_PATCH) >> $@
+ @echo Requires: hiredis >> $@
+ @echo Libs: -L\$${libdir} -lhiredis_ssl >> $@
+ @echo Libs.private: -lssl -lcrypto >> $@
+
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
- mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
- $(INSTALL) hiredis.h async.h read.h sds.h adapters $(INSTALL_INCLUDE_PATH)
+ mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
+ $(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH)
+ $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
$(INSTALL) $(STLIBNAME) $(INSTALL_LIBRARY_PATH)
diff --git a/deps/hiredis/README.md b/deps/hiredis/README.md
index 01223ea59..c0b432f07 100644
--- a/deps/hiredis/README.md
+++ b/deps/hiredis/README.md
@@ -286,6 +286,7 @@ return `REDIS_ERR`. The function to set the disconnect callback has the followin
```c
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
```
+`ac->data` may be used to pass user data to this callback, the same can be done for redisConnectCallback.
### Sending commands and their callbacks
In an asynchronous context, commands are automatically pipelined due to the nature of an event loop.
@@ -406,6 +407,6 @@ as soon as possible in order to prevent allocation of useless memory.
## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and
-Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
+Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license.
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and
Jan-Erik Rediger (janerik at fnordig dot com)
diff --git a/deps/hiredis/adapters/libevent.h b/deps/hiredis/adapters/libevent.h
index 273d8b2dd..a4952776c 100644
--- a/deps/hiredis/adapters/libevent.h
+++ b/deps/hiredis/adapters/libevent.h
@@ -34,48 +34,113 @@
#include "../hiredis.h"
#include "../async.h"
+#define REDIS_LIBEVENT_DELETED 0x01
+#define REDIS_LIBEVENT_ENTERED 0x02
+
typedef struct redisLibeventEvents {
redisAsyncContext *context;
- struct event *rev, *wev;
+ struct event *ev;
+ struct event_base *base;
+ struct timeval tv;
+ short flags;
+ short state;
} redisLibeventEvents;
-static void redisLibeventReadEvent(int fd, short event, void *arg) {
- ((void)fd); ((void)event);
- redisLibeventEvents *e = (redisLibeventEvents*)arg;
- redisAsyncHandleRead(e->context);
+static void redisLibeventDestroy(redisLibeventEvents *e) {
+ free(e);
}
-static void redisLibeventWriteEvent(int fd, short event, void *arg) {
- ((void)fd); ((void)event);
+static void redisLibeventHandler(int fd, short event, void *arg) {
+ ((void)fd);
redisLibeventEvents *e = (redisLibeventEvents*)arg;
- redisAsyncHandleWrite(e->context);
+ e->state |= REDIS_LIBEVENT_ENTERED;
+
+ #define CHECK_DELETED() if (e->state & REDIS_LIBEVENT_DELETED) {\
+ redisLibeventDestroy(e);\
+ return; \
+ }
+
+ if ((event & EV_TIMEOUT) && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleTimeout(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_READ) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleRead(e->context);
+ CHECK_DELETED();
+ }
+
+ if ((event & EV_WRITE) && e->context && (e->state & REDIS_LIBEVENT_DELETED) == 0) {
+ redisAsyncHandleWrite(e->context);
+ CHECK_DELETED();
+ }
+
+ e->state &= ~REDIS_LIBEVENT_ENTERED;
+ #undef CHECK_DELETED
+}
+
+static void redisLibeventUpdate(void *privdata, short flag, int isRemove) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ const struct timeval *tv = e->tv.tv_sec || e->tv.tv_usec ? &e->tv : NULL;
+
+ if (isRemove) {
+ if ((e->flags & flag) == 0) {
+ return;
+ } else {
+ e->flags &= ~flag;
+ }
+ } else {
+ if (e->flags & flag) {
+ return;
+ } else {
+ e->flags |= flag;
+ }
+ }
+
+ event_del(e->ev);
+ event_assign(e->ev, e->base, e->context->c.fd, e->flags | EV_PERSIST,
+ redisLibeventHandler, privdata);
+ event_add(e->ev, tv);
}
static void redisLibeventAddRead(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_add(e->rev,NULL);
+ redisLibeventUpdate(privdata, EV_READ, 0);
}
static void redisLibeventDelRead(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_del(e->rev);
+ redisLibeventUpdate(privdata, EV_READ, 1);
}
static void redisLibeventAddWrite(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_add(e->wev,NULL);
+ redisLibeventUpdate(privdata, EV_WRITE, 0);
}
static void redisLibeventDelWrite(void *privdata) {
- redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_del(e->wev);
+ redisLibeventUpdate(privdata, EV_WRITE, 1);
}
static void redisLibeventCleanup(void *privdata) {
redisLibeventEvents *e = (redisLibeventEvents*)privdata;
- event_del(e->rev);
- event_del(e->wev);
- free(e);
+ if (!e) {
+ return;
+ }
+ event_del(e->ev);
+ event_free(e->ev);
+ e->ev = NULL;
+
+ if (e->state & REDIS_LIBEVENT_ENTERED) {
+ e->state |= REDIS_LIBEVENT_DELETED;
+ } else {
+ redisLibeventDestroy(e);
+ }
+}
+
+static void redisLibeventSetTimeout(void *privdata, struct timeval tv) {
+ redisLibeventEvents *e = (redisLibeventEvents *)privdata;
+ short flags = e->flags;
+ e->flags = 0;
+ e->tv = tv;
+ redisLibeventUpdate(e, flags, 0);
}
static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
@@ -87,7 +152,7 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR;
/* Create container for context and r/w events */
- e = (redisLibeventEvents*)malloc(sizeof(*e));
+ e = (redisLibeventEvents*)calloc(1, sizeof(*e));
e->context = ac;
/* Register functions to start/stop listening for events */
@@ -96,13 +161,12 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
ac->ev.addWrite = redisLibeventAddWrite;
ac->ev.delWrite = redisLibeventDelWrite;
ac->ev.cleanup = redisLibeventCleanup;
+ ac->ev.scheduleTimer = redisLibeventSetTimeout;
ac->ev.data = e;
/* Initialize and install read/write events */
- e->rev = event_new(base, c->fd, EV_READ, redisLibeventReadEvent, e);
- e->wev = event_new(base, c->fd, EV_WRITE, redisLibeventWriteEvent, e);
- event_add(e->rev, NULL);
- event_add(e->wev, NULL);
+ e->ev = event_new(base, c->fd, EV_READ | EV_WRITE, redisLibeventHandler, e);
+ e->base = base;
return REDIS_OK;
}
#endif
diff --git a/deps/hiredis/adapters/libuv.h b/deps/hiredis/adapters/libuv.h
index ff08c25e1..39ef7cf5e 100644
--- a/deps/hiredis/adapters/libuv.h
+++ b/deps/hiredis/adapters/libuv.h
@@ -15,15 +15,12 @@ typedef struct redisLibuvEvents {
static void redisLibuvPoll(uv_poll_t* handle, int status, int events) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
+ int ev = (status ? p->events : events);
- if (status != 0) {
- return;
- }
-
- if (p->context != NULL && (events & UV_READABLE)) {
+ if (p->context != NULL && (ev & UV_READABLE)) {
redisAsyncHandleRead(p->context);
}
- if (p->context != NULL && (events & UV_WRITABLE)) {
+ if (p->context != NULL && (ev & UV_WRITABLE)) {
redisAsyncHandleWrite(p->context);
}
}
diff --git a/deps/hiredis/appveyor.yml b/deps/hiredis/appveyor.yml
index 06bbef117..5b43fdbeb 100644
--- a/deps/hiredis/appveyor.yml
+++ b/deps/hiredis/appveyor.yml
@@ -1,24 +1,14 @@
# Appveyor configuration file for CI build of hiredis on Windows (under Cygwin)
environment:
matrix:
- - CYG_ROOT: C:\cygwin64
- CYG_SETUP: setup-x86_64.exe
- CYG_MIRROR: http://cygwin.mirror.constant.com
- CYG_CACHE: C:\cygwin64\var\cache\setup
- CYG_BASH: C:\cygwin64\bin\bash
+ - CYG_BASH: C:\cygwin64\bin\bash
CC: gcc
- - CYG_ROOT: C:\cygwin
- CYG_SETUP: setup-x86.exe
- CYG_MIRROR: http://cygwin.mirror.constant.com
- CYG_CACHE: C:\cygwin\var\cache\setup
- CYG_BASH: C:\cygwin\bin\bash
+ - CYG_BASH: C:\cygwin\bin\bash
CC: gcc
- TARGET: 32bit
- TARGET_VARS: 32bit-vars
+ CFLAGS: -m32
+ CXXFLAGS: -m32
+ LDFLAGS: -m32
-# Cache Cygwin files to speed up build
-cache:
- - '%CYG_CACHE%'
clone_depth: 1
# Attempt to ensure we don't try to convert line endings to Win32 CRLF as this will cause build to fail
@@ -27,10 +17,8 @@ init:
# Install needed build dependencies
install:
- - ps: 'Start-FileDownload "http://cygwin.com/$env:CYG_SETUP" -FileName "$env:CYG_SETUP"'
- - '%CYG_SETUP% --quiet-mode --no-shortcuts --only-site --root "%CYG_ROOT%" --site "%CYG_MIRROR%" --local-package-dir "%CYG_CACHE%" --packages automake,bison,gcc-core,libtool,make,gettext-devel,gettext,intltool,pkg-config,clang,llvm > NUL 2>&1'
- '%CYG_BASH% -lc "cygcheck -dc cygwin"'
build_script:
- 'echo building...'
- - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; make LDFLAGS=$LDFLAGS CC=$CC $TARGET CFLAGS=$CFLAGS && make LDFLAGS=$LDFLAGS CC=$CC $TARGET_VARS hiredis-example"'
+ - '%CYG_BASH% -lc "cd $APPVEYOR_BUILD_FOLDER; exec 0</dev/null; mkdir build && cd build && cmake .. -G \"Unix Makefiles\" && make VERBOSE=1"'
diff --git a/deps/hiredis/async.c b/deps/hiredis/async.c
index d955203f8..4f422d566 100644
--- a/deps/hiredis/async.c
+++ b/deps/hiredis/async.c
@@ -32,7 +32,9 @@
#include "fmacros.h"
#include <stdlib.h>
#include <string.h>
+#ifndef _MSC_VER
#include <strings.h>
+#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
@@ -40,22 +42,9 @@
#include "net.h"
#include "dict.c"
#include "sds.h"
+#include "win32.h"
-#define _EL_ADD_READ(ctx) do { \
- if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_READ(ctx) do { \
- if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
- } while(0)
-#define _EL_ADD_WRITE(ctx) do { \
- if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_DEL_WRITE(ctx) do { \
- if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
- } while(0)
-#define _EL_CLEANUP(ctx) do { \
- if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
- } while(0);
+#include "async_private.h"
/* Forward declaration of function in hiredis.c */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
@@ -126,6 +115,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->ev.addWrite = NULL;
ac->ev.delWrite = NULL;
ac->ev.cleanup = NULL;
+ ac->ev.scheduleTimer = NULL;
ac->onConnect = NULL;
ac->onDisconnect = NULL;
@@ -150,56 +140,52 @@ static void __redisAsyncCopyError(redisAsyncContext *ac) {
ac->errstr = c->errstr;
}
-redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
+ redisOptions myOptions = *options;
redisContext *c;
redisAsyncContext *ac;
- c = redisConnectNonBlock(ip,port);
- if (c == NULL)
+ myOptions.options |= REDIS_OPT_NONBLOCK;
+ c = redisConnectWithOptions(&myOptions);
+ if (c == NULL) {
return NULL;
-
+ }
ac = redisAsyncInitialize(c);
if (ac == NULL) {
redisFree(c);
return NULL;
}
-
__redisAsyncCopyError(ac);
return ac;
}
+redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisAsyncConnectWithOptions(&options);
+}
+
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisConnectBindNonBlockWithReuse(ip,port,source_addr);
- redisAsyncContext *ac = redisAsyncInitialize(c);
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_REUSEADDR;
+ options.endpoint.tcp.source_addr = source_addr;
+ return redisAsyncConnectWithOptions(&options);
}
redisAsyncContext *redisAsyncConnectUnix(const char *path) {
- redisContext *c;
- redisAsyncContext *ac;
-
- c = redisConnectUnixNonBlock(path);
- if (c == NULL)
- return NULL;
-
- ac = redisAsyncInitialize(c);
- if (ac == NULL) {
- redisFree(c);
- return NULL;
- }
-
- __redisAsyncCopyError(ac);
- return ac;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisAsyncConnectWithOptions(&options);
}
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
@@ -328,7 +314,7 @@ void redisAsyncFree(redisAsyncContext *ac) {
}
/* Helper function to make the disconnect happen and clean up. */
-static void __redisAsyncDisconnect(redisAsyncContext *ac) {
+void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
/* Make sure error is accessible if there is any */
@@ -336,16 +322,23 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
if (ac->err == 0) {
/* For clean disconnects, there should be no pending callbacks. */
- assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
+ int ret = __redisShiftCallback(&ac->replies,NULL);
+ assert(ret == REDIS_ERR);
} else {
/* Disconnection is caused by an error, make sure that pending
* callbacks cannot call new commands. */
c->flags |= REDIS_DISCONNECTING;
}
+ /* cleanup event library on disconnect.
+ * this is safe to call multiple times */
+ _EL_CLEANUP(ac);
+
/* For non-clean disconnects, __redisAsyncFree() will execute pending
* callbacks with a NULL-reply. */
- __redisAsyncFree(ac);
+ if (!(c->flags & REDIS_NO_AUTO_FREE)) {
+ __redisAsyncFree(ac);
+ }
}
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
@@ -357,6 +350,9 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING;
+
+ /** unset the auto-free flag here, because disconnect undoes this */
+ c->flags &= ~REDIS_NO_AUTO_FREE;
if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
__redisAsyncDisconnect(ac);
}
@@ -364,6 +360,7 @@ void redisAsyncDisconnect(redisAsyncContext *ac) {
static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
redisContext *c = &(ac->c);
dict *callbacks;
+ redisCallback *cb;
dictEntry *de;
int pvariant;
char *stype;
@@ -387,16 +384,28 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
de = dictFind(callbacks,sname);
if (de != NULL) {
- memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
+ cb = dictGetEntryVal(de);
+
+ /* If this is an subscribe reply decrease pending counter. */
+ if (strcasecmp(stype+pvariant,"subscribe") == 0) {
+ cb->pending_subs -= 1;
+ }
+
+ memcpy(dstcb,cb,sizeof(*dstcb));
/* If this is an unsubscribe message, remove it. */
if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
- dictDelete(callbacks,sname);
+ if (cb->pending_subs == 0)
+ dictDelete(callbacks,sname);
/* If this was the last unsubscribe message, revert to
* non-subscribe mode. */
assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
- if (reply->element[2]->integer == 0)
+
+ /* Unset subscribed flag only when no pipelined pending subscribe. */
+ if (reply->element[2]->integer == 0
+ && dictSize(ac->sub.channels) == 0
+ && dictSize(ac->sub.patterns) == 0)
c->flags &= ~REDIS_SUBSCRIBED;
}
}
@@ -410,7 +419,7 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- redisCallback cb = {NULL, NULL, NULL};
+ redisCallback cb = {NULL, NULL, 0, NULL};
void *reply = NULL;
int status;
@@ -492,22 +501,34 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
* write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
+ int completed = 0;
redisContext *c = &(ac->c);
-
- if (redisCheckSocketError(c) == REDIS_ERR) {
- /* Try again later when connect(2) is still in progress. */
- if (errno == EINPROGRESS)
- return REDIS_OK;
-
- if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
+ if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
+ /* Error! */
+ redisCheckSocketError(c);
+ if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
__redisAsyncDisconnect(ac);
return REDIS_ERR;
+ } else if (completed == 1) {
+ /* connected! */
+ if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
+ c->flags |= REDIS_CONNECTED;
+ return REDIS_OK;
+ } else {
+ return REDIS_OK;
}
+}
- /* Mark context as connected. */
- c->flags |= REDIS_CONNECTED;
- if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
- return REDIS_OK;
+void redisAsyncRead(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+
+ if (redisBufferRead(c) == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ /* Always re-schedule reads */
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
}
/* This function should be called when the socket is readable.
@@ -525,18 +546,29 @@ void redisAsyncHandleRead(redisAsyncContext *ac) {
return;
}
- if (redisBufferRead(c) == REDIS_ERR) {
+ c->funcs->async_read(ac);
+}
+
+void redisAsyncWrite(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ int done = 0;
+
+ if (redisBufferWrite(c,&done) == REDIS_ERR) {
__redisAsyncDisconnect(ac);
} else {
- /* Always re-schedule reads */
+ /* Continue writing when not done, stop writing otherwise */
+ if (!done)
+ _EL_ADD_WRITE(ac);
+ else
+ _EL_DEL_WRITE(ac);
+
+ /* Always schedule reads after writes */
_EL_ADD_READ(ac);
- redisProcessCallbacks(ac);
}
}
void redisAsyncHandleWrite(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
- int done = 0;
if (!(c->flags & REDIS_CONNECTED)) {
/* Abort connect was not successful. */
@@ -547,18 +579,37 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
return;
}
- if (redisBufferWrite(c,&done) == REDIS_ERR) {
- __redisAsyncDisconnect(ac);
- } else {
- /* Continue writing when not done, stop writing otherwise */
- if (!done)
- _EL_ADD_WRITE(ac);
- else
- _EL_DEL_WRITE(ac);
+ c->funcs->async_write(ac);
+}
- /* Always schedule reads after writes */
- _EL_ADD_READ(ac);
+void __redisSetError(redisContext *c, int type, const char *str);
+
+void redisAsyncHandleTimeout(redisAsyncContext *ac) {
+ redisContext *c = &(ac->c);
+ redisCallback cb;
+
+ if ((c->flags & REDIS_CONNECTED) && ac->replies.head == NULL) {
+ /* Nothing to do - just an idle timeout */
+ return;
}
+
+ if (!c->err) {
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "Timeout");
+ }
+
+ if (!(c->flags & REDIS_CONNECTED) && ac->onConnect) {
+ ac->onConnect(ac, REDIS_ERR);
+ }
+
+ while (__redisShiftCallback(&ac->replies, &cb) == REDIS_OK) {
+ __redisRunCallback(ac, &cb, NULL);
+ }
+
+ /**
+ * TODO: Don't automatically sever the connection,
+ * rather, allow to ignore <x> responses before the queue is clear
+ */
+ __redisAsyncDisconnect(ac);
}
/* Sets a pointer to the first argument and its length starting at p. Returns
@@ -583,6 +634,9 @@ static const char *nextArgument(const char *start, const char **str, size_t *len
static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *cmd, size_t len) {
redisContext *c = &(ac->c);
redisCallback cb;
+ struct dict *cbdict;
+ dictEntry *de;
+ redisCallback *existcb;
int pvariant, hasnext;
const char *cstr, *astr;
size_t clen, alen;
@@ -596,6 +650,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Setup callback */
cb.fn = fn;
cb.privdata = privdata;
+ cb.pending_subs = 1;
/* Find out which command will be appended. */
p = nextArgument(cmd,&cstr,&clen);
@@ -612,9 +667,18 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen);
if (pvariant)
- ret = dictReplace(ac->sub.patterns,sname,&cb);
+ cbdict = ac->sub.patterns;
else
- ret = dictReplace(ac->sub.channels,sname,&cb);
+ cbdict = ac->sub.channels;
+
+ de = dictFind(cbdict,sname);
+
+ if (de != NULL) {
+ existcb = dictGetEntryVal(de);
+ cb.pending_subs = existcb->pending_subs + 1;
+ }
+
+ ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname);
}
@@ -676,6 +740,8 @@ int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *priv
int len;
int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
+ if (len < 0)
+ return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd);
return status;
@@ -685,3 +751,16 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
int status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
return status;
}
+
+void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
+ if (!ac->c.timeout) {
+ ac->c.timeout = calloc(1, sizeof(tv));
+ }
+
+ if (tv.tv_sec == ac->c.timeout->tv_sec &&
+ tv.tv_usec == ac->c.timeout->tv_usec) {
+ return;
+ }
+
+ *ac->c.timeout = tv;
+}
diff --git a/deps/hiredis/async.h b/deps/hiredis/async.h
index 59cbf469b..4f6b3b783 100644
--- a/deps/hiredis/async.h
+++ b/deps/hiredis/async.h
@@ -45,6 +45,7 @@ typedef void (redisCallbackFn)(struct redisAsyncContext*, void*, void*);
typedef struct redisCallback {
struct redisCallback *next; /* simple singly linked list */
redisCallbackFn *fn;
+ int pending_subs;
void *privdata;
} redisCallback;
@@ -56,6 +57,7 @@ typedef struct redisCallbackList {
/* Connection callback prototypes */
typedef void (redisDisconnectCallback)(const struct redisAsyncContext*, int status);
typedef void (redisConnectCallback)(const struct redisAsyncContext*, int status);
+typedef void(redisTimerCallback)(void *timer, void *privdata);
/* Context for an async connection to Redis */
typedef struct redisAsyncContext {
@@ -80,6 +82,7 @@ typedef struct redisAsyncContext {
void (*addWrite)(void *privdata);
void (*delWrite)(void *privdata);
void (*cleanup)(void *privdata);
+ void (*scheduleTimer)(void *privdata, struct timeval tv);
} ev;
/* Called when either the connection is terminated due to an error or per
@@ -92,6 +95,10 @@ typedef struct redisAsyncContext {
/* Regular command callbacks */
redisCallbackList replies;
+ /* Address used for connect() */
+ struct sockaddr *saddr;
+ size_t addrlen;
+
/* Subscription callbacks */
struct {
redisCallbackList invalid;
@@ -101,6 +108,7 @@ typedef struct redisAsyncContext {
} redisAsyncContext;
/* Functions that proxy to hiredis */
+redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options);
redisAsyncContext *redisAsyncConnect(const char *ip, int port);
redisAsyncContext *redisAsyncConnectBind(const char *ip, int port, const char *source_addr);
redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
@@ -108,12 +116,17 @@ redisAsyncContext *redisAsyncConnectBindWithReuse(const char *ip, int port,
redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
+
+void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac);
/* Handle read/write events */
void redisAsyncHandleRead(redisAsyncContext *ac);
void redisAsyncHandleWrite(redisAsyncContext *ac);
+void redisAsyncHandleTimeout(redisAsyncContext *ac);
+void redisAsyncRead(redisAsyncContext *ac);
+void redisAsyncWrite(redisAsyncContext *ac);
/* Command functions for an async context. Write the command to the
* output buffer and register the provided callback. */
diff --git a/deps/hiredis/async_private.h b/deps/hiredis/async_private.h
new file mode 100644
index 000000000..d0133ae18
--- /dev/null
+++ b/deps/hiredis/async_private.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_ASYNC_PRIVATE_H
+#define __HIREDIS_ASYNC_PRIVATE_H
+
+#define _EL_ADD_READ(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_READ(ctx) do { \
+ if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
+ } while(0)
+#define _EL_ADD_WRITE(ctx) \
+ do { \
+ refreshTimeout(ctx); \
+ if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
+ } while (0)
+#define _EL_DEL_WRITE(ctx) do { \
+ if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
+ } while(0)
+#define _EL_CLEANUP(ctx) do { \
+ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
+ ctx->ev.cleanup = NULL; \
+ } while(0);
+
+static inline void refreshTimeout(redisAsyncContext *ctx) {
+ if (ctx->c.timeout && ctx->ev.scheduleTimer &&
+ (ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) {
+ ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
+ // } else {
+ // printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout);
+ // if (ctx->c.timeout){
+ // printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec,
+ // ctx->c.timeout->tv_usec);
+ // }
+ }
+}
+
+void __redisAsyncDisconnect(redisAsyncContext *ac);
+void redisProcessCallbacks(redisAsyncContext *ac);
+
+#endif /* __HIREDIS_ASYNC_PRIVATE_H */
diff --git a/deps/hiredis/examples/CMakeLists.txt b/deps/hiredis/examples/CMakeLists.txt
new file mode 100644
index 000000000..dd3a313ac
--- /dev/null
+++ b/deps/hiredis/examples/CMakeLists.txt
@@ -0,0 +1,46 @@
+INCLUDE(FindPkgConfig)
+# Check for GLib
+
+PKG_CHECK_MODULES(GLIB2 glib-2.0)
+if (GLIB2_FOUND)
+ INCLUDE_DIRECTORIES(${GLIB2_INCLUDE_DIRS})
+ LINK_DIRECTORIES(${GLIB2_LIBRARY_DIRS})
+ ADD_EXECUTABLE(example-glib example-glib.c)
+ TARGET_LINK_LIBRARIES(example-glib hiredis ${GLIB2_LIBRARIES})
+ENDIF(GLIB2_FOUND)
+
+FIND_PATH(LIBEV ev.h
+ HINTS /usr/local /usr/opt/local
+ ENV LIBEV_INCLUDE_DIR)
+
+if (LIBEV)
+ # Just compile and link with libev
+ ADD_EXECUTABLE(example-libev example-libev.c)
+ TARGET_LINK_LIBRARIES(example-libev hiredis ev)
+ENDIF()
+
+FIND_PATH(LIBEVENT event.h)
+if (LIBEVENT)
+ ADD_EXECUTABLE(example-libevent example-libevent)
+ TARGET_LINK_LIBRARIES(example-libevent hiredis event)
+ENDIF()
+
+FIND_PATH(LIBUV uv.h)
+IF (LIBUV)
+ ADD_EXECUTABLE(example-libuv example-libuv.c)
+ TARGET_LINK_LIBRARIES(example-libuv hiredis uv)
+ENDIF()
+
+IF (APPLE)
+ FIND_LIBRARY(CF CoreFoundation)
+ ADD_EXECUTABLE(example-macosx example-macosx.c)
+ TARGET_LINK_LIBRARIES(example-macosx hiredis ${CF})
+ENDIF()
+
+IF (ENABLE_SSL)
+ ADD_EXECUTABLE(example-ssl example-ssl.c)
+ TARGET_LINK_LIBRARIES(example-ssl hiredis hiredis_ssl)
+ENDIF()
+
+ADD_EXECUTABLE(example example.c)
+TARGET_LINK_LIBRARIES(example hiredis)
diff --git a/deps/hiredis/examples/example-libevent-ssl.c b/deps/hiredis/examples/example-libevent-ssl.c
new file mode 100644
index 000000000..1021113b9
--- /dev/null
+++ b/deps/hiredis/examples/example-libevent-ssl.c
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+#include <async.h>
+#include <adapters/libevent.h>
+
+void getCallback(redisAsyncContext *c, void *r, void *privdata) {
+ redisReply *reply = r;
+ if (reply == NULL) return;
+ printf("argv[%s]: %s\n", (char*)privdata, reply->str);
+
+ /* Disconnect after receiving the reply to GET */
+ redisAsyncDisconnect(c);
+}
+
+void connectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Connected...\n");
+}
+
+void disconnectCallback(const redisAsyncContext *c, int status) {
+ if (status != REDIS_OK) {
+ printf("Error: %s\n", c->errstr);
+ return;
+ }
+ printf("Disconnected...\n");
+}
+
+int main (int argc, char **argv) {
+ signal(SIGPIPE, SIG_IGN);
+ struct event_base *base = event_base_new();
+ if (argc < 5) {
+ fprintf(stderr,
+ "Usage: %s <key> <host> <port> <cert> <certKey> [ca]\n", argv[0]);
+ exit(1);
+ }
+
+ const char *value = argv[1];
+ size_t nvalue = strlen(value);
+
+ const char *hostname = argv[2];
+ int port = atoi(argv[3]);
+
+ const char *cert = argv[4];
+ const char *certKey = argv[5];
+ const char *caCert = argc > 5 ? argv[6] : NULL;
+
+ redisAsyncContext *c = redisAsyncConnect(hostname, port);
+ if (c->err) {
+ /* Let *c leak for now... */
+ printf("Error: %s\n", c->errstr);
+ return 1;
+ }
+ if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) {
+ printf("SSL Error!\n");
+ exit(1);
+ }
+
+ redisLibeventAttach(c,base);
+ redisAsyncSetConnectCallback(c,connectCallback);
+ redisAsyncSetDisconnectCallback(c,disconnectCallback);
+ redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
+ redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
+ event_base_dispatch(base);
+ return 0;
+}
diff --git a/deps/hiredis/examples/example-libevent.c b/deps/hiredis/examples/example-libevent.c
index d333c22b7..1fe71ae4e 100644
--- a/deps/hiredis/examples/example-libevent.c
+++ b/deps/hiredis/examples/example-libevent.c
@@ -9,7 +9,12 @@
void getCallback(redisAsyncContext *c, void *r, void *privdata) {
redisReply *reply = r;
- if (reply == NULL) return;
+ if (reply == NULL) {
+ if (c->errstr) {
+ printf("errstr: %s\n", c->errstr);
+ }
+ return;
+ }
printf("argv[%s]: %s\n", (char*)privdata, reply->str);
/* Disconnect after receiving the reply to GET */
@@ -35,8 +40,14 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
int main (int argc, char **argv) {
signal(SIGPIPE, SIG_IGN);
struct event_base *base = event_base_new();
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
+ struct timeval tv = {0};
+ tv.tv_sec = 1;
+ options.timeout = &tv;
+
- redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
+ redisAsyncContext *c = redisAsyncConnectWithOptions(&options);
if (c->err) {
/* Let *c leak for now... */
printf("Error: %s\n", c->errstr);
diff --git a/deps/hiredis/examples/example-ssl.c b/deps/hiredis/examples/example-ssl.c
new file mode 100644
index 000000000..81f4648c6
--- /dev/null
+++ b/deps/hiredis/examples/example-ssl.c
@@ -0,0 +1,97 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <hiredis.h>
+#include <hiredis_ssl.h>
+
+int main(int argc, char **argv) {
+ unsigned int j;
+ redisContext *c;
+ redisReply *reply;
+ if (argc < 4) {
+ printf("Usage: %s <host> <port> <cert> <key> [ca]\n", argv[0]);
+ exit(1);
+ }
+ const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+ int port = atoi(argv[2]);
+ const char *cert = argv[3];
+ const char *key = argv[4];
+ const char *ca = argc > 4 ? argv[5] : NULL;
+
+ struct timeval tv = { 1, 500000 }; // 1.5 seconds
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, hostname, port);
+ options.timeout = &tv;
+ c = redisConnectWithOptions(&options);
+
+ if (c == NULL || c->err) {
+ if (c) {
+ printf("Connection error: %s\n", c->errstr);
+ redisFree(c);
+ } else {
+ printf("Connection error: can't allocate redis context\n");
+ }
+ exit(1);
+ }
+
+ if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) {
+ printf("Couldn't initialize SSL!\n");
+ printf("Error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+
+ /* PING server */
+ reply = redisCommand(c,"PING");
+ printf("PING: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key */
+ reply = redisCommand(c,"SET %s %s", "foo", "hello world");
+ printf("SET: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Set a key using binary safe API */
+ reply = redisCommand(c,"SET %b %b", "bar", (size_t) 3, "hello", (size_t) 5);
+ printf("SET (binary API): %s\n", reply->str);
+ freeReplyObject(reply);
+
+ /* Try a GET and two INCR */
+ reply = redisCommand(c,"GET foo");
+ printf("GET foo: %s\n", reply->str);
+ freeReplyObject(reply);
+
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+ /* again ... */
+ reply = redisCommand(c,"INCR counter");
+ printf("INCR counter: %lld\n", reply->integer);
+ freeReplyObject(reply);
+
+ /* Create a list of numbers, from 0 to 9 */
+ reply = redisCommand(c,"DEL mylist");
+ freeReplyObject(reply);
+ for (j = 0; j < 10; j++) {
+ char buf[64];
+
+ snprintf(buf,64,"%u",j);
+ reply = redisCommand(c,"LPUSH mylist element-%s", buf);
+ freeReplyObject(reply);
+ }
+
+ /* Let's check what we have inside the list */
+ reply = redisCommand(c,"LRANGE mylist 0 -1");
+ if (reply->type == REDIS_REPLY_ARRAY) {
+ for (j = 0; j < reply->elements; j++) {
+ printf("%u) %s\n", j, reply->element[j]->str);
+ }
+ }
+ freeReplyObject(reply);
+
+ /* Disconnects and frees the context */
+ redisFree(c);
+
+ return 0;
+}
diff --git a/deps/hiredis/examples/example.c b/deps/hiredis/examples/example.c
index 4d494c55a..0e93fc8b3 100644
--- a/deps/hiredis/examples/example.c
+++ b/deps/hiredis/examples/example.c
@@ -5,14 +5,27 @@
#include <hiredis.h>
int main(int argc, char **argv) {
- unsigned int j;
+ unsigned int j, isunix = 0;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
+
+ if (argc > 2) {
+ if (*argv[2] == 'u' || *argv[2] == 'U') {
+ isunix = 1;
+ /* in this case, host is the path to the unix socket */
+ printf("Will connect to unix socket @%s\n", hostname);
+ }
+ }
+
int port = (argc > 2) ? atoi(argv[2]) : 6379;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
- c = redisConnectWithTimeout(hostname, port, timeout);
+ if (isunix) {
+ c = redisConnectUnixWithTimeout(hostname, timeout);
+ } else {
+ c = redisConnectWithTimeout(hostname, port, timeout);
+ }
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
diff --git a/deps/hiredis/fmacros.h b/deps/hiredis/fmacros.h
index 14fed6060..3227faafd 100644
--- a/deps/hiredis/fmacros.h
+++ b/deps/hiredis/fmacros.h
@@ -1,25 +1,12 @@
#ifndef __HIREDIS_FMACRO_H
#define __HIREDIS_FMACRO_H
-#if defined(__linux__)
-#define _BSD_SOURCE
-#define _DEFAULT_SOURCE
-#endif
-
-#if defined(__CYGWIN__)
-#include <sys/cdefs.h>
-#endif
-
-#if defined(__sun__)
-#define _POSIX_C_SOURCE 200112L
-#else
-#if !(defined(__APPLE__) && defined(__MACH__))
#define _XOPEN_SOURCE 600
-#endif
-#endif
+#define _POSIX_C_SOURCE 200112L
#if defined(__APPLE__) && defined(__MACH__)
-#define _OSX
+/* Enable TCP_KEEPALIVE */
+#define _DARWIN_C_SOURCE
#endif
#endif
diff --git a/deps/hiredis/hiredis.c b/deps/hiredis/hiredis.c
index 18bdfc99c..abd94c01d 100644
--- a/deps/hiredis/hiredis.c
+++ b/deps/hiredis/hiredis.c
@@ -34,7 +34,6 @@
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
-#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
@@ -42,12 +41,24 @@
#include "hiredis.h"
#include "net.h"
#include "sds.h"
+#include "async.h"
+#include "win32.h"
+
+static redisContextFuncs redisContextDefaultFuncs = {
+ .free_privdata = NULL,
+ .async_read = redisAsyncRead,
+ .async_write = redisAsyncWrite,
+ .read = redisNetRead,
+ .write = redisNetWrite
+};
static redisReply *createReplyObject(int type);
static void *createStringObject(const redisReadTask *task, char *str, size_t len);
-static void *createArrayObject(const redisReadTask *task, int elements);
+static void *createArrayObject(const redisReadTask *task, size_t elements);
static void *createIntegerObject(const redisReadTask *task, long long value);
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len);
static void *createNilObject(const redisReadTask *task);
+static void *createBoolObject(const redisReadTask *task, int bval);
/* Default set of functions to build the reply. Keep in mind that such a
* function returning NULL is interpreted as OOM. */
@@ -55,7 +66,9 @@ static redisReplyObjectFunctions defaultFunctions = {
createStringObject,
createArrayObject,
createIntegerObject,
+ createDoubleObject,
createNilObject,
+ createBoolObject,
freeReplyObject
};
@@ -82,18 +95,19 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_INTEGER:
break; /* Nothing to free */
case REDIS_REPLY_ARRAY:
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
if (r->element != NULL) {
for (j = 0; j < r->elements; j++)
- if (r->element[j] != NULL)
- freeReplyObject(r->element[j]);
+ freeReplyObject(r->element[j]);
free(r->element);
}
break;
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
- if (r->str != NULL)
- free(r->str);
+ case REDIS_REPLY_DOUBLE:
+ free(r->str);
break;
}
free(r);
@@ -107,34 +121,49 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
if (r == NULL)
return NULL;
- buf = malloc(len+1);
- if (buf == NULL) {
- freeReplyObject(r);
- return NULL;
- }
-
assert(task->type == REDIS_REPLY_ERROR ||
task->type == REDIS_REPLY_STATUS ||
- task->type == REDIS_REPLY_STRING);
+ task->type == REDIS_REPLY_STRING ||
+ task->type == REDIS_REPLY_VERB);
/* Copy string value */
- memcpy(buf,str,len);
- buf[len] = '\0';
+ if (task->type == REDIS_REPLY_VERB) {
+ buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
+ if (buf == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+ memcpy(r->vtype,str,3);
+ r->vtype[3] = '\0';
+ memcpy(buf,str+4,len-4);
+ buf[len-4] = '\0';
+ r->len = len-4;
+ } else {
+ buf = malloc(len+1);
+ if (buf == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+ memcpy(buf,str,len);
+ buf[len] = '\0';
+ r->len = len;
+ }
r->str = buf;
- r->len = len;
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
}
-static void *createArrayObject(const redisReadTask *task, int elements) {
+static void *createArrayObject(const redisReadTask *task, size_t elements) {
redisReply *r, *parent;
- r = createReplyObject(REDIS_REPLY_ARRAY);
+ r = createReplyObject(task->type);
if (r == NULL)
return NULL;
@@ -150,7 +179,9 @@ static void *createArrayObject(const redisReadTask *task, int elements) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
@@ -167,7 +198,41 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createDoubleObject(const redisReadTask *task, double value, char *str, size_t len) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_DOUBLE);
+ if (r == NULL)
+ return NULL;
+
+ r->dval = value;
+ r->str = malloc(len+1);
+ if (r->str == NULL) {
+ freeReplyObject(r);
+ return NULL;
+ }
+
+ /* The double reply also has the original protocol string representing a
+ * double as a null terminated string. This way the caller does not need
+ * to format back for string conversion, especially since Redis does efforts
+ * to make the string more human readable avoiding the calssical double
+ * decimal string conversion artifacts. */
+ memcpy(r->str, str, len);
+ r->str[len] = '\0';
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
@@ -182,7 +247,28 @@ static void *createNilObject(const redisReadTask *task) {
if (task->parent) {
parent = task->parent->obj;
- assert(parent->type == REDIS_REPLY_ARRAY);
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
+ parent->element[task->idx] = r;
+ }
+ return r;
+}
+
+static void *createBoolObject(const redisReadTask *task, int bval) {
+ redisReply *r, *parent;
+
+ r = createReplyObject(REDIS_REPLY_BOOL);
+ if (r == NULL)
+ return NULL;
+
+ r->integer = bval != 0;
+
+ if (task->parent) {
+ parent = task->parent->obj;
+ assert(parent->type == REDIS_REPLY_ARRAY ||
+ parent->type == REDIS_REPLY_MAP ||
+ parent->type == REDIS_REPLY_SET);
parent->element[task->idx] = r;
}
return r;
@@ -432,11 +518,7 @@ cleanup:
}
sdsfree(curarg);
-
- /* No need to check cmd since it is the last statement that can fail,
- * but do it anyway to be as defensive as possible. */
- if (cmd != NULL)
- free(cmd);
+ free(cmd);
return error_type;
}
@@ -581,7 +663,7 @@ void __redisSetError(redisContext *c, int type, const char *str) {
} else {
/* Only REDIS_ERR_IO may lack a description! */
assert(type == REDIS_ERR_IO);
- __redis_strerror_r(errno, c->errstr, sizeof(c->errstr));
+ strerror_r(errno, c->errstr, sizeof(c->errstr));
}
}
@@ -589,53 +671,48 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions);
}
-static redisContext *redisContextInit(void) {
+static redisContext *redisContextInit(const redisOptions *options) {
redisContext *c;
- c = calloc(1,sizeof(redisContext));
+ c = calloc(1, sizeof(*c));
if (c == NULL)
return NULL;
- c->err = 0;
- c->errstr[0] = '\0';
+ c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty();
c->reader = redisReaderCreate();
- c->tcp.host = NULL;
- c->tcp.source_addr = NULL;
- c->unix_sock.path = NULL;
- c->timeout = NULL;
+ c->fd = REDIS_INVALID_FD;
if (c->obuf == NULL || c->reader == NULL) {
redisFree(c);
return NULL;
}
-
+ (void)options; /* options are used in other functions */
return c;
}
void redisFree(redisContext *c) {
if (c == NULL)
return;
- if (c->fd > 0)
- close(c->fd);
- if (c->obuf != NULL)
- sdsfree(c->obuf);
- if (c->reader != NULL)
- redisReaderFree(c->reader);
- if (c->tcp.host)
- free(c->tcp.host);
- if (c->tcp.source_addr)
- free(c->tcp.source_addr);
- if (c->unix_sock.path)
- free(c->unix_sock.path);
- if (c->timeout)
- free(c->timeout);
+ redisNetClose(c);
+
+ sdsfree(c->obuf);
+ redisReaderFree(c->reader);
+ free(c->tcp.host);
+ free(c->tcp.source_addr);
+ free(c->unix_sock.path);
+ free(c->timeout);
+ free(c->saddr);
+ if (c->funcs->free_privdata) {
+ c->funcs->free_privdata(c->privdata);
+ }
+ memset(c, 0xff, sizeof(*c));
free(c);
}
-int redisFreeKeepFd(redisContext *c) {
- int fd = c->fd;
- c->fd = -1;
+redisFD redisFreeKeepFd(redisContext *c) {
+ redisFD fd = c->fd;
+ c->fd = REDIS_INVALID_FD;
redisFree(c);
return fd;
}
@@ -644,10 +721,13 @@ int redisReconnect(redisContext *c) {
c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr));
- if (c->fd > 0) {
- close(c->fd);
+ if (c->privdata && c->funcs->free_privdata) {
+ c->funcs->free_privdata(c->privdata);
+ c->privdata = NULL;
}
+ redisNetClose(c);
+
sdsfree(c->obuf);
redisReaderFree(c->reader);
@@ -668,108 +748,107 @@ int redisReconnect(redisContext *c) {
return REDIS_ERR;
}
+redisContext *redisConnectWithOptions(const redisOptions *options) {
+ redisContext *c = redisContextInit(options);
+ if (c == NULL) {
+ return NULL;
+ }
+ if (!(options->options & REDIS_OPT_NONBLOCK)) {
+ c->flags |= REDIS_BLOCK;
+ }
+ if (options->options & REDIS_OPT_REUSEADDR) {
+ c->flags |= REDIS_REUSEADDR;
+ }
+ if (options->options & REDIS_OPT_NOAUTOFREE) {
+ c->flags |= REDIS_NO_AUTO_FREE;
+ }
+
+ if (options->type == REDIS_CONN_TCP) {
+ redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
+ options->endpoint.tcp.port, options->timeout,
+ options->endpoint.tcp.source_addr);
+ } else if (options->type == REDIS_CONN_UNIX) {
+ redisContextConnectUnix(c, options->endpoint.unix_socket,
+ options->timeout);
+ } else if (options->type == REDIS_CONN_USERFD) {
+ c->fd = options->endpoint.fd;
+ c->flags |= REDIS_CONNECTED;
+ } else {
+ // Unknown type - FIXME - FREE
+ return NULL;
+ }
+ if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
+ redisContextSetTimeout(c, *options->timeout);
+ }
+ return c;
+}
+
/* Connect to a Redis instance. On error the field error in the returned
* context will be set to the return value of the error function.
* When no set of reply functions is given, the default set will be used. */
redisContext *redisConnect(const char *ip, int port) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,&tv);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.timeout = &tv;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectNonBlock(const char *ip, int port) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectTcp(c,ip,port,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlock(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
const char *source_addr) {
- redisContext *c = redisContextInit();
- c->flags &= ~REDIS_BLOCK;
- c->flags |= REDIS_REUSEADDR;
- redisContextConnectBindTcp(c,ip,port,NULL,source_addr);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_TCP(&options, ip, port);
+ options.endpoint.tcp.source_addr = source_addr;
+ options.options |= REDIS_OPT_NONBLOCK|REDIS_OPT_REUSEADDR;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnix(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags |= REDIS_BLOCK;
- redisContextConnectUnix(c,path,&tv);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.timeout = &tv;
+ return redisConnectWithOptions(&options);
}
redisContext *redisConnectUnixNonBlock(const char *path) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->flags &= ~REDIS_BLOCK;
- redisContextConnectUnix(c,path,NULL);
- return c;
+ redisOptions options = {0};
+ REDIS_OPTIONS_SET_UNIX(&options, path);
+ options.options |= REDIS_OPT_NONBLOCK;
+ return redisConnectWithOptions(&options);
}
-redisContext *redisConnectFd(int fd) {
- redisContext *c;
-
- c = redisContextInit();
- if (c == NULL)
- return NULL;
-
- c->fd = fd;
- c->flags |= REDIS_BLOCK | REDIS_CONNECTED;
- return c;
+redisContext *redisConnectFd(redisFD fd) {
+ redisOptions options = {0};
+ options.type = REDIS_CONN_USERFD;
+ options.endpoint.fd = fd;
+ return redisConnectWithOptions(&options);
}
/* Set read/write timeout on a blocking socket. */
@@ -789,7 +868,7 @@ int redisEnableKeepAlive(redisContext *c) {
/* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser.
*
- * After this function is called, you may use redisContextReadReply to
+ * After this function is called, you may use redisGetReplyFromReader to
* see if there is a reply available. */
int redisBufferRead(redisContext *c) {
char buf[1024*16];
@@ -799,22 +878,15 @@ int redisBufferRead(redisContext *c) {
if (c->err)
return REDIS_ERR;
- nread = read(c->fd,buf,sizeof(buf));
- if (nread == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
+ nread = c->funcs->read(c, buf, sizeof(buf));
+ if (nread > 0) {
+ if (redisReaderFeed(c->reader, buf, nread) != REDIS_OK) {
+ __redisSetError(c, c->reader->err, c->reader->errstr);
return REDIS_ERR;
+ } else {
}
- } else if (nread == 0) {
- __redisSetError(c,REDIS_ERR_EOF,"Server closed the connection");
+ } else if (nread < 0) {
return REDIS_ERR;
- } else {
- if (redisReaderFeed(c->reader,buf,nread) != REDIS_OK) {
- __redisSetError(c,c->reader->err,c->reader->errstr);
- return REDIS_ERR;
- }
}
return REDIS_OK;
}
@@ -829,21 +901,15 @@ int redisBufferRead(redisContext *c) {
* c->errstr to hold the appropriate error string.
*/
int redisBufferWrite(redisContext *c, int *done) {
- int nwritten;
/* Return early when the context has seen an error. */
if (c->err)
return REDIS_ERR;
if (sdslen(c->obuf) > 0) {
- nwritten = write(c->fd,c->obuf,sdslen(c->obuf));
- if (nwritten == -1) {
- if ((errno == EAGAIN && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
- /* Try again later */
- } else {
- __redisSetError(c,REDIS_ERR_IO,NULL);
- return REDIS_ERR;
- }
+ int nwritten = c->funcs->write(c);
+ if (nwritten < 0) {
+ return REDIS_ERR;
} else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) {
sdsfree(c->obuf);
@@ -1007,9 +1073,8 @@ void *redisvCommand(redisContext *c, const char *format, va_list ap) {
void *redisCommand(redisContext *c, const char *format, ...) {
va_list ap;
- void *reply = NULL;
va_start(ap,format);
- reply = redisvCommand(c,format,ap);
+ void *reply = redisvCommand(c,format,ap);
va_end(ap);
return reply;
}
diff --git a/deps/hiredis/hiredis.h b/deps/hiredis/hiredis.h
index 423d5e504..69dc39c5e 100644
--- a/deps/hiredis/hiredis.h
+++ b/deps/hiredis/hiredis.h
@@ -35,14 +35,18 @@
#define __HIREDIS_H
#include "read.h"
#include <stdarg.h> /* for va_list */
+#ifndef _MSC_VER
#include <sys/time.h> /* for struct timeval */
+#else
+struct timeval; /* forward declaration */
+#endif
#include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */
#define HIREDIS_MAJOR 0
-#define HIREDIS_MINOR 13
-#define HIREDIS_PATCH 3
-#define HIREDIS_SONAME 0.13
+#define HIREDIS_MINOR 14
+#define HIREDIS_PATCH 0
+#define HIREDIS_SONAME 0.14
/* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */
@@ -74,36 +78,18 @@
/* Flag that is set when we should set SO_REUSEADDR before calling bind() */
#define REDIS_REUSEADDR 0x80
+/**
+ * Flag that indicates the user does not want the context to
+ * be automatically freed upon error
+ */
+#define REDIS_NO_AUTO_FREE 0x200
+
#define REDIS_KEEPALIVE_INTERVAL 15 /* seconds */
/* number of times we retry to connect in the case of EADDRNOTAVAIL and
* SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10
-/* strerror_r has two completely different prototypes and behaviors
- * depending on system issues, so we need to operate on the error buffer
- * differently depending on which strerror_r we're using. */
-#ifndef _GNU_SOURCE
-/* "regular" POSIX strerror_r that does the right thing. */
-#define __redis_strerror_r(errno, buf, len) \
- do { \
- strerror_r((errno), (buf), (len)); \
- } while (0)
-#else
-/* "bad" GNU strerror_r we need to clean up after. */
-#define __redis_strerror_r(errno, buf, len) \
- do { \
- char *err_str = strerror_r((errno), (buf), (len)); \
- /* If return value _isn't_ the start of the buffer we passed in, \
- * then GNU strerror_r returned an internal static buffer and we \
- * need to copy the result into our private buffer. */ \
- if (err_str != (buf)) { \
- strncpy((buf), err_str, ((len) - 1)); \
- buf[(len)-1] = '\0'; \
- } \
- } while (0)
-#endif
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -112,8 +98,12 @@ extern "C" {
typedef struct redisReply {
int type; /* REDIS_REPLY_* */
long long integer; /* The integer when type is REDIS_REPLY_INTEGER */
+ double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */
- char *str; /* Used for both REDIS_REPLY_ERROR and REDIS_REPLY_STRING */
+ char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
+ and REDIS_REPLY_DOUBLE (in additionl to dval). */
+ char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
+ terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
struct redisReply **element; /* elements vector for REDIS_REPLY_ARRAY */
} redisReply;
@@ -133,14 +123,93 @@ void redisFreeSdsCommand(sds cmd);
enum redisConnectionType {
REDIS_CONN_TCP,
- REDIS_CONN_UNIX
+ REDIS_CONN_UNIX,
+ REDIS_CONN_USERFD
};
+struct redisSsl;
+
+#define REDIS_OPT_NONBLOCK 0x01
+#define REDIS_OPT_REUSEADDR 0x02
+
+/**
+ * Don't automatically free the async object on a connection failure,
+ * or other implicit conditions. Only free on an explicit call to disconnect() or free()
+ */
+#define REDIS_OPT_NOAUTOFREE 0x04
+
+/* In Unix systems a file descriptor is a regular signed int, with -1
+ * representing an invalid descriptor. In Windows it is a SOCKET
+ * (32- or 64-bit unsigned integer depending on the architecture), where
+ * all bits set (~0) is INVALID_SOCKET. */
+#ifndef _WIN32
+typedef int redisFD;
+#define REDIS_INVALID_FD -1
+#else
+#ifdef _WIN64
+typedef unsigned long long redisFD; /* SOCKET = 64-bit UINT_PTR */
+#else
+typedef unsigned long redisFD; /* SOCKET = 32-bit UINT_PTR */
+#endif
+#define REDIS_INVALID_FD ((redisFD)(~0)) /* INVALID_SOCKET */
+#endif
+
+typedef struct {
+ /*
+ * the type of connection to use. This also indicates which
+ * `endpoint` member field to use
+ */
+ int type;
+ /* bit field of REDIS_OPT_xxx */
+ int options;
+ /* timeout value. if NULL, no timeout is used */
+ const struct timeval *timeout;
+ union {
+ /** use this field for tcp/ip connections */
+ struct {
+ const char *source_addr;
+ const char *ip;
+ int port;
+ } tcp;
+ /** use this field for unix domain sockets */
+ const char *unix_socket;
+ /**
+ * use this field to have hiredis operate an already-open
+ * file descriptor */
+ redisFD fd;
+ } endpoint;
+} redisOptions;
+
+/**
+ * Helper macros to initialize options to their specified fields.
+ */
+#define REDIS_OPTIONS_SET_TCP(opts, ip_, port_) \
+ (opts)->type = REDIS_CONN_TCP; \
+ (opts)->endpoint.tcp.ip = ip_; \
+ (opts)->endpoint.tcp.port = port_;
+
+#define REDIS_OPTIONS_SET_UNIX(opts, path) \
+ (opts)->type = REDIS_CONN_UNIX; \
+ (opts)->endpoint.unix_socket = path;
+
+struct redisAsyncContext;
+struct redisContext;
+
+typedef struct redisContextFuncs {
+ void (*free_privdata)(void *);
+ void (*async_read)(struct redisAsyncContext *);
+ void (*async_write)(struct redisAsyncContext *);
+ int (*read)(struct redisContext *, char *, size_t);
+ int (*write)(struct redisContext *);
+} redisContextFuncs;
+
/* Context for a connection to Redis */
typedef struct redisContext {
+ const redisContextFuncs *funcs; /* Function table */
+
int err; /* Error flags, 0 when there is no error */
char errstr[128]; /* String representation of error when applicable */
- int fd;
+ redisFD fd;
int flags;
char *obuf; /* Write buffer */
redisReader *reader; /* Protocol reader */
@@ -158,8 +227,15 @@ typedef struct redisContext {
char *path;
} unix_sock;
+ /* For non-blocking connect */
+ struct sockadr *saddr;
+ size_t addrlen;
+
+ /* Additional private data for hiredis addons such as SSL */
+ void *privdata;
} redisContext;
+redisContext *redisConnectWithOptions(const redisOptions *options);
redisContext *redisConnect(const char *ip, int port);
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv);
redisContext *redisConnectNonBlock(const char *ip, int port);
@@ -170,7 +246,7 @@ redisContext *redisConnectBindNonBlockWithReuse(const char *ip, int port,
redisContext *redisConnectUnix(const char *path);
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv);
redisContext *redisConnectUnixNonBlock(const char *path);
-redisContext *redisConnectFd(int fd);
+redisContext *redisConnectFd(redisFD fd);
/**
* Reconnect the given context using the saved information.
@@ -186,7 +262,7 @@ int redisReconnect(redisContext *c);
int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c);
-int redisFreeKeepFd(redisContext *c);
+redisFD redisFreeKeepFd(redisContext *c);
int redisBufferRead(redisContext *c);
int redisBufferWrite(redisContext *c, int *done);
diff --git a/deps/hiredis/hiredis.pc.in b/deps/hiredis/hiredis.pc.in
new file mode 100644
index 000000000..140b040f1
--- /dev/null
+++ b/deps/hiredis/hiredis.pc.in
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis
+Description: Minimalistic C client library for Redis.
+Version: @PROJECT_VERSION@
+Libs: -L${libdir} -lhiredis
+Cflags: -I${pkgincludedir} -D_FILE_OFFSET_BITS=64
diff --git a/deps/hiredis/hiredis_ssl.h b/deps/hiredis/hiredis_ssl.h
new file mode 100644
index 000000000..f844f9548
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.h
@@ -0,0 +1,53 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __HIREDIS_SSL_H
+#define __HIREDIS_SSL_H
+
+/* This is the underlying struct for SSL in ssl.h, which is not included to
+ * keep build dependencies short here.
+ */
+struct ssl_st;
+
+/**
+ * Secure the connection using SSL. This should be done before any command is
+ * executed on the connection.
+ */
+int redisSecureConnection(redisContext *c, const char *capath, const char *certpath,
+ const char *keypath, const char *servername);
+
+/**
+ * Initiate SSL/TLS negotiation on a provided context.
+ */
+
+int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
+
+#endif /* __HIREDIS_SSL_H */
diff --git a/deps/hiredis/hiredis_ssl.pc.in b/deps/hiredis/hiredis_ssl.pc.in
new file mode 100644
index 000000000..588a978a5
--- /dev/null
+++ b/deps/hiredis/hiredis_ssl.pc.in
@@ -0,0 +1,12 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=${exec_prefix}/lib
+includedir=${prefix}/include
+pkgincludedir=${includedir}/hiredis
+
+Name: hiredis_ssl
+Description: SSL Support for hiredis.
+Version: @PROJECT_VERSION@
+Requires: hiredis
+Libs: -L${libdir} -lhiredis_ssl
+Libs.private: -lssl -lcrypto
diff --git a/deps/hiredis/net.c b/deps/hiredis/net.c
index 7d4120985..e5f40b0a4 100644
--- a/deps/hiredis/net.c
+++ b/deps/hiredis/net.c
@@ -34,43 +34,72 @@
#include "fmacros.h"
#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/select.h>
-#include <sys/un.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <unistd.h>
#include <fcntl.h>
#include <string.h>
-#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
-#include <poll.h>
#include <limits.h>
#include <stdlib.h>
#include "net.h"
#include "sds.h"
+#include "sockcompat.h"
+#include "win32.h"
/* Defined in hiredis.c */
void __redisSetError(redisContext *c, int type, const char *str);
-static void redisContextCloseFd(redisContext *c) {
- if (c && c->fd >= 0) {
+void redisNetClose(redisContext *c) {
+ if (c && c->fd != REDIS_INVALID_FD) {
close(c->fd);
- c->fd = -1;
+ c->fd = REDIS_INVALID_FD;
}
}
+int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
+ int nread = recv(c->fd, buf, bufcap, 0);
+ if (nread == -1) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ return 0;
+ } else if(errno == ETIMEDOUT && (c->flags & REDIS_BLOCK)) {
+ /* especially in windows */
+ __redisSetError(c, REDIS_ERR_TIMEOUT, "recv timeout");
+ return -1;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ return nread;
+ }
+}
+
+int redisNetWrite(redisContext *c) {
+ int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0);
+ if (nwritten < 0) {
+ if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
+ /* Try again later */
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return nwritten;
+}
+
static void __redisSetErrorFromErrno(redisContext *c, int type, const char *prefix) {
+ int errorno = errno; /* snprintf() may change errno */
char buf[128] = { 0 };
size_t len = 0;
if (prefix != NULL)
len = snprintf(buf,sizeof(buf),"%s: ",prefix);
- __redis_strerror_r(errno, (char *)(buf + len), sizeof(buf) - len);
+ strerror_r(errorno, (char *)(buf + len), sizeof(buf) - len);
__redisSetError(c,type,buf);
}
@@ -78,15 +107,15 @@ static int redisSetReuseAddr(redisContext *c) {
int on = 1;
if (setsockopt(c->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
}
static int redisCreateSocket(redisContext *c, int type) {
- int s;
- if ((s = socket(type, SOCK_STREAM, 0)) == -1) {
+ redisFD s;
+ if ((s = socket(type, SOCK_STREAM, 0)) == REDIS_INVALID_FD) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
return REDIS_ERR;
}
@@ -100,6 +129,7 @@ static int redisCreateSocket(redisContext *c, int type) {
}
static int redisSetBlocking(redisContext *c, int blocking) {
+#ifndef _WIN32
int flags;
/* Set the socket nonblocking.
@@ -107,7 +137,7 @@ static int redisSetBlocking(redisContext *c, int blocking) {
* interrupted by a signal. */
if ((flags = fcntl(c->fd, F_GETFL)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_GETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
@@ -118,15 +148,23 @@ static int redisSetBlocking(redisContext *c, int blocking) {
if (fcntl(c->fd, F_SETFL, flags) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"fcntl(F_SETFL)");
- redisContextCloseFd(c);
+ redisNetClose(c);
+ return REDIS_ERR;
+ }
+#else
+ u_long mode = blocking ? 0 : 1;
+ if (ioctl(c->fd, FIONBIO, &mode) == -1) {
+ __redisSetErrorFromErrno(c, REDIS_ERR_IO, "ioctl(FIONBIO)");
+ redisNetClose(c);
return REDIS_ERR;
}
+#endif /* _WIN32 */
return REDIS_OK;
}
int redisKeepAlive(redisContext *c, int interval) {
int val = 1;
- int fd = c->fd;
+ redisFD fd = c->fd;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)) == -1){
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
@@ -135,14 +173,13 @@ int redisKeepAlive(redisContext *c, int interval) {
val = interval;
-#ifdef _OSX
+#if defined(__APPLE__) && defined(__MACH__)
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPALIVE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
}
#else
#if defined(__GLIBC__) && !defined(__FreeBSD_kernel__)
- val = interval;
if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val)) < 0) {
__redisSetError(c,REDIS_ERR_OTHER,strerror(errno));
return REDIS_ERR;
@@ -170,7 +207,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
return REDIS_OK;
@@ -212,28 +249,50 @@ static int redisContextWaitReady(redisContext *c, long msec) {
if ((res = poll(wfd, 1, msec)) == -1) {
__redisSetErrorFromErrno(c, REDIS_ERR_IO, "poll(2)");
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
} else if (res == 0) {
errno = ETIMEDOUT;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
- if (redisCheckSocketError(c) != REDIS_OK)
+ if (redisCheckConnectDone(c, &res) != REDIS_OK || res == 0) {
+ redisCheckSocketError(c);
return REDIS_ERR;
+ }
return REDIS_OK;
}
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
- redisContextCloseFd(c);
+ redisNetClose(c);
return REDIS_ERR;
}
+int redisCheckConnectDone(redisContext *c, int *completed) {
+ int rc = connect(c->fd, (const struct sockaddr *)c->saddr, c->addrlen);
+ if (rc == 0) {
+ *completed = 1;
+ return REDIS_OK;
+ }
+ switch (errno) {
+ case EISCONN:
+ *completed = 1;
+ return REDIS_OK;
+ case EALREADY:
+ case EINPROGRESS:
+ case EWOULDBLOCK:
+ *completed = 0;
+ return REDIS_OK;
+ default:
+ return REDIS_ERR;
+ }
+}
+
int redisCheckSocketError(redisContext *c) {
- int err = 0;
+ int err = 0, errno_saved = errno;
socklen_t errlen = sizeof(err);
if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) == -1) {
@@ -241,6 +300,10 @@ int redisCheckSocketError(redisContext *c) {
return REDIS_ERR;
}
+ if (err == 0) {
+ err = errno_saved;
+ }
+
if (err) {
errno = err;
__redisSetErrorFromErrno(c,REDIS_ERR_IO,NULL);
@@ -251,11 +314,18 @@ int redisCheckSocketError(redisContext *c) {
}
int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
- if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) == -1) {
+ const void *to_ptr = &tv;
+ size_t to_sz = sizeof(tv);
+#ifdef _WIN32
+ DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
+ to_ptr = &timeout_msec;
+ to_sz = sizeof(timeout_msec);
+#endif
+ if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR;
}
- if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,&tv,sizeof(tv)) == -1) {
+ if (setsockopt(c->fd,SOL_SOCKET,SO_SNDTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_SNDTIMEO)");
return REDIS_ERR;
}
@@ -265,7 +335,8 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout,
const char *source_addr) {
- int s, rv, n;
+ redisFD s;
+ int rv, n;
char _port[6]; /* strlen("65535"); */
struct addrinfo hints, *servinfo, *bservinfo, *p, *b;
int blocking = (c->flags & REDIS_BLOCK);
@@ -285,8 +356,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory.
**/
if (c->tcp.host != addr) {
- if (c->tcp.host)
- free(c->tcp.host);
+ free(c->tcp.host);
c->tcp.host = strdup(addr);
}
@@ -299,8 +369,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
- if (c->timeout)
- free(c->timeout);
+ free(c->timeout);
c->timeout = NULL;
}
@@ -336,7 +405,7 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
}
for (p = servinfo; p != NULL; p = p->ai_next) {
addrretry:
- if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == -1)
+ if ((s = socket(p->ai_family,p->ai_socktype,p->ai_protocol)) == REDIS_INVALID_FD)
continue;
c->fd = s;
@@ -356,6 +425,7 @@ addrretry:
n = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*) &n,
sizeof(n)) < 0) {
+ freeaddrinfo(bservinfo);
goto error;
}
}
@@ -374,20 +444,34 @@ addrretry:
goto error;
}
}
+
+ /* For repeat connection */
+ free(c->saddr);
+ c->saddr = malloc(p->ai_addrlen);
+ memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
+ c->addrlen = p->ai_addrlen;
+
if (connect(s,p->ai_addr,p->ai_addrlen) == -1) {
if (errno == EHOSTUNREACH) {
- redisContextCloseFd(c);
+ redisNetClose(c);
continue;
- } else if (errno == EINPROGRESS && !blocking) {
- /* This is ok. */
+ } else if (errno == EINPROGRESS) {
+ if (blocking) {
+ goto wait_for_ready;
+ }
+ /* This is ok.
+ * Note that even when it's in blocking mode, we unset blocking
+ * for `connect()`
+ */
} else if (errno == EADDRNOTAVAIL && reuseaddr) {
if (++reuses >= REDIS_CONNECT_RETRIES) {
goto error;
} else {
- redisContextCloseFd(c);
+ redisNetClose(c);
goto addrretry;
}
} else {
+ wait_for_ready:
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error;
}
@@ -411,7 +495,10 @@ addrretry:
error:
rv = REDIS_ERR;
end:
- freeaddrinfo(servinfo);
+ if(servinfo) {
+ freeaddrinfo(servinfo);
+ }
+
return rv; // Need to return REDIS_OK if alright
}
@@ -427,11 +514,12 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
}
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout) {
+#ifndef _WIN32
int blocking = (c->flags & REDIS_BLOCK);
- struct sockaddr_un sa;
+ struct sockaddr_un *sa;
long timeout_msec = -1;
- if (redisCreateSocket(c,AF_LOCAL) < 0)
+ if (redisCreateSocket(c,AF_UNIX) < 0)
return REDIS_ERR;
if (redisSetBlocking(c,0) != REDIS_OK)
return REDIS_ERR;
@@ -448,17 +536,18 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else {
- if (c->timeout)
- free(c->timeout);
+ free(c->timeout);
c->timeout = NULL;
}
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR;
- sa.sun_family = AF_LOCAL;
- strncpy(sa.sun_path,path,sizeof(sa.sun_path)-1);
- if (connect(c->fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
+ sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un)));
+ c->addrlen = sizeof(struct sockaddr_un);
+ sa->sun_family = AF_UNIX;
+ strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
+ if (connect(c->fd, (struct sockaddr*)sa, sizeof(*sa)) == -1) {
if (errno == EINPROGRESS && !blocking) {
/* This is ok. */
} else {
@@ -473,4 +562,10 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
c->flags |= REDIS_CONNECTED;
return REDIS_OK;
+#else
+ /* We currently do not support Unix sockets for Windows. */
+ /* TODO(m): https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/ */
+ errno = EPROTONOSUPPORT;
+ return REDIS_ERR;
+#endif /* _WIN32 */
}
diff --git a/deps/hiredis/net.h b/deps/hiredis/net.h
index 2f1a0bf85..a4393c06b 100644
--- a/deps/hiredis/net.h
+++ b/deps/hiredis/net.h
@@ -37,9 +37,9 @@
#include "hiredis.h"
-#if defined(__sun)
-#define AF_LOCAL AF_UNIX
-#endif
+void redisNetClose(redisContext *c);
+int redisNetRead(redisContext *c, char *buf, size_t bufcap);
+int redisNetWrite(redisContext *c);
int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv);
@@ -49,5 +49,6 @@ int redisContextConnectBindTcp(redisContext *c, const char *addr, int port,
const char *source_addr);
int redisContextConnectUnix(redisContext *c, const char *path, const struct timeval *timeout);
int redisKeepAlive(redisContext *c, int interval);
+int redisCheckConnectDone(redisContext *c, int *completed);
#endif
diff --git a/deps/hiredis/read.c b/deps/hiredis/read.c
index 50333b534..b9853ea9a 100644
--- a/deps/hiredis/read.c
+++ b/deps/hiredis/read.c
@@ -29,19 +29,22 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-
#include "fmacros.h"
#include <string.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
+#include <strings.h>
#endif
#include <assert.h>
#include <errno.h>
#include <ctype.h>
+#include <limits.h>
+#include <math.h>
#include "read.h"
#include "sds.h"
+#include "win32.h"
static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len;
@@ -52,11 +55,9 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
}
/* Clear input buffer on errors. */
- if (r->buf != NULL) {
- sdsfree(r->buf);
- r->buf = NULL;
- r->pos = r->len = 0;
- }
+ sdsfree(r->buf);
+ r->buf = NULL;
+ r->pos = r->len = 0;
/* Reset task stack. */
r->ridx = -1;
@@ -143,33 +144,79 @@ static char *seekNewline(char *s, size_t len) {
return NULL;
}
-/* Read a long long value starting at *s, under the assumption that it will be
- * terminated by \r\n. Ambiguously returns -1 for unexpected input. */
-static long long readLongLong(char *s) {
- long long v = 0;
- int dec, mult = 1;
- char c;
-
- if (*s == '-') {
- mult = -1;
- s++;
- } else if (*s == '+') {
- mult = 1;
- s++;
+/* Convert a string into a long long. Returns REDIS_OK if the string could be
+ * parsed into a (non-overflowing) long long, REDIS_ERR otherwise. The value
+ * will be set to the parsed value when appropriate.
+ *
+ * Note that this function demands that the string strictly represents
+ * a long long: no spaces or other characters before or after the string
+ * representing the number are accepted, nor zeroes at the start if not
+ * for the string "0" representing the zero number.
+ *
+ * Because of its strictness, it is safe to use this function to check if
+ * you can convert a string into a long long, and obtain back the string
+ * from the number without any loss in the string representation. */
+static int string2ll(const char *s, size_t slen, long long *value) {
+ const char *p = s;
+ size_t plen = 0;
+ int negative = 0;
+ unsigned long long v;
+
+ if (plen == slen)
+ return REDIS_ERR;
+
+ /* Special case: first and only digit is 0. */
+ if (slen == 1 && p[0] == '0') {
+ if (value != NULL) *value = 0;
+ return REDIS_OK;
}
- while ((c = *(s++)) != '\r') {
- dec = c - '0';
- if (dec >= 0 && dec < 10) {
- v *= 10;
- v += dec;
- } else {
- /* Should not happen... */
- return -1;
- }
+ if (p[0] == '-') {
+ negative = 1;
+ p++; plen++;
+
+ /* Abort on only a negative sign. */
+ if (plen == slen)
+ return REDIS_ERR;
+ }
+
+ /* First digit should be 1-9, otherwise the string should just be 0. */
+ if (p[0] >= '1' && p[0] <= '9') {
+ v = p[0]-'0';
+ p++; plen++;
+ } else if (p[0] == '0' && slen == 1) {
+ *value = 0;
+ return REDIS_OK;
+ } else {
+ return REDIS_ERR;
}
- return mult*v;
+ while (plen < slen && p[0] >= '0' && p[0] <= '9') {
+ if (v > (ULLONG_MAX / 10)) /* Overflow. */
+ return REDIS_ERR;
+ v *= 10;
+
+ if (v > (ULLONG_MAX - (p[0]-'0'))) /* Overflow. */
+ return REDIS_ERR;
+ v += p[0]-'0';
+
+ p++; plen++;
+ }
+
+ /* Return if not all bytes were used. */
+ if (plen < slen)
+ return REDIS_ERR;
+
+ if (negative) {
+ if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = -v;
+ } else {
+ if (v > LLONG_MAX) /* Overflow. */
+ return REDIS_ERR;
+ if (value != NULL) *value = v;
+ }
+ return REDIS_OK;
}
static char *readLine(redisReader *r, int *_len) {
@@ -198,7 +245,9 @@ static void moveToNextTask(redisReader *r) {
cur = &(r->rstack[r->ridx]);
prv = &(r->rstack[r->ridx-1]);
- assert(prv->type == REDIS_REPLY_ARRAY);
+ assert(prv->type == REDIS_REPLY_ARRAY ||
+ prv->type == REDIS_REPLY_MAP ||
+ prv->type == REDIS_REPLY_SET);
if (cur->idx == prv->elements-1) {
r->ridx--;
} else {
@@ -220,10 +269,58 @@ static int processLineItem(redisReader *r) {
if ((p = readLine(r,&len)) != NULL) {
if (cur->type == REDIS_REPLY_INTEGER) {
- if (r->fn && r->fn->createInteger)
- obj = r->fn->createInteger(cur,readLongLong(p));
- else
+ if (r->fn && r->fn->createInteger) {
+ long long v;
+ if (string2ll(p, len, &v) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad integer value");
+ return REDIS_ERR;
+ }
+ obj = r->fn->createInteger(cur,v);
+ } else {
obj = (void*)REDIS_REPLY_INTEGER;
+ }
+ } else if (cur->type == REDIS_REPLY_DOUBLE) {
+ if (r->fn && r->fn->createDouble) {
+ char buf[326], *eptr;
+ double d;
+
+ if ((size_t)len >= sizeof(buf)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Double value is too large");
+ return REDIS_ERR;
+ }
+
+ memcpy(buf,p,len);
+ buf[len] = '\0';
+
+ if (strcasecmp(buf,",inf") == 0) {
+ d = INFINITY; /* Positive infinite. */
+ } else if (strcasecmp(buf,",-inf") == 0) {
+ d = -INFINITY; /* Nevative infinite. */
+ } else {
+ d = strtod((char*)buf,&eptr);
+ if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad double value");
+ return REDIS_ERR;
+ }
+ }
+ obj = r->fn->createDouble(cur,d,buf,len);
+ } else {
+ obj = (void*)REDIS_REPLY_DOUBLE;
+ }
+ } else if (cur->type == REDIS_REPLY_NIL) {
+ if (r->fn && r->fn->createNil)
+ obj = r->fn->createNil(cur);
+ else
+ obj = (void*)REDIS_REPLY_NIL;
+ } else if (cur->type == REDIS_REPLY_BOOL) {
+ int bval = p[0] == 't' || p[0] == 'T';
+ if (r->fn && r->fn->createBool)
+ obj = r->fn->createBool(cur,bval);
+ else
+ obj = (void*)REDIS_REPLY_BOOL;
} else {
/* Type will be error or status. */
if (r->fn && r->fn->createString)
@@ -250,7 +347,7 @@ static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj = NULL;
char *p, *s;
- long len;
+ long long len;
unsigned long bytelen;
int success = 0;
@@ -259,9 +356,20 @@ static int processBulkItem(redisReader *r) {
if (s != NULL) {
p = r->buf+r->pos;
bytelen = s-(r->buf+r->pos)+2; /* include \r\n */
- len = readLongLong(p);
- if (len < 0) {
+ if (string2ll(p, bytelen - 2, &len) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad bulk string length");
+ return REDIS_ERR;
+ }
+
+ if (len < -1 || (LLONG_MAX > SIZE_MAX && len > (long long)SIZE_MAX)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bulk string length out of range");
+ return REDIS_ERR;
+ }
+
+ if (len == -1) {
/* The nil object can always be created. */
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
@@ -272,10 +380,18 @@ static int processBulkItem(redisReader *r) {
/* Only continue when the buffer contains the entire bulk item. */
bytelen += len+2; /* include \r\n */
if (r->pos+bytelen <= r->len) {
+ if ((cur->type == REDIS_REPLY_VERB && len < 4) ||
+ (cur->type == REDIS_REPLY_VERB && s[5] != ':'))
+ {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Verbatim string 4 bytes of content type are "
+ "missing or incorrectly encoded.");
+ return REDIS_ERR;
+ }
if (r->fn && r->fn->createString)
obj = r->fn->createString(cur,s+2,len);
else
- obj = (void*)REDIS_REPLY_STRING;
+ obj = (void*)(long)cur->type;
success = 1;
}
}
@@ -299,12 +415,13 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR;
}
-static int processMultiBulkItem(redisReader *r) {
+/* Process the array, map and set types. */
+static int processAggregateItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]);
void *obj;
char *p;
- long elements;
- int root = 0;
+ long long elements;
+ int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) {
@@ -313,10 +430,21 @@ static int processMultiBulkItem(redisReader *r) {
return REDIS_ERR;
}
- if ((p = readLine(r,NULL)) != NULL) {
- elements = readLongLong(p);
+ if ((p = readLine(r,&len)) != NULL) {
+ if (string2ll(p, len, &elements) == REDIS_ERR) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Bad multi-bulk length");
+ return REDIS_ERR;
+ }
+
root = (r->ridx == 0);
+ if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) {
+ __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
+ "Multi-bulk length out of range");
+ return REDIS_ERR;
+ }
+
if (elements == -1) {
if (r->fn && r->fn->createNil)
obj = r->fn->createNil(cur);
@@ -330,10 +458,12 @@ static int processMultiBulkItem(redisReader *r) {
moveToNextTask(r);
} else {
+ if (cur->type == REDIS_REPLY_MAP) elements *= 2;
+
if (r->fn && r->fn->createArray)
obj = r->fn->createArray(cur,elements);
else
- obj = (void*)REDIS_REPLY_ARRAY;
+ obj = (void*)(long)cur->type;
if (obj == NULL) {
__redisReaderSetErrorOOM(r);
@@ -381,12 +511,30 @@ static int processItem(redisReader *r) {
case ':':
cur->type = REDIS_REPLY_INTEGER;
break;
+ case ',':
+ cur->type = REDIS_REPLY_DOUBLE;
+ break;
+ case '_':
+ cur->type = REDIS_REPLY_NIL;
+ break;
case '$':
cur->type = REDIS_REPLY_STRING;
break;
case '*':
cur->type = REDIS_REPLY_ARRAY;
break;
+ case '%':
+ cur->type = REDIS_REPLY_MAP;
+ break;
+ case '~':
+ cur->type = REDIS_REPLY_SET;
+ break;
+ case '#':
+ cur->type = REDIS_REPLY_BOOL;
+ break;
+ case '=':
+ cur->type = REDIS_REPLY_VERB;
+ break;
default:
__redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR;
@@ -402,11 +550,17 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS:
case REDIS_REPLY_INTEGER:
+ case REDIS_REPLY_DOUBLE:
+ case REDIS_REPLY_NIL:
+ case REDIS_REPLY_BOOL:
return processLineItem(r);
case REDIS_REPLY_STRING:
+ case REDIS_REPLY_VERB:
return processBulkItem(r);
case REDIS_REPLY_ARRAY:
- return processMultiBulkItem(r);
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
+ return processAggregateItem(r);
default:
assert(NULL);
return REDIS_ERR; /* Avoid warning. */
@@ -416,12 +570,10 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r;
- r = calloc(sizeof(redisReader),1);
+ r = calloc(1,sizeof(redisReader));
if (r == NULL)
return NULL;
- r->err = 0;
- r->errstr[0] = '\0';
r->fn = fn;
r->buf = sdsempty();
r->maxbuf = REDIS_READER_MAX_BUF;
@@ -435,10 +587,11 @@ redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
}
void redisReaderFree(redisReader *r) {
+ if (r == NULL)
+ return;
if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply);
- if (r->buf != NULL)
- sdsfree(r->buf);
+ sdsfree(r->buf);
free(r);
}
@@ -517,8 +670,11 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Emit a reply when there is one. */
if (r->ridx == -1) {
- if (reply != NULL)
+ if (reply != NULL) {
*reply = r->reply;
+ } else if (r->reply != NULL && r->fn && r->fn->freeObject) {
+ r->fn->freeObject(r->reply);
+ }
r->reply = NULL;
}
return REDIS_OK;
diff --git a/deps/hiredis/read.h b/deps/hiredis/read.h
index 2988aa453..58105312a 100644
--- a/deps/hiredis/read.h
+++ b/deps/hiredis/read.h
@@ -45,6 +45,7 @@
#define REDIS_ERR_EOF 3 /* End of file */
#define REDIS_ERR_PROTOCOL 4 /* Protocol error */
#define REDIS_ERR_OOM 5 /* Out of memory */
+#define REDIS_ERR_TIMEOUT 6 /* Timed out */
#define REDIS_ERR_OTHER 2 /* Everything else... */
#define REDIS_REPLY_STRING 1
@@ -53,6 +54,14 @@
#define REDIS_REPLY_NIL 4
#define REDIS_REPLY_STATUS 5
#define REDIS_REPLY_ERROR 6
+#define REDIS_REPLY_DOUBLE 7
+#define REDIS_REPLY_BOOL 8
+#define REDIS_REPLY_MAP 9
+#define REDIS_REPLY_SET 10
+#define REDIS_REPLY_ATTR 11
+#define REDIS_REPLY_PUSH 12
+#define REDIS_REPLY_BIGNUM 13
+#define REDIS_REPLY_VERB 14
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */
@@ -71,9 +80,11 @@ typedef struct redisReadTask {
typedef struct redisReplyObjectFunctions {
void *(*createString)(const redisReadTask*, char*, size_t);
- void *(*createArray)(const redisReadTask*, int);
+ void *(*createArray)(const redisReadTask*, size_t);
void *(*createInteger)(const redisReadTask*, long long);
+ void *(*createDouble)(const redisReadTask*, double, char*, size_t);
void *(*createNil)(const redisReadTask*);
+ void *(*createBool)(const redisReadTask*, int);
void (*freeObject)(void*);
} redisReplyObjectFunctions;
diff --git a/deps/hiredis/sds.c b/deps/hiredis/sds.c
index 923ffd82f..6cf75841c 100644
--- a/deps/hiredis/sds.c
+++ b/deps/hiredis/sds.c
@@ -219,7 +219,10 @@ sds sdsMakeRoomFor(sds s, size_t addlen) {
hdrlen = sdsHdrSize(type);
if (oldtype==type) {
newsh = s_realloc(sh, hdrlen+newlen+1);
- if (newsh == NULL) return NULL;
+ if (newsh == NULL) {
+ s_free(sh);
+ return NULL;
+ }
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
@@ -592,6 +595,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Make sure there is always space for at least 1 char. */
if (sdsavail(s)==0) {
s = sdsMakeRoomFor(s,1);
+ if (s == NULL) goto fmt_error;
}
switch(*f) {
@@ -605,6 +609,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = (next == 's') ? strlen(str) : sdslen(str);
if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,str,l);
sdsinclen(s,l);
@@ -621,6 +626,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsll2str(buf,num);
if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,buf,l);
sdsinclen(s,l);
@@ -638,6 +644,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
l = sdsull2str(buf,unum);
if (sdsavail(s) < l) {
s = sdsMakeRoomFor(s,l);
+ if (s == NULL) goto fmt_error;
}
memcpy(s+i,buf,l);
sdsinclen(s,l);
@@ -662,6 +669,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
/* Add null-term */
s[i] = '\0';
return s;
+
+fmt_error:
+ va_end(ap);
+ return NULL;
}
/* Remove the part of the string from left and from right composed just of
@@ -1018,10 +1029,18 @@ sds *sdssplitargs(const char *line, int *argc) {
if (*p) p++;
}
/* add the token to the vector */
- vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
- vector[*argc] = current;
- (*argc)++;
- current = NULL;
+ {
+ char **new_vector = s_realloc(vector,((*argc)+1)*sizeof(char*));
+ if (new_vector == NULL) {
+ s_free(vector);
+ return NULL;
+ }
+
+ vector = new_vector;
+ vector[*argc] = current;
+ (*argc)++;
+ current = NULL;
+ }
} else {
/* Even on empty input string return something not NULL. */
if (vector == NULL) vector = s_malloc(sizeof(void*));
diff --git a/deps/hiredis/sds.h b/deps/hiredis/sds.h
index 13be75a9f..3f9a96457 100644
--- a/deps/hiredis/sds.h
+++ b/deps/hiredis/sds.h
@@ -34,6 +34,9 @@
#define __SDS_H
#define SDS_MAX_PREALLOC (1024*1024)
+#ifdef _MSC_VER
+#define __attribute__(x)
+#endif
#include <sys/types.h>
#include <stdarg.h>
@@ -132,20 +135,20 @@ static inline void sdssetlen(sds s, size_t newlen) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
- *fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
+ *fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS));
}
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->len = newlen;
+ SDS_HDR(8,s)->len = (uint8_t)newlen;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->len = newlen;
+ SDS_HDR(16,s)->len = (uint16_t)newlen;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->len = newlen;
+ SDS_HDR(32,s)->len = (uint32_t)newlen;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->len = newlen;
+ SDS_HDR(64,s)->len = (uint64_t)newlen;
break;
}
}
@@ -156,21 +159,21 @@ static inline void sdsinclen(sds s, size_t inc) {
case SDS_TYPE_5:
{
unsigned char *fp = ((unsigned char*)s)-1;
- unsigned char newlen = SDS_TYPE_5_LEN(flags)+inc;
+ unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS);
}
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->len += inc;
+ SDS_HDR(8,s)->len += (uint8_t)inc;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->len += inc;
+ SDS_HDR(16,s)->len += (uint16_t)inc;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->len += inc;
+ SDS_HDR(32,s)->len += (uint32_t)inc;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->len += inc;
+ SDS_HDR(64,s)->len += (uint64_t)inc;
break;
}
}
@@ -200,16 +203,16 @@ static inline void sdssetalloc(sds s, size_t newlen) {
/* Nothing to do, this type has no total allocation info. */
break;
case SDS_TYPE_8:
- SDS_HDR(8,s)->alloc = newlen;
+ SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break;
case SDS_TYPE_16:
- SDS_HDR(16,s)->alloc = newlen;
+ SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break;
case SDS_TYPE_32:
- SDS_HDR(32,s)->alloc = newlen;
+ SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break;
case SDS_TYPE_64:
- SDS_HDR(64,s)->alloc = newlen;
+ SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break;
}
}
diff --git a/deps/hiredis/sockcompat.c b/deps/hiredis/sockcompat.c
new file mode 100644
index 000000000..4cc2f414f
--- /dev/null
+++ b/deps/hiredis/sockcompat.c
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDIS_SOCKCOMPAT_IMPLEMENTATION
+#include "sockcompat.h"
+
+#ifdef _WIN32
+static int _wsaErrorToErrno(int err) {
+ switch (err) {
+ case WSAEWOULDBLOCK:
+ return EWOULDBLOCK;
+ case WSAEINPROGRESS:
+ return EINPROGRESS;
+ case WSAEALREADY:
+ return EALREADY;
+ case WSAENOTSOCK:
+ return ENOTSOCK;
+ case WSAEDESTADDRREQ:
+ return EDESTADDRREQ;
+ case WSAEMSGSIZE:
+ return EMSGSIZE;
+ case WSAEPROTOTYPE:
+ return EPROTOTYPE;
+ case WSAENOPROTOOPT:
+ return ENOPROTOOPT;
+ case WSAEPROTONOSUPPORT:
+ return EPROTONOSUPPORT;
+ case WSAEOPNOTSUPP:
+ return EOPNOTSUPP;
+ case WSAEAFNOSUPPORT:
+ return EAFNOSUPPORT;
+ case WSAEADDRINUSE:
+ return EADDRINUSE;
+ case WSAEADDRNOTAVAIL:
+ return EADDRNOTAVAIL;
+ case WSAENETDOWN:
+ return ENETDOWN;
+ case WSAENETUNREACH:
+ return ENETUNREACH;
+ case WSAENETRESET:
+ return ENETRESET;
+ case WSAECONNABORTED:
+ return ECONNABORTED;
+ case WSAECONNRESET:
+ return ECONNRESET;
+ case WSAENOBUFS:
+ return ENOBUFS;
+ case WSAEISCONN:
+ return EISCONN;
+ case WSAENOTCONN:
+ return ENOTCONN;
+ case WSAETIMEDOUT:
+ return ETIMEDOUT;
+ case WSAECONNREFUSED:
+ return ECONNREFUSED;
+ case WSAELOOP:
+ return ELOOP;
+ case WSAENAMETOOLONG:
+ return ENAMETOOLONG;
+ case WSAEHOSTUNREACH:
+ return EHOSTUNREACH;
+ case WSAENOTEMPTY:
+ return ENOTEMPTY;
+ default:
+ /* We just return a generic I/O error if we could not find a relevant error. */
+ return EIO;
+ }
+}
+
+static void _updateErrno(int success) {
+ errno = success ? 0 : _wsaErrorToErrno(WSAGetLastError());
+}
+
+static int _initWinsock() {
+ static int s_initialized = 0;
+ if (!s_initialized) {
+ static WSADATA wsadata;
+ int err = WSAStartup(MAKEWORD(2,2), &wsadata);
+ if (err != 0) {
+ errno = _wsaErrorToErrno(err);
+ return 0;
+ }
+ s_initialized = 1;
+ }
+ return 1;
+}
+
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return EAI_FAIL;
+ }
+
+ switch (getaddrinfo(node, service, hints, res)) {
+ case 0: return 0;
+ case WSATRY_AGAIN: return EAI_AGAIN;
+ case WSAEINVAL: return EAI_BADFLAGS;
+ case WSAEAFNOSUPPORT: return EAI_FAMILY;
+ case WSA_NOT_ENOUGH_MEMORY: return EAI_MEMORY;
+ case WSAHOST_NOT_FOUND: return EAI_NONAME;
+ case WSATYPE_NOT_FOUND: return EAI_SERVICE;
+ case WSAESOCKTNOSUPPORT: return EAI_SOCKTYPE;
+ default: return EAI_FAIL; /* Including WSANO_RECOVERY */
+ }
+}
+
+const char *win32_gai_strerror(int errcode) {
+ switch (errcode) {
+ case 0: errcode = 0; break;
+ case EAI_AGAIN: errcode = WSATRY_AGAIN; break;
+ case EAI_BADFLAGS: errcode = WSAEINVAL; break;
+ case EAI_FAMILY: errcode = WSAEAFNOSUPPORT; break;
+ case EAI_MEMORY: errcode = WSA_NOT_ENOUGH_MEMORY; break;
+ case EAI_NONAME: errcode = WSAHOST_NOT_FOUND; break;
+ case EAI_SERVICE: errcode = WSATYPE_NOT_FOUND; break;
+ case EAI_SOCKTYPE: errcode = WSAESOCKTNOSUPPORT; break;
+ default: errcode = WSANO_RECOVERY; break; /* Including EAI_FAIL */
+ }
+ return gai_strerror(errcode);
+}
+
+void win32_freeaddrinfo(struct addrinfo *res) {
+ freeaddrinfo(res);
+}
+
+SOCKET win32_socket(int domain, int type, int protocol) {
+ SOCKET s;
+
+ /* Note: This function is likely to be called before other functions, so run init here. */
+ if (!_initWinsock()) {
+ return INVALID_SOCKET;
+ }
+
+ _updateErrno((s = socket(domain, type, protocol)) != INVALID_SOCKET);
+ return s;
+}
+
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp) {
+ int ret = ioctlsocket(fd, (long)request, argp);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = bind(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen) {
+ int ret = connect(sockfd, addr, addrlen);
+ _updateErrno(ret != SOCKET_ERROR);
+
+ /* For Winsock connect(), the WSAEWOULDBLOCK error means the same thing as
+ * EINPROGRESS for POSIX connect(), so we do that translation to keep POSIX
+ * logic consistent. */
+ if (errno == EWOULDBLOCK) {
+ errno = EINPROGRESS;
+ }
+
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ if (*optlen >= sizeof (struct timeval)) {
+ struct timeval *tv = optval;
+ DWORD timeout = 0;
+ socklen_t dwlen = 0;
+ ret = getsockopt(sockfd, level, optname, (char *)&timeout, &dwlen);
+ tv->tv_sec = timeout / 1000;
+ tv->tv_usec = (timeout * 1000) % 1000000;
+ } else {
+ ret = WSAEFAULT;
+ }
+ *optlen = sizeof (struct timeval);
+ } else {
+ ret = getsockopt(sockfd, level, optname, (char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
+ int ret = 0;
+ if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
+ struct timeval *tv = optval;
+ DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
+ ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
+ } else {
+ ret = setsockopt(sockfd, level, optname, (const char*)optval, optlen);
+ }
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_close(SOCKET fd) {
+ int ret = closesocket(fd);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags) {
+ int ret = recv(sockfd, (char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags) {
+ int ret = send(sockfd, (const char*)buf, (int)len, flags);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout) {
+ int ret = WSAPoll(fds, nfds, timeout);
+ _updateErrno(ret != SOCKET_ERROR);
+ return ret != SOCKET_ERROR ? ret : -1;
+}
+#endif /* _WIN32 */
diff --git a/deps/hiredis/sockcompat.h b/deps/hiredis/sockcompat.h
new file mode 100644
index 000000000..56006c163
--- /dev/null
+++ b/deps/hiredis/sockcompat.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, Marcus Geelnard <m at bitsnbites dot eu>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __SOCKCOMPAT_H
+#define __SOCKCOMPAT_H
+
+#ifndef _WIN32
+/* For POSIX systems we use the standard BSD socket API. */
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <poll.h>
+#else
+/* For Windows we use winsock. */
+#undef _WIN32_WINNT
+#define _WIN32_WINNT 0x0600 /* To get WSAPoll etc. */
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <stddef.h>
+
+#ifdef _MSC_VER
+typedef signed long ssize_t;
+#endif
+
+/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */
+int win32_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
+const char *win32_gai_strerror(int errcode);
+void win32_freeaddrinfo(struct addrinfo *res);
+SOCKET win32_socket(int domain, int type, int protocol);
+int win32_ioctl(SOCKET fd, unsigned long request, unsigned long *argp);
+int win32_bind(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_connect(SOCKET sockfd, const struct sockaddr *addr, socklen_t addrlen);
+int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, socklen_t *optlen);
+int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen);
+int win32_close(SOCKET fd);
+ssize_t win32_recv(SOCKET sockfd, void *buf, size_t len, int flags);
+ssize_t win32_send(SOCKET sockfd, const void *buf, size_t len, int flags);
+typedef ULONG nfds_t;
+int win32_poll(struct pollfd *fds, nfds_t nfds, int timeout);
+
+#ifndef REDIS_SOCKCOMPAT_IMPLEMENTATION
+#define getaddrinfo(node, service, hints, res) win32_getaddrinfo(node, service, hints, res)
+#undef gai_strerror
+#define gai_strerror(errcode) win32_gai_strerror(errcode)
+#define freeaddrinfo(res) win32_freeaddrinfo(res)
+#define socket(domain, type, protocol) win32_socket(domain, type, protocol)
+#define ioctl(fd, request, argp) win32_ioctl(fd, request, argp)
+#define bind(sockfd, addr, addrlen) win32_bind(sockfd, addr, addrlen)
+#define connect(sockfd, addr, addrlen) win32_connect(sockfd, addr, addrlen)
+#define getsockopt(sockfd, level, optname, optval, optlen) win32_getsockopt(sockfd, level, optname, optval, optlen)
+#define setsockopt(sockfd, level, optname, optval, optlen) win32_setsockopt(sockfd, level, optname, optval, optlen)
+#define close(fd) win32_close(fd)
+#define recv(sockfd, buf, len, flags) win32_recv(sockfd, buf, len, flags)
+#define send(sockfd, buf, len, flags) win32_send(sockfd, buf, len, flags)
+#define poll(fds, nfds, timeout) win32_poll(fds, nfds, timeout)
+#endif /* REDIS_SOCKCOMPAT_IMPLEMENTATION */
+#endif /* _WIN32 */
+
+#endif /* __SOCKCOMPAT_H */
diff --git a/deps/hiredis/ssl.c b/deps/hiredis/ssl.c
new file mode 100644
index 000000000..78ab9e43e
--- /dev/null
+++ b/deps/hiredis/ssl.c
@@ -0,0 +1,448 @@
+/*
+ * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ * Copyright (c) 2019, Redis Labs
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "hiredis.h"
+#include "async.h"
+
+#include <assert.h>
+#include <pthread.h>
+#include <errno.h>
+#include <string.h>
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "async_private.h"
+
+void __redisSetError(redisContext *c, int type, const char *str);
+
+/* The SSL context is attached to SSL/TLS connections as a privdata. */
+typedef struct redisSSLContext {
+ /**
+ * OpenSSL SSL_CTX; It is optional and will not be set when using
+ * user-supplied SSL.
+ */
+ SSL_CTX *ssl_ctx;
+
+ /**
+ * OpenSSL SSL object.
+ */
+ SSL *ssl;
+
+ /**
+ * SSL_write() requires to be called again with the same arguments it was
+ * previously called with in the event of an SSL_read/SSL_write situation
+ */
+ size_t lastLen;
+
+ /** Whether the SSL layer requires read (possibly before a write) */
+ int wantRead;
+
+ /**
+ * Whether a write was requested prior to a read. If set, the write()
+ * should resume whenever a read takes place, if possible
+ */
+ int pendingWrite;
+} redisSSLContext;
+
+/* Forward declaration */
+redisContextFuncs redisContextSSLFuncs;
+
+#ifdef HIREDIS_SSL_TRACE
+/**
+ * Callback used for debugging
+ */
+static void sslLogCallback(const SSL *ssl, int where, int ret) {
+ const char *retstr = "";
+ int should_log = 1;
+ /* Ignore low-level SSL stuff */
+
+ if (where & SSL_CB_ALERT) {
+ should_log = 1;
+ }
+ if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
+ should_log = 1;
+ }
+ if ((where & SSL_CB_EXIT) && ret == 0) {
+ should_log = 1;
+ }
+
+ if (!should_log) {
+ return;
+ }
+
+ retstr = SSL_alert_type_string(ret);
+ printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
+
+ if (where == SSL_CB_HANDSHAKE_DONE) {
+ printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
+ }
+}
+#endif
+
+/**
+ * OpenSSL global initialization and locking handling callbacks.
+ * Note that this is only required for OpenSSL < 1.1.0.
+ */
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#define HIREDIS_USE_CRYPTO_LOCKS
+#endif
+
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+typedef pthread_mutex_t sslLockType;
+static void sslLockInit(sslLockType *l) {
+ pthread_mutex_init(l, NULL);
+}
+static void sslLockAcquire(sslLockType *l) {
+ pthread_mutex_lock(l);
+}
+static void sslLockRelease(sslLockType *l) {
+ pthread_mutex_unlock(l);
+}
+static pthread_mutex_t *ossl_locks;
+
+static void opensslDoLock(int mode, int lkid, const char *f, int line) {
+ sslLockType *l = ossl_locks + lkid;
+
+ if (mode & CRYPTO_LOCK) {
+ sslLockAcquire(l);
+ } else {
+ sslLockRelease(l);
+ }
+
+ (void)f;
+ (void)line;
+}
+
+static void initOpensslLocks(void) {
+ unsigned ii, nlocks;
+ if (CRYPTO_get_locking_callback() != NULL) {
+ /* Someone already set the callback before us. Don't destroy it! */
+ return;
+ }
+ nlocks = CRYPTO_num_locks();
+ ossl_locks = malloc(sizeof(*ossl_locks) * nlocks);
+ for (ii = 0; ii < nlocks; ii++) {
+ sslLockInit(ossl_locks + ii);
+ }
+ CRYPTO_set_locking_callback(opensslDoLock);
+}
+#endif /* HIREDIS_USE_CRYPTO_LOCKS */
+
+/**
+ * SSL Connection initialization.
+ */
+
+static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
+ if (c->privdata) {
+ __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
+ return REDIS_ERR;
+ }
+ c->privdata = calloc(1, sizeof(redisSSLContext));
+
+ c->funcs = &redisContextSSLFuncs;
+ redisSSLContext *rssl = c->privdata;
+
+ rssl->ssl_ctx = ssl_ctx;
+ rssl->ssl = ssl;
+
+ SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_set_fd(rssl->ssl, c->fd);
+ SSL_set_connect_state(rssl->ssl);
+
+ ERR_clear_error();
+ int rv = SSL_connect(rssl->ssl);
+ if (rv == 1) {
+ return REDIS_OK;
+ }
+
+ rv = SSL_get_error(rssl->ssl, rv);
+ if (((c->flags & REDIS_BLOCK) == 0) &&
+ (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
+ return REDIS_OK;
+ }
+
+ if (c->err == 0) {
+ char err[512];
+ if (rv == SSL_ERROR_SYSCALL)
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",strerror(errno));
+ else {
+ unsigned long e = ERR_peek_last_error();
+ snprintf(err,sizeof(err)-1,"SSL_connect failed: %s",
+ ERR_reason_error_string(e));
+ }
+ __redisSetError(c, REDIS_ERR_IO, err);
+ }
+ return REDIS_ERR;
+}
+
+int redisInitiateSSL(redisContext *c, SSL *ssl) {
+ return redisSSLConnect(c, NULL, ssl);
+}
+
+int redisSecureConnection(redisContext *c, const char *capath,
+ const char *certpath, const char *keypath, const char *servername) {
+
+ SSL_CTX *ssl_ctx = NULL;
+ SSL *ssl = NULL;
+
+ /* Initialize global OpenSSL stuff */
+ static int isInit = 0;
+ if (!isInit) {
+ isInit = 1;
+ SSL_library_init();
+#ifdef HIREDIS_USE_CRYPTO_LOCKS
+ initOpensslLocks();
+#endif
+ }
+
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ssl_ctx) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
+ goto error;
+ }
+
+#ifdef HIREDIS_SSL_TRACE
+ SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
+#endif
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+ if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
+ goto error;
+ }
+
+ if (capath) {
+ if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
+ goto error;
+ }
+ }
+ if (certpath) {
+ if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
+ goto error;
+ }
+ if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
+ goto error;
+ }
+ }
+
+ ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
+ goto error;
+ }
+ if (servername) {
+ if (!SSL_set_tlsext_host_name(ssl, servername)) {
+ __redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication");
+ goto error;
+ }
+ }
+
+ return redisSSLConnect(c, ssl_ctx, ssl);
+
+error:
+ if (ssl) SSL_free(ssl);
+ if (ssl_ctx) SSL_CTX_free(ssl_ctx);
+ return REDIS_ERR;
+}
+
+static int maybeCheckWant(redisSSLContext *rssl, int rv) {
+ /**
+ * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
+ * and true is returned. False is returned otherwise
+ */
+ if (rv == SSL_ERROR_WANT_READ) {
+ rssl->wantRead = 1;
+ return 1;
+ } else if (rv == SSL_ERROR_WANT_WRITE) {
+ rssl->pendingWrite = 1;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/**
+ * Implementation of redisContextFuncs for SSL connections.
+ */
+
+static void redisSSLFreeContext(void *privdata){
+ redisSSLContext *rsc = privdata;
+
+ if (!rsc) return;
+ if (rsc->ssl) {
+ SSL_free(rsc->ssl);
+ rsc->ssl = NULL;
+ }
+ if (rsc->ssl_ctx) {
+ SSL_CTX_free(rsc->ssl_ctx);
+ rsc->ssl_ctx = NULL;
+ }
+ free(rsc);
+}
+
+static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
+ redisSSLContext *rssl = c->privdata;
+
+ int nread = SSL_read(rssl->ssl, buf, bufcap);
+ if (nread > 0) {
+ return nread;
+ } else if (nread == 0) {
+ __redisSetError(c, REDIS_ERR_EOF, "Server closed the connection");
+ return -1;
+ } else {
+ int err = SSL_get_error(rssl->ssl, nread);
+ if (c->flags & REDIS_BLOCK) {
+ /**
+ * In blocking mode, we should never end up in a situation where
+ * we get an error without it being an actual error, except
+ * in the case of EINTR, which can be spuriously received from
+ * debuggers or whatever.
+ */
+ if (errno == EINTR) {
+ return 0;
+ } else {
+ const char *msg = NULL;
+ if (errno == EAGAIN) {
+ msg = "Resource temporarily unavailable";
+ }
+ __redisSetError(c, REDIS_ERR_IO, msg);
+ return -1;
+ }
+ }
+
+ /**
+ * We can very well get an EWOULDBLOCK/EAGAIN, however
+ */
+ if (maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+}
+
+static int redisSSLWrite(redisContext *c) {
+ redisSSLContext *rssl = c->privdata;
+
+ size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf);
+ int rv = SSL_write(rssl->ssl, c->obuf, len);
+
+ if (rv > 0) {
+ rssl->lastLen = 0;
+ } else if (rv < 0) {
+ rssl->lastLen = len;
+
+ int err = SSL_get_error(rssl->ssl, rv);
+ if ((c->flags & REDIS_BLOCK) == 0 && maybeCheckWant(rssl, err)) {
+ return 0;
+ } else {
+ __redisSetError(c, REDIS_ERR_IO, NULL);
+ return -1;
+ }
+ }
+ return rv;
+}
+
+static void redisSSLAsyncRead(redisAsyncContext *ac) {
+ int rv;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->wantRead = 0;
+
+ if (rssl->pendingWrite) {
+ int done;
+
+ /* This is probably just a write event */
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ } else if (!done) {
+ _EL_ADD_WRITE(ac);
+ }
+ }
+
+ rv = redisBufferRead(c);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ } else {
+ _EL_ADD_READ(ac);
+ redisProcessCallbacks(ac);
+ }
+}
+
+static void redisSSLAsyncWrite(redisAsyncContext *ac) {
+ int rv, done = 0;
+ redisSSLContext *rssl = ac->c.privdata;
+ redisContext *c = &ac->c;
+
+ rssl->pendingWrite = 0;
+ rv = redisBufferWrite(c, &done);
+ if (rv == REDIS_ERR) {
+ __redisAsyncDisconnect(ac);
+ return;
+ }
+
+ if (!done) {
+ if (rssl->wantRead) {
+ /* Need to read-before-write */
+ rssl->pendingWrite = 1;
+ _EL_DEL_WRITE(ac);
+ } else {
+ /* No extra reads needed, just need to write more */
+ _EL_ADD_WRITE(ac);
+ }
+ } else {
+ /* Already done! */
+ _EL_DEL_WRITE(ac);
+ }
+
+ /* Always reschedule a read */
+ _EL_ADD_READ(ac);
+}
+
+redisContextFuncs redisContextSSLFuncs = {
+ .free_privdata = redisSSLFreeContext,
+ .async_read = redisSSLAsyncRead,
+ .async_write = redisSSLAsyncWrite,
+ .read = redisSSLRead,
+ .write = redisSSLWrite
+};
+
diff --git a/deps/hiredis/test.c b/deps/hiredis/test.c
index a23d60676..8668e1856 100644
--- a/deps/hiredis/test.c
+++ b/deps/hiredis/test.c
@@ -3,7 +3,9 @@
#include <stdlib.h>
#include <string.h>
#include <strings.h>
+#include <sys/socket.h>
#include <sys/time.h>
+#include <netdb.h>
#include <assert.h>
#include <unistd.h>
#include <signal.h>
@@ -11,12 +13,16 @@
#include <limits.h>
#include "hiredis.h"
+#ifdef HIREDIS_TEST_SSL
+#include "hiredis_ssl.h"
+#endif
#include "net.h"
enum connection_type {
CONN_TCP,
CONN_UNIX,
- CONN_FD
+ CONN_FD,
+ CONN_SSL
};
struct config {
@@ -31,6 +37,14 @@ struct config {
struct {
const char *path;
} unix_sock;
+
+ struct {
+ const char *host;
+ int port;
+ const char *ca_cert;
+ const char *cert;
+ const char *key;
+ } ssl;
};
/* The following lines make up our testing "framework" :) */
@@ -91,11 +105,27 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1;
}
-static redisContext *connect(struct config config) {
+static void do_ssl_handshake(redisContext *c, struct config config) {
+#ifdef HIREDIS_TEST_SSL
+ redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL);
+ if (c->err) {
+ printf("SSL error: %s\n", c->errstr);
+ redisFree(c);
+ exit(1);
+ }
+#else
+ (void) c;
+ (void) config;
+#endif
+}
+
+static redisContext *do_connect(struct config config) {
redisContext *c = NULL;
if (config.type == CONN_TCP) {
c = redisConnect(config.tcp.host, config.tcp.port);
+ } else if (config.type == CONN_SSL) {
+ c = redisConnect(config.ssl.host, config.ssl.port);
} else if (config.type == CONN_UNIX) {
c = redisConnectUnix(config.unix_sock.path);
} else if (config.type == CONN_FD) {
@@ -119,9 +149,21 @@ static redisContext *connect(struct config config) {
exit(1);
}
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c, config);
+ }
+
return select_database(c);
}
+static void do_reconnect(redisContext *c, struct config config) {
+ redisReconnect(c);
+
+ if (config.type == CONN_SSL) {
+ do_ssl_handshake(c, config);
+ }
+}
+
static void test_format_commands(void) {
char *cmd;
int len;
@@ -248,7 +290,7 @@ static void test_append_formatted_commands(struct config config) {
char *cmd;
int len;
- c = connect(config);
+ c = do_connect(config);
test("Append format command: ");
@@ -302,6 +344,82 @@ static void test_reply_reader(void) {
strncasecmp(reader->errstr,"No support for",14) == 0);
redisReaderFree(reader);
+ test("Correctly parses LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775807\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MAX);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when > LLONG_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":9223372036854775808\r\n",22);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Correctly parses LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775808\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_OK &&
+ ((redisReply*)reply)->type == REDIS_REPLY_INTEGER &&
+ ((redisReply*)reply)->integer == LLONG_MIN);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when < LLONG_MIN: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, ":-9223372036854775809\r\n",23);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bad integer value") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when array < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*-2\r\n+asdf\r\n",12);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk < -1: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$-2\r\nasdf\r\n",11);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+#if LLONG_MAX > SIZE_MAX
+ test("Set error when array > SIZE_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "*9223372036854775807\r\n+asdf\r\n",29);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Multi-bulk length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+
+ test("Set error when bulk > SIZE_MAX: ");
+ reader = redisReaderCreate();
+ redisReaderFeed(reader, "$9223372036854775807\r\nasdf\r\n",28);
+ ret = redisReaderGetReply(reader,&reply);
+ test_cond(ret == REDIS_ERR &&
+ strcasecmp(reader->errstr,"Bulk string length out of range") == 0);
+ freeReplyObject(reply);
+ redisReaderFree(reader);
+#endif
+
test("Works with NULL functions for reply: ");
reader = redisReaderCreate();
reader->fn = NULL;
@@ -356,20 +474,35 @@ static void test_free_null(void) {
test_cond(reply == NULL);
}
+#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) {
redisContext *c;
-
- test("Returns error when host cannot be resolved: ");
- c = redisConnect((char*)"idontexist.test", 6379);
- test_cond(c->err == REDIS_ERR_OTHER &&
- (strcmp(c->errstr,"Name or service not known") == 0 ||
- strcmp(c->errstr,"Can't resolve: idontexist.test") == 0 ||
- strcmp(c->errstr,"nodename nor servname provided, or not known") == 0 ||
- strcmp(c->errstr,"No address associated with hostname") == 0 ||
- strcmp(c->errstr,"Temporary failure in name resolution") == 0 ||
- strcmp(c->errstr,"hostname nor servname provided, or not known") == 0 ||
- strcmp(c->errstr,"no address associated with name") == 0));
- redisFree(c);
+ struct addrinfo hints = {.ai_family = AF_INET};
+ struct addrinfo *ai_tmp = NULL;
+
+ int rv = getaddrinfo(HIREDIS_BAD_DOMAIN, "6379", &hints, &ai_tmp);
+ if (rv != 0) {
+ // Address does *not* exist
+ test("Returns error when host cannot be resolved: ");
+ // First see if this domain name *actually* resolves to NXDOMAIN
+ c = redisConnect(HIREDIS_BAD_DOMAIN, 6379);
+ test_cond(
+ c->err == REDIS_ERR_OTHER &&
+ (strcmp(c->errstr, "Name or service not known") == 0 ||
+ strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
+ strcmp(c->errstr, "Name does not resolve") == 0 ||
+ strcmp(c->errstr,
+ "nodename nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "No address associated with hostname") == 0 ||
+ strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
+ strcmp(c->errstr,
+ "hostname nor servname provided, or not known") == 0 ||
+ strcmp(c->errstr, "no address associated with name") == 0));
+ redisFree(c);
+ } else {
+ printf("Skipping NXDOMAIN test. Found evil ISP!\n");
+ freeaddrinfo(ai_tmp);
+ }
test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1);
@@ -387,7 +520,7 @@ static void test_blocking_connection(struct config config) {
redisContext *c;
redisReply *reply;
- c = connect(config);
+ c = do_connect(config);
test("Is able to deliver commands: ");
reply = redisCommand(c,"PING");
@@ -468,7 +601,7 @@ static void test_blocking_connection_timeouts(struct config config) {
const char *cmd = "DEBUG SLEEP 3\r\n";
struct timeval tv;
- c = connect(config);
+ c = do_connect(config);
test("Successfully completes a command when the timeout is not exceeded: ");
reply = redisCommand(c,"SET foo fast");
freeReplyObject(reply);
@@ -480,9 +613,10 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply);
disconnect(c, 0);
- c = connect(config);
+ c = do_connect(config);
test("Does not return a reply when the command times out: ");
- s = write(c->fd, cmd, strlen(cmd));
+ redisAppendFormattedCommand(c, cmd, strlen(cmd));
+ s = c->funcs->write(c);
tv.tv_sec = 0;
tv.tv_usec = 10000;
redisSetTimeout(c, tv);
@@ -491,7 +625,7 @@ static void test_blocking_connection_timeouts(struct config config) {
freeReplyObject(reply);
test("Reconnect properly reconnects after a timeout: ");
- redisReconnect(c);
+ do_reconnect(c, config);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
@@ -499,7 +633,7 @@ static void test_blocking_connection_timeouts(struct config config) {
test("Reconnect properly uses owned parameters: ");
config.tcp.host = "foo";
config.unix_sock.path = "foo";
- redisReconnect(c);
+ do_reconnect(c, config);
reply = redisCommand(c, "PING");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && strcmp(reply->str, "PONG") == 0);
freeReplyObject(reply);
@@ -514,7 +648,7 @@ static void test_blocking_io_errors(struct config config) {
int major, minor;
/* Connect to target given by config. */
- c = connect(config);
+ c = do_connect(config);
{
/* Find out Redis version to determine the path for the next test */
const char *field = "redis_version:";
@@ -549,7 +683,7 @@ static void test_blocking_io_errors(struct config config) {
strcmp(c->errstr,"Server closed the connection") == 0);
redisFree(c);
- c = connect(config);
+ c = do_connect(config);
test("Returns I/O error on socket timeout: ");
struct timeval tv = { 0, 1000 };
assert(redisSetTimeout(c,tv) == REDIS_OK);
@@ -583,7 +717,7 @@ static void test_invalid_timeout_errors(struct config config) {
}
static void test_throughput(struct config config) {
- redisContext *c = connect(config);
+ redisContext *c = do_connect(config);
redisReply **replies;
int i, num;
long long t1, t2;
@@ -616,6 +750,17 @@ static void test_throughput(struct config config) {
free(replies);
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
+ replies = malloc(sizeof(redisReply*)*num);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
+
num = 10000;
replies = malloc(sizeof(redisReply*)*num);
for (i = 0; i < num; i++)
@@ -644,6 +789,19 @@ static void test_throughput(struct config config) {
free(replies);
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+ replies = malloc(sizeof(redisReply*)*num);
+ for (i = 0; i < num; i++)
+ redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
+ t1 = usec();
+ for (i = 0; i < num; i++) {
+ assert(redisGetReply(c, (void*)&replies[i]) == REDIS_OK);
+ assert(replies[i] != NULL && replies[i]->type == REDIS_REPLY_INTEGER);
+ }
+ t2 = usec();
+ for (i = 0; i < num; i++) freeReplyObject(replies[i]);
+ free(replies);
+ printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
+
disconnect(c, 0);
}
@@ -778,6 +936,23 @@ int main(int argc, char **argv) {
throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0;
+#ifdef HIREDIS_TEST_SSL
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
+ argv++; argc--;
+ cfg.ssl.port = atoi(argv[0]);
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-host")) {
+ argv++; argc--;
+ cfg.ssl.host = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-ca-cert")) {
+ argv++; argc--;
+ cfg.ssl.ca_cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-cert")) {
+ argv++; argc--;
+ cfg.ssl.cert = argv[0];
+ } else if (argc >= 2 && !strcmp(argv[0],"--ssl-key")) {
+ argv++; argc--;
+ cfg.ssl.key = argv[0];
+#endif
} else {
fprintf(stderr, "Invalid argument: %s\n", argv[0]);
exit(1);
@@ -806,6 +981,20 @@ int main(int argc, char **argv) {
test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg);
+#ifdef HIREDIS_TEST_SSL
+ if (cfg.ssl.port && cfg.ssl.host) {
+ printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
+ cfg.type = CONN_SSL;
+
+ test_blocking_connection(cfg);
+ test_blocking_connection_timeouts(cfg);
+ test_blocking_io_errors(cfg);
+ test_invalid_timeout_errors(cfg);
+ test_append_formatted_commands(cfg);
+ if (throughput) test_throughput(cfg);
+ }
+#endif
+
if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path);
cfg.type = CONN_FD;
diff --git a/deps/hiredis/test.sh b/deps/hiredis/test.sh
new file mode 100755
index 000000000..2cab9e6fb
--- /dev/null
+++ b/deps/hiredis/test.sh
@@ -0,0 +1,70 @@
+#!/bin/sh -ue
+
+REDIS_SERVER=${REDIS_SERVER:-redis-server}
+REDIS_PORT=${REDIS_PORT:-56379}
+REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
+TEST_SSL=${TEST_SSL:-0}
+SSL_TEST_ARGS=
+
+tmpdir=$(mktemp -d)
+PID_FILE=${tmpdir}/hiredis-test-redis.pid
+SOCK_FILE=${tmpdir}/hiredis-test-redis.sock
+
+if [ "$TEST_SSL" = "1" ]; then
+ SSL_CA_CERT=${tmpdir}/ca.crt
+ SSL_CA_KEY=${tmpdir}/ca.key
+ SSL_CERT=${tmpdir}/redis.crt
+ SSL_KEY=${tmpdir}/redis.key
+
+ openssl genrsa -out ${tmpdir}/ca.key 4096
+ openssl req \
+ -x509 -new -nodes -sha256 \
+ -key ${SSL_CA_KEY} \
+ -days 3650 \
+ -subj '/CN=Hiredis Test CA' \
+ -out ${SSL_CA_CERT}
+ openssl genrsa -out ${SSL_KEY} 2048
+ openssl req \
+ -new -sha256 \
+ -key ${SSL_KEY} \
+ -subj '/CN=Hiredis Test Cert' | \
+ openssl x509 \
+ -req -sha256 \
+ -CA ${SSL_CA_CERT} \
+ -CAkey ${SSL_CA_KEY} \
+ -CAserial ${tmpdir}/ca.txt \
+ -CAcreateserial \
+ -days 365 \
+ -out ${SSL_CERT}
+
+ SSL_TEST_ARGS="--ssl-host 127.0.0.1 --ssl-port ${REDIS_SSL_PORT} --ssl-ca-cert ${SSL_CA_CERT} --ssl-cert ${SSL_CERT} --ssl-key ${SSL_KEY}"
+fi
+
+cleanup() {
+ set +e
+ kill $(cat ${PID_FILE})
+ rm -rf ${tmpdir}
+}
+trap cleanup INT TERM EXIT
+
+cat > ${tmpdir}/redis.conf <<EOF
+daemonize yes
+pidfile ${PID_FILE}
+port ${REDIS_PORT}
+bind 127.0.0.1
+unixsocket ${SOCK_FILE}
+EOF
+
+if [ "$TEST_SSL" = "1" ]; then
+ cat >> ${tmpdir}/redis.conf <<EOF
+tls-port ${REDIS_SSL_PORT}
+tls-ca-cert-file ${SSL_CA_CERT}
+tls-cert-file ${SSL_CERT}
+tls-key-file ${SSL_KEY}
+EOF
+fi
+
+cat ${tmpdir}/redis.conf
+${REDIS_SERVER} ${tmpdir}/redis.conf
+
+${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS}
diff --git a/deps/hiredis/win32.h b/deps/hiredis/win32.h
index 1a27c18f2..04289c696 100644
--- a/deps/hiredis/win32.h
+++ b/deps/hiredis/win32.h
@@ -2,10 +2,20 @@
#define _WIN32_HELPER_INCLUDE
#ifdef _MSC_VER
+#include <winsock2.h> /* for struct timeval */
+
#ifndef inline
#define inline __inline
#endif
+#ifndef strcasecmp
+#define strcasecmp stricmp
+#endif
+
+#ifndef strncasecmp
+#define strncasecmp strnicmp
+#endif
+
#ifndef va_copy
#define va_copy(d,s) ((d) = (s))
#endif
@@ -37,6 +47,10 @@ __inline int c99_snprintf(char* str, size_t size, const char* format, ...)
return count;
}
#endif
+#endif /* _MSC_VER */
-#endif
-#endif \ No newline at end of file
+#ifdef _WIN32
+#define strerror_r(errno,buf,len) strerror_s(buf,len,errno)
+#endif /* _WIN32 */
+
+#endif /* _WIN32_HELPER_INCLUDE */
diff --git a/deps/jemalloc/.appveyor.yml b/deps/jemalloc/.appveyor.yml
new file mode 100644
index 000000000..9a7d00a99
--- /dev/null
+++ b/deps/jemalloc/.appveyor.yml
@@ -0,0 +1,42 @@
+version: '{build}'
+
+environment:
+ matrix:
+ - MSYSTEM: MINGW64
+ CPU: x86_64
+ MSVC: amd64
+ - MSYSTEM: MINGW32
+ CPU: i686
+ MSVC: x86
+ - MSYSTEM: MINGW64
+ CPU: x86_64
+ - MSYSTEM: MINGW32
+ CPU: i686
+ - MSYSTEM: MINGW64
+ CPU: x86_64
+ MSVC: amd64
+ CONFIG_FLAGS: --enable-debug
+ - MSYSTEM: MINGW32
+ CPU: i686
+ MSVC: x86
+ CONFIG_FLAGS: --enable-debug
+ - MSYSTEM: MINGW64
+ CPU: x86_64
+ CONFIG_FLAGS: --enable-debug
+ - MSYSTEM: MINGW32
+ CPU: i686
+ CONFIG_FLAGS: --enable-debug
+
+install:
+ - set PATH=c:\msys64\%MSYSTEM%\bin;c:\msys64\usr\bin;%PATH%
+ - if defined MSVC call "c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" %MSVC%
+ - if defined MSVC pacman --noconfirm -Rsc mingw-w64-%CPU%-gcc gcc
+ - pacman --noconfirm -Suy mingw-w64-%CPU%-make
+
+build_script:
+ - bash -c "autoconf"
+ - bash -c "./configure $CONFIG_FLAGS"
+ - mingw32-make
+ - file lib/jemalloc.dll
+ - mingw32-make tests
+ - mingw32-make -k check
diff --git a/deps/jemalloc/.gitignore b/deps/jemalloc/.gitignore
index d0e393619..19199ccb7 100644
--- a/deps/jemalloc/.gitignore
+++ b/deps/jemalloc/.gitignore
@@ -1,5 +1,3 @@
-/*.gcov.*
-
/bin/jemalloc-config
/bin/jemalloc.sh
/bin/jeprof
@@ -21,10 +19,14 @@
/Makefile
-/include/jemalloc/internal/jemalloc_internal.h
+/include/jemalloc/internal/jemalloc_preamble.h
/include/jemalloc/internal/jemalloc_internal_defs.h
+/include/jemalloc/internal/private_namespace.gen.h
/include/jemalloc/internal/private_namespace.h
-/include/jemalloc/internal/private_unnamespace.h
+/include/jemalloc/internal/private_namespace_jet.gen.h
+/include/jemalloc/internal/private_namespace_jet.h
+/include/jemalloc/internal/private_symbols.awk
+/include/jemalloc/internal/private_symbols_jet.awk
/include/jemalloc/internal/public_namespace.h
/include/jemalloc/internal/public_symbols.txt
/include/jemalloc/internal/public_unnamespace.h
@@ -40,8 +42,9 @@
/include/jemalloc/jemalloc_typedefs.h
/src/*.[od]
-/src/*.gcda
-/src/*.gcno
+/src/*.sym
+
+/run_tests.out/
/test/test.sh
test/include/test/jemalloc_test.h
@@ -50,26 +53,41 @@ test/include/test/jemalloc_test_defs.h
/test/integration/[A-Za-z]*
!/test/integration/[A-Za-z]*.*
/test/integration/*.[od]
-/test/integration/*.gcda
-/test/integration/*.gcno
/test/integration/*.out
+/test/integration/cpp/[A-Za-z]*
+!/test/integration/cpp/[A-Za-z]*.*
+/test/integration/cpp/*.[od]
+/test/integration/cpp/*.out
+
/test/src/*.[od]
-/test/src/*.gcda
-/test/src/*.gcno
/test/stress/[A-Za-z]*
!/test/stress/[A-Za-z]*.*
/test/stress/*.[od]
-/test/stress/*.gcda
-/test/stress/*.gcno
/test/stress/*.out
/test/unit/[A-Za-z]*
!/test/unit/[A-Za-z]*.*
/test/unit/*.[od]
-/test/unit/*.gcda
-/test/unit/*.gcno
/test/unit/*.out
/VERSION
+
+*.pdb
+*.sdf
+*.opendb
+*.VC.db
+*.opensdf
+*.cachefile
+*.suo
+*.user
+*.sln.docstates
+*.tmp
+.vs/
+/msvc/Win32/
+/msvc/x64/
+/msvc/projects/*/*/Debug*/
+/msvc/projects/*/*/Release*/
+/msvc/projects/*/*/Win32/
+/msvc/projects/*/*/x64/
diff --git a/deps/jemalloc/.travis.yml b/deps/jemalloc/.travis.yml
new file mode 100644
index 000000000..4cc116e5f
--- /dev/null
+++ b/deps/jemalloc/.travis.yml
@@ -0,0 +1,156 @@
+language: generic
+dist: precise
+
+matrix:
+ include:
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: osx
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=clang CXX=clang++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--enable-debug" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="-m32" CONFIGURE_FLAGS="--with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ addons:
+ apt:
+ packages:
+ - gcc-multilib
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --enable-prof" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-debug --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof --disable-stats" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--enable-prof --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=tcache:false" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--disable-stats --with-malloc-conf=background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,dss:primary" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=tcache:false,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary,percpu_arena:percpu" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=dss:primary,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+ - os: linux
+ env: CC=gcc CXX=g++ COMPILER_FLAGS="" CONFIGURE_FLAGS="--with-malloc-conf=percpu_arena:percpu,background_thread:true" EXTRA_CFLAGS="-Werror -Wno-array-bounds"
+
+
+before_script:
+ - autoconf
+ - ./configure ${COMPILER_FLAGS:+ CC="$CC $COMPILER_FLAGS" CXX="$CXX $COMPILER_FLAGS" } $CONFIGURE_FLAGS
+ - make -j3
+ - make -j3 tests
+
+script:
+ - make check
+
diff --git a/deps/jemalloc/COPYING b/deps/jemalloc/COPYING
index 611968cda..98458d971 100644
--- a/deps/jemalloc/COPYING
+++ b/deps/jemalloc/COPYING
@@ -1,10 +1,10 @@
Unless otherwise specified, files in the jemalloc source distribution are
subject to the following license:
--------------------------------------------------------------------------------
-Copyright (C) 2002-2015 Jason Evans <jasone@canonware.com>.
+Copyright (C) 2002-2018 Jason Evans <jasone@canonware.com>.
All rights reserved.
Copyright (C) 2007-2012 Mozilla Foundation. All rights reserved.
-Copyright (C) 2009-2015 Facebook, Inc. All rights reserved.
+Copyright (C) 2009-2018 Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
diff --git a/deps/jemalloc/ChangeLog b/deps/jemalloc/ChangeLog
index e3b0a5190..29a00fb78 100644
--- a/deps/jemalloc/ChangeLog
+++ b/deps/jemalloc/ChangeLog
@@ -4,6 +4,600 @@ brevity. Much more detail can be found in the git revision history:
https://github.com/jemalloc/jemalloc
+* 5.1.0 (May 4th, 2018)
+
+ This release is primarily about fine-tuning, ranging from several new features
+ to numerous notable performance and portability enhancements. The release and
+ prior dev versions have been running in multiple large scale applications for
+ months, and the cumulative improvements are substantial in many cases.
+
+ Given the long and successful production runs, this release is likely a good
+ candidate for applications to upgrade, from both jemalloc 5.0 and before. For
+ performance-critical applications, the newly added TUNING.md provides
+ guidelines on jemalloc tuning.
+
+ New features:
+ - Implement transparent huge page support for internal metadata. (@interwq)
+ - Add opt.thp to allow enabling / disabling transparent huge pages for all
+ mappings. (@interwq)
+ - Add maximum background thread count option. (@djwatson)
+ - Allow prof_active to control opt.lg_prof_interval and prof.gdump.
+ (@interwq)
+ - Allow arena index lookup based on allocation addresses via mallctl.
+ (@lionkov)
+ - Allow disabling initial-exec TLS model. (@davidtgoldblatt, @KenMacD)
+ - Add opt.lg_extent_max_active_fit to set the max ratio between the size of
+ the active extent selected (to split off from) and the size of the requested
+ allocation. (@interwq, @davidtgoldblatt)
+ - Add retain_grow_limit to set the max size when growing virtual address
+ space. (@interwq)
+ - Add mallctl interfaces:
+ + arena.<i>.retain_grow_limit (@interwq)
+ + arenas.lookup (@lionkov)
+ + max_background_threads (@djwatson)
+ + opt.lg_extent_max_active_fit (@interwq)
+ + opt.max_background_threads (@djwatson)
+ + opt.metadata_thp (@interwq)
+ + opt.thp (@interwq)
+ + stats.metadata_thp (@interwq)
+
+ Portability improvements:
+ - Support GNU/kFreeBSD configuration. (@paravoid)
+ - Support m68k, nios2 and SH3 architectures. (@paravoid)
+ - Fall back to FD_CLOEXEC when O_CLOEXEC is unavailable. (@zonyitoo)
+ - Fix symbol listing for cross-compiling. (@tamird)
+ - Fix high bits computation on ARM. (@davidtgoldblatt, @paravoid)
+ - Disable the CPU_SPINWAIT macro for Power. (@davidtgoldblatt, @marxin)
+ - Fix MSVC 2015 & 2017 builds. (@rustyx)
+ - Improve RISC-V support. (@EdSchouten)
+ - Set name mangling script in strict mode. (@nicolov)
+ - Avoid MADV_HUGEPAGE on ARM. (@marxin)
+ - Modify configure to determine return value of strerror_r.
+ (@davidtgoldblatt, @cferris1000)
+ - Make sure CXXFLAGS is tested with CPP compiler. (@nehaljwani)
+ - Fix 32-bit build on MSVC. (@rustyx)
+ - Fix external symbol on MSVC. (@maksqwe)
+ - Avoid a printf format specifier warning. (@jasone)
+ - Add configure option --disable-initial-exec-tls which can allow jemalloc to
+ be dynamically loaded after program startup. (@davidtgoldblatt, @KenMacD)
+ - AArch64: Add ILP32 support. (@cmuellner)
+ - Add --with-lg-vaddr configure option to support cross compiling.
+ (@cmuellner, @davidtgoldblatt)
+
+ Optimizations and refactors:
+ - Improve active extent fit with extent_max_active_fit. This considerably
+ reduces fragmentation over time and improves virtual memory and metadata
+ usage. (@davidtgoldblatt, @interwq)
+ - Eagerly coalesce large extents to reduce fragmentation. (@interwq)
+ - sdallocx: only read size info when page aligned (i.e. possibly sampled),
+ which speeds up the sized deallocation path significantly. (@interwq)
+ - Avoid attempting new mappings for in place expansion with retain, since
+ it rarely succeeds in practice and causes high overhead. (@interwq)
+ - Refactor OOM handling in newImpl. (@wqfish)
+ - Add internal fine-grained logging functionality for debugging use.
+ (@davidtgoldblatt)
+ - Refactor arena / tcache interactions. (@davidtgoldblatt)
+ - Refactor extent management with dumpable flag. (@davidtgoldblatt)
+ - Add runtime detection of lazy purging. (@interwq)
+ - Use pairing heap instead of red-black tree for extents_avail. (@djwatson)
+ - Use sysctl on startup in FreeBSD. (@trasz)
+ - Use thread local prng state instead of atomic. (@djwatson)
+ - Make decay to always purge one more extent than before, because in
+ practice large extents are usually the ones that cross the decay threshold.
+ Purging the additional extent helps save memory as well as reduce VM
+ fragmentation. (@interwq)
+ - Fast division by dynamic values. (@davidtgoldblatt)
+ - Improve the fit for aligned allocation. (@interwq, @edwinsmith)
+ - Refactor extent_t bitpacking. (@rkmisra)
+ - Optimize the generated assembly for ticker operations. (@davidtgoldblatt)
+ - Convert stats printing to use a structured text emitter. (@davidtgoldblatt)
+ - Remove preserve_lru feature for extents management. (@djwatson)
+ - Consolidate two memory loads into one on the fast deallocation path.
+ (@davidtgoldblatt, @interwq)
+
+ Bug fixes (most of the issues are only relevant to jemalloc 5.0):
+ - Fix deadlock with multithreaded fork in OS X. (@davidtgoldblatt)
+ - Validate returned file descriptor before use. (@zonyitoo)
+ - Fix a few background thread initialization and shutdown issues. (@interwq)
+ - Fix an extent coalesce + decay race by taking both coalescing extents off
+ the LRU list. (@interwq)
+ - Fix potentially unbound increase during decay, caused by one thread keep
+ stashing memory to purge while other threads generating new pages. The
+ number of pages to purge is checked to prevent this. (@interwq)
+ - Fix a FreeBSD bootstrap assertion. (@strejda, @interwq)
+ - Handle 32 bit mutex counters. (@rkmisra)
+ - Fix a indexing bug when creating background threads. (@davidtgoldblatt,
+ @binliu19)
+ - Fix arguments passed to extent_init. (@yuleniwo, @interwq)
+ - Fix addresses used for ordering mutexes. (@rkmisra)
+ - Fix abort_conf processing during bootstrap. (@interwq)
+ - Fix include path order for out-of-tree builds. (@cmuellner)
+
+ Incompatible changes:
+ - Remove --disable-thp. (@interwq)
+ - Remove mallctl interfaces:
+ + config.thp (@interwq)
+
+ Documentation:
+ - Add TUNING.md. (@interwq, @davidtgoldblatt, @djwatson)
+
+* 5.0.1 (July 1, 2017)
+
+ This bugfix release fixes several issues, most of which are obscure enough
+ that typical applications are not impacted.
+
+ Bug fixes:
+ - Update decay->nunpurged before purging, in order to avoid potential update
+ races and subsequent incorrect purging volume. (@interwq)
+ - Only abort on dlsym(3) error if the failure impacts an enabled feature (lazy
+ locking and/or background threads). This mitigates an initialization
+ failure bug for which we still do not have a clear reproduction test case.
+ (@interwq)
+ - Modify tsd management so that it neither crashes nor leaks if a thread's
+ only allocation activity is to call free() after TLS destructors have been
+ executed. This behavior was observed when operating with GNU libc, and is
+ unlikely to be an issue with other libc implementations. (@interwq)
+ - Mask signals during background thread creation. This prevents signals from
+ being inadvertently delivered to background threads. (@jasone,
+ @davidtgoldblatt, @interwq)
+ - Avoid inactivity checks within background threads, in order to prevent
+ recursive mutex acquisition. (@interwq)
+ - Fix extent_grow_retained() to use the specified hooks when the
+ arena.<i>.extent_hooks mallctl is used to override the default hooks.
+ (@interwq)
+ - Add missing reentrancy support for custom extent hooks which allocate.
+ (@interwq)
+ - Post-fork(2), re-initialize the list of tcaches associated with each arena
+ to contain no tcaches except the forking thread's. (@interwq)
+ - Add missing post-fork(2) mutex reinitialization for extent_grow_mtx. This
+ fixes potential deadlocks after fork(2). (@interwq)
+ - Enforce minimum autoconf version (currently 2.68), since 2.63 is known to
+ generate corrupt configure scripts. (@jasone)
+ - Ensure that the configured page size (--with-lg-page) is no larger than the
+ configured huge page size (--with-lg-hugepage). (@jasone)
+
+* 5.0.0 (June 13, 2017)
+
+ Unlike all previous jemalloc releases, this release does not use naturally
+ aligned "chunks" for virtual memory management, and instead uses page-aligned
+ "extents". This change has few externally visible effects, but the internal
+ impacts are... extensive. Many other internal changes combine to make this
+ the most cohesively designed version of jemalloc so far, with ample
+ opportunity for further enhancements.
+
+ Continuous integration is now an integral aspect of development thanks to the
+ efforts of @davidtgoldblatt, and the dev branch tends to remain reasonably
+ stable on the tested platforms (Linux, FreeBSD, macOS, and Windows). As a
+ side effect the official release frequency may decrease over time.
+
+ New features:
+ - Implement optional per-CPU arena support; threads choose which arena to use
+ based on current CPU rather than on fixed thread-->arena associations.
+ (@interwq)
+ - Implement two-phase decay of unused dirty pages. Pages transition from
+ dirty-->muzzy-->clean, where the first phase transition relies on
+ madvise(... MADV_FREE) semantics, and the second phase transition discards
+ pages such that they are replaced with demand-zeroed pages on next access.
+ (@jasone)
+ - Increase decay time resolution from seconds to milliseconds. (@jasone)
+ - Implement opt-in per CPU background threads, and use them for asynchronous
+ decay-driven unused dirty page purging. (@interwq)
+ - Add mutex profiling, which collects a variety of statistics useful for
+ diagnosing overhead/contention issues. (@interwq)
+ - Add C++ new/delete operator bindings. (@djwatson)
+ - Support manually created arena destruction, such that all data and metadata
+ are discarded. Add MALLCTL_ARENAS_DESTROYED for accessing merged stats
+ associated with destroyed arenas. (@jasone)
+ - Add MALLCTL_ARENAS_ALL as a fixed index for use in accessing
+ merged/destroyed arena statistics via mallctl. (@jasone)
+ - Add opt.abort_conf to optionally abort if invalid configuration options are
+ detected during initialization. (@interwq)
+ - Add opt.stats_print_opts, so that e.g. JSON output can be selected for the
+ stats dumped during exit if opt.stats_print is true. (@jasone)
+ - Add --with-version=VERSION for use when embedding jemalloc into another
+ project's git repository. (@jasone)
+ - Add --disable-thp to support cross compiling. (@jasone)
+ - Add --with-lg-hugepage to support cross compiling. (@jasone)
+ - Add mallctl interfaces (various authors):
+ + background_thread
+ + opt.abort_conf
+ + opt.retain
+ + opt.percpu_arena
+ + opt.background_thread
+ + opt.{dirty,muzzy}_decay_ms
+ + opt.stats_print_opts
+ + arena.<i>.initialized
+ + arena.<i>.destroy
+ + arena.<i>.{dirty,muzzy}_decay_ms
+ + arena.<i>.extent_hooks
+ + arenas.{dirty,muzzy}_decay_ms
+ + arenas.bin.<i>.slab_size
+ + arenas.nlextents
+ + arenas.lextent.<i>.size
+ + arenas.create
+ + stats.background_thread.{num_threads,num_runs,run_interval}
+ + stats.mutexes.{ctl,background_thread,prof,reset}.
+ {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds,
+ num_owner_switch}
+ + stats.arenas.<i>.{dirty,muzzy}_decay_ms
+ + stats.arenas.<i>.uptime
+ + stats.arenas.<i>.{pmuzzy,base,internal,resident}
+ + stats.arenas.<i>.{dirty,muzzy}_{npurge,nmadvise,purged}
+ + stats.arenas.<i>.bins.<j>.{nslabs,reslabs,curslabs}
+ + stats.arenas.<i>.bins.<j>.mutex.
+ {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds,
+ num_owner_switch}
+ + stats.arenas.<i>.lextents.<j>.{nmalloc,ndalloc,nrequests,curlextents}
+ + stats.arenas.i.mutexes.{large,extent_avail,extents_dirty,extents_muzzy,
+ extents_retained,decay_dirty,decay_muzzy,base,tcache_list}.
+ {num_ops,num_spin_acq,num_wait,max_wait_time,total_wait_time,max_num_thds,
+ num_owner_switch}
+
+ Portability improvements:
+ - Improve reentrant allocation support, such that deadlock is less likely if
+ e.g. a system library call in turn allocates memory. (@davidtgoldblatt,
+ @interwq)
+ - Support static linking of jemalloc with glibc. (@djwatson)
+
+ Optimizations and refactors:
+ - Organize virtual memory as "extents" of virtual memory pages, rather than as
+ naturally aligned "chunks", and store all metadata in arbitrarily distant
+ locations. This reduces virtual memory external fragmentation, and will
+ interact better with huge pages (not yet explicitly supported). (@jasone)
+ - Fold large and huge size classes together; only small and large size classes
+ remain. (@jasone)
+ - Unify the allocation paths, and merge most fast-path branching decisions.
+ (@davidtgoldblatt, @interwq)
+ - Embed per thread automatic tcache into thread-specific data, which reduces
+ conditional branches and dereferences. Also reorganize tcache to increase
+ fast-path data locality. (@interwq)
+ - Rewrite atomics to closely model the C11 API, convert various
+ synchronization from mutex-based to atomic, and use the explicit memory
+ ordering control to resolve various hypothetical races without increasing
+ synchronization overhead. (@davidtgoldblatt)
+ - Extensively optimize rtree via various methods:
+ + Add multiple layers of rtree lookup caching, since rtree lookups are now
+ part of fast-path deallocation. (@interwq)
+ + Determine rtree layout at compile time. (@jasone)
+ + Make the tree shallower for common configurations. (@jasone)
+ + Embed the root node in the top-level rtree data structure, thus avoiding
+ one level of indirection. (@jasone)
+ + Further specialize leaf elements as compared to internal node elements,
+ and directly embed extent metadata needed for fast-path deallocation.
+ (@jasone)
+ + Ignore leading always-zero address bits (architecture-specific).
+ (@jasone)
+ - Reorganize headers (ongoing work) to make them hermetic, and disentangle
+ various module dependencies. (@davidtgoldblatt)
+ - Convert various internal data structures such as size class metadata from
+ boot-time-initialized to compile-time-initialized. Propagate resulting data
+ structure simplifications, such as making arena metadata fixed-size.
+ (@jasone)
+ - Simplify size class lookups when constrained to size classes that are
+ multiples of the page size. This speeds lookups, but the primary benefit is
+ complexity reduction in code that was the source of numerous regressions.
+ (@jasone)
+ - Lock individual extents when possible for localized extent operations,
+ rather than relying on a top-level arena lock. (@davidtgoldblatt, @jasone)
+ - Use first fit layout policy instead of best fit, in order to improve
+ packing. (@jasone)
+ - If munmap(2) is not in use, use an exponential series to grow each arena's
+ virtual memory, so that the number of disjoint virtual memory mappings
+ remains low. (@jasone)
+ - Implement per arena base allocators, so that arenas never share any virtual
+ memory pages. (@jasone)
+ - Automatically generate private symbol name mangling macros. (@jasone)
+
+ Incompatible changes:
+ - Replace chunk hooks with an expanded/normalized set of extent hooks.
+ (@jasone)
+ - Remove ratio-based purging. (@jasone)
+ - Remove --disable-tcache. (@jasone)
+ - Remove --disable-tls. (@jasone)
+ - Remove --enable-ivsalloc. (@jasone)
+ - Remove --with-lg-size-class-group. (@jasone)
+ - Remove --with-lg-tiny-min. (@jasone)
+ - Remove --disable-cc-silence. (@jasone)
+ - Remove --enable-code-coverage. (@jasone)
+ - Remove --disable-munmap (replaced by opt.retain). (@jasone)
+ - Remove Valgrind support. (@jasone)
+ - Remove quarantine support. (@jasone)
+ - Remove redzone support. (@jasone)
+ - Remove mallctl interfaces (various authors):
+ + config.munmap
+ + config.tcache
+ + config.tls
+ + config.valgrind
+ + opt.lg_chunk
+ + opt.purge
+ + opt.lg_dirty_mult
+ + opt.decay_time
+ + opt.quarantine
+ + opt.redzone
+ + opt.thp
+ + arena.<i>.lg_dirty_mult
+ + arena.<i>.decay_time
+ + arena.<i>.chunk_hooks
+ + arenas.initialized
+ + arenas.lg_dirty_mult
+ + arenas.decay_time
+ + arenas.bin.<i>.run_size
+ + arenas.nlruns
+ + arenas.lrun.<i>.size
+ + arenas.nhchunks
+ + arenas.hchunk.<i>.size
+ + arenas.extend
+ + stats.cactive
+ + stats.arenas.<i>.lg_dirty_mult
+ + stats.arenas.<i>.decay_time
+ + stats.arenas.<i>.metadata.{mapped,allocated}
+ + stats.arenas.<i>.{npurge,nmadvise,purged}
+ + stats.arenas.<i>.huge.{allocated,nmalloc,ndalloc,nrequests}
+ + stats.arenas.<i>.bins.<j>.{nruns,reruns,curruns}
+ + stats.arenas.<i>.lruns.<j>.{nmalloc,ndalloc,nrequests,curruns}
+ + stats.arenas.<i>.hchunks.<j>.{nmalloc,ndalloc,nrequests,curhchunks}
+
+ Bug fixes:
+ - Improve interval-based profile dump triggering to dump only one profile when
+ a single allocation's size exceeds the interval. (@jasone)
+ - Use prefixed function names (as controlled by --with-jemalloc-prefix) when
+ pruning backtrace frames in jeprof. (@jasone)
+
+* 4.5.0 (February 28, 2017)
+
+ This is the first release to benefit from much broader continuous integration
+ testing, thanks to @davidtgoldblatt. Had we had this testing infrastructure
+ in place for prior releases, it would have caught all of the most serious
+ regressions fixed by this release.
+
+ New features:
+ - Add --disable-thp and the opt.thp mallctl to provide opt-out mechanisms for
+ transparent huge page integration. (@jasone)
+ - Update zone allocator integration to work with macOS 10.12. (@glandium)
+ - Restructure *CFLAGS configuration, so that CFLAGS behaves typically, and
+ EXTRA_CFLAGS provides a way to specify e.g. -Werror during building, but not
+ during configuration. (@jasone, @ronawho)
+
+ Bug fixes:
+ - Fix DSS (sbrk(2)-based) allocation. This regression was first released in
+ 4.3.0. (@jasone)
+ - Handle race in per size class utilization computation. This functionality
+ was first released in 4.0.0. (@interwq)
+ - Fix lock order reversal during gdump. (@jasone)
+ - Fix/refactor tcache synchronization. This regression was first released in
+ 4.0.0. (@jasone)
+ - Fix various JSON-formatted malloc_stats_print() bugs. This functionality
+ was first released in 4.3.0. (@jasone)
+ - Fix huge-aligned allocation. This regression was first released in 4.4.0.
+ (@jasone)
+ - When transparent huge page integration is enabled, detect what state pages
+ start in according to the kernel's current operating mode, and only convert
+ arena chunks to non-huge during purging if that is not their initial state.
+ This functionality was first released in 4.4.0. (@jasone)
+ - Fix lg_chunk clamping for the --enable-cache-oblivious --disable-fill case.
+ This regression was first released in 4.0.0. (@jasone, @428desmo)
+ - Properly detect sparc64 when building for Linux. (@glaubitz)
+
+* 4.4.0 (December 3, 2016)
+
+ New features:
+ - Add configure support for *-*-linux-android. (@cferris1000, @jasone)
+ - Add the --disable-syscall configure option, for use on systems that place
+ security-motivated limitations on syscall(2). (@jasone)
+ - Add support for Debian GNU/kFreeBSD. (@thesam)
+
+ Optimizations:
+ - Add extent serial numbers and use them where appropriate as a sort key that
+ is higher priority than address, so that the allocation policy prefers older
+ extents. This tends to improve locality (decrease fragmentation) when
+ memory grows downward. (@jasone)
+ - Refactor madvise(2) configuration so that MADV_FREE is detected and utilized
+ on Linux 4.5 and newer. (@jasone)
+ - Mark partially purged arena chunks as non-huge-page. This improves
+ interaction with Linux's transparent huge page functionality. (@jasone)
+
+ Bug fixes:
+ - Fix size class computations for edge conditions involving extremely large
+ allocations. This regression was first released in 4.0.0. (@jasone,
+ @ingvarha)
+ - Remove overly restrictive assertions related to the cactive statistic. This
+ regression was first released in 4.1.0. (@jasone)
+ - Implement a more reliable detection scheme for os_unfair_lock on macOS.
+ (@jszakmeister)
+
+* 4.3.1 (November 7, 2016)
+
+ Bug fixes:
+ - Fix a severe virtual memory leak. This regression was first released in
+ 4.3.0. (@interwq, @jasone)
+ - Refactor atomic and prng APIs to restore support for 32-bit platforms that
+ use pre-C11 toolchains, e.g. FreeBSD's mips. (@jasone)
+
+* 4.3.0 (November 4, 2016)
+
+ This is the first release that passes the test suite for multiple Windows
+ configurations, thanks in large part to @glandium setting up continuous
+ integration via AppVeyor (and Travis CI for Linux and OS X).
+
+ New features:
+ - Add "J" (JSON) support to malloc_stats_print(). (@jasone)
+ - Add Cray compiler support. (@ronawho)
+
+ Optimizations:
+ - Add/use adaptive spinning for bootstrapping and radix tree node
+ initialization. (@jasone)
+
+ Bug fixes:
+ - Fix large allocation to search starting in the optimal size class heap,
+ which can substantially reduce virtual memory churn and fragmentation. This
+ regression was first released in 4.0.0. (@mjp41, @jasone)
+ - Fix stats.arenas.<i>.nthreads accounting. (@interwq)
+ - Fix and simplify decay-based purging. (@jasone)
+ - Make DSS (sbrk(2)-related) operations lockless, which resolves potential
+ deadlocks during thread exit. (@jasone)
+ - Fix over-sized allocation of radix tree leaf nodes. (@mjp41, @ogaun,
+ @jasone)
+ - Fix over-sized allocation of arena_t (plus associated stats) data
+ structures. (@jasone, @interwq)
+ - Fix EXTRA_CFLAGS to not affect configuration. (@jasone)
+ - Fix a Valgrind integration bug. (@ronawho)
+ - Disallow 0x5a junk filling when running in Valgrind. (@jasone)
+ - Fix a file descriptor leak on Linux. This regression was first released in
+ 4.2.0. (@vsarunas, @jasone)
+ - Fix static linking of jemalloc with glibc. (@djwatson)
+ - Use syscall(2) rather than {open,read,close}(2) during boot on Linux. This
+ works around other libraries' system call wrappers performing reentrant
+ allocation. (@kspinka, @Whissi, @jasone)
+ - Fix OS X default zone replacement to work with OS X 10.12. (@glandium,
+ @jasone)
+ - Fix cached memory management to avoid needless commit/decommit operations
+ during purging, which resolves permanent virtual memory map fragmentation
+ issues on Windows. (@mjp41, @jasone)
+ - Fix TSD fetches to avoid (recursive) allocation. This is relevant to
+ non-TLS and Windows configurations. (@jasone)
+ - Fix malloc_conf overriding to work on Windows. (@jasone)
+ - Forcibly disable lazy-lock on Windows (was forcibly *enabled*). (@jasone)
+
+* 4.2.1 (June 8, 2016)
+
+ Bug fixes:
+ - Fix bootstrapping issues for configurations that require allocation during
+ tsd initialization (e.g. --disable-tls). (@cferris1000, @jasone)
+ - Fix gettimeofday() version of nstime_update(). (@ronawho)
+ - Fix Valgrind regressions in calloc() and chunk_alloc_wrapper(). (@ronawho)
+ - Fix potential VM map fragmentation regression. (@jasone)
+ - Fix opt_zero-triggered in-place huge reallocation zeroing. (@jasone)
+ - Fix heap profiling context leaks in reallocation edge cases. (@jasone)
+
+* 4.2.0 (May 12, 2016)
+
+ New features:
+ - Add the arena.<i>.reset mallctl, which makes it possible to discard all of
+ an arena's allocations in a single operation. (@jasone)
+ - Add the stats.retained and stats.arenas.<i>.retained statistics. (@jasone)
+ - Add the --with-version configure option. (@jasone)
+ - Support --with-lg-page values larger than actual page size. (@jasone)
+
+ Optimizations:
+ - Use pairing heaps rather than red-black trees for various hot data
+ structures. (@djwatson, @jasone)
+ - Streamline fast paths of rtree operations. (@jasone)
+ - Optimize the fast paths of calloc() and [m,d,sd]allocx(). (@jasone)
+ - Decommit unused virtual memory if the OS does not overcommit. (@jasone)
+ - Specify MAP_NORESERVE on Linux if [heuristic] overcommit is active, in order
+ to avoid unfortunate interactions during fork(2). (@jasone)
+
+ Bug fixes:
+ - Fix chunk accounting related to triggering gdump profiles. (@jasone)
+ - Link against librt for clock_gettime(2) if glibc < 2.17. (@jasone)
+ - Scale leak report summary according to sampling probability. (@jasone)
+
+* 4.1.1 (May 3, 2016)
+
+ This bugfix release resolves a variety of mostly minor issues, though the
+ bitmap fix is critical for 64-bit Windows.
+
+ Bug fixes:
+ - Fix the linear scan version of bitmap_sfu() to shift by the proper amount
+ even when sizeof(long) is not the same as sizeof(void *), as on 64-bit
+ Windows. (@jasone)
+ - Fix hashing functions to avoid unaligned memory accesses (and resulting
+ crashes). This is relevant at least to some ARM-based platforms.
+ (@rkmisra)
+ - Fix fork()-related lock rank ordering reversals. These reversals were
+ unlikely to cause deadlocks in practice except when heap profiling was
+ enabled and active. (@jasone)
+ - Fix various chunk leaks in OOM code paths. (@jasone)
+ - Fix malloc_stats_print() to print opt.narenas correctly. (@jasone)
+ - Fix MSVC-specific build/test issues. (@rustyx, @yuslepukhin)
+ - Fix a variety of test failures that were due to test fragility rather than
+ core bugs. (@jasone)
+
+* 4.1.0 (February 28, 2016)
+
+ This release is primarily about optimizations, but it also incorporates a lot
+ of portability-motivated refactoring and enhancements. Many people worked on
+ this release, to an extent that even with the omission here of minor changes
+ (see git revision history), and of the people who reported and diagnosed
+ issues, so much of the work was contributed that starting with this release,
+ changes are annotated with author credits to help reflect the collaborative
+ effort involved.
+
+ New features:
+ - Implement decay-based unused dirty page purging, a major optimization with
+ mallctl API impact. This is an alternative to the existing ratio-based
+ unused dirty page purging, and is intended to eventually become the sole
+ purging mechanism. New mallctls:
+ + opt.purge
+ + opt.decay_time
+ + arena.<i>.decay
+ + arena.<i>.decay_time
+ + arenas.decay_time
+ + stats.arenas.<i>.decay_time
+ (@jasone, @cevans87)
+ - Add --with-malloc-conf, which makes it possible to embed a default
+ options string during configuration. This was motivated by the desire to
+ specify --with-malloc-conf=purge:decay , since the default must remain
+ purge:ratio until the 5.0.0 release. (@jasone)
+ - Add MS Visual Studio 2015 support. (@rustyx, @yuslepukhin)
+ - Make *allocx() size class overflow behavior defined. The maximum
+ size class is now less than PTRDIFF_MAX to protect applications against
+ numerical overflow, and all allocation functions are guaranteed to indicate
+ errors rather than potentially crashing if the request size exceeds the
+ maximum size class. (@jasone)
+ - jeprof:
+ + Add raw heap profile support. (@jasone)
+ + Add --retain and --exclude for backtrace symbol filtering. (@jasone)
+
+ Optimizations:
+ - Optimize the fast path to combine various bootstrapping and configuration
+ checks and execute more streamlined code in the common case. (@interwq)
+ - Use linear scan for small bitmaps (used for small object tracking). In
+ addition to speeding up bitmap operations on 64-bit systems, this reduces
+ allocator metadata overhead by approximately 0.2%. (@djwatson)
+ - Separate arena_avail trees, which substantially speeds up run tree
+ operations. (@djwatson)
+ - Use memoization (boot-time-computed table) for run quantization. Separate
+ arena_avail trees reduced the importance of this optimization. (@jasone)
+ - Attempt mmap-based in-place huge reallocation. This can dramatically speed
+ up incremental huge reallocation. (@jasone)
+
+ Incompatible changes:
+ - Make opt.narenas unsigned rather than size_t. (@jasone)
+
+ Bug fixes:
+ - Fix stats.cactive accounting regression. (@rustyx, @jasone)
+ - Handle unaligned keys in hash(). This caused problems for some ARM systems.
+ (@jasone, @cferris1000)
+ - Refactor arenas array. In addition to fixing a fork-related deadlock, this
+ makes arena lookups faster and simpler. (@jasone)
+ - Move retained memory allocation out of the default chunk allocation
+ function, to a location that gets executed even if the application installs
+ a custom chunk allocation function. This resolves a virtual memory leak.
+ (@buchgr)
+ - Fix a potential tsd cleanup leak. (@cferris1000, @jasone)
+ - Fix run quantization. In practice this bug had no impact unless
+ applications requested memory with alignment exceeding one page.
+ (@jasone, @djwatson)
+ - Fix LinuxThreads-specific bootstrapping deadlock. (Cosmin Paraschiv)
+ - jeprof:
+ + Don't discard curl options if timeout is not defined. (@djwatson)
+ + Detect failed profile fetches. (@djwatson)
+ - Fix stats.arenas.<i>.{dss,lg_dirty_mult,decay_time,pactive,pdirty} for
+ --disable-stats case. (@jasone)
+
+* 4.0.4 (October 24, 2015)
+
+ This bugfix release fixes another xallocx() regression. No other regressions
+ have come to light in over a month, so this is likely a good starting point
+ for people who prefer to wait for "dot one" releases with all the major issues
+ shaken out.
+
+ Bug fixes:
+ - Fix xallocx(..., MALLOCX_ZERO to zero the last full trailing page of large
+ allocations that have been randomly assigned an offset of 0 when
+ --enable-cache-oblivious configure option is enabled.
+
* 4.0.3 (September 24, 2015)
This bugfix release continues the trend of xallocx() and heap profiling fixes.
@@ -38,7 +632,7 @@ brevity. Much more detail can be found in the git revision history:
these fixes, xallocx() now tries harder to partially fulfill requests for
optional extra space. Note that a couple of minor heap profiling
optimizations are included, but these are better thought of as performance
- fixes that were integral to disovering most of the other bugs.
+ fixes that were integral to discovering most of the other bugs.
Optimizations:
- Avoid a chunk metadata read in arena_prof_tctx_set(), since it is in the
diff --git a/deps/jemalloc/INSTALL b/deps/jemalloc/INSTALL.md
index 8d3968745..ef328c60f 100644
--- a/deps/jemalloc/INSTALL
+++ b/deps/jemalloc/INSTALL.md
@@ -18,16 +18,19 @@ would create a dependency on xsltproc in packaged releases, hence the
requirement to either run 'make dist' or avoid installing docs via the various
install_* targets documented below.
-=== Advanced configuration =====================================================
+
+## Advanced configuration
The 'configure' script supports numerous options that allow control of which
functionality is enabled, where jemalloc is installed, etc. Optionally, pass
any of the following arguments (not a definitive list) to 'configure':
---help
+* `--help`
+
Print a definitive list of options.
---prefix=<install-root-dir>
+* `--prefix=<install-root-dir>`
+
Set the base directory in which to install. For example:
./configure --prefix=/usr/local
@@ -35,11 +38,29 @@ any of the following arguments (not a definitive list) to 'configure':
will cause files to be installed into /usr/local/include, /usr/local/lib,
and /usr/local/man.
---with-rpath=<colon-separated-rpath>
+* `--with-version=(<major>.<minor>.<bugfix>-<nrev>-g<gid>|VERSION)`
+
+ The VERSION file is mandatory for successful configuration, and the
+ following steps are taken to assure its presence:
+ 1) If --with-version=<major>.<minor>.<bugfix>-<nrev>-g<gid> is specified,
+ generate VERSION using the specified value.
+ 2) If --with-version is not specified in either form and the source
+ directory is inside a git repository, try to generate VERSION via 'git
+ describe' invocations that pattern-match release tags.
+ 3) If VERSION is missing, generate it with a bogus version:
+ 0.0.0-0-g0000000000000000000000000000000000000000
+
+ Note that --with-version=VERSION bypasses (1) and (2), which simplifies
+ VERSION configuration when embedding a jemalloc release into another
+ project's git repository.
+
+* `--with-rpath=<colon-separated-rpath>`
+
Embed one or more library paths, so that libjemalloc can find the libraries
it is linked to. This works only on ELF-based systems.
---with-mangling=<map>
+* `--with-mangling=<map>`
+
Mangle public symbols specified in <map> which is a comma-separated list of
name:mangled pairs.
@@ -52,7 +73,8 @@ any of the following arguments (not a definitive list) to 'configure':
--with-jemalloc-prefix, and mangled symbols are then ignored when applying
the prefix.
---with-jemalloc-prefix=<prefix>
+* `--with-jemalloc-prefix=<prefix>`
+
Prefix all public APIs with <prefix>. For example, if <prefix> is
"prefix_", API changes like the following occur:
@@ -68,55 +90,46 @@ any of the following arguments (not a definitive list) to 'configure':
jemalloc overlays the default malloc zone, but makes no attempt to actually
replace the "malloc", "calloc", etc. symbols.
---without-export
+* `--without-export`
+
Don't export public APIs. This can be useful when building jemalloc as a
static library, or to avoid exporting public APIs when using the zone
allocator on OSX.
---with-private-namespace=<prefix>
+* `--with-private-namespace=<prefix>`
+
Prefix all library-private APIs with <prefix>je_. For shared libraries,
symbol visibility mechanisms prevent these symbols from being exported, but
for static libraries, naming collisions are a real possibility. By
default, <prefix> is empty, which results in a symbol prefix of je_ .
---with-install-suffix=<suffix>
+* `--with-install-suffix=<suffix>`
+
Append <suffix> to the base name of all installed files, such that multiple
versions of jemalloc can coexist in the same installation directory. For
example, libjemalloc.so.0 becomes libjemalloc<suffix>.so.0.
---disable-cc-silence
- Disable code that silences non-useful compiler warnings. This is mainly
- useful during development when auditing the set of warnings that are being
- silenced.
+* `--with-malloc-conf=<malloc_conf>`
---enable-debug
- Enable assertions and validation code. This incurs a substantial
- performance hit, but is very useful during application development.
- Implies --enable-ivsalloc.
+ Embed `<malloc_conf>` as a run-time options string that is processed prior to
+ the malloc_conf global variable, the /etc/malloc.conf symlink, and the
+ MALLOC_CONF environment variable. For example, to change the default decay
+ time to 30 seconds:
+
+ --with-malloc-conf=decay_ms:30000
---enable-code-coverage
- Enable code coverage support, for use during jemalloc test development.
- Additional testing targets are available if this option is enabled:
+* `--enable-debug`
- coverage
- coverage_unit
- coverage_integration
- coverage_stress
+ Enable assertions and validation code. This incurs a substantial
+ performance hit, but is very useful during application development.
- These targets do not clear code coverage results from previous runs, and
- there are interactions between the various coverage targets, so it is
- usually advisable to run 'make clean' between repeated code coverage runs.
+* `--disable-stats`
---disable-stats
Disable statistics gathering functionality. See the "opt.stats_print"
option documentation for usage details.
---enable-ivsalloc
- Enable validation code, which verifies that pointers reside within
- jemalloc-owned chunks before dereferencing them. This incurs a minor
- performance hit.
+* `--enable-prof`
---enable-prof
Enable heap profiling and leak detection functionality. See the "opt.prof"
option documentation for usage details. When enabled, there are several
approaches to backtracing, and the configure script chooses the first one
@@ -126,66 +139,55 @@ any of the following arguments (not a definitive list) to 'configure':
+ libgcc (unless --disable-prof-libgcc)
+ gcc intrinsics (unless --disable-prof-gcc)
---enable-prof-libunwind
+* `--enable-prof-libunwind`
+
Use the libunwind library (http://www.nongnu.org/libunwind/) for stack
backtracing.
---disable-prof-libgcc
+* `--disable-prof-libgcc`
+
Disable the use of libgcc's backtracing functionality.
---disable-prof-gcc
+* `--disable-prof-gcc`
+
Disable the use of gcc intrinsics for backtracing.
---with-static-libunwind=<libunwind.a>
+* `--with-static-libunwind=<libunwind.a>`
+
Statically link against the specified libunwind.a rather than dynamically
linking with -lunwind.
---disable-tcache
- Disable thread-specific caches for small objects. Objects are cached and
- released in bulk, thus reducing the total number of mutex operations. See
- the "opt.tcache" option for usage details.
+* `--disable-fill`
---disable-munmap
- Disable virtual memory deallocation via munmap(2); instead keep track of
- the virtual memory for later use. munmap() is disabled by default (i.e.
- --disable-munmap is implied) on Linux, which has a quirk in its virtual
- memory allocation algorithm that causes semi-permanent VM map holes under
- normal jemalloc operation.
+ Disable support for junk/zero filling of memory. See the "opt.junk" and
+ "opt.zero" option documentation for usage details.
---disable-fill
- Disable support for junk/zero filling of memory, quarantine, and redzones.
- See the "opt.junk", "opt.zero", "opt.quarantine", and "opt.redzone" option
- documentation for usage details.
+* `--disable-zone-allocator`
---disable-valgrind
- Disable support for Valgrind.
-
---disable-zone-allocator
Disable zone allocator for Darwin. This means jemalloc won't be hooked as
the default allocator on OSX/iOS.
---enable-utrace
+* `--enable-utrace`
+
Enable utrace(2)-based allocation tracing. This feature is not broadly
portable (FreeBSD has it, but Linux and OS X do not).
---enable-xmalloc
+* `--enable-xmalloc`
+
Enable support for optional immediate termination due to out-of-memory
errors, as is commonly implemented by "xmalloc" wrapper function for malloc.
See the "opt.xmalloc" option documentation for usage details.
---enable-lazy-lock
+* `--enable-lazy-lock`
+
Enable code that wraps pthread_create() to detect when an application
switches from single-threaded to multi-threaded mode, so that it can avoid
mutex locking/unlocking operations while in single-threaded mode. In
practice, this feature usually has little impact on performance unless
thread-specific caching is disabled.
---disable-tls
- Disable thread-local storage (TLS), which allows for fast access to
- thread-local variables via the __thread keyword. If TLS is available,
- jemalloc uses it for several purposes.
+* `--disable-cache-oblivious`
---disable-cache-oblivious
Disable cache-oblivious large allocation alignment for large allocation
requests with no alignment constraints. If this feature is disabled, all
large allocations are page-aligned as an implementation artifact, which can
@@ -194,56 +196,51 @@ any of the following arguments (not a definitive list) to 'configure':
most extreme case increases physical memory usage for the 16 KiB size class
to 20 KiB.
---with-xslroot=<path>
- Specify where to find DocBook XSL stylesheets when building the
- documentation.
+* `--disable-syscall`
---with-lg-page=<lg-page>
- Specify the base 2 log of the system page size. This option is only useful
- when cross compiling, since the configure script automatically determines
- the host's page size by default.
+ Disable use of syscall(2) rather than {open,read,write,close}(2). This is
+ intended as a workaround for systems that place security limitations on
+ syscall(2).
---with-lg-page-sizes=<lg-page-sizes>
- Specify the comma-separated base 2 logs of the page sizes to support. This
- option may be useful when cross-compiling in combination with
- --with-lg-page, but its primary use case is for integration with FreeBSD's
- libc, wherein jemalloc is embedded.
+* `--disable-cxx`
+
+ Disable C++ integration. This will cause new and delete operator
+ implementations to be omitted.
---with-lg-size-class-group=<lg-size-class-group>
- Specify the base 2 log of how many size classes to use for each doubling in
- size. By default jemalloc uses <lg-size-class-group>=2, which results in
- e.g. the following size classes:
+* `--with-xslroot=<path>`
- [...], 64,
- 80, 96, 112, 128,
- 160, [...]
+ Specify where to find DocBook XSL stylesheets when building the
+ documentation.
+
+* `--with-lg-page=<lg-page>`
+
+ Specify the base 2 log of the allocator page size, which must in turn be at
+ least as large as the system page size. By default the configure script
+ determines the host's page size and sets the allocator page size equal to
+ the system page size, so this option need not be specified unless the
+ system page size may change between configuration and execution, e.g. when
+ cross compiling.
- <lg-size-class-group>=3 results in e.g. the following size classes:
+* `--with-lg-page-sizes=<lg-page-sizes>`
- [...], 64,
- 72, 80, 88, 96, 104, 112, 120, 128,
- 144, [...]
+ Specify the comma-separated base 2 logs of the page sizes to support. This
+ option may be useful when cross compiling in combination with
+ `--with-lg-page`, but its primary use case is for integration with FreeBSD's
+ libc, wherein jemalloc is embedded.
- The minimal <lg-size-class-group>=0 causes jemalloc to only provide size
- classes that are powers of 2:
+* `--with-lg-hugepage=<lg-hugepage>`
- [...],
- 64,
- 128,
- 256,
- [...]
+ Specify the base 2 log of the system huge page size. This option is useful
+ when cross compiling, or when overriding the default for systems that do
+ not explicitly support huge pages.
- An implementation detail currently limits the total number of small size
- classes to 255, and a compilation error will result if the
- <lg-size-class-group> you specify cannot be supported. The limit is
- roughly <lg-size-class-group>=4, depending on page size.
+* `--with-lg-quantum=<lg-quantum>`
---with-lg-quantum=<lg-quantum>
Specify the base 2 log of the minimum allocation alignment. jemalloc needs
to know the minimum alignment that meets the following C standard
requirement (quoted from the April 12, 2011 draft of the C11 standard):
- The pointer returned if the allocation succeeds is suitably aligned so
+ > The pointer returned if the allocation succeeds is suitably aligned so
that it may be assigned to a pointer to any type of object with a
fundamental alignment requirement and then used to access such an object
or an array of such objects in the space allocated [...]
@@ -251,71 +248,82 @@ any of the following arguments (not a definitive list) to 'configure':
This setting is architecture-specific, and although jemalloc includes known
safe values for the most commonly used modern architectures, there is a
wrinkle related to GNU libc (glibc) that may impact your choice of
- <lg-quantum>. On most modern architectures, this mandates 16-byte alignment
- (<lg-quantum>=4), but the glibc developers chose not to meet this
+ <lg-quantum>. On most modern architectures, this mandates 16-byte
+ alignment (<lg-quantum>=4), but the glibc developers chose not to meet this
requirement for performance reasons. An old discussion can be found at
- https://sourceware.org/bugzilla/show_bug.cgi?id=206 . Unlike glibc,
+ <https://sourceware.org/bugzilla/show_bug.cgi?id=206> . Unlike glibc,
jemalloc does follow the C standard by default (caveat: jemalloc
- technically cheats if --with-lg-tiny-min is smaller than
- --with-lg-quantum), but the fact that Linux systems already work around
- this allocator noncompliance means that it is generally safe in practice to
- let jemalloc's minimum alignment follow glibc's lead. If you specify
- --with-lg-quantum=3 during configuration, jemalloc will provide additional
- size classes that are not 16-byte-aligned (24, 40, and 56, assuming
- --with-lg-size-class-group=2).
-
---with-lg-tiny-min=<lg-tiny-min>
- Specify the base 2 log of the minimum tiny size class to support. Tiny
- size classes are powers of 2 less than the quantum, and are only
- incorporated if <lg-tiny-min> is less than <lg-quantum> (see
- --with-lg-quantum). Tiny size classes technically violate the C standard
- requirement for minimum alignment, and crashes could conceivably result if
- the compiler were to generate instructions that made alignment assumptions,
- both because illegal instruction traps could result, and because accesses
- could straddle page boundaries and cause segmentation faults due to
- accessing unmapped addresses.
-
- The default of <lg-tiny-min>=3 works well in practice even on architectures
- that technically require 16-byte alignment, probably for the same reason
- --with-lg-quantum=3 works. Smaller tiny size classes can, and will, cause
- crashes (see https://bugzilla.mozilla.org/show_bug.cgi?id=691003 for an
- example).
-
- This option is rarely useful, and is mainly provided as documentation of a
- subtle implementation detail. If you do use this option, specify a
- value in [3, ..., <lg-quantum>].
+ technically cheats for size classes smaller than the quantum), but the fact
+ that Linux systems already work around this allocator noncompliance means
+ that it is generally safe in practice to let jemalloc's minimum alignment
+ follow glibc's lead. If you specify `--with-lg-quantum=3` during
+ configuration, jemalloc will provide additional size classes that are not
+ 16-byte-aligned (24, 40, and 56).
+
+* `--with-lg-vaddr=<lg-vaddr>`
+
+ Specify the number of significant virtual address bits. By default, the
+ configure script attempts to detect virtual address size on those platforms
+ where it knows how, and picks a default otherwise. This option may be
+ useful when cross-compiling.
+
+* `--disable-initial-exec-tls`
+
+ Disable the initial-exec TLS model for jemalloc's internal thread-local
+ storage (on those platforms that support explicit settings). This can allow
+ jemalloc to be dynamically loaded after program startup (e.g. using dlopen).
+ Note that in this case, there will be two malloc implementations operating
+ in the same process, which will almost certainly result in confusing runtime
+ crashes if pointers leak from one implementation to the other.
The following environment variables (not a definitive list) impact configure's
behavior:
-CFLAGS="?"
- Pass these flags to the compiler. You probably shouldn't define this unless
- you know what you are doing. (Use EXTRA_CFLAGS instead.)
+* `CFLAGS="?"`
+* `CXXFLAGS="?"`
+
+ Pass these flags to the C/C++ compiler. Any flags set by the configure
+ script are prepended, which means explicitly set flags generally take
+ precedence. Take care when specifying flags such as -Werror, because
+ configure tests may be affected in undesirable ways.
+
+* `EXTRA_CFLAGS="?"`
+* `EXTRA_CXXFLAGS="?"`
-EXTRA_CFLAGS="?"
- Append these flags to CFLAGS. This makes it possible to add flags such as
- -Werror, while allowing the configure script to determine what other flags
- are appropriate for the specified configuration.
+ Append these flags to CFLAGS/CXXFLAGS, without passing them to the
+ compiler(s) during configuration. This makes it possible to add flags such
+ as -Werror, while allowing the configure script to determine what other
+ flags are appropriate for the specified configuration.
- The configure script specifically checks whether an optimization flag (-O*)
- is specified in EXTRA_CFLAGS, and refrains from specifying an optimization
- level if it finds that one has already been specified.
+* `CPPFLAGS="?"`
-CPPFLAGS="?"
Pass these flags to the C preprocessor. Note that CFLAGS is not passed to
'cpp' when 'configure' is looking for include files, so you must use
CPPFLAGS instead if you need to help 'configure' find header files.
-LD_LIBRARY_PATH="?"
+* `LD_LIBRARY_PATH="?"`
+
'ld' uses this colon-separated list to find libraries.
-LDFLAGS="?"
+* `LDFLAGS="?"`
+
Pass these flags when linking.
-PATH="?"
+* `PATH="?"`
+
'configure' uses this to find programs.
-=== Advanced compilation =======================================================
+In some cases it may be necessary to work around configuration results that do
+not match reality. For example, Linux 4.5 added support for the MADV_FREE flag
+to madvise(2), which can cause problems if building on a host with MADV_FREE
+support and deploying to a target without. To work around this, use a cache
+file to override the relevant configuration variable defined in configure.ac,
+e.g.:
+
+ echo "je_cv_madv_free=no" > config.cache && ./configure -C
+
+
+## Advanced compilation
To build only parts of jemalloc, use the following targets:
@@ -332,6 +340,7 @@ To install only parts of jemalloc, use the following targets:
install_include
install_lib_shared
install_lib_static
+ install_lib_pc
install_lib
install_doc_html
install_doc_man
@@ -343,40 +352,51 @@ To clean up build results to varying degrees, use the following make targets:
distclean
relclean
-=== Advanced installation ======================================================
+
+## Advanced installation
Optionally, define make variables when invoking make, including (not
exclusively):
-INCLUDEDIR="?"
+* `INCLUDEDIR="?"`
+
Use this as the installation prefix for header files.
-LIBDIR="?"
+* `LIBDIR="?"`
+
Use this as the installation prefix for libraries.
-MANDIR="?"
+* `MANDIR="?"`
+
Use this as the installation prefix for man pages.
-DESTDIR="?"
+* `DESTDIR="?"`
+
Prepend DESTDIR to INCLUDEDIR, LIBDIR, DATADIR, and MANDIR. This is useful
when installing to a different path than was specified via --prefix.
-CC="?"
+* `CC="?"`
+
Use this to invoke the C compiler.
-CFLAGS="?"
+* `CFLAGS="?"`
+
Pass these flags to the compiler.
-CPPFLAGS="?"
+* `CPPFLAGS="?"`
+
Pass these flags to the C preprocessor.
-LDFLAGS="?"
+* `LDFLAGS="?"`
+
Pass these flags when linking.
-PATH="?"
+* `PATH="?"`
+
Use this to search for programs used during configuration and building.
-=== Development ================================================================
+
+## Development
If you intend to make non-trivial changes to jemalloc, use the 'autogen.sh'
script rather than 'configure'. This re-generates 'configure', enables
@@ -393,7 +413,8 @@ directory, issue configuration and build commands:
../configure --enable-autogen
make
-=== Documentation ==============================================================
+
+## Documentation
The manual page is generated in both html and roff formats. Any web browser
can be used to view the html manual. The roff manual page can be formatted
diff --git a/deps/jemalloc/Makefile.in b/deps/jemalloc/Makefile.in
index 1ac6f2926..9b9347fff 100644
--- a/deps/jemalloc/Makefile.in
+++ b/deps/jemalloc/Makefile.in
@@ -9,6 +9,7 @@ vpath % .
SHELL := /bin/sh
CC := @CC@
+CXX := @CXX@
# Configuration parameters.
DESTDIR =
@@ -23,12 +24,18 @@ abs_srcroot := @abs_srcroot@
abs_objroot := @abs_objroot@
# Build parameters.
-CPPFLAGS := @CPPFLAGS@ -I$(srcroot)include -I$(objroot)include
-CFLAGS := @CFLAGS@
+CPPFLAGS := @CPPFLAGS@ -I$(objroot)include -I$(srcroot)include
+CONFIGURE_CFLAGS := @CONFIGURE_CFLAGS@
+SPECIFIED_CFLAGS := @SPECIFIED_CFLAGS@
+EXTRA_CFLAGS := @EXTRA_CFLAGS@
+CFLAGS := $(strip $(CONFIGURE_CFLAGS) $(SPECIFIED_CFLAGS) $(EXTRA_CFLAGS))
+CONFIGURE_CXXFLAGS := @CONFIGURE_CXXFLAGS@
+SPECIFIED_CXXFLAGS := @SPECIFIED_CXXFLAGS@
+EXTRA_CXXFLAGS := @EXTRA_CXXFLAGS@
+CXXFLAGS := $(strip $(CONFIGURE_CXXFLAGS) $(SPECIFIED_CXXFLAGS) $(EXTRA_CXXFLAGS))
LDFLAGS := @LDFLAGS@
EXTRA_LDFLAGS := @EXTRA_LDFLAGS@
LIBS := @LIBS@
-TESTLIBS := @TESTLIBS@
RPATH_EXTRA := @RPATH_EXTRA@
SO := @so@
IMPORTLIB := @importlib@
@@ -48,20 +55,24 @@ cfghdrs_out := @cfghdrs_out@
cfgoutputs_in := $(addprefix $(srcroot),@cfgoutputs_in@)
cfgoutputs_out := @cfgoutputs_out@
enable_autogen := @enable_autogen@
-enable_code_coverage := @enable_code_coverage@
enable_prof := @enable_prof@
-enable_valgrind := @enable_valgrind@
enable_zone_allocator := @enable_zone_allocator@
MALLOC_CONF := @JEMALLOC_CPREFIX@MALLOC_CONF
+link_whole_archive := @link_whole_archive@
DSO_LDFLAGS = @DSO_LDFLAGS@
SOREV = @SOREV@
PIC_CFLAGS = @PIC_CFLAGS@
CTARGET = @CTARGET@
LDTARGET = @LDTARGET@
+TEST_LD_MODE = @TEST_LD_MODE@
MKLIB = @MKLIB@
AR = @AR@
ARFLAGS = @ARFLAGS@
+DUMP_SYMS = @DUMP_SYMS@
+AWK := @AWK@
CC_MM = @CC_MM@
+LM := @LM@
+INSTALL = @INSTALL@
ifeq (macho, $(ABI))
TEST_LIBRARY_PATH := DYLD_FALLBACK_LIBRARY_PATH="$(objroot)lib"
@@ -78,18 +89,36 @@ LIBJEMALLOC := $(LIBPREFIX)jemalloc$(install_suffix)
# Lists of files.
BINS := $(objroot)bin/jemalloc-config $(objroot)bin/jemalloc.sh $(objroot)bin/jeprof
C_HDRS := $(objroot)include/jemalloc/jemalloc$(install_suffix).h
-C_SRCS := $(srcroot)src/jemalloc.c $(srcroot)src/arena.c \
- $(srcroot)src/atomic.c $(srcroot)src/base.c $(srcroot)src/bitmap.c \
- $(srcroot)src/chunk.c $(srcroot)src/chunk_dss.c \
- $(srcroot)src/chunk_mmap.c $(srcroot)src/ckh.c $(srcroot)src/ctl.c \
- $(srcroot)src/extent.c $(srcroot)src/hash.c $(srcroot)src/huge.c \
- $(srcroot)src/mb.c $(srcroot)src/mutex.c $(srcroot)src/pages.c \
- $(srcroot)src/prof.c $(srcroot)src/quarantine.c $(srcroot)src/rtree.c \
- $(srcroot)src/stats.c $(srcroot)src/tcache.c $(srcroot)src/util.c \
- $(srcroot)src/tsd.c
-ifeq ($(enable_valgrind), 1)
-C_SRCS += $(srcroot)src/valgrind.c
-endif
+C_SRCS := $(srcroot)src/jemalloc.c \
+ $(srcroot)src/arena.c \
+ $(srcroot)src/background_thread.c \
+ $(srcroot)src/base.c \
+ $(srcroot)src/bin.c \
+ $(srcroot)src/bitmap.c \
+ $(srcroot)src/ckh.c \
+ $(srcroot)src/ctl.c \
+ $(srcroot)src/div.c \
+ $(srcroot)src/extent.c \
+ $(srcroot)src/extent_dss.c \
+ $(srcroot)src/extent_mmap.c \
+ $(srcroot)src/hash.c \
+ $(srcroot)src/hooks.c \
+ $(srcroot)src/large.c \
+ $(srcroot)src/log.c \
+ $(srcroot)src/malloc_io.c \
+ $(srcroot)src/mutex.c \
+ $(srcroot)src/mutex_pool.c \
+ $(srcroot)src/nstime.c \
+ $(srcroot)src/pages.c \
+ $(srcroot)src/prng.c \
+ $(srcroot)src/prof.c \
+ $(srcroot)src/rtree.c \
+ $(srcroot)src/stats.c \
+ $(srcroot)src/sz.c \
+ $(srcroot)src/tcache.c \
+ $(srcroot)src/ticker.c \
+ $(srcroot)src/tsd.c \
+ $(srcroot)src/witness.c
ifeq ($(enable_zone_allocator), 1)
C_SRCS += $(srcroot)src/zone.c
endif
@@ -105,6 +134,11 @@ DSOS := $(objroot)lib/$(LIBJEMALLOC).$(SOREV)
ifneq ($(SOREV),$(SO))
DSOS += $(objroot)lib/$(LIBJEMALLOC).$(SO)
endif
+ifeq (1, $(link_whole_archive))
+LJEMALLOC := -Wl,--whole-archive -L$(objroot)lib -l$(LIBJEMALLOC) -Wl,--no-whole-archive
+else
+LJEMALLOC := $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB)
+endif
PC := $(objroot)jemalloc.pc
MAN3 := $(objroot)doc/jemalloc$(install_suffix).3
DOCS_XML := $(objroot)doc/jemalloc$(install_suffix).xml
@@ -116,53 +150,103 @@ C_TESTLIB_SRCS := $(srcroot)test/src/btalloc.c $(srcroot)test/src/btalloc_0.c \
$(srcroot)test/src/mtx.c $(srcroot)test/src/mq.c \
$(srcroot)test/src/SFMT.c $(srcroot)test/src/test.c \
$(srcroot)test/src/thd.c $(srcroot)test/src/timer.c
-C_UTIL_INTEGRATION_SRCS := $(srcroot)src/util.c
-TESTS_UNIT := $(srcroot)test/unit/atomic.c \
+ifeq (1, $(link_whole_archive))
+C_UTIL_INTEGRATION_SRCS :=
+C_UTIL_CPP_SRCS :=
+else
+C_UTIL_INTEGRATION_SRCS := $(srcroot)src/nstime.c $(srcroot)src/malloc_io.c
+C_UTIL_CPP_SRCS := $(srcroot)src/nstime.c $(srcroot)src/malloc_io.c
+endif
+TESTS_UNIT := \
+ $(srcroot)test/unit/a0.c \
+ $(srcroot)test/unit/arena_reset.c \
+ $(srcroot)test/unit/atomic.c \
+ $(srcroot)test/unit/background_thread.c \
+ $(srcroot)test/unit/background_thread_enable.c \
+ $(srcroot)test/unit/base.c \
$(srcroot)test/unit/bitmap.c \
$(srcroot)test/unit/ckh.c \
+ $(srcroot)test/unit/decay.c \
+ $(srcroot)test/unit/div.c \
+ $(srcroot)test/unit/emitter.c \
+ $(srcroot)test/unit/extent_quantize.c \
+ $(srcroot)test/unit/fork.c \
$(srcroot)test/unit/hash.c \
+ $(srcroot)test/unit/hooks.c \
$(srcroot)test/unit/junk.c \
$(srcroot)test/unit/junk_alloc.c \
$(srcroot)test/unit/junk_free.c \
- $(srcroot)test/unit/lg_chunk.c \
+ $(srcroot)test/unit/log.c \
$(srcroot)test/unit/mallctl.c \
+ $(srcroot)test/unit/malloc_io.c \
$(srcroot)test/unit/math.c \
$(srcroot)test/unit/mq.c \
$(srcroot)test/unit/mtx.c \
+ $(srcroot)test/unit/pack.c \
+ $(srcroot)test/unit/pages.c \
+ $(srcroot)test/unit/ph.c \
+ $(srcroot)test/unit/prng.c \
$(srcroot)test/unit/prof_accum.c \
$(srcroot)test/unit/prof_active.c \
$(srcroot)test/unit/prof_gdump.c \
$(srcroot)test/unit/prof_idump.c \
$(srcroot)test/unit/prof_reset.c \
+ $(srcroot)test/unit/prof_tctx.c \
$(srcroot)test/unit/prof_thread_name.c \
$(srcroot)test/unit/ql.c \
$(srcroot)test/unit/qr.c \
- $(srcroot)test/unit/quarantine.c \
$(srcroot)test/unit/rb.c \
+ $(srcroot)test/unit/retained.c \
$(srcroot)test/unit/rtree.c \
$(srcroot)test/unit/SFMT.c \
$(srcroot)test/unit/size_classes.c \
+ $(srcroot)test/unit/slab.c \
+ $(srcroot)test/unit/smoothstep.c \
+ $(srcroot)test/unit/spin.c \
$(srcroot)test/unit/stats.c \
+ $(srcroot)test/unit/stats_print.c \
+ $(srcroot)test/unit/ticker.c \
+ $(srcroot)test/unit/nstime.c \
$(srcroot)test/unit/tsd.c \
- $(srcroot)test/unit/util.c \
+ $(srcroot)test/unit/witness.c \
$(srcroot)test/unit/zero.c
+ifeq (@enable_prof@, 1)
+TESTS_UNIT += \
+ $(srcroot)test/unit/arena_reset_prof.c
+endif
TESTS_INTEGRATION := $(srcroot)test/integration/aligned_alloc.c \
$(srcroot)test/integration/allocated.c \
- $(srcroot)test/integration/sdallocx.c \
+ $(srcroot)test/integration/extent.c \
$(srcroot)test/integration/mallocx.c \
$(srcroot)test/integration/MALLOCX_ARENA.c \
$(srcroot)test/integration/overflow.c \
$(srcroot)test/integration/posix_memalign.c \
$(srcroot)test/integration/rallocx.c \
+ $(srcroot)test/integration/sdallocx.c \
$(srcroot)test/integration/thread_arena.c \
$(srcroot)test/integration/thread_tcache_enabled.c \
- $(srcroot)test/integration/xallocx.c \
- $(srcroot)test/integration/chunk.c
+ $(srcroot)test/integration/xallocx.c
+ifeq (@enable_cxx@, 1)
+CPP_SRCS := $(srcroot)src/jemalloc_cpp.cpp
+TESTS_INTEGRATION_CPP := $(srcroot)test/integration/cpp/basic.cpp
+else
+CPP_SRCS :=
+TESTS_INTEGRATION_CPP :=
+endif
TESTS_STRESS := $(srcroot)test/stress/microbench.c
-TESTS := $(TESTS_UNIT) $(TESTS_INTEGRATION) $(TESTS_STRESS)
+TESTS := $(TESTS_UNIT) $(TESTS_INTEGRATION) $(TESTS_INTEGRATION_CPP) $(TESTS_STRESS)
+
+PRIVATE_NAMESPACE_HDRS := $(objroot)include/jemalloc/internal/private_namespace.h $(objroot)include/jemalloc/internal/private_namespace_jet.h
+PRIVATE_NAMESPACE_GEN_HDRS := $(PRIVATE_NAMESPACE_HDRS:%.h=%.gen.h)
+C_SYM_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.sym.$(O))
+C_SYMS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.sym)
C_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.$(O))
+CPP_OBJS := $(CPP_SRCS:$(srcroot)%.cpp=$(objroot)%.$(O))
C_PIC_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.pic.$(O))
+CPP_PIC_OBJS := $(CPP_SRCS:$(srcroot)%.cpp=$(objroot)%.pic.$(O))
+C_JET_SYM_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.sym.$(O))
+C_JET_SYMS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.sym)
C_JET_OBJS := $(C_SRCS:$(srcroot)%.c=$(objroot)%.jet.$(O))
C_TESTLIB_UNIT_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.unit.$(O))
C_TESTLIB_INTEGRATION_OBJS := $(C_TESTLIB_SRCS:$(srcroot)%.c=$(objroot)%.integration.$(O))
@@ -172,15 +256,17 @@ C_TESTLIB_OBJS := $(C_TESTLIB_UNIT_OBJS) $(C_TESTLIB_INTEGRATION_OBJS) $(C_UTIL_
TESTS_UNIT_OBJS := $(TESTS_UNIT:$(srcroot)%.c=$(objroot)%.$(O))
TESTS_INTEGRATION_OBJS := $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%.$(O))
+TESTS_INTEGRATION_CPP_OBJS := $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%.$(O))
TESTS_STRESS_OBJS := $(TESTS_STRESS:$(srcroot)%.c=$(objroot)%.$(O))
TESTS_OBJS := $(TESTS_UNIT_OBJS) $(TESTS_INTEGRATION_OBJS) $(TESTS_STRESS_OBJS)
+TESTS_CPP_OBJS := $(TESTS_INTEGRATION_CPP_OBJS)
.PHONY: all dist build_doc_html build_doc_man build_doc
.PHONY: install_bin install_include install_lib
.PHONY: install_doc_html install_doc_man install_doc install
.PHONY: tests check clean distclean relclean
-.SECONDARY : $(TESTS_OBJS)
+.SECONDARY : $(PRIVATE_NAMESPACE_GEN_HDRS) $(TESTS_OBJS) $(TESTS_CPP_OBJS)
# Default target.
all: build_lib
@@ -201,18 +287,32 @@ build_doc: $(DOCS)
# Include generated dependency files.
#
ifdef CC_MM
+-include $(C_SYM_OBJS:%.$(O)=%.d)
-include $(C_OBJS:%.$(O)=%.d)
+-include $(CPP_OBJS:%.$(O)=%.d)
-include $(C_PIC_OBJS:%.$(O)=%.d)
+-include $(CPP_PIC_OBJS:%.$(O)=%.d)
+-include $(C_JET_SYM_OBJS:%.$(O)=%.d)
-include $(C_JET_OBJS:%.$(O)=%.d)
-include $(C_TESTLIB_OBJS:%.$(O)=%.d)
-include $(TESTS_OBJS:%.$(O)=%.d)
+-include $(TESTS_CPP_OBJS:%.$(O)=%.d)
endif
+$(C_SYM_OBJS): $(objroot)src/%.sym.$(O): $(srcroot)src/%.c
+$(C_SYM_OBJS): CPPFLAGS += -DJEMALLOC_NO_PRIVATE_NAMESPACE
+$(C_SYMS): $(objroot)src/%.sym: $(objroot)src/%.sym.$(O)
$(C_OBJS): $(objroot)src/%.$(O): $(srcroot)src/%.c
+$(CPP_OBJS): $(objroot)src/%.$(O): $(srcroot)src/%.cpp
$(C_PIC_OBJS): $(objroot)src/%.pic.$(O): $(srcroot)src/%.c
$(C_PIC_OBJS): CFLAGS += $(PIC_CFLAGS)
+$(CPP_PIC_OBJS): $(objroot)src/%.pic.$(O): $(srcroot)src/%.cpp
+$(CPP_PIC_OBJS): CXXFLAGS += $(PIC_CFLAGS)
+$(C_JET_SYM_OBJS): $(objroot)src/%.jet.sym.$(O): $(srcroot)src/%.c
+$(C_JET_SYM_OBJS): CPPFLAGS += -DJEMALLOC_JET -DJEMALLOC_NO_PRIVATE_NAMESPACE
+$(C_JET_SYMS): $(objroot)src/%.jet.sym: $(objroot)src/%.jet.sym.$(O)
$(C_JET_OBJS): $(objroot)src/%.jet.$(O): $(srcroot)src/%.c
-$(C_JET_OBJS): CFLAGS += -DJEMALLOC_JET
+$(C_JET_OBJS): CPPFLAGS += -DJEMALLOC_JET
$(C_TESTLIB_UNIT_OBJS): $(objroot)test/src/%.unit.$(O): $(srcroot)test/src/%.c
$(C_TESTLIB_UNIT_OBJS): CPPFLAGS += -DJEMALLOC_UNIT_TEST
$(C_TESTLIB_INTEGRATION_OBJS): $(objroot)test/src/%.integration.$(O): $(srcroot)test/src/%.c
@@ -223,112 +323,146 @@ $(C_TESTLIB_STRESS_OBJS): CPPFLAGS += -DJEMALLOC_STRESS_TEST -DJEMALLOC_STRESS_T
$(C_TESTLIB_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include
$(TESTS_UNIT_OBJS): CPPFLAGS += -DJEMALLOC_UNIT_TEST
$(TESTS_INTEGRATION_OBJS): CPPFLAGS += -DJEMALLOC_INTEGRATION_TEST
+$(TESTS_INTEGRATION_CPP_OBJS): CPPFLAGS += -DJEMALLOC_INTEGRATION_CPP_TEST
$(TESTS_STRESS_OBJS): CPPFLAGS += -DJEMALLOC_STRESS_TEST
$(TESTS_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.c
+$(TESTS_CPP_OBJS): $(objroot)test/%.$(O): $(srcroot)test/%.cpp
$(TESTS_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include
+$(TESTS_CPP_OBJS): CPPFLAGS += -I$(srcroot)test/include -I$(objroot)test/include
ifneq ($(IMPORTLIB),$(SO))
-$(C_OBJS) $(C_JET_OBJS): CPPFLAGS += -DDLLEXPORT
+$(CPP_OBJS) $(C_SYM_OBJS) $(C_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS): CPPFLAGS += -DDLLEXPORT
endif
-ifndef CC_MM
# Dependencies.
+ifndef CC_MM
HEADER_DIRS = $(srcroot)include/jemalloc/internal \
$(objroot)include/jemalloc $(objroot)include/jemalloc/internal
-HEADERS = $(wildcard $(foreach dir,$(HEADER_DIRS),$(dir)/*.h))
-$(C_OBJS) $(C_PIC_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS): $(HEADERS)
-$(TESTS_OBJS): $(objroot)test/include/test/jemalloc_test.h
+HEADERS = $(filter-out $(PRIVATE_NAMESPACE_HDRS),$(wildcard $(foreach dir,$(HEADER_DIRS),$(dir)/*.h)))
+$(C_SYM_OBJS) $(C_OBJS) $(CPP_OBJS) $(C_PIC_OBJS) $(CPP_PIC_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS) $(TESTS_CPP_OBJS): $(HEADERS)
+$(TESTS_OBJS) $(TESTS_CPP_OBJS): $(objroot)test/include/test/jemalloc_test.h
endif
-$(C_OBJS) $(C_PIC_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS): %.$(O):
+$(C_OBJS) $(CPP_OBJS) $(C_PIC_OBJS) $(CPP_PIC_OBJS) $(C_TESTLIB_INTEGRATION_OBJS) $(C_UTIL_INTEGRATION_OBJS) $(TESTS_INTEGRATION_OBJS) $(TESTS_INTEGRATION_CPP_OBJS): $(objroot)include/jemalloc/internal/private_namespace.h
+$(C_JET_OBJS) $(C_TESTLIB_UNIT_OBJS) $(C_TESTLIB_STRESS_OBJS) $(TESTS_UNIT_OBJS) $(TESTS_STRESS_OBJS): $(objroot)include/jemalloc/internal/private_namespace_jet.h
+
+$(C_SYM_OBJS) $(C_OBJS) $(C_PIC_OBJS) $(C_JET_SYM_OBJS) $(C_JET_OBJS) $(C_TESTLIB_OBJS) $(TESTS_OBJS): %.$(O):
@mkdir -p $(@D)
$(CC) $(CFLAGS) -c $(CPPFLAGS) $(CTARGET) $<
ifdef CC_MM
@$(CC) -MM $(CPPFLAGS) -MT $@ -o $(@:%.$(O)=%.d) $<
endif
+$(C_SYMS): %.sym:
+ @mkdir -p $(@D)
+ $(DUMP_SYMS) $< | $(AWK) -f $(objroot)include/jemalloc/internal/private_symbols.awk > $@
+
+$(C_JET_SYMS): %.sym:
+ @mkdir -p $(@D)
+ $(DUMP_SYMS) $< | $(AWK) -f $(objroot)include/jemalloc/internal/private_symbols_jet.awk > $@
+
+$(objroot)include/jemalloc/internal/private_namespace.gen.h: $(C_SYMS)
+ $(SHELL) $(srcroot)include/jemalloc/internal/private_namespace.sh $^ > $@
+
+$(objroot)include/jemalloc/internal/private_namespace_jet.gen.h: $(C_JET_SYMS)
+ $(SHELL) $(srcroot)include/jemalloc/internal/private_namespace.sh $^ > $@
+
+%.h: %.gen.h
+ @if ! `cmp -s $< $@` ; then echo "cp $< $<"; cp $< $@ ; fi
+
+$(CPP_OBJS) $(CPP_PIC_OBJS) $(TESTS_CPP_OBJS): %.$(O):
+ @mkdir -p $(@D)
+ $(CXX) $(CXXFLAGS) -c $(CPPFLAGS) $(CTARGET) $<
+ifdef CC_MM
+ @$(CXX) -MM $(CPPFLAGS) -MT $@ -o $(@:%.$(O)=%.d) $<
+endif
+
ifneq ($(SOREV),$(SO))
%.$(SO) : %.$(SOREV)
@mkdir -p $(@D)
ln -sf $(<F) $@
endif
-$(objroot)lib/$(LIBJEMALLOC).$(SOREV) : $(if $(PIC_CFLAGS),$(C_PIC_OBJS),$(C_OBJS))
+$(objroot)lib/$(LIBJEMALLOC).$(SOREV) : $(if $(PIC_CFLAGS),$(C_PIC_OBJS),$(C_OBJS)) $(if $(PIC_CFLAGS),$(CPP_PIC_OBJS),$(CPP_OBJS))
@mkdir -p $(@D)
$(CC) $(DSO_LDFLAGS) $(call RPATH,$(RPATH_EXTRA)) $(LDTARGET) $+ $(LDFLAGS) $(LIBS) $(EXTRA_LDFLAGS)
-$(objroot)lib/$(LIBJEMALLOC)_pic.$(A) : $(C_PIC_OBJS)
-$(objroot)lib/$(LIBJEMALLOC).$(A) : $(C_OBJS)
-$(objroot)lib/$(LIBJEMALLOC)_s.$(A) : $(C_OBJS)
+$(objroot)lib/$(LIBJEMALLOC)_pic.$(A) : $(C_PIC_OBJS) $(CPP_PIC_OBJS)
+$(objroot)lib/$(LIBJEMALLOC).$(A) : $(C_OBJS) $(CPP_OBJS)
+$(objroot)lib/$(LIBJEMALLOC)_s.$(A) : $(C_OBJS) $(CPP_OBJS)
$(STATIC_LIBS):
@mkdir -p $(@D)
$(AR) $(ARFLAGS)@AROUT@ $+
-$(objroot)test/unit/%$(EXE): $(objroot)test/unit/%.$(O) $(TESTS_UNIT_LINK_OBJS) $(C_JET_OBJS) $(C_TESTLIB_UNIT_OBJS)
+$(objroot)test/unit/%$(EXE): $(objroot)test/unit/%.$(O) $(C_JET_OBJS) $(C_TESTLIB_UNIT_OBJS)
@mkdir -p $(@D)
- $(CC) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(LDFLAGS) $(filter-out -lm,$(LIBS)) -lm $(TESTLIBS) $(EXTRA_LDFLAGS)
+ $(CC) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(LDFLAGS) $(filter-out -lm,$(LIBS)) $(LM) $(EXTRA_LDFLAGS)
$(objroot)test/integration/%$(EXE): $(objroot)test/integration/%.$(O) $(C_TESTLIB_INTEGRATION_OBJS) $(C_UTIL_INTEGRATION_OBJS) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB)
@mkdir -p $(@D)
- $(CC) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB) $(LDFLAGS) $(filter-out -lm,$(filter -lpthread,$(LIBS))) -lm $(TESTLIBS) $(EXTRA_LDFLAGS)
+ $(CC) $(TEST_LD_MODE) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(LJEMALLOC) $(LDFLAGS) $(filter-out -lm,$(filter -lrt -lpthread -lstdc++,$(LIBS))) $(LM) $(EXTRA_LDFLAGS)
+
+$(objroot)test/integration/cpp/%$(EXE): $(objroot)test/integration/cpp/%.$(O) $(C_TESTLIB_INTEGRATION_OBJS) $(C_UTIL_INTEGRATION_OBJS) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB)
+ @mkdir -p $(@D)
+ $(CXX) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB) $(LDFLAGS) $(filter-out -lm,$(LIBS)) -lm $(EXTRA_LDFLAGS)
$(objroot)test/stress/%$(EXE): $(objroot)test/stress/%.$(O) $(C_JET_OBJS) $(C_TESTLIB_STRESS_OBJS) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB)
@mkdir -p $(@D)
- $(CC) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB) $(LDFLAGS) $(filter-out -lm,$(LIBS)) -lm $(TESTLIBS) $(EXTRA_LDFLAGS)
+ $(CC) $(TEST_LD_MODE) $(LDTARGET) $(filter %.$(O),$^) $(call RPATH,$(objroot)lib) $(objroot)lib/$(LIBJEMALLOC).$(IMPORTLIB) $(LDFLAGS) $(filter-out -lm,$(LIBS)) $(LM) $(EXTRA_LDFLAGS)
build_lib_shared: $(DSOS)
build_lib_static: $(STATIC_LIBS)
build_lib: build_lib_shared build_lib_static
install_bin:
- install -d $(BINDIR)
+ $(INSTALL) -d $(BINDIR)
@for b in $(BINS); do \
- echo "install -m 755 $$b $(BINDIR)"; \
- install -m 755 $$b $(BINDIR); \
+ echo "$(INSTALL) -m 755 $$b $(BINDIR)"; \
+ $(INSTALL) -m 755 $$b $(BINDIR); \
done
install_include:
- install -d $(INCLUDEDIR)/jemalloc
+ $(INSTALL) -d $(INCLUDEDIR)/jemalloc
@for h in $(C_HDRS); do \
- echo "install -m 644 $$h $(INCLUDEDIR)/jemalloc"; \
- install -m 644 $$h $(INCLUDEDIR)/jemalloc; \
+ echo "$(INSTALL) -m 644 $$h $(INCLUDEDIR)/jemalloc"; \
+ $(INSTALL) -m 644 $$h $(INCLUDEDIR)/jemalloc; \
done
install_lib_shared: $(DSOS)
- install -d $(LIBDIR)
- install -m 755 $(objroot)lib/$(LIBJEMALLOC).$(SOREV) $(LIBDIR)
+ $(INSTALL) -d $(LIBDIR)
+ $(INSTALL) -m 755 $(objroot)lib/$(LIBJEMALLOC).$(SOREV) $(LIBDIR)
ifneq ($(SOREV),$(SO))
ln -sf $(LIBJEMALLOC).$(SOREV) $(LIBDIR)/$(LIBJEMALLOC).$(SO)
endif
install_lib_static: $(STATIC_LIBS)
- install -d $(LIBDIR)
+ $(INSTALL) -d $(LIBDIR)
@for l in $(STATIC_LIBS); do \
- echo "install -m 755 $$l $(LIBDIR)"; \
- install -m 755 $$l $(LIBDIR); \
+ echo "$(INSTALL) -m 755 $$l $(LIBDIR)"; \
+ $(INSTALL) -m 755 $$l $(LIBDIR); \
done
install_lib_pc: $(PC)
- install -d $(LIBDIR)/pkgconfig
+ $(INSTALL) -d $(LIBDIR)/pkgconfig
@for l in $(PC); do \
- echo "install -m 644 $$l $(LIBDIR)/pkgconfig"; \
- install -m 644 $$l $(LIBDIR)/pkgconfig; \
+ echo "$(INSTALL) -m 644 $$l $(LIBDIR)/pkgconfig"; \
+ $(INSTALL) -m 644 $$l $(LIBDIR)/pkgconfig; \
done
install_lib: install_lib_shared install_lib_static install_lib_pc
install_doc_html:
- install -d $(DATADIR)/doc/jemalloc$(install_suffix)
+ $(INSTALL) -d $(DATADIR)/doc/jemalloc$(install_suffix)
@for d in $(DOCS_HTML); do \
- echo "install -m 644 $$d $(DATADIR)/doc/jemalloc$(install_suffix)"; \
- install -m 644 $$d $(DATADIR)/doc/jemalloc$(install_suffix); \
+ echo "$(INSTALL) -m 644 $$d $(DATADIR)/doc/jemalloc$(install_suffix)"; \
+ $(INSTALL) -m 644 $$d $(DATADIR)/doc/jemalloc$(install_suffix); \
done
install_doc_man:
- install -d $(MANDIR)/man3
+ $(INSTALL) -d $(MANDIR)/man3
@for d in $(DOCS_MAN3); do \
- echo "install -m 644 $$d $(MANDIR)/man3"; \
- install -m 644 $$d $(MANDIR)/man3; \
+ echo "$(INSTALL) -m 644 $$d $(MANDIR)/man3"; \
+ $(INSTALL) -m 644 $$d $(MANDIR)/man3; \
done
install_doc: install_doc_html install_doc_man
@@ -336,7 +470,7 @@ install_doc: install_doc_html install_doc_man
install: install_bin install_include install_lib install_doc
tests_unit: $(TESTS_UNIT:$(srcroot)%.c=$(objroot)%$(EXE))
-tests_integration: $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%$(EXE))
+tests_integration: $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%$(EXE)) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%$(EXE))
tests_stress: $(TESTS_STRESS:$(srcroot)%.c=$(objroot)%$(EXE))
tests: tests_unit tests_integration tests_stress
@@ -352,71 +486,48 @@ check_unit: tests_unit check_unit_dir
$(SHELL) $(objroot)test/test.sh $(TESTS_UNIT:$(srcroot)%.c=$(objroot)%)
check_integration_prof: tests_integration check_integration_dir
ifeq ($(enable_prof), 1)
- $(MALLOC_CONF)="prof:true" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%)
- $(MALLOC_CONF)="prof:true,prof_active:false" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%)
+ $(MALLOC_CONF)="prof:true" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%)
+ $(MALLOC_CONF)="prof:true,prof_active:false" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%)
endif
+check_integration_decay: tests_integration check_integration_dir
+ $(MALLOC_CONF)="dirty_decay_ms:-1,muzzy_decay_ms:-1" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%)
+ $(MALLOC_CONF)="dirty_decay_ms:0,muzzy_decay_ms:0" $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%)
check_integration: tests_integration check_integration_dir
- $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%)
+ $(SHELL) $(objroot)test/test.sh $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION_CPP:$(srcroot)%.cpp=$(objroot)%)
stress: tests_stress stress_dir
$(SHELL) $(objroot)test/test.sh $(TESTS_STRESS:$(srcroot)%.c=$(objroot)%)
-check: tests check_dir check_integration_prof
- $(SHELL) $(objroot)test/test.sh $(TESTS_UNIT:$(srcroot)%.c=$(objroot)%) $(TESTS_INTEGRATION:$(srcroot)%.c=$(objroot)%)
-
-ifeq ($(enable_code_coverage), 1)
-coverage_unit: check_unit
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src jet $(C_JET_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src unit $(C_TESTLIB_UNIT_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/unit unit $(TESTS_UNIT_OBJS)
-
-coverage_integration: check_integration
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src pic $(C_PIC_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src integration $(C_UTIL_INTEGRATION_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src integration $(C_TESTLIB_INTEGRATION_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/integration integration $(TESTS_INTEGRATION_OBJS)
-
-coverage_stress: stress
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src pic $(C_PIC_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src jet $(C_JET_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src stress $(C_TESTLIB_STRESS_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/stress stress $(TESTS_STRESS_OBJS)
-
-coverage: check
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src pic $(C_PIC_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src jet $(C_JET_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)src integration $(C_UTIL_INTEGRATION_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src unit $(C_TESTLIB_UNIT_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src integration $(C_TESTLIB_INTEGRATION_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/src stress $(C_TESTLIB_STRESS_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/unit unit $(TESTS_UNIT_OBJS) $(TESTS_UNIT_AUX_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/integration integration $(TESTS_INTEGRATION_OBJS)
- $(SHELL) $(srcroot)coverage.sh $(srcroot)test/stress integration $(TESTS_STRESS_OBJS)
-endif
+check: check_unit check_integration check_integration_decay check_integration_prof
clean:
+ rm -f $(PRIVATE_NAMESPACE_HDRS)
+ rm -f $(PRIVATE_NAMESPACE_GEN_HDRS)
+ rm -f $(C_SYM_OBJS)
+ rm -f $(C_SYMS)
rm -f $(C_OBJS)
+ rm -f $(CPP_OBJS)
rm -f $(C_PIC_OBJS)
+ rm -f $(CPP_PIC_OBJS)
+ rm -f $(C_JET_SYM_OBJS)
+ rm -f $(C_JET_SYMS)
rm -f $(C_JET_OBJS)
rm -f $(C_TESTLIB_OBJS)
+ rm -f $(C_SYM_OBJS:%.$(O)=%.d)
rm -f $(C_OBJS:%.$(O)=%.d)
- rm -f $(C_OBJS:%.$(O)=%.gcda)
- rm -f $(C_OBJS:%.$(O)=%.gcno)
+ rm -f $(CPP_OBJS:%.$(O)=%.d)
rm -f $(C_PIC_OBJS:%.$(O)=%.d)
- rm -f $(C_PIC_OBJS:%.$(O)=%.gcda)
- rm -f $(C_PIC_OBJS:%.$(O)=%.gcno)
+ rm -f $(CPP_PIC_OBJS:%.$(O)=%.d)
+ rm -f $(C_JET_SYM_OBJS:%.$(O)=%.d)
rm -f $(C_JET_OBJS:%.$(O)=%.d)
- rm -f $(C_JET_OBJS:%.$(O)=%.gcda)
- rm -f $(C_JET_OBJS:%.$(O)=%.gcno)
rm -f $(C_TESTLIB_OBJS:%.$(O)=%.d)
- rm -f $(C_TESTLIB_OBJS:%.$(O)=%.gcda)
- rm -f $(C_TESTLIB_OBJS:%.$(O)=%.gcno)
rm -f $(TESTS_OBJS:%.$(O)=%$(EXE))
rm -f $(TESTS_OBJS)
rm -f $(TESTS_OBJS:%.$(O)=%.d)
- rm -f $(TESTS_OBJS:%.$(O)=%.gcda)
- rm -f $(TESTS_OBJS:%.$(O)=%.gcno)
rm -f $(TESTS_OBJS:%.$(O)=%.out)
+ rm -f $(TESTS_CPP_OBJS:%.$(O)=%$(EXE))
+ rm -f $(TESTS_CPP_OBJS)
+ rm -f $(TESTS_CPP_OBJS:%.$(O)=%.d)
+ rm -f $(TESTS_CPP_OBJS:%.$(O)=%.out)
rm -f $(DSOS) $(STATIC_LIBS)
- rm -f $(objroot)*.gcov.*
distclean: clean
rm -f $(objroot)bin/jemalloc-config
diff --git a/deps/jemalloc/README b/deps/jemalloc/README
index 9b268f422..3a6e0d272 100644
--- a/deps/jemalloc/README
+++ b/deps/jemalloc/README
@@ -3,12 +3,12 @@ fragmentation avoidance and scalable concurrency support. jemalloc first came
into use as the FreeBSD libc allocator in 2005, and since then it has found its
way into numerous applications that rely on its predictable behavior. In 2010
jemalloc development efforts broadened to include developer support features
-such as heap profiling, Valgrind integration, and extensive monitoring/tuning
-hooks. Modern jemalloc releases continue to be integrated back into FreeBSD,
-and therefore versatility remains critical. Ongoing development efforts trend
-toward making jemalloc among the best allocators for a broad range of demanding
-applications, and eliminating/mitigating weaknesses that have practical
-repercussions for real world applications.
+such as heap profiling and extensive monitoring/tuning hooks. Modern jemalloc
+releases continue to be integrated back into FreeBSD, and therefore versatility
+remains critical. Ongoing development efforts trend toward making jemalloc
+among the best allocators for a broad range of demanding applications, and
+eliminating/mitigating weaknesses that have practical repercussions for real
+world applications.
The COPYING file contains copyright and licensing information.
@@ -17,4 +17,4 @@ jemalloc.
The ChangeLog file contains a brief summary of changes for each release.
-URL: http://www.canonware.com/jemalloc/
+URL: http://jemalloc.net/
diff --git a/deps/jemalloc/TUNING.md b/deps/jemalloc/TUNING.md
new file mode 100644
index 000000000..34fca05b4
--- /dev/null
+++ b/deps/jemalloc/TUNING.md
@@ -0,0 +1,129 @@
+This document summarizes the common approaches for performance fine tuning with
+jemalloc (as of 5.1.0). The default configuration of jemalloc tends to work
+reasonably well in practice, and most applications should not have to tune any
+options. However, in order to cover a wide range of applications and avoid
+pathological cases, the default setting is sometimes kept conservative and
+suboptimal, even for many common workloads. When jemalloc is properly tuned for
+a specific application / workload, it is common to improve system level metrics
+by a few percent, or make favorable trade-offs.
+
+
+## Notable runtime options for performance tuning
+
+Runtime options can be set via
+[malloc_conf](http://jemalloc.net/jemalloc.3.html#tuning).
+
+* [background_thread](http://jemalloc.net/jemalloc.3.html#background_thread)
+
+ Enabling jemalloc background threads generally improves the tail latency for
+ application threads, since unused memory purging is shifted to the dedicated
+ background threads. In addition, unintended purging delay caused by
+ application inactivity is avoided with background threads.
+
+ Suggested: `background_thread:true` when jemalloc managed threads can be
+ allowed.
+
+* [metadata_thp](http://jemalloc.net/jemalloc.3.html#opt.metadata_thp)
+
+ Allowing jemalloc to utilize transparent huge pages for its internal
+ metadata usually reduces TLB misses significantly, especially for programs
+ with large memory footprint and frequent allocation / deallocation
+ activities. Metadata memory usage may increase due to the use of huge
+ pages.
+
+ Suggested for allocation intensive programs: `metadata_thp:auto` or
+ `metadata_thp:always`, which is expected to improve CPU utilization at a
+ small memory cost.
+
+* [dirty_decay_ms](http://jemalloc.net/jemalloc.3.html#opt.dirty_decay_ms) and
+ [muzzy_decay_ms](http://jemalloc.net/jemalloc.3.html#opt.muzzy_decay_ms)
+
+ Decay time determines how fast jemalloc returns unused pages back to the
+ operating system, and therefore provides a fairly straightforward trade-off
+ between CPU and memory usage. Shorter decay time purges unused pages faster
+ to reduces memory usage (usually at the cost of more CPU cycles spent on
+ purging), and vice versa.
+
+ Suggested: tune the values based on the desired trade-offs.
+
+* [narenas](http://jemalloc.net/jemalloc.3.html#opt.narenas)
+
+ By default jemalloc uses multiple arenas to reduce internal lock contention.
+ However high arena count may also increase overall memory fragmentation,
+ since arenas manage memory independently. When high degree of parallelism
+ is not expected at the allocator level, lower number of arenas often
+ improves memory usage.
+
+ Suggested: if low parallelism is expected, try lower arena count while
+ monitoring CPU and memory usage.
+
+* [percpu_arena](http://jemalloc.net/jemalloc.3.html#opt.percpu_arena)
+
+ Enable dynamic thread to arena association based on running CPU. This has
+ the potential to improve locality, e.g. when thread to CPU affinity is
+ present.
+
+ Suggested: try `percpu_arena:percpu` or `percpu_arena:phycpu` if
+ thread migration between processors is expected to be infrequent.
+
+Examples:
+
+* High resource consumption application, prioritizing CPU utilization:
+
+ `background_thread:true,metadata_thp:auto` combined with relaxed decay time
+ (increased `dirty_decay_ms` and / or `muzzy_decay_ms`,
+ e.g. `dirty_decay_ms:30000,muzzy_decay_ms:30000`).
+
+* High resource consumption application, prioritizing memory usage:
+
+ `background_thread:true` combined with shorter decay time (decreased
+ `dirty_decay_ms` and / or `muzzy_decay_ms`,
+ e.g. `dirty_decay_ms:5000,muzzy_decay_ms:5000`), and lower arena count
+ (e.g. number of CPUs).
+
+* Low resource consumption application:
+
+ `narenas:1,lg_tcache_max:13` combined with shorter decay time (decreased
+ `dirty_decay_ms` and / or `muzzy_decay_ms`,e.g.
+ `dirty_decay_ms:1000,muzzy_decay_ms:0`).
+
+* Extremely conservative -- minimize memory usage at all costs, only suitable when
+allocation activity is very rare:
+
+ `narenas:1,tcache:false,dirty_decay_ms:0,muzzy_decay_ms:0`
+
+Note that it is recommended to combine the options with `abort_conf:true` which
+aborts immediately on illegal options.
+
+## Beyond runtime options
+
+In addition to the runtime options, there are a number of programmatic ways to
+improve application performance with jemalloc.
+
+* [Explicit arenas](http://jemalloc.net/jemalloc.3.html#arenas.create)
+
+ Manually created arenas can help performance in various ways, e.g. by
+ managing locality and contention for specific usages. For example,
+ applications can explicitly allocate frequently accessed objects from a
+ dedicated arena with
+ [mallocx()](http://jemalloc.net/jemalloc.3.html#MALLOCX_ARENA) to improve
+ locality. In addition, explicit arenas often benefit from individually
+ tuned options, e.g. relaxed [decay
+ time](http://jemalloc.net/jemalloc.3.html#arena.i.dirty_decay_ms) if
+ frequent reuse is expected.
+
+* [Extent hooks](http://jemalloc.net/jemalloc.3.html#arena.i.extent_hooks)
+
+ Extent hooks allow customization for managing underlying memory. One use
+ case for performance purpose is to utilize huge pages -- for example,
+ [HHVM](https://github.com/facebook/hhvm/blob/master/hphp/util/alloc.cpp)
+ uses explicit arenas with customized extent hooks to manage 1GB huge pages
+ for frequently accessed data, which reduces TLB misses significantly.
+
+* [Explicit thread-to-arena
+ binding](http://jemalloc.net/jemalloc.3.html#thread.arena)
+
+ It is common for some threads in an application to have different memory
+ access / allocation patterns. Threads with heavy workloads often benefit
+ from explicit binding, e.g. binding very active threads to dedicated arenas
+ may reduce contention at the allocator level.
diff --git a/deps/jemalloc/VERSION b/deps/jemalloc/VERSION
index f1f9f1c61..5c2e26d43 100644
--- a/deps/jemalloc/VERSION
+++ b/deps/jemalloc/VERSION
@@ -1 +1 @@
-4.0.3-0-ge9192eacf8935e29fc62fddc2701f7942b1cc02c
+5.1.0-0-g0
diff --git a/deps/jemalloc/bin/jemalloc-config.in b/deps/jemalloc/bin/jemalloc-config.in
index b016c8d33..80eca2e64 100644
--- a/deps/jemalloc/bin/jemalloc-config.in
+++ b/deps/jemalloc/bin/jemalloc-config.in
@@ -18,6 +18,7 @@ Options:
--cc : Print compiler used to build jemalloc.
--cflags : Print compiler flags used to build jemalloc.
--cppflags : Print preprocessor flags used to build jemalloc.
+ --cxxflags : Print C++ compiler flags used to build jemalloc.
--ldflags : Print library flags used to build jemalloc.
--libs : Print libraries jemalloc was linked against.
EOF
@@ -67,6 +68,9 @@ case "$1" in
--cppflags)
echo "@CPPFLAGS@"
;;
+--cxxflags)
+ echo "@CXXFLAGS@"
+ ;;
--ldflags)
echo "@LDFLAGS@ @EXTRA_LDFLAGS@"
;;
diff --git a/deps/jemalloc/bin/jeprof.in b/deps/jemalloc/bin/jeprof.in
index e7178078a..588c6b438 100644
--- a/deps/jemalloc/bin/jeprof.in
+++ b/deps/jemalloc/bin/jeprof.in
@@ -71,6 +71,7 @@
use strict;
use warnings;
use Getopt::Long;
+use Cwd;
my $JEPROF_VERSION = "@jemalloc_version@";
my $PPROF_VERSION = "2.0";
@@ -95,7 +96,7 @@ my @EVINCE = ("evince"); # could also be xpdf or perhaps acroread
my @KCACHEGRIND = ("kcachegrind");
my @PS2PDF = ("ps2pdf");
# These are used for dynamic profiles
-my @URL_FETCHER = ("curl", "-s");
+my @URL_FETCHER = ("curl", "-s", "--fail");
# These are the web pages that servers need to support for dynamic profiles
my $HEAP_PAGE = "/pprof/heap";
@@ -223,12 +224,14 @@ Call-graph Options:
--nodefraction=<f> Hide nodes below <f>*total [default=.005]
--edgefraction=<f> Hide edges below <f>*total [default=.001]
--maxdegree=<n> Max incoming/outgoing edges per node [default=8]
- --focus=<regexp> Focus on nodes matching <regexp>
+ --focus=<regexp> Focus on backtraces with nodes matching <regexp>
--thread=<n> Show profile for thread <n>
- --ignore=<regexp> Ignore nodes matching <regexp>
+ --ignore=<regexp> Ignore backtraces with nodes matching <regexp>
--scale=<n> Set GV scaling [default=0]
--heapcheck Make nodes with non-0 object counts
(i.e. direct leak generators) more visible
+ --retain=<regexp> Retain only nodes that match <regexp>
+ --exclude=<regexp> Exclude all nodes that match <regexp>
Miscellaneous:
--tools=<prefix or binary:fullpath>[,...] \$PATH for object tool pathnames
@@ -339,6 +342,8 @@ sub Init() {
$main::opt_ignore = '';
$main::opt_scale = 0;
$main::opt_heapcheck = 0;
+ $main::opt_retain = '';
+ $main::opt_exclude = '';
$main::opt_seconds = 30;
$main::opt_lib = "";
@@ -410,6 +415,8 @@ sub Init() {
"ignore=s" => \$main::opt_ignore,
"scale=i" => \$main::opt_scale,
"heapcheck" => \$main::opt_heapcheck,
+ "retain=s" => \$main::opt_retain,
+ "exclude=s" => \$main::opt_exclude,
"inuse_space!" => \$main::opt_inuse_space,
"inuse_objects!" => \$main::opt_inuse_objects,
"alloc_space!" => \$main::opt_alloc_space,
@@ -1160,8 +1167,21 @@ sub PrintSymbolizedProfile {
}
print '---', "\n";
- $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash
- my $profile_marker = $&;
+ my $profile_marker;
+ if ($main::profile_type eq 'heap') {
+ $HEAP_PAGE =~ m,[^/]+$,; # matches everything after the last slash
+ $profile_marker = $&;
+ } elsif ($main::profile_type eq 'growth') {
+ $GROWTH_PAGE =~ m,[^/]+$,; # matches everything after the last slash
+ $profile_marker = $&;
+ } elsif ($main::profile_type eq 'contention') {
+ $CONTENTION_PAGE =~ m,[^/]+$,; # matches everything after the last slash
+ $profile_marker = $&;
+ } else { # elsif ($main::profile_type eq 'cpu')
+ $PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash
+ $profile_marker = $&;
+ }
+
print '--- ', $profile_marker, "\n";
if (defined($main::collected_profile)) {
# if used with remote fetch, simply dump the collected profile to output.
@@ -1171,6 +1191,12 @@ sub PrintSymbolizedProfile {
}
close(SRC);
} else {
+ # --raw/http: For everything to work correctly for non-remote profiles, we
+ # would need to extend PrintProfileData() to handle all possible profile
+ # types, re-enable the code that is currently disabled in ReadCPUProfile()
+ # and FixCallerAddresses(), and remove the remote profile dumping code in
+ # the block above.
+ die "--raw/http: jeprof can only dump remote profiles for --raw\n";
# dump a cpu-format profile to standard out
PrintProfileData($profile);
}
@@ -2821,6 +2847,43 @@ sub ExtractCalls {
return $calls;
}
+sub FilterFrames {
+ my $symbols = shift;
+ my $profile = shift;
+
+ if ($main::opt_retain eq '' && $main::opt_exclude eq '') {
+ return $profile;
+ }
+
+ my $result = {};
+ foreach my $k (keys(%{$profile})) {
+ my $count = $profile->{$k};
+ my @addrs = split(/\n/, $k);
+ my @path = ();
+ foreach my $a (@addrs) {
+ my $sym;
+ if (exists($symbols->{$a})) {
+ $sym = $symbols->{$a}->[0];
+ } else {
+ $sym = $a;
+ }
+ if ($main::opt_retain ne '' && $sym !~ m/$main::opt_retain/) {
+ next;
+ }
+ if ($main::opt_exclude ne '' && $sym =~ m/$main::opt_exclude/) {
+ next;
+ }
+ push(@path, $a);
+ }
+ if (scalar(@path) > 0) {
+ my $reduced_path = join("\n", @path);
+ AddEntry($result, $reduced_path, $count);
+ }
+ }
+
+ return $result;
+}
+
sub RemoveUninterestingFrames {
my $symbols = shift;
my $profile = shift;
@@ -2829,21 +2892,23 @@ sub RemoveUninterestingFrames {
my %skip = ();
my $skip_regexp = 'NOMATCH';
if ($main::profile_type eq 'heap' || $main::profile_type eq 'growth') {
- foreach my $name ('calloc',
+ foreach my $name ('@JEMALLOC_PREFIX@calloc',
'cfree',
- 'malloc',
- 'free',
- 'memalign',
- 'posix_memalign',
- 'aligned_alloc',
+ '@JEMALLOC_PREFIX@malloc',
+ 'newImpl',
+ 'void* newImpl',
+ '@JEMALLOC_PREFIX@free',
+ '@JEMALLOC_PREFIX@memalign',
+ '@JEMALLOC_PREFIX@posix_memalign',
+ '@JEMALLOC_PREFIX@aligned_alloc',
'pvalloc',
- 'valloc',
- 'realloc',
- 'mallocx', # jemalloc
- 'rallocx', # jemalloc
- 'xallocx', # jemalloc
- 'dallocx', # jemalloc
- 'sdallocx', # jemalloc
+ '@JEMALLOC_PREFIX@valloc',
+ '@JEMALLOC_PREFIX@realloc',
+ '@JEMALLOC_PREFIX@mallocx',
+ '@JEMALLOC_PREFIX@rallocx',
+ '@JEMALLOC_PREFIX@xallocx',
+ '@JEMALLOC_PREFIX@dallocx',
+ '@JEMALLOC_PREFIX@sdallocx',
'tc_calloc',
'tc_cfree',
'tc_malloc',
@@ -2965,6 +3030,9 @@ sub RemoveUninterestingFrames {
my $reduced_path = join("\n", @path);
AddEntry($result, $reduced_path, $count);
}
+
+ $result = FilterFrames($symbols, $result);
+
return $result;
}
@@ -3274,7 +3342,7 @@ sub ResolveRedirectionForCurl {
# Add a timeout flat to URL_FETCHER. Returns a new list.
sub AddFetchTimeout {
my $timeout = shift;
- my @fetcher = shift;
+ my @fetcher = @_;
if (defined($timeout)) {
if (join(" ", @fetcher) =~ m/\bcurl -s/) {
push(@fetcher, "--max-time", sprintf("%d", $timeout));
@@ -3320,6 +3388,27 @@ sub ReadSymbols {
return $map;
}
+sub URLEncode {
+ my $str = shift;
+ $str =~ s/([^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%02x", ord $1 /eg;
+ return $str;
+}
+
+sub AppendSymbolFilterParams {
+ my $url = shift;
+ my @params = ();
+ if ($main::opt_retain ne '') {
+ push(@params, sprintf("retain=%s", URLEncode($main::opt_retain)));
+ }
+ if ($main::opt_exclude ne '') {
+ push(@params, sprintf("exclude=%s", URLEncode($main::opt_exclude)));
+ }
+ if (scalar @params > 0) {
+ $url = sprintf("%s?%s", $url, join("&", @params));
+ }
+ return $url;
+}
+
# Fetches and processes symbols to prepare them for use in the profile output
# code. If the optional 'symbol_map' arg is not given, fetches symbols from
# $SYMBOL_PAGE for all PC values found in profile. Otherwise, the raw symbols
@@ -3344,9 +3433,11 @@ sub FetchSymbols {
my $command_line;
if (join(" ", @URL_FETCHER) =~ m/\bcurl -s/) {
$url = ResolveRedirectionForCurl($url);
+ $url = AppendSymbolFilterParams($url);
$command_line = ShellEscape(@URL_FETCHER, "-d", "\@$main::tmpfile_sym",
$url);
} else {
+ $url = AppendSymbolFilterParams($url);
$command_line = (ShellEscape(@URL_FETCHER, "--post", $url)
. " < " . ShellEscape($main::tmpfile_sym));
}
@@ -3427,12 +3518,22 @@ sub FetchDynamicProfile {
}
$url .= sprintf("seconds=%d", $main::opt_seconds);
$fetch_timeout = $main::opt_seconds * 1.01 + 60;
+ # Set $profile_type for consumption by PrintSymbolizedProfile.
+ $main::profile_type = 'cpu';
} else {
# For non-CPU profiles, we add a type-extension to
# the target profile file name.
my $suffix = $path;
$suffix =~ s,/,.,g;
$profile_file .= $suffix;
+ # Set $profile_type for consumption by PrintSymbolizedProfile.
+ if ($path =~ m/$HEAP_PAGE/) {
+ $main::profile_type = 'heap';
+ } elsif ($path =~ m/$GROWTH_PAGE/) {
+ $main::profile_type = 'growth';
+ } elsif ($path =~ m/$CONTENTION_PAGE/) {
+ $main::profile_type = 'contention';
+ }
}
my $profile_dir = $ENV{"JEPROF_TMPDIR"} || ($ENV{HOME} . "/jeprof");
@@ -3730,6 +3831,8 @@ sub ReadProfile {
my $symbol_marker = $&;
$PROFILE_PAGE =~ m,[^/]+$,; # matches everything after the last slash
my $profile_marker = $&;
+ $HEAP_PAGE =~ m,[^/]+$,; # matches everything after the last slash
+ my $heap_marker = $&;
# Look at first line to see if it is a heap or a CPU profile.
# CPU profile may start with no header at all, and just binary data
@@ -3756,7 +3859,13 @@ sub ReadProfile {
$header = ReadProfileHeader(*PROFILE) || "";
}
+ if ($header =~ m/^--- *($heap_marker|$growth_marker)/o) {
+ # Skip "--- ..." line for profile types that have their own headers.
+ $header = ReadProfileHeader(*PROFILE) || "";
+ }
+
$main::profile_type = '';
+
if ($header =~ m/^heap profile:.*$growth_marker/o) {
$main::profile_type = 'growth';
$result = ReadHeapProfile($prog, *PROFILE, $header);
@@ -3808,9 +3917,9 @@ sub ReadProfile {
# independent implementation.
sub FixCallerAddresses {
my $stack = shift;
- if ($main::use_symbolized_profile) {
- return $stack;
- } else {
+ # --raw/http: Always subtract one from pc's, because PrintSymbolizedProfile()
+ # dumps unadjusted profiles.
+ {
$stack =~ /(\s)/;
my $delimiter = $1;
my @addrs = split(' ', $stack);
@@ -3878,12 +3987,7 @@ sub ReadCPUProfile {
for (my $j = 0; $j < $d; $j++) {
my $pc = $slots->get($i+$j);
# Subtract one from caller pc so we map back to call instr.
- # However, don't do this if we're reading a symbolized profile
- # file, in which case the subtract-one was done when the file
- # was written.
- if ($j > 0 && !$main::use_symbolized_profile) {
- $pc--;
- }
+ $pc--;
$pc = sprintf("%0*x", $address_length, $pc);
$pcs->{$pc} = 1;
push @k, $pc;
@@ -4469,7 +4573,7 @@ sub ParseTextSectionHeader {
# Split /proc/pid/maps dump into a list of libraries
sub ParseLibraries {
return if $main::use_symbol_page; # We don't need libraries info.
- my $prog = shift;
+ my $prog = Cwd::abs_path(shift);
my $map = shift;
my $pcs = shift;
@@ -4502,6 +4606,16 @@ sub ParseLibraries {
$finish = HexExtend($2);
$offset = $zero_offset;
$lib = $3;
+ } elsif (($l =~ /^($h)-($h)\s+..x.\s+($h)\s+\S+:\S+\s+\d+\s+(\S+)$/i) && ($4 eq $prog)) {
+ # PIEs and address space randomization do not play well with our
+ # default assumption that main executable is at lowest
+ # addresses. So we're detecting main executable in
+ # /proc/self/maps as well.
+ $start = HexExtend($1);
+ $finish = HexExtend($2);
+ $offset = HexExtend($3);
+ $lib = $4;
+ $lib =~ s|\\|/|g; # turn windows-style paths into unix-style paths
}
# FreeBSD 10.0 virtual memory map /proc/curproc/map as defined in
# function procfs_doprocmap (sys/fs/procfs/procfs_map.c)
diff --git a/deps/jemalloc/config.guess b/deps/jemalloc/build-aux/config.guess
index 1f5c50c0d..2e9ad7fe8 100755
--- a/deps/jemalloc/config.guess
+++ b/deps/jemalloc/build-aux/config.guess
@@ -1,8 +1,8 @@
#! /bin/sh
# Attempt to guess a canonical system name.
-# Copyright 1992-2014 Free Software Foundation, Inc.
+# Copyright 1992-2016 Free Software Foundation, Inc.
-timestamp='2014-03-23'
+timestamp='2016-10-02'
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
@@ -24,12 +24,12 @@ timestamp='2014-03-23'
# program. This Exception is an additional permission under section 7
# of the GNU General Public License, version 3 ("GPLv3").
#
-# Originally written by Per Bothner.
+# Originally written by Per Bothner; maintained since 2000 by Ben Elliston.
#
# You can get the latest version of this script from:
-# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess
#
-# Please send patches with a ChangeLog entry to config-patches@gnu.org.
+# Please send patches to <config-patches@gnu.org>.
me=`echo "$0" | sed -e 's,.*/,,'`
@@ -50,7 +50,7 @@ version="\
GNU config.guess ($timestamp)
Originally written by Per Bothner.
-Copyright 1992-2014 Free Software Foundation, Inc.
+Copyright 1992-2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -168,19 +168,29 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
# Note: NetBSD doesn't particularly care about the vendor
# portion of the name. We always set it to "unknown".
sysctl="sysctl -n hw.machine_arch"
- UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \
- /usr/sbin/$sysctl 2>/dev/null || echo unknown)`
+ UNAME_MACHINE_ARCH=`(uname -p 2>/dev/null || \
+ /sbin/$sysctl 2>/dev/null || \
+ /usr/sbin/$sysctl 2>/dev/null || \
+ echo unknown)`
case "${UNAME_MACHINE_ARCH}" in
armeb) machine=armeb-unknown ;;
arm*) machine=arm-unknown ;;
sh3el) machine=shl-unknown ;;
sh3eb) machine=sh-unknown ;;
sh5el) machine=sh5le-unknown ;;
+ earmv*)
+ arch=`echo ${UNAME_MACHINE_ARCH} | sed -e 's,^e\(armv[0-9]\).*$,\1,'`
+ endian=`echo ${UNAME_MACHINE_ARCH} | sed -ne 's,^.*\(eb\)$,\1,p'`
+ machine=${arch}${endian}-unknown
+ ;;
*) machine=${UNAME_MACHINE_ARCH}-unknown ;;
esac
# The Operating System including object format, if it has switched
- # to ELF recently, or will in the future.
+ # to ELF recently (or will in the future) and ABI.
case "${UNAME_MACHINE_ARCH}" in
+ earm*)
+ os=netbsdelf
+ ;;
arm*|i386|m68k|ns32k|sh3*|sparc|vax)
eval $set_cc_for_build
if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \
@@ -197,6 +207,13 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
os=netbsd
;;
esac
+ # Determine ABI tags.
+ case "${UNAME_MACHINE_ARCH}" in
+ earm*)
+ expr='s/^earmv[0-9]/-eabi/;s/eb$//'
+ abi=`echo ${UNAME_MACHINE_ARCH} | sed -e "$expr"`
+ ;;
+ esac
# The OS release
# Debian GNU/NetBSD machines have a different userland, and
# thus, need a distinct triplet. However, they do not need
@@ -207,13 +224,13 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
release='-gnu'
;;
*)
- release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'`
+ release=`echo ${UNAME_RELEASE} | sed -e 's/[-_].*//' | cut -d. -f1,2`
;;
esac
# Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM:
# contains redundant information, the shorter form:
# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used.
- echo "${machine}-${os}${release}"
+ echo "${machine}-${os}${release}${abi}"
exit ;;
*:Bitrig:*:*)
UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'`
@@ -223,6 +240,10 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'`
echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE}
exit ;;
+ *:LibertyBSD:*:*)
+ UNAME_MACHINE_ARCH=`arch | sed 's/^.*BSD\.//'`
+ echo ${UNAME_MACHINE_ARCH}-unknown-libertybsd${UNAME_RELEASE}
+ exit ;;
*:ekkoBSD:*:*)
echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE}
exit ;;
@@ -235,6 +256,9 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
*:MirBSD:*:*)
echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE}
exit ;;
+ *:Sortix:*:*)
+ echo ${UNAME_MACHINE}-unknown-sortix
+ exit ;;
alpha:OSF1:*:*)
case $UNAME_RELEASE in
*4.0)
@@ -251,42 +275,42 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1`
case "$ALPHA_CPU_TYPE" in
"EV4 (21064)")
- UNAME_MACHINE="alpha" ;;
+ UNAME_MACHINE=alpha ;;
"EV4.5 (21064)")
- UNAME_MACHINE="alpha" ;;
+ UNAME_MACHINE=alpha ;;
"LCA4 (21066/21068)")
- UNAME_MACHINE="alpha" ;;
+ UNAME_MACHINE=alpha ;;
"EV5 (21164)")
- UNAME_MACHINE="alphaev5" ;;
+ UNAME_MACHINE=alphaev5 ;;
"EV5.6 (21164A)")
- UNAME_MACHINE="alphaev56" ;;
+ UNAME_MACHINE=alphaev56 ;;
"EV5.6 (21164PC)")
- UNAME_MACHINE="alphapca56" ;;
+ UNAME_MACHINE=alphapca56 ;;
"EV5.7 (21164PC)")
- UNAME_MACHINE="alphapca57" ;;
+ UNAME_MACHINE=alphapca57 ;;
"EV6 (21264)")
- UNAME_MACHINE="alphaev6" ;;
+ UNAME_MACHINE=alphaev6 ;;
"EV6.7 (21264A)")
- UNAME_MACHINE="alphaev67" ;;
+ UNAME_MACHINE=alphaev67 ;;
"EV6.8CB (21264C)")
- UNAME_MACHINE="alphaev68" ;;
+ UNAME_MACHINE=alphaev68 ;;
"EV6.8AL (21264B)")
- UNAME_MACHINE="alphaev68" ;;
+ UNAME_MACHINE=alphaev68 ;;
"EV6.8CX (21264D)")
- UNAME_MACHINE="alphaev68" ;;
+ UNAME_MACHINE=alphaev68 ;;
"EV6.9A (21264/EV69A)")
- UNAME_MACHINE="alphaev69" ;;
+ UNAME_MACHINE=alphaev69 ;;
"EV7 (21364)")
- UNAME_MACHINE="alphaev7" ;;
+ UNAME_MACHINE=alphaev7 ;;
"EV7.9 (21364A)")
- UNAME_MACHINE="alphaev79" ;;
+ UNAME_MACHINE=alphaev79 ;;
esac
# A Pn.n version is a patched version.
# A Vn.n version is a released version.
# A Tn.n version is a released field test version.
# A Xn.n version is an unreleased experimental baselevel.
# 1.2 uses "1.2" for uname -r.
- echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
+ echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
# Reset EXIT trap before exiting to avoid spurious non-zero exit code.
exitcode=$?
trap '' 0
@@ -359,16 +383,16 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
exit ;;
i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*)
eval $set_cc_for_build
- SUN_ARCH="i386"
+ SUN_ARCH=i386
# If there is a compiler, see if it is configured for 64-bit objects.
# Note that the Sun cc does not turn __LP64__ into 1 like gcc does.
# This test works for both compilers.
- if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+ if [ "$CC_FOR_BUILD" != no_compiler_found ]; then
if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \
- (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
grep IS_64BIT_ARCH >/dev/null
then
- SUN_ARCH="x86_64"
+ SUN_ARCH=x86_64
fi
fi
echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'`
@@ -393,7 +417,7 @@ case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in
exit ;;
sun*:*:4.2BSD:*)
UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null`
- test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3
+ test "x${UNAME_RELEASE}" = x && UNAME_RELEASE=3
case "`/bin/arch`" in
sun3)
echo m68k-sun-sunos${UNAME_RELEASE}
@@ -579,8 +603,9 @@ EOF
else
IBM_ARCH=powerpc
fi
- if [ -x /usr/bin/oslevel ] ; then
- IBM_REV=`/usr/bin/oslevel`
+ if [ -x /usr/bin/lslpp ] ; then
+ IBM_REV=`/usr/bin/lslpp -Lqc bos.rte.libc |
+ awk -F: '{ print $3 }' | sed s/[0-9]*$/0/`
else
IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE}
fi
@@ -617,13 +642,13 @@ EOF
sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null`
sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null`
case "${sc_cpu_version}" in
- 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0
- 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1
+ 523) HP_ARCH=hppa1.0 ;; # CPU_PA_RISC1_0
+ 528) HP_ARCH=hppa1.1 ;; # CPU_PA_RISC1_1
532) # CPU_PA_RISC2_0
case "${sc_kernel_bits}" in
- 32) HP_ARCH="hppa2.0n" ;;
- 64) HP_ARCH="hppa2.0w" ;;
- '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20
+ 32) HP_ARCH=hppa2.0n ;;
+ 64) HP_ARCH=hppa2.0w ;;
+ '') HP_ARCH=hppa2.0 ;; # HP-UX 10.20
esac ;;
esac
fi
@@ -662,11 +687,11 @@ EOF
exit (0);
}
EOF
- (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
+ (CCOPTS="" $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy`
test -z "$HP_ARCH" && HP_ARCH=hppa
fi ;;
esac
- if [ ${HP_ARCH} = "hppa2.0w" ]
+ if [ ${HP_ARCH} = hppa2.0w ]
then
eval $set_cc_for_build
@@ -679,12 +704,12 @@ EOF
# $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess
# => hppa64-hp-hpux11.23
- if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) |
+ if echo __LP64__ | (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) |
grep -q __LP64__
then
- HP_ARCH="hppa2.0w"
+ HP_ARCH=hppa2.0w
else
- HP_ARCH="hppa64"
+ HP_ARCH=hppa64
fi
fi
echo ${HP_ARCH}-hp-hpux${HPUX_REV}
@@ -789,14 +814,14 @@ EOF
echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/'
exit ;;
F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*)
- FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'`
- FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
+ FUJITSU_PROC=`uname -m | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz`
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'`
echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
exit ;;
5000:UNIX_System_V:4.*:*)
- FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'`
- FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'`
+ FUJITSU_SYS=`uname -p | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/\///'`
+ FUJITSU_REL=`echo ${UNAME_RELEASE} | tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | sed -e 's/ /_/'`
echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}"
exit ;;
i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*)
@@ -878,7 +903,7 @@ EOF
exit ;;
*:GNU/*:*:*)
# other systems with GNU libc and userland
- echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC}
+ echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr "[:upper:]" "[:lower:]"``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC}
exit ;;
i*86:Minix:*:*)
echo ${UNAME_MACHINE}-pc-minix
@@ -901,7 +926,7 @@ EOF
EV68*) UNAME_MACHINE=alphaev68 ;;
esac
objdump --private-headers /bin/sh | grep -q ld.so.1
- if test "$?" = 0 ; then LIBC="gnulibc1" ; fi
+ if test "$?" = 0 ; then LIBC=gnulibc1 ; fi
echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
exit ;;
arc:Linux:*:* | arceb:Linux:*:*)
@@ -932,6 +957,9 @@ EOF
crisv32:Linux:*:*)
echo ${UNAME_MACHINE}-axis-linux-${LIBC}
exit ;;
+ e2k:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
frv:Linux:*:*)
echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
exit ;;
@@ -944,6 +972,9 @@ EOF
ia64:Linux:*:*)
echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
exit ;;
+ k1om:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
m32r*:Linux:*:*)
echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
exit ;;
@@ -969,6 +1000,9 @@ EOF
eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'`
test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; }
;;
+ mips64el:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
openrisc*:Linux:*:*)
echo or1k-unknown-linux-${LIBC}
exit ;;
@@ -1001,6 +1035,9 @@ EOF
ppcle:Linux:*:*)
echo powerpcle-unknown-linux-${LIBC}
exit ;;
+ riscv32:Linux:*:* | riscv64:Linux:*:*)
+ echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ exit ;;
s390:Linux:*:* | s390x:Linux:*:*)
echo ${UNAME_MACHINE}-ibm-linux-${LIBC}
exit ;;
@@ -1020,7 +1057,7 @@ EOF
echo ${UNAME_MACHINE}-dec-linux-${LIBC}
exit ;;
x86_64:Linux:*:*)
- echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
+ echo ${UNAME_MACHINE}-pc-linux-${LIBC}
exit ;;
xtensa*:Linux:*:*)
echo ${UNAME_MACHINE}-unknown-linux-${LIBC}
@@ -1099,7 +1136,7 @@ EOF
# uname -m prints for DJGPP always 'pc', but it prints nothing about
# the processor, so we play safe by assuming i586.
# Note: whatever this is, it MUST be the same as what config.sub
- # prints for the "djgpp" host, or else GDB configury will decide that
+ # prints for the "djgpp" host, or else GDB configure will decide that
# this is a cross-build.
echo i586-pc-msdosdjgpp
exit ;;
@@ -1248,6 +1285,9 @@ EOF
SX-8R:SUPER-UX:*:*)
echo sx8r-nec-superux${UNAME_RELEASE}
exit ;;
+ SX-ACE:SUPER-UX:*:*)
+ echo sxace-nec-superux${UNAME_RELEASE}
+ exit ;;
Power*:Rhapsody:*:*)
echo powerpc-apple-rhapsody${UNAME_RELEASE}
exit ;;
@@ -1261,9 +1301,9 @@ EOF
UNAME_PROCESSOR=powerpc
fi
if test `echo "$UNAME_RELEASE" | sed -e 's/\..*//'` -le 10 ; then
- if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then
+ if [ "$CC_FOR_BUILD" != no_compiler_found ]; then
if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \
- (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \
+ (CCOPTS="" $CC_FOR_BUILD -E - 2>/dev/null) | \
grep IS_64BIT_ARCH >/dev/null
then
case $UNAME_PROCESSOR in
@@ -1285,7 +1325,7 @@ EOF
exit ;;
*:procnto*:*:* | *:QNX:[0123456789]*:*)
UNAME_PROCESSOR=`uname -p`
- if test "$UNAME_PROCESSOR" = "x86"; then
+ if test "$UNAME_PROCESSOR" = x86; then
UNAME_PROCESSOR=i386
UNAME_MACHINE=pc
fi
@@ -1316,7 +1356,7 @@ EOF
# "uname -m" is not consistent, so use $cputype instead. 386
# is converted to i386 for consistency with other x86
# operating systems.
- if test "$cputype" = "386"; then
+ if test "$cputype" = 386; then
UNAME_MACHINE=i386
else
UNAME_MACHINE="$cputype"
@@ -1358,7 +1398,7 @@ EOF
echo i386-pc-xenix
exit ;;
i*86:skyos:*:*)
- echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//'
+ echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE} | sed -e 's/ .*$//'`
exit ;;
i*86:rdos:*:*)
echo ${UNAME_MACHINE}-pc-rdos
@@ -1369,23 +1409,25 @@ EOF
x86_64:VMkernel:*:*)
echo ${UNAME_MACHINE}-unknown-esx
exit ;;
+ amd64:Isilon\ OneFS:*:*)
+ echo x86_64-unknown-onefs
+ exit ;;
esac
cat >&2 <<EOF
$0: unable to guess system type
-This script, last modified $timestamp, has failed to recognize
-the operating system you are using. It is advised that you
-download the most up to date version of the config scripts from
+This script (version $timestamp), has failed to recognize the
+operating system you are using. If your script is old, overwrite
+config.guess and config.sub with the latest versions from:
- http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess
and
- http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+ http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub
-If the version you run ($0) is already up to date, please
-send the following data and any information you think might be
-pertinent to <config-patches@gnu.org> in order to provide the needed
-information to handle your system.
+If $0 has already been updated, send the following data and any
+information you think might be pertinent to config-patches@gnu.org to
+provide the necessary information to handle your system.
config.guess timestamp = $timestamp
diff --git a/deps/jemalloc/config.sub b/deps/jemalloc/build-aux/config.sub
index 0ccff7706..dd2ca93c6 100755
--- a/deps/jemalloc/config.sub
+++ b/deps/jemalloc/build-aux/config.sub
@@ -1,8 +1,8 @@
#! /bin/sh
# Configuration validation subroutine script.
-# Copyright 1992-2014 Free Software Foundation, Inc.
+# Copyright 1992-2016 Free Software Foundation, Inc.
-timestamp='2014-05-01'
+timestamp='2016-11-04'
# This file is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ timestamp='2014-05-01'
# of the GNU General Public License, version 3 ("GPLv3").
-# Please send patches with a ChangeLog entry to config-patches@gnu.org.
+# Please send patches to <config-patches@gnu.org>.
#
# Configuration subroutine to validate and canonicalize a configuration type.
# Supply the specified configuration type as an argument.
@@ -33,7 +33,7 @@ timestamp='2014-05-01'
# Otherwise, we print the canonical config type on stdout and succeed.
# You can get the latest version of this script from:
-# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD
+# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub
# This file is supposed to be the same for all GNU packages
# and recognize all the CPU types, system types and aliases
@@ -53,8 +53,7 @@ timestamp='2014-05-01'
me=`echo "$0" | sed -e 's,.*/,,'`
usage="\
-Usage: $0 [OPTION] CPU-MFR-OPSYS
- $0 [OPTION] ALIAS
+Usage: $0 [OPTION] CPU-MFR-OPSYS or ALIAS
Canonicalize a configuration name.
@@ -68,7 +67,7 @@ Report bugs and patches to <config-patches@gnu.org>."
version="\
GNU config.sub ($timestamp)
-Copyright 1992-2014 Free Software Foundation, Inc.
+Copyright 1992-2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE."
@@ -117,8 +116,8 @@ maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'`
case $maybe_os in
nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \
linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \
- knetbsd*-gnu* | netbsd*-gnu* | \
- kopensolaris*-gnu* | \
+ knetbsd*-gnu* | netbsd*-gnu* | netbsd*-eabi* | \
+ kopensolaris*-gnu* | cloudabi*-eabi* | \
storm-chaos* | os2-emx* | rtmk-nova*)
os=-$maybe_os
basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`
@@ -255,12 +254,13 @@ case $basic_machine in
| arc | arceb \
| arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \
| avr | avr32 \
+ | ba \
| be32 | be64 \
| bfin \
| c4x | c8051 | clipper \
| d10v | d30v | dlx | dsp16xx \
- | epiphany \
- | fido | fr30 | frv \
+ | e2k | epiphany \
+ | fido | fr30 | frv | ft32 \
| h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \
| hexagon \
| i370 | i860 | i960 | ia64 \
@@ -301,10 +301,12 @@ case $basic_machine in
| open8 | or1k | or1knd | or32 \
| pdp10 | pdp11 | pj | pjl \
| powerpc | powerpc64 | powerpc64le | powerpcle \
+ | pru \
| pyramid \
+ | riscv32 | riscv64 \
| rl78 | rx \
| score \
- | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
+ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[234]eb | sheb | shbe | shle | sh[1234]le | sh3ele \
| sh64 | sh64le \
| sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \
| sparcv8 | sparcv9 | sparcv9b | sparcv9v \
@@ -312,6 +314,7 @@ case $basic_machine in
| tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \
| ubicom32 \
| v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \
+ | visium \
| we32k \
| x86 | xc16x | xstormy16 | xtensa \
| z8k | z80)
@@ -326,6 +329,9 @@ case $basic_machine in
c6x)
basic_machine=tic6x-unknown
;;
+ leon|leon[3-9])
+ basic_machine=sparc-$basic_machine
+ ;;
m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | nvptx | picochip)
basic_machine=$basic_machine-unknown
os=-none
@@ -371,12 +377,13 @@ case $basic_machine in
| alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \
| arm-* | armbe-* | armle-* | armeb-* | armv*-* \
| avr-* | avr32-* \
+ | ba-* \
| be32-* | be64-* \
| bfin-* | bs2000-* \
| c[123]* | c30-* | [cjt]90-* | c4x-* \
| c8051-* | clipper-* | craynv-* | cydra-* \
| d10v-* | d30v-* | dlx-* \
- | elxsi-* \
+ | e2k-* | elxsi-* \
| f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \
| h8300-* | h8500-* \
| hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \
@@ -422,13 +429,15 @@ case $basic_machine in
| orion-* \
| pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \
| powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \
+ | pru-* \
| pyramid-* \
+ | riscv32-* | riscv64-* \
| rl78-* | romp-* | rs6000-* | rx-* \
| sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \
| shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \
| sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \
| sparclite-* \
- | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \
+ | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx*-* \
| tahoe-* \
| tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \
| tile*-* \
@@ -436,6 +445,7 @@ case $basic_machine in
| ubicom32-* \
| v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \
| vax-* \
+ | visium-* \
| we32k-* \
| x86-* | x86_64-* | xc16x-* | xps100-* \
| xstormy16-* | xtensa*-* \
@@ -512,6 +522,9 @@ case $basic_machine in
basic_machine=i386-pc
os=-aros
;;
+ asmjs)
+ basic_machine=asmjs-unknown
+ ;;
aux)
basic_machine=m68k-apple
os=-aux
@@ -632,6 +645,14 @@ case $basic_machine in
basic_machine=m68k-bull
os=-sysv3
;;
+ e500v[12])
+ basic_machine=powerpc-unknown
+ os=$os"spe"
+ ;;
+ e500v[12]-*)
+ basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
+ os=$os"spe"
+ ;;
ebmon29k)
basic_machine=a29k-amd
os=-ebmon
@@ -773,6 +794,9 @@ case $basic_machine in
basic_machine=m68k-isi
os=-sysv
;;
+ leon-*|leon[3-9]-*)
+ basic_machine=sparc-`echo $basic_machine | sed 's/-.*//'`
+ ;;
m68knommu)
basic_machine=m68k-unknown
os=-linux
@@ -828,6 +852,10 @@ case $basic_machine in
basic_machine=powerpc-unknown
os=-morphos
;;
+ moxiebox)
+ basic_machine=moxie-unknown
+ os=-moxiebox
+ ;;
msdos)
basic_machine=i386-pc
os=-msdos
@@ -1004,7 +1032,7 @@ case $basic_machine in
ppc-* | ppcbe-*)
basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
- ppcle | powerpclittle | ppc-le | powerpc-little)
+ ppcle | powerpclittle)
basic_machine=powerpcle-unknown
;;
ppcle-* | powerpclittle-*)
@@ -1014,7 +1042,7 @@ case $basic_machine in
;;
ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'`
;;
- ppc64le | powerpc64little | ppc64-le | powerpc64-little)
+ ppc64le | powerpc64little)
basic_machine=powerpc64le-unknown
;;
ppc64le-* | powerpc64little-*)
@@ -1360,27 +1388,28 @@ case $os in
| -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \
| -sym* | -kopensolaris* | -plan9* \
| -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \
- | -aos* | -aros* \
+ | -aos* | -aros* | -cloudabi* | -sortix* \
| -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \
| -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \
| -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \
- | -bitrig* | -openbsd* | -solidbsd* \
+ | -bitrig* | -openbsd* | -solidbsd* | -libertybsd* \
| -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \
| -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \
| -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \
| -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \
| -chorusos* | -chorusrdb* | -cegcc* \
| -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \
- | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
+ | -midipix* | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \
| -linux-newlib* | -linux-musl* | -linux-uclibc* \
- | -uxpv* | -beos* | -mpeix* | -udk* \
+ | -uxpv* | -beos* | -mpeix* | -udk* | -moxiebox* \
| -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \
| -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \
| -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \
| -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \
| -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \
| -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \
- | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* | -tirtos*)
+ | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es* \
+ | -onefs* | -tirtos* | -phoenix* | -fuchsia*)
# Remember, each alternative MUST END IN *, to match a version number.
;;
-qnx*)
@@ -1404,9 +1433,6 @@ case $os in
-mac*)
os=`echo $os | sed -e 's|mac|macos|'`
;;
- # Apple iOS
- -ios*)
- ;;
-linux-dietlibc)
os=-linux-dietlibc
;;
@@ -1515,6 +1541,8 @@ case $os in
;;
-nacl*)
;;
+ -ios)
+ ;;
-none)
;;
*)
diff --git a/deps/jemalloc/install-sh b/deps/jemalloc/build-aux/install-sh
index ebc66913e..ebc66913e 100755
--- a/deps/jemalloc/install-sh
+++ b/deps/jemalloc/build-aux/install-sh
diff --git a/deps/jemalloc/configure b/deps/jemalloc/configure
index 8c56c92a1..6aebfad0d 100755
--- a/deps/jemalloc/configure
+++ b/deps/jemalloc/configure
@@ -625,23 +625,21 @@ cfgoutputs_out
cfgoutputs_in
cfghdrs_out
cfghdrs_in
+enable_initial_exec_tls
enable_zone_allocator
enable_tls
enable_lazy_lock
-TESTLIBS
jemalloc_version_gid
jemalloc_version_nrev
jemalloc_version_bugfix
jemalloc_version_minor
jemalloc_version_major
jemalloc_version
+enable_log
enable_cache_oblivious
enable_xmalloc
-enable_valgrind
enable_utrace
enable_fill
-enable_munmap
-enable_tcache
enable_prof
enable_stats
enable_debug
@@ -649,7 +647,7 @@ je_
install_suffix
private_namespace
JEMALLOC_CPREFIX
-enable_code_coverage
+JEMALLOC_PREFIX
AUTOCONF
LD
RANLIB
@@ -658,16 +656,20 @@ INSTALL_SCRIPT
INSTALL_PROGRAM
enable_autogen
RPATH_EXTRA
+LM
CC_MM
+DUMP_SYMS
AROUT
ARFLAGS
MKLIB
+TEST_LD_MODE
LDTARGET
CTARGET
PIC_CFLAGS
SOREV
EXTRA_LDFLAGS
DSO_LDFLAGS
+link_whole_archive
libprefix
exe
a
@@ -677,6 +679,8 @@ so
LD_PRELOAD_VAR
RPATH
abi
+AWK
+NM
AR
host_os
host_vendor
@@ -688,7 +692,18 @@ build_cpu
build
EGREP
GREP
+EXTRA_CXXFLAGS
+SPECIFIED_CXXFLAGS
+CONFIGURE_CXXFLAGS
+enable_cxx
+HAVE_CXX14
+ac_ct_CXX
+CXXFLAGS
+CXX
CPP
+EXTRA_CFLAGS
+SPECIFIED_CFLAGS
+CONFIGURE_CFLAGS
OBJEXT
EXEEXT
ac_ct_CC
@@ -752,38 +767,37 @@ ac_subst_files=''
ac_user_opts='
enable_option_checking
with_xslroot
+enable_cxx
+with_lg_vaddr
with_rpath
enable_autogen
-enable_code_coverage
with_mangling
with_jemalloc_prefix
with_export
with_private_namespace
with_install_suffix
-enable_cc_silence
+with_malloc_conf
enable_debug
-enable_ivsalloc
enable_stats
enable_prof
enable_prof_libunwind
with_static_libunwind
enable_prof_libgcc
enable_prof_gcc
-enable_tcache
-enable_munmap
enable_fill
enable_utrace
-enable_valgrind
enable_xmalloc
enable_cache_oblivious
-with_lg_tiny_min
+enable_log
with_lg_quantum
with_lg_page
+with_lg_hugepage
with_lg_page_sizes
-with_lg_size_class_group
+with_version
+enable_syscall
enable_lazy_lock
-enable_tls
enable_zone_allocator
+enable_initial_exec_tls
'
ac_precious_vars='build_alias
host_alias
@@ -793,7 +807,10 @@ CFLAGS
LDFLAGS
LIBS
CPPFLAGS
-CPP'
+CPP
+CXX
+CXXFLAGS
+CCC'
# Initialize some variables set by options.
@@ -1405,35 +1422,34 @@ Optional Features:
--disable-option-checking ignore unrecognized --enable/--with options
--disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no)
--enable-FEATURE[=ARG] include FEATURE [ARG=yes]
+ --disable-cxx Disable C++ integration
--enable-autogen Automatically regenerate configure output
- --enable-code-coverage Enable code coverage
- --disable-cc-silence Do not silence irrelevant compiler warnings
- --enable-debug Build debugging code (implies --enable-ivsalloc)
- --enable-ivsalloc Validate pointers passed through the public API
+ --enable-debug Build debugging code
--disable-stats Disable statistics calculation/reporting
--enable-prof Enable allocation profiling
--enable-prof-libunwind Use libunwind for backtracing
--disable-prof-libgcc Do not use libgcc for backtracing
--disable-prof-gcc Do not use gcc intrinsics for backtracing
- --disable-tcache Disable per thread caches
- --disable-munmap Disable VM deallocation via munmap(2)
- --disable-fill Disable support for junk/zero filling, quarantine,
- and redzones
+ --disable-fill Disable support for junk/zero filling
--enable-utrace Enable utrace(2)-based tracing
- --disable-valgrind Disable support for Valgrind
--enable-xmalloc Support xmalloc option
--disable-cache-oblivious
Disable support for cache-oblivious allocation
alignment
+ --enable-log Support debug logging
+ --disable-syscall Disable use of syscall(2)
--enable-lazy-lock Enable lazy locking (only lock when multi-threaded)
- --disable-tls Disable thread-local storage (__thread keyword)
--disable-zone-allocator
Disable zone allocator for Darwin
+ --disable-initial-exec-tls
+ Disable the initial-exec tls model
Optional Packages:
--with-PACKAGE[=ARG] use PACKAGE [ARG=yes]
--without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no)
--with-xslroot=<path> XSL stylesheet root path
+ --with-lg-vaddr=<lg-vaddr>
+ Number of significant virtual address bits
--with-rpath=<rpath> Colon-separated rpath (ELF systems only)
--with-mangling=<map> Mangle symbols in <map>
--with-jemalloc-prefix=<prefix>
@@ -1443,19 +1459,21 @@ Optional Packages:
Prefix to prepend to all library-private APIs
--with-install-suffix=<suffix>
Suffix to append to all installed files
+ --with-malloc-conf=<malloc_conf>
+ config.malloc_conf options string
--with-static-libunwind=<libunwind.a>
Path to static libunwind library; use rather than
dynamically linking
- --with-lg-tiny-min=<lg-tiny-min>
- Base 2 log of minimum tiny size class to support
--with-lg-quantum=<lg-quantum>
Base 2 log of minimum allocation alignment
--with-lg-page=<lg-page>
Base 2 log of system page size
+ --with-lg-hugepage=<lg-hugepage>
+ Base 2 log of system huge page size
--with-lg-page-sizes=<lg-page-sizes>
Base 2 logs of system page sizes to support
- --with-lg-size-class-group=<lg-size-class-group>
- Base 2 log of size classes per doubling
+ --with-version=<major>.<minor>.<bugfix>-<nrev>-g<gid>
+ Version string
Some influential environment variables:
CC C compiler command
@@ -1466,6 +1484,8 @@ Some influential environment variables:
CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if
you have headers in a nonstandard directory <include dir>
CPP C preprocessor
+ CXX C++ compiler command
+ CXXFLAGS C++ compiler flags
Use these variables to override the choices made by `configure' or to help
it to find libraries and programs with nonstandard names/locations.
@@ -1622,6 +1642,90 @@ fi
} # ac_fn_c_try_cpp
+# ac_fn_cxx_try_compile LINENO
+# ----------------------------
+# Try to compile conftest.$ac_ext, and return whether this succeeded.
+ac_fn_cxx_try_compile ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext
+ if { { ac_try="$ac_compile"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compile") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_cxx_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest.$ac_objext; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_cxx_try_compile
+
+# ac_fn_c_try_link LINENO
+# -----------------------
+# Try to link conftest.$ac_ext, and return whether this succeeded.
+ac_fn_c_try_link ()
+{
+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
+ rm -f conftest.$ac_objext conftest$ac_exeext
+ if { { ac_try="$ac_link"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_link") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ grep -v '^ *+' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ mv -f conftest.er1 conftest.err
+ fi
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; } && {
+ test -z "$ac_c_werror_flag" ||
+ test ! -s conftest.err
+ } && test -s conftest$ac_exeext && {
+ test "$cross_compiling" = yes ||
+ test -x conftest$ac_exeext
+ }; then :
+ ac_retval=0
+else
+ $as_echo "$as_me: failed program was:" >&5
+sed 's/^/| /' conftest.$ac_ext >&5
+
+ ac_retval=1
+fi
+ # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
+ # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
+ # interfere with the next link command; also delete a directory that is
+ # left behind by Apple's compiler. We do this before executing the actions.
+ rm -rf conftest.dSYM conftest_ipa8_conftest.oo
+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
+ as_fn_set_status $ac_retval
+
+} # ac_fn_c_try_link
+
# ac_fn_c_try_run LINENO
# ----------------------
# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes
@@ -1878,52 +1982,6 @@ rm -f conftest.val
} # ac_fn_c_compute_int
-# ac_fn_c_try_link LINENO
-# -----------------------
-# Try to link conftest.$ac_ext, and return whether this succeeded.
-ac_fn_c_try_link ()
-{
- as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack
- rm -f conftest.$ac_objext conftest$ac_exeext
- if { { ac_try="$ac_link"
-case "(($ac_try" in
- *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
- *) ac_try_echo=$ac_try;;
-esac
-eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
-$as_echo "$ac_try_echo"; } >&5
- (eval "$ac_link") 2>conftest.err
- ac_status=$?
- if test -s conftest.err; then
- grep -v '^ *+' conftest.err >conftest.er1
- cat conftest.er1 >&5
- mv -f conftest.er1 conftest.err
- fi
- $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
- test $ac_status = 0; } && {
- test -z "$ac_c_werror_flag" ||
- test ! -s conftest.err
- } && test -s conftest$ac_exeext && {
- test "$cross_compiling" = yes ||
- test -x conftest$ac_exeext
- }; then :
- ac_retval=0
-else
- $as_echo "$as_me: failed program was:" >&5
-sed 's/^/| /' conftest.$ac_ext >&5
-
- ac_retval=1
-fi
- # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information
- # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would
- # interfere with the next link command; also delete a directory that is
- # left behind by Apple's compiler. We do this before executing the actions.
- rm -rf conftest.dSYM conftest_ipa8_conftest.oo
- eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno
- as_fn_set_status $ac_retval
-
-} # ac_fn_c_try_link
-
# ac_fn_c_check_header_mongrel LINENO HEADER VAR INCLUDES
# -------------------------------------------------------
# Tests whether HEADER exists, giving a warning if it cannot be compiled using
@@ -2484,10 +2542,53 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu
+ac_aux_dir=
+for ac_dir in build-aux "$srcdir"/build-aux; do
+ if test -f "$ac_dir/install-sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install-sh -c"
+ break
+ elif test -f "$ac_dir/install.sh"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/install.sh -c"
+ break
+ elif test -f "$ac_dir/shtool"; then
+ ac_aux_dir=$ac_dir
+ ac_install_sh="$ac_aux_dir/shtool install -c"
+ break
+ fi
+done
+if test -z "$ac_aux_dir"; then
+ as_fn_error $? "cannot find install-sh, install.sh, or shtool in build-aux \"$srcdir\"/build-aux" "$LINENO" 5
+fi
+
+# These three variables are undocumented and unsupported,
+# and are intended to be withdrawn in a future Autoconf release.
+# They can cause serious problems if a builder's source tree is in a directory
+# whose full name contains unusual characters.
+ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
+ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
+ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
+
+
+
+
+
+
+
+
+CONFIGURE_CFLAGS=
+SPECIFIED_CFLAGS="${CFLAGS}"
+
+CONFIGURE_CXXFLAGS=
+SPECIFIED_CXXFLAGS="${CXXFLAGS}"
+
+
+
CONFIG=`echo ${ac_configure_args} | sed -e 's#'"'"'\([^ ]*\)'"'"'#\1#g'`
@@ -3390,6 +3491,7 @@ ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
if test "x$GCC" != "xyes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler is MSVC" >&5
@@ -3423,18 +3525,155 @@ fi
$as_echo "$je_cv_msvc" >&6; }
fi
-if test "x$CFLAGS" = "x" ; then
- no_CFLAGS="yes"
- if test "x$GCC" = "xyes" ; then
+je_cv_cray_prgenv_wrapper=""
+if test "x${PE_ENV}" != "x" ; then
+ case "${CC}" in
+ CC|cc)
+ je_cv_cray_prgenv_wrapper="yes"
+ ;;
+ *)
+ ;;
+ esac
+fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler is cray" >&5
+$as_echo_n "checking whether compiler is cray... " >&6; }
+if ${je_cv_cray+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+#ifndef _CRAYC
+ int fail-1;
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cray=yes
+else
+ je_cv_cray=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_cray" >&5
+$as_echo "$je_cv_cray" >&6; }
+
+if test "x${je_cv_cray}" = "xyes" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether cray compiler version is 8.4" >&5
+$as_echo_n "checking whether cray compiler version is 8.4... " >&6; }
+if ${je_cv_cray_84+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+#if !(_RELEASE_MAJOR == 8 && _RELEASE_MINOR == 4)
+ int fail-1;
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cray_84=yes
+else
+ je_cv_cray_84=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_cray_84" >&5
+$as_echo "$je_cv_cray_84" >&6; }
+fi
+
+if test "x$GCC" = "xyes" ; then
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -std=gnu11" >&5
+$as_echo_n "checking whether compiler supports -std=gnu11... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-std=gnu11
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-std=gnu11
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+ if test "x$je_cv_cflags_added" = "x-std=gnu11" ; then
+ cat >>confdefs.h <<_ACEOF
+#define JEMALLOC_HAS_RESTRICT 1
+_ACEOF
+
+ else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -std=gnu99" >&5
$as_echo_n "checking whether compiler supports -std=gnu99... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-std=gnu99"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-std=gnu99
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -std=gnu99"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3450,33 +3689,202 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-std=gnu99
+ je_cv_cflags_added=-std=gnu99
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
- if test "x$je_cv_cflags_appended" = "x-std=gnu99" ; then
+ if test "x$je_cv_cflags_added" = "x-std=gnu99" ; then
cat >>confdefs.h <<_ACEOF
#define JEMALLOC_HAS_RESTRICT 1
_ACEOF
fi
+ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wall" >&5
$as_echo_n "checking whether compiler supports -Wall... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Wall"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Wall
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Wall
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wshorten-64-to-32" >&5
+$as_echo_n "checking whether compiler supports -Wshorten-64-to-32... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Wshorten-64-to-32
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Wshorten-64-to-32
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wsign-compare" >&5
+$as_echo_n "checking whether compiler supports -Wsign-compare... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Wsign-compare
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Wsign-compare
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
else
- CFLAGS="${CFLAGS} -Wall"
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wundef" >&5
+$as_echo_n "checking whether compiler supports -Wundef... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Wundef
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3492,27 +3900,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Wall
+ je_cv_cflags_added=-Wundef
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror=declaration-after-statement" >&5
-$as_echo_n "checking whether compiler supports -Werror=declaration-after-statement... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Werror=declaration-after-statement"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wno-format-zero-length" >&5
+$as_echo_n "checking whether compiler supports -Wno-format-zero-length... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Wno-format-zero-length
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -Werror=declaration-after-statement"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3528,27 +3951,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Werror=declaration-after-statement
+ je_cv_cflags_added=-Wno-format-zero-length
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -pipe" >&5
$as_echo_n "checking whether compiler supports -pipe... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-pipe"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-pipe
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -pipe"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3564,27 +4002,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-pipe
+ je_cv_cflags_added=-pipe
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -g3" >&5
$as_echo_n "checking whether compiler supports -g3... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-g3"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-g3
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -g3"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3600,29 +4053,44 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-g3
+ je_cv_cflags_added=-g3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
- elif test "x$je_cv_msvc" = "xyes" ; then
- CC="$CC -nologo"
+
+elif test "x$je_cv_msvc" = "xyes" ; then
+ CC="$CC -nologo"
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Zi" >&5
$as_echo_n "checking whether compiler supports -Zi... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Zi"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Zi
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -Zi"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3638,27 +4106,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Zi
+ je_cv_cflags_added=-Zi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -MT" >&5
$as_echo_n "checking whether compiler supports -MT... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-MT"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-MT
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -MT"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3674,27 +4157,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-MT
+ je_cv_cflags_added=-MT
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -W3" >&5
$as_echo_n "checking whether compiler supports -W3... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-W3"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-W3
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -W3"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3710,27 +4208,42 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-W3
+ je_cv_cflags_added=-W3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -FS" >&5
$as_echo_n "checking whether compiler supports -FS... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-FS"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-FS
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -FS"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3746,31 +4259,156 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-FS
+ je_cv_cflags_added=-FS
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+ T_APPEND_V=-I${srcdir}/include/msvc_compat
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
+fi
+if test "x$je_cv_cray" = "xyes" ; then
+ if test "x$je_cv_cray_84" = "xyes" ; then
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hipa2" >&5
+$as_echo_n "checking whether compiler supports -hipa2... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-hipa2
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-hipa2
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnognu" >&5
+$as_echo_n "checking whether compiler supports -hnognu... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-hnognu
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-hnognu
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
- CPPFLAGS="$CPPFLAGS -I${srcdir}/include/msvc_compat"
fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnomessage=128" >&5
+$as_echo_n "checking whether compiler supports -hnomessage=128... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-hnomessage=128
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
-if test "x$EXTRA_CFLAGS" != "x" ; then
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports $EXTRA_CFLAGS" >&5
-$as_echo_n "checking whether compiler supports $EXTRA_CFLAGS... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="$EXTRA_CFLAGS"
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} $EXTRA_CFLAGS"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -3786,19 +4424,79 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=$EXTRA_CFLAGS
+ je_cv_cflags_added=-hnomessage=128
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -hnomessage=1357" >&5
+$as_echo_n "checking whether compiler supports -hnomessage=1357... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-hnomessage=1357
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-hnomessage=1357
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+fi
+
+
+
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
@@ -3937,6 +4635,1422 @@ ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $
ac_compiler_gnu=$ac_cv_c_compiler_gnu
+# Check whether --enable-cxx was given.
+if test "${enable_cxx+set}" = set; then :
+ enableval=$enable_cxx; if test "x$enable_cxx" = "xno" ; then
+ enable_cxx="0"
+else
+ enable_cxx="1"
+fi
+
+else
+ enable_cxx="1"
+
+fi
+
+if test "x$enable_cxx" = "x1" ; then
+ # ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional])
+#
+# DESCRIPTION
+#
+# Check for baseline language coverage in the compiler for the specified
+# version of the C++ standard. If necessary, add switches to CXX and
+# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard)
+# or '14' (for the C++14 standard).
+#
+# The second argument, if specified, indicates whether you insist on an
+# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+# -std=c++11). If neither is specified, you get whatever works, with
+# preference for an extended mode.
+#
+# The third argument, if specified 'mandatory' or if left unspecified,
+# indicates that baseline support for the specified C++ standard is
+# required and that the macro should error out if no mode with that
+# support is found. If specified 'optional', then configuration proceeds
+# regardless, after defining HAVE_CXX${VERSION} if and only if a
+# supporting mode is found.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+# Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+# Copyright (c) 2015 Paul Norman <penorman@mac.com>
+# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+if test -z "$CXX"; then
+ if test -n "$CCC"; then
+ CXX=$CCC
+ else
+ if test -n "$ac_tool_prefix"; then
+ for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+ do
+ # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args.
+set dummy $ac_tool_prefix$ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$CXX"; then
+ ac_cv_prog_CXX="$CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_CXX="$ac_tool_prefix$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+CXX=$ac_cv_prog_CXX
+if test -n "$CXX"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CXX" >&5
+$as_echo "$CXX" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$CXX" && break
+ done
+fi
+if test -z "$CXX"; then
+ ac_ct_CXX=$CXX
+ for ac_prog in g++ c++ gpp aCC CC cxx cc++ cl.exe FCC KCC RCC xlC_r xlC
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_CXX+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_CXX"; then
+ ac_cv_prog_ac_ct_CXX="$ac_ct_CXX" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_CXX="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_CXX=$ac_cv_prog_ac_ct_CXX
+if test -n "$ac_ct_CXX"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CXX" >&5
+$as_echo "$ac_ct_CXX" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$ac_ct_CXX" && break
+done
+
+ if test "x$ac_ct_CXX" = x; then
+ CXX="g++"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ CXX=$ac_ct_CXX
+ fi
+fi
+
+ fi
+fi
+# Provide some information about the compiler.
+$as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ compiler version" >&5
+set X $ac_compile
+ac_compiler=$2
+for ac_option in --version -v -V -qversion; do
+ { { ac_try="$ac_compiler $ac_option >&5"
+case "(($ac_try" in
+ *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
+ *) ac_try_echo=$ac_try;;
+esac
+eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\""
+$as_echo "$ac_try_echo"; } >&5
+ (eval "$ac_compiler $ac_option >&5") 2>conftest.err
+ ac_status=$?
+ if test -s conftest.err; then
+ sed '10a\
+... rest of stderr output deleted ...
+ 10q' conftest.err >conftest.er1
+ cat conftest.er1 >&5
+ fi
+ rm -f conftest.er1 conftest.err
+ $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+ test $ac_status = 0; }
+done
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C++ compiler" >&5
+$as_echo_n "checking whether we are using the GNU C++ compiler... " >&6; }
+if ${ac_cv_cxx_compiler_gnu+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+#ifndef __GNUC__
+ choke me
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_compiler_gnu=yes
+else
+ ac_compiler_gnu=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_cv_cxx_compiler_gnu=$ac_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_cxx_compiler_gnu" >&5
+$as_echo "$ac_cv_cxx_compiler_gnu" >&6; }
+if test $ac_compiler_gnu = yes; then
+ GXX=yes
+else
+ GXX=
+fi
+ac_test_CXXFLAGS=${CXXFLAGS+set}
+ac_save_CXXFLAGS=$CXXFLAGS
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX accepts -g" >&5
+$as_echo_n "checking whether $CXX accepts -g... " >&6; }
+if ${ac_cv_prog_cxx_g+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_cxx_werror_flag=$ac_cxx_werror_flag
+ ac_cxx_werror_flag=yes
+ ac_cv_prog_cxx_g=no
+ CXXFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_cv_prog_cxx_g=yes
+else
+ CXXFLAGS=""
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+
+else
+ ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+ CXXFLAGS="-g"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ac_cv_prog_cxx_g=yes
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ ac_cxx_werror_flag=$ac_save_cxx_werror_flag
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cxx_g" >&5
+$as_echo "$ac_cv_prog_cxx_g" >&6; }
+if test "$ac_test_CXXFLAGS" = set; then
+ CXXFLAGS=$ac_save_CXXFLAGS
+elif test $ac_cv_prog_cxx_g = yes; then
+ if test "$GXX" = yes; then
+ CXXFLAGS="-g -O2"
+ else
+ CXXFLAGS="-g"
+ fi
+else
+ if test "$GXX" = yes; then
+ CXXFLAGS="-O2"
+ else
+ CXXFLAGS=
+ fi
+fi
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+
+ ax_cxx_compile_cxx14_required=false
+ ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+ ac_success=no
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features by default" >&5
+$as_echo_n "checking whether $CXX supports C++14 features by default... " >&6; }
+if ${ax_cv_cxx_compile_cxx14+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+// If the compiler admits that it is not ready for C++11, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201103L
+
+#error "This is not a C++11 compiler"
+
+#else
+
+namespace cxx11
+{
+
+ namespace test_static_assert
+ {
+
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ }
+
+ namespace test_final_override
+ {
+
+ struct Base
+ {
+ virtual void f() {}
+ };
+
+ struct Derived : public Base
+ {
+ virtual void f() override {}
+ };
+
+ }
+
+ namespace test_double_right_angle_brackets
+ {
+
+ template < typename T >
+ struct check {};
+
+ typedef check<void> single_type;
+ typedef check<check<void>> double_type;
+ typedef check<check<check<void>>> triple_type;
+ typedef check<check<check<check<void>>>> quadruple_type;
+
+ }
+
+ namespace test_decltype
+ {
+
+ int
+ f()
+ {
+ int a = 1;
+ decltype(a) b = 2;
+ return a + b;
+ }
+
+ }
+
+ namespace test_type_deduction
+ {
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static const bool value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static const bool value = true;
+ };
+
+ template < typename T1, typename T2 >
+ auto
+ add(T1 a1, T2 a2) -> decltype(a1 + a2)
+ {
+ return a1 + a2;
+ }
+
+ int
+ test(const int c, volatile int v)
+ {
+ static_assert(is_same<int, decltype(0)>::value == true, "");
+ static_assert(is_same<int, decltype(c)>::value == false, "");
+ static_assert(is_same<int, decltype(v)>::value == false, "");
+ auto ac = c;
+ auto av = v;
+ auto sumi = ac + av + 'x';
+ auto sumf = ac + av + 1.0;
+ static_assert(is_same<int, decltype(ac)>::value == true, "");
+ static_assert(is_same<int, decltype(av)>::value == true, "");
+ static_assert(is_same<int, decltype(sumi)>::value == true, "");
+ static_assert(is_same<int, decltype(sumf)>::value == false, "");
+ static_assert(is_same<int, decltype(add(c, v))>::value == true, "");
+ return (sumf > 0.0) ? sumi : add(c, v);
+ }
+
+ }
+
+ namespace test_noexcept
+ {
+
+ int f() { return 0; }
+ int g() noexcept { return 0; }
+
+ static_assert(noexcept(f()) == false, "");
+ static_assert(noexcept(g()) == true, "");
+
+ }
+
+ namespace test_constexpr
+ {
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c_r(const CharT *const s, const unsigned long acc) noexcept
+ {
+ return *s ? strlen_c_r(s + 1, acc + 1) : acc;
+ }
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c(const CharT *const s) noexcept
+ {
+ return strlen_c_r(s, 0UL);
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("1") == 1UL, "");
+ static_assert(strlen_c("example") == 7UL, "");
+ static_assert(strlen_c("another\0example") == 7UL, "");
+
+ }
+
+ namespace test_rvalue_references
+ {
+
+ template < int N >
+ struct answer
+ {
+ static constexpr int value = N;
+ };
+
+ answer<1> f(int&) { return answer<1>(); }
+ answer<2> f(const int&) { return answer<2>(); }
+ answer<3> f(int&&) { return answer<3>(); }
+
+ void
+ test()
+ {
+ int i = 0;
+ const int c = 0;
+ static_assert(decltype(f(i))::value == 1, "");
+ static_assert(decltype(f(c))::value == 2, "");
+ static_assert(decltype(f(0))::value == 3, "");
+ }
+
+ }
+
+ namespace test_uniform_initialization
+ {
+
+ struct test
+ {
+ static const int zero {};
+ static const int one {1};
+ };
+
+ static_assert(test::zero == 0, "");
+ static_assert(test::one == 1, "");
+
+ }
+
+ namespace test_lambdas
+ {
+
+ void
+ test1()
+ {
+ auto lambda1 = [](){};
+ auto lambda2 = lambda1;
+ lambda1();
+ lambda2();
+ }
+
+ int
+ test2()
+ {
+ auto a = [](int i, int j){ return i + j; }(1, 2);
+ auto b = []() -> int { return '0'; }();
+ auto c = [=](){ return a + b; }();
+ auto d = [&](){ return c; }();
+ auto e = [a, &b](int x) mutable {
+ const auto identity = [](int y){ return y; };
+ for (auto i = 0; i < a; ++i)
+ a += b--;
+ return x + identity(a + b);
+ }(0);
+ return a + b + c + d + e;
+ }
+
+ int
+ test3()
+ {
+ const auto nullary = [](){ return 0; };
+ const auto unary = [](int x){ return x; };
+ using nullary_t = decltype(nullary);
+ using unary_t = decltype(unary);
+ const auto higher1st = [](nullary_t f){ return f(); };
+ const auto higher2nd = [unary](nullary_t f1){
+ return [unary, f1](unary_t f2){ return f2(unary(f1())); };
+ };
+ return higher1st(nullary) + higher2nd(nullary)(unary);
+ }
+
+ }
+
+ namespace test_variadic_templates
+ {
+
+ template <int...>
+ struct sum;
+
+ template <int N0, int... N1toN>
+ struct sum<N0, N1toN...>
+ {
+ static constexpr auto value = N0 + sum<N1toN...>::value;
+ };
+
+ template <>
+ struct sum<>
+ {
+ static constexpr auto value = 0;
+ };
+
+ static_assert(sum<>::value == 0, "");
+ static_assert(sum<1>::value == 1, "");
+ static_assert(sum<23>::value == 23, "");
+ static_assert(sum<1, 2>::value == 3, "");
+ static_assert(sum<5, 5, 11>::value == 21, "");
+ static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, "");
+
+ }
+
+ // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae
+ // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function
+ // because of this.
+ namespace test_template_alias_sfinae
+ {
+
+ struct foo {};
+
+ template<typename T>
+ using member = typename T::member_type;
+
+ template<typename T>
+ void func(...) {}
+
+ template<typename T>
+ void func(member<T>*) {}
+
+ void test();
+
+ void test() { func<foo>(0); }
+
+ }
+
+} // namespace cxx11
+
+#endif // __cplusplus >= 201103L
+
+
+
+
+// If the compiler admits that it is not ready for C++14, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201402L
+
+#error "This is not a C++14 compiler"
+
+#else
+
+namespace cxx14
+{
+
+ namespace test_polymorphic_lambdas
+ {
+
+ int
+ test()
+ {
+ const auto lambda = [](auto&&... args){
+ const auto istiny = [](auto x){
+ return (sizeof(x) == 1UL) ? 1 : 0;
+ };
+ const int aretiny[] = { istiny(args)... };
+ return aretiny[0];
+ };
+ return lambda(1, 1L, 1.0f, '1');
+ }
+
+ }
+
+ namespace test_binary_literals
+ {
+
+ constexpr auto ivii = 0b0000000000101010;
+ static_assert(ivii == 42, "wrong value");
+
+ }
+
+ namespace test_generalized_constexpr
+ {
+
+ template < typename CharT >
+ constexpr unsigned long
+ strlen_c(const CharT *const s) noexcept
+ {
+ auto length = 0UL;
+ for (auto p = s; *p; ++p)
+ ++length;
+ return length;
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("x") == 1UL, "");
+ static_assert(strlen_c("test") == 4UL, "");
+ static_assert(strlen_c("another\0test") == 7UL, "");
+
+ }
+
+ namespace test_lambda_init_capture
+ {
+
+ int
+ test()
+ {
+ auto x = 0;
+ const auto lambda1 = [a = x](int b){ return a + b; };
+ const auto lambda2 = [a = lambda1(x)](){ return a; };
+ return lambda2();
+ }
+
+ }
+
+ namespace test_digit_seperators
+ {
+
+ constexpr auto ten_million = 100'000'000;
+ static_assert(ten_million == 100000000, "");
+
+ }
+
+ namespace test_return_type_deduction
+ {
+
+ auto f(int& x) { return x; }
+ decltype(auto) g(int& x) { return x; }
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static constexpr auto value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static constexpr auto value = true;
+ };
+
+ int
+ test()
+ {
+ auto x = 0;
+ static_assert(is_same<int, decltype(f(x))>::value, "");
+ static_assert(is_same<int&, decltype(g(x))>::value, "");
+ return x;
+ }
+
+ }
+
+} // namespace cxx14
+
+#endif // __cplusplus >= 201402L
+
+
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ ax_cv_cxx_compile_cxx14=yes
+else
+ ax_cv_cxx_compile_cxx14=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ax_cv_cxx_compile_cxx14" >&5
+$as_echo "$ax_cv_cxx_compile_cxx14" >&6; }
+ if test x$ax_cv_cxx_compile_cxx14 = xyes; then
+ ac_success=yes
+ fi
+
+
+
+ if test x$ac_success = xno; then
+ for switch in -std=c++14 -std=c++0x +std=c++14 "-h std=c++14"; do
+ cachevar=`$as_echo "ax_cv_cxx_compile_cxx14_$switch" | $as_tr_sh`
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CXX supports C++14 features with $switch" >&5
+$as_echo_n "checking whether $CXX supports C++14 features with $switch... " >&6; }
+if eval \${$cachevar+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_save_CXX="$CXX"
+ CXX="$CXX $switch"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+// If the compiler admits that it is not ready for C++11, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201103L
+
+#error "This is not a C++11 compiler"
+
+#else
+
+namespace cxx11
+{
+
+ namespace test_static_assert
+ {
+
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ }
+
+ namespace test_final_override
+ {
+
+ struct Base
+ {
+ virtual void f() {}
+ };
+
+ struct Derived : public Base
+ {
+ virtual void f() override {}
+ };
+
+ }
+
+ namespace test_double_right_angle_brackets
+ {
+
+ template < typename T >
+ struct check {};
+
+ typedef check<void> single_type;
+ typedef check<check<void>> double_type;
+ typedef check<check<check<void>>> triple_type;
+ typedef check<check<check<check<void>>>> quadruple_type;
+
+ }
+
+ namespace test_decltype
+ {
+
+ int
+ f()
+ {
+ int a = 1;
+ decltype(a) b = 2;
+ return a + b;
+ }
+
+ }
+
+ namespace test_type_deduction
+ {
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static const bool value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static const bool value = true;
+ };
+
+ template < typename T1, typename T2 >
+ auto
+ add(T1 a1, T2 a2) -> decltype(a1 + a2)
+ {
+ return a1 + a2;
+ }
+
+ int
+ test(const int c, volatile int v)
+ {
+ static_assert(is_same<int, decltype(0)>::value == true, "");
+ static_assert(is_same<int, decltype(c)>::value == false, "");
+ static_assert(is_same<int, decltype(v)>::value == false, "");
+ auto ac = c;
+ auto av = v;
+ auto sumi = ac + av + 'x';
+ auto sumf = ac + av + 1.0;
+ static_assert(is_same<int, decltype(ac)>::value == true, "");
+ static_assert(is_same<int, decltype(av)>::value == true, "");
+ static_assert(is_same<int, decltype(sumi)>::value == true, "");
+ static_assert(is_same<int, decltype(sumf)>::value == false, "");
+ static_assert(is_same<int, decltype(add(c, v))>::value == true, "");
+ return (sumf > 0.0) ? sumi : add(c, v);
+ }
+
+ }
+
+ namespace test_noexcept
+ {
+
+ int f() { return 0; }
+ int g() noexcept { return 0; }
+
+ static_assert(noexcept(f()) == false, "");
+ static_assert(noexcept(g()) == true, "");
+
+ }
+
+ namespace test_constexpr
+ {
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c_r(const CharT *const s, const unsigned long acc) noexcept
+ {
+ return *s ? strlen_c_r(s + 1, acc + 1) : acc;
+ }
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c(const CharT *const s) noexcept
+ {
+ return strlen_c_r(s, 0UL);
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("1") == 1UL, "");
+ static_assert(strlen_c("example") == 7UL, "");
+ static_assert(strlen_c("another\0example") == 7UL, "");
+
+ }
+
+ namespace test_rvalue_references
+ {
+
+ template < int N >
+ struct answer
+ {
+ static constexpr int value = N;
+ };
+
+ answer<1> f(int&) { return answer<1>(); }
+ answer<2> f(const int&) { return answer<2>(); }
+ answer<3> f(int&&) { return answer<3>(); }
+
+ void
+ test()
+ {
+ int i = 0;
+ const int c = 0;
+ static_assert(decltype(f(i))::value == 1, "");
+ static_assert(decltype(f(c))::value == 2, "");
+ static_assert(decltype(f(0))::value == 3, "");
+ }
+
+ }
+
+ namespace test_uniform_initialization
+ {
+
+ struct test
+ {
+ static const int zero {};
+ static const int one {1};
+ };
+
+ static_assert(test::zero == 0, "");
+ static_assert(test::one == 1, "");
+
+ }
+
+ namespace test_lambdas
+ {
+
+ void
+ test1()
+ {
+ auto lambda1 = [](){};
+ auto lambda2 = lambda1;
+ lambda1();
+ lambda2();
+ }
+
+ int
+ test2()
+ {
+ auto a = [](int i, int j){ return i + j; }(1, 2);
+ auto b = []() -> int { return '0'; }();
+ auto c = [=](){ return a + b; }();
+ auto d = [&](){ return c; }();
+ auto e = [a, &b](int x) mutable {
+ const auto identity = [](int y){ return y; };
+ for (auto i = 0; i < a; ++i)
+ a += b--;
+ return x + identity(a + b);
+ }(0);
+ return a + b + c + d + e;
+ }
+
+ int
+ test3()
+ {
+ const auto nullary = [](){ return 0; };
+ const auto unary = [](int x){ return x; };
+ using nullary_t = decltype(nullary);
+ using unary_t = decltype(unary);
+ const auto higher1st = [](nullary_t f){ return f(); };
+ const auto higher2nd = [unary](nullary_t f1){
+ return [unary, f1](unary_t f2){ return f2(unary(f1())); };
+ };
+ return higher1st(nullary) + higher2nd(nullary)(unary);
+ }
+
+ }
+
+ namespace test_variadic_templates
+ {
+
+ template <int...>
+ struct sum;
+
+ template <int N0, int... N1toN>
+ struct sum<N0, N1toN...>
+ {
+ static constexpr auto value = N0 + sum<N1toN...>::value;
+ };
+
+ template <>
+ struct sum<>
+ {
+ static constexpr auto value = 0;
+ };
+
+ static_assert(sum<>::value == 0, "");
+ static_assert(sum<1>::value == 1, "");
+ static_assert(sum<23>::value == 23, "");
+ static_assert(sum<1, 2>::value == 3, "");
+ static_assert(sum<5, 5, 11>::value == 21, "");
+ static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, "");
+
+ }
+
+ // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae
+ // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function
+ // because of this.
+ namespace test_template_alias_sfinae
+ {
+
+ struct foo {};
+
+ template<typename T>
+ using member = typename T::member_type;
+
+ template<typename T>
+ void func(...) {}
+
+ template<typename T>
+ void func(member<T>*) {}
+
+ void test();
+
+ void test() { func<foo>(0); }
+
+ }
+
+} // namespace cxx11
+
+#endif // __cplusplus >= 201103L
+
+
+
+
+// If the compiler admits that it is not ready for C++14, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201402L
+
+#error "This is not a C++14 compiler"
+
+#else
+
+namespace cxx14
+{
+
+ namespace test_polymorphic_lambdas
+ {
+
+ int
+ test()
+ {
+ const auto lambda = [](auto&&... args){
+ const auto istiny = [](auto x){
+ return (sizeof(x) == 1UL) ? 1 : 0;
+ };
+ const int aretiny[] = { istiny(args)... };
+ return aretiny[0];
+ };
+ return lambda(1, 1L, 1.0f, '1');
+ }
+
+ }
+
+ namespace test_binary_literals
+ {
+
+ constexpr auto ivii = 0b0000000000101010;
+ static_assert(ivii == 42, "wrong value");
+
+ }
+
+ namespace test_generalized_constexpr
+ {
+
+ template < typename CharT >
+ constexpr unsigned long
+ strlen_c(const CharT *const s) noexcept
+ {
+ auto length = 0UL;
+ for (auto p = s; *p; ++p)
+ ++length;
+ return length;
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("x") == 1UL, "");
+ static_assert(strlen_c("test") == 4UL, "");
+ static_assert(strlen_c("another\0test") == 7UL, "");
+
+ }
+
+ namespace test_lambda_init_capture
+ {
+
+ int
+ test()
+ {
+ auto x = 0;
+ const auto lambda1 = [a = x](int b){ return a + b; };
+ const auto lambda2 = [a = lambda1(x)](){ return a; };
+ return lambda2();
+ }
+
+ }
+
+ namespace test_digit_seperators
+ {
+
+ constexpr auto ten_million = 100'000'000;
+ static_assert(ten_million == 100000000, "");
+
+ }
+
+ namespace test_return_type_deduction
+ {
+
+ auto f(int& x) { return x; }
+ decltype(auto) g(int& x) { return x; }
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static constexpr auto value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static constexpr auto value = true;
+ };
+
+ int
+ test()
+ {
+ auto x = 0;
+ static_assert(is_same<int, decltype(f(x))>::value, "");
+ static_assert(is_same<int&, decltype(g(x))>::value, "");
+ return x;
+ }
+
+ }
+
+} // namespace cxx14
+
+#endif // __cplusplus >= 201402L
+
+
+
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ eval $cachevar=yes
+else
+ eval $cachevar=no
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ CXX="$ac_save_CXX"
+fi
+eval ac_res=\$$cachevar
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5
+$as_echo "$ac_res" >&6; }
+ if eval test x\$$cachevar = xyes; then
+ CXX="$CXX $switch"
+ if test -n "$CXXCPP" ; then
+ CXXCPP="$CXXCPP $switch"
+ fi
+ ac_success=yes
+ break
+ fi
+ done
+ fi
+ ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+ if test x$ax_cxx_compile_cxx14_required = xtrue; then
+ if test x$ac_success = xno; then
+ as_fn_error $? "*** A compiler with support for C++14 language features is required." "$LINENO" 5
+ fi
+ fi
+ if test x$ac_success = xno; then
+ HAVE_CXX14=0
+ { $as_echo "$as_me:${as_lineno-$LINENO}: No compiler with C++14 support was found" >&5
+$as_echo "$as_me: No compiler with C++14 support was found" >&6;}
+ else
+ HAVE_CXX14=1
+
+$as_echo "#define HAVE_CXX14 1" >>confdefs.h
+
+ fi
+
+
+ if test "x${HAVE_CXX14}" = "x1" ; then
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Wall" >&5
+$as_echo_n "checking whether compiler supports -Wall... " >&6; }
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-Wall
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-Wall
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cxxflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -g3" >&5
+$as_echo_n "checking whether compiler supports -g3... " >&6; }
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-g3
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-g3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cxxflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+
+
+ SAVED_LIBS="${LIBS}"
+ T_APPEND_V=-lstdc++
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether libstdc++ linkage is compilable" >&5
+$as_echo_n "checking whether libstdc++ linkage is compilable... " >&6; }
+if ${je_cv_libstdcxx+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <stdlib.h>
+
+int
+main ()
+{
+
+ int *arr = (int *)malloc(sizeof(int) * 42);
+ if (arr == NULL)
+ return 1;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_libstdcxx=yes
+else
+ je_cv_libstdcxx=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_libstdcxx" >&5
+$as_echo "$je_cv_libstdcxx" >&6; }
+
+ if test "x${je_cv_libstdcxx}" = "xno" ; then
+ LIBS="${SAVED_LIBS}"
+ fi
+ else
+ enable_cxx="0"
+ fi
+fi
+
+
+
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for grep that handles long lines and -e" >&5
$as_echo_n "checking for grep that handles long lines and -e... " >&6; }
@@ -4428,10 +6542,22 @@ _ACEOF
fi
if test "x${je_cv_msvc}" = "xyes" -a "x${ac_cv_header_inttypes_h}" = "xno"; then
- CPPFLAGS="$CPPFLAGS -I${srcdir}/include/msvc_compat/C99"
+ T_APPEND_V=-I${srcdir}/include/msvc_compat/C99
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
fi
-# The cast to long int works around a bug in the HP C Compiler
+
+fi
+
+if test "x${je_cv_msvc}" = "xyes" ; then
+ LG_SIZEOF_PTR=LG_SIZEOF_PTR_WIN
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit" >&5
+$as_echo "Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit" >&6; }
+else
+ # The cast to long int works around a bug in the HP C Compiler
# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
# This bug is HP SR number 8606223364.
@@ -4464,12 +6590,13 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
-if test "x${ac_cv_sizeof_void_p}" = "x8" ; then
- LG_SIZEOF_PTR=3
-elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then
- LG_SIZEOF_PTR=2
-else
- as_fn_error $? "Unsupported pointer size: ${ac_cv_sizeof_void_p}" "$LINENO" 5
+ if test "x${ac_cv_sizeof_void_p}" = "x8" ; then
+ LG_SIZEOF_PTR=3
+ elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then
+ LG_SIZEOF_PTR=2
+ else
+ as_fn_error $? "Unsupported pointer size: ${ac_cv_sizeof_void_p}" "$LINENO" 5
+ fi
fi
cat >>confdefs.h <<_ACEOF
#define LG_SIZEOF_PTR $LG_SIZEOF_PTR
@@ -4570,6 +6697,51 @@ _ACEOF
# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
# This bug is HP SR number 8606223364.
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of long long" >&5
+$as_echo_n "checking size of long long... " >&6; }
+if ${ac_cv_sizeof_long_long+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if ac_fn_c_compute_int "$LINENO" "(long int) (sizeof (long long))" "ac_cv_sizeof_long_long" "$ac_includes_default"; then :
+
+else
+ if test "$ac_cv_type_long_long" = yes; then
+ { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5
+$as_echo "$as_me: error: in \`$ac_pwd':" >&2;}
+as_fn_error 77 "cannot compute sizeof (long long)
+See \`config.log' for more details" "$LINENO" 5; }
+ else
+ ac_cv_sizeof_long_long=0
+ fi
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_sizeof_long_long" >&5
+$as_echo "$ac_cv_sizeof_long_long" >&6; }
+
+
+
+cat >>confdefs.h <<_ACEOF
+#define SIZEOF_LONG_LONG $ac_cv_sizeof_long_long
+_ACEOF
+
+
+if test "x${ac_cv_sizeof_long_long}" = "x8" ; then
+ LG_SIZEOF_LONG_LONG=3
+elif test "x${ac_cv_sizeof_long_long}" = "x4" ; then
+ LG_SIZEOF_LONG_LONG=2
+else
+ as_fn_error $? "Unsupported long long size: ${ac_cv_sizeof_long_long}" "$LINENO" 5
+fi
+cat >>confdefs.h <<_ACEOF
+#define LG_SIZEOF_LONG_LONG $LG_SIZEOF_LONG_LONG
+_ACEOF
+
+
+# The cast to long int works around a bug in the HP C Compiler
+# version HP92453-01 B.11.11.23709.GP, which incorrectly rejects
+# declarations like `int a3[[(sizeof (unsigned char)) >= 0]];'.
+# This bug is HP SR number 8606223364.
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking size of intmax_t" >&5
$as_echo_n "checking size of intmax_t... " >&6; }
if ${ac_cv_sizeof_intmax_t+:} false; then :
@@ -4613,35 +6785,6 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
-ac_aux_dir=
-for ac_dir in "$srcdir" "$srcdir/.." "$srcdir/../.."; do
- if test -f "$ac_dir/install-sh"; then
- ac_aux_dir=$ac_dir
- ac_install_sh="$ac_aux_dir/install-sh -c"
- break
- elif test -f "$ac_dir/install.sh"; then
- ac_aux_dir=$ac_dir
- ac_install_sh="$ac_aux_dir/install.sh -c"
- break
- elif test -f "$ac_dir/shtool"; then
- ac_aux_dir=$ac_dir
- ac_install_sh="$ac_aux_dir/shtool install -c"
- break
- fi
-done
-if test -z "$ac_aux_dir"; then
- as_fn_error $? "cannot find install-sh, install.sh, or shtool in \"$srcdir\" \"$srcdir/..\" \"$srcdir/../..\"" "$LINENO" 5
-fi
-
-# These three variables are undocumented and unsupported,
-# and are intended to be withdrawn in a future Autoconf release.
-# They can cause serious problems if a builder's source tree is in a directory
-# whose full name contains unusual characters.
-ac_config_guess="$SHELL $ac_aux_dir/config.guess" # Please don't use this var.
-ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var.
-ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var.
-
-
# Make sure we can run config.sub.
$SHELL "$ac_aux_dir/config.sub" sun4 >/dev/null 2>&1 ||
as_fn_error $? "cannot run $SHELL $ac_aux_dir/config.sub" "$LINENO" 5
@@ -4716,7 +6859,46 @@ case $host_os in *\ *) host_os=`echo "$host_os" | sed 's/ /-/g'`;; esac
CPU_SPINWAIT=""
case "${host_cpu}" in
i686|x86_64)
- if ${je_cv_pause+:} false; then :
+ HAVE_CPU_SPINWAIT=1
+ if test "x${je_cv_msvc}" = "xyes" ; then
+ if ${je_cv_pause_msvc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pause instruction MSVC is compilable" >&5
+$as_echo_n "checking whether pause instruction MSVC is compilable... " >&6; }
+if ${je_cv_pause_msvc+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+_mm_pause(); return 0;
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_pause_msvc=yes
+else
+ je_cv_pause_msvc=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_pause_msvc" >&5
+$as_echo "$je_cv_pause_msvc" >&6; }
+
+fi
+
+ if test "x${je_cv_pause_msvc}" = "xyes" ; then
+ CPU_SPINWAIT='_mm_pause()'
+ fi
+ else
+ if ${je_cv_pause+:} false; then :
$as_echo_n "(cached) " >&6
else
@@ -4749,24 +6931,150 @@ $as_echo "$je_cv_pause" >&6; }
fi
- if test "x${je_cv_pause}" = "xyes" ; then
- CPU_SPINWAIT='__asm__ volatile("pause")'
+ if test "x${je_cv_pause}" = "xyes" ; then
+ CPU_SPINWAIT='__asm__ volatile("pause")'
+ fi
fi
;;
- powerpc)
- cat >>confdefs.h <<_ACEOF
-#define HAVE_ALTIVEC
-_ACEOF
-
- ;;
*)
+ HAVE_CPU_SPINWAIT=0
;;
esac
cat >>confdefs.h <<_ACEOF
+#define HAVE_CPU_SPINWAIT $HAVE_CPU_SPINWAIT
+_ACEOF
+
+cat >>confdefs.h <<_ACEOF
#define CPU_SPINWAIT $CPU_SPINWAIT
_ACEOF
+
+# Check whether --with-lg_vaddr was given.
+if test "${with_lg_vaddr+set}" = set; then :
+ withval=$with_lg_vaddr; LG_VADDR="$with_lg_vaddr"
+else
+ LG_VADDR="detect"
+fi
+
+
+case "${host_cpu}" in
+ aarch64)
+ if test "x$LG_VADDR" = "xdetect"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5
+$as_echo_n "checking number of significant virtual address bits... " >&6; }
+ if test "x${LG_SIZEOF_PTR}" = "x2" ; then
+ #aarch64 ILP32
+ LG_VADDR=32
+ else
+ #aarch64 LP64
+ LG_VADDR=48
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LG_VADDR" >&5
+$as_echo "$LG_VADDR" >&6; }
+ fi
+ ;;
+ x86_64)
+ if test "x$LG_VADDR" = "xdetect"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5
+$as_echo_n "checking number of significant virtual address bits... " >&6; }
+if ${je_cv_lg_vaddr+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test "$cross_compiling" = yes; then :
+ je_cv_lg_vaddr=57
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <stdio.h>
+#ifdef _WIN32
+#include <limits.h>
+#include <intrin.h>
+typedef unsigned __int32 uint32_t;
+#else
+#include <stdint.h>
+#endif
+
+int
+main ()
+{
+
+ uint32_t r[4];
+ uint32_t eax_in = 0x80000008U;
+#ifdef _WIN32
+ __cpuid((int *)r, (int)eax_in);
+#else
+ asm volatile ("cpuid"
+ : "=a" (r[0]), "=b" (r[1]), "=c" (r[2]), "=d" (r[3])
+ : "a" (eax_in), "c" (0)
+ );
+#endif
+ uint32_t eax_out = r[0];
+ uint32_t vaddr = ((eax_out & 0x0000ff00U) >> 8);
+ FILE *f = fopen("conftest.out", "w");
+ if (f == NULL) {
+ return 1;
+ }
+ if (vaddr > (sizeof(void *) << 3)) {
+ vaddr = sizeof(void *) << 3;
+ }
+ fprintf(f, "%u", vaddr);
+ fclose(f);
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_run "$LINENO"; then :
+ je_cv_lg_vaddr=`cat conftest.out`
+else
+ je_cv_lg_vaddr=error
+fi
+rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \
+ conftest.$ac_objext conftest.beam conftest.$ac_ext
+fi
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_lg_vaddr" >&5
+$as_echo "$je_cv_lg_vaddr" >&6; }
+ if test "x${je_cv_lg_vaddr}" != "x" ; then
+ LG_VADDR="${je_cv_lg_vaddr}"
+ fi
+ if test "x${LG_VADDR}" != "xerror" ; then
+ cat >>confdefs.h <<_ACEOF
+#define LG_VADDR $LG_VADDR
+_ACEOF
+
+ else
+ as_fn_error $? "cannot determine number of significant virtual address bits" "$LINENO" 5
+ fi
+ fi
+ ;;
+ *)
+ if test "x$LG_VADDR" = "xdetect"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking number of significant virtual address bits" >&5
+$as_echo_n "checking number of significant virtual address bits... " >&6; }
+ if test "x${LG_SIZEOF_PTR}" = "x3" ; then
+ LG_VADDR=64
+ elif test "x${LG_SIZEOF_PTR}" = "x2" ; then
+ LG_VADDR=32
+ elif test "x${LG_SIZEOF_PTR}" = "xLG_SIZEOF_PTR_WIN" ; then
+ LG_VADDR="(1U << (LG_SIZEOF_PTR_WIN+3))"
+ else
+ as_fn_error $? "Unsupported lg(pointer size): ${LG_SIZEOF_PTR}" "$LINENO" 5
+ fi
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $LG_VADDR" >&5
+$as_echo "$LG_VADDR" >&6; }
+ fi
+ ;;
+esac
+cat >>confdefs.h <<_ACEOF
+#define LG_VADDR $LG_VADDR
+_ACEOF
+
+
LD_PRELOAD_VAR="LD_PRELOAD"
so="so"
importlib="${so}"
@@ -4774,17 +7082,27 @@ o="$ac_objext"
a="a"
exe="$ac_exeext"
libprefix="lib"
+link_whole_archive="0"
DSO_LDFLAGS='-shared -Wl,-soname,$(@F)'
RPATH='-Wl,-rpath,$(1)'
SOREV="${so}.${rev}"
PIC_CFLAGS='-fPIC -DPIC'
CTARGET='-o $@'
LDTARGET='-o $@'
+TEST_LD_MODE=
EXTRA_LDFLAGS=
ARFLAGS='crus'
AROUT=' $@'
CC_MM=1
+if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then
+ TEST_LD_MODE='-dynamic'
+fi
+
+if test "x${je_cv_cray}" = "xyes" ; then
+ CC_MM=
+fi
+
@@ -4881,14 +7199,152 @@ else
fi
-default_munmap="1"
+
+
+
+if test -n "$ac_tool_prefix"; then
+ # Extract the first word of "${ac_tool_prefix}nm", so it can be a program name with args.
+set dummy ${ac_tool_prefix}nm; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_NM+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$NM"; then
+ ac_cv_prog_NM="$NM" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_NM="${ac_tool_prefix}nm"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+NM=$ac_cv_prog_NM
+if test -n "$NM"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $NM" >&5
+$as_echo "$NM" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+fi
+if test -z "$ac_cv_prog_NM"; then
+ ac_ct_NM=$NM
+ # Extract the first word of "nm", so it can be a program name with args.
+set dummy nm; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_ac_ct_NM+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$ac_ct_NM"; then
+ ac_cv_prog_ac_ct_NM="$ac_ct_NM" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_ac_ct_NM="nm"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+ac_ct_NM=$ac_cv_prog_ac_ct_NM
+if test -n "$ac_ct_NM"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_NM" >&5
+$as_echo "$ac_ct_NM" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+ if test "x$ac_ct_NM" = x; then
+ NM=":"
+ else
+ case $cross_compiling:$ac_tool_warned in
+yes:)
+{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5
+$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;}
+ac_tool_warned=yes ;;
+esac
+ NM=$ac_ct_NM
+ fi
+else
+ NM="$ac_cv_prog_NM"
+fi
+
+
+for ac_prog in gawk mawk nawk awk
+do
+ # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_AWK+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ if test -n "$AWK"; then
+ ac_cv_prog_AWK="$AWK" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+ IFS=$as_save_IFS
+ test -z "$as_dir" && as_dir=.
+ for ac_exec_ext in '' $ac_executable_extensions; do
+ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+ ac_cv_prog_AWK="$ac_prog"
+ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+ break 2
+ fi
+done
+ done
+IFS=$as_save_IFS
+
+fi
+fi
+AWK=$ac_cv_prog_AWK
+if test -n "$AWK"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $AWK" >&5
+$as_echo "$AWK" >&6; }
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+ test -n "$AWK" && break
+done
+
+
+default_retain="0"
maps_coalesce="1"
+DUMP_SYMS="${NM} -a"
+SYM_PREFIX=""
case "${host}" in
*-*-darwin* | *-*-ios*)
- CFLAGS="$CFLAGS"
abi="macho"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
-
RPATH=""
LD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES"
so="dylib"
@@ -4897,46 +7353,91 @@ case "${host}" in
DSO_LDFLAGS='-shared -Wl,-install_name,$(LIBDIR)/$(@F)'
SOREV="${rev}.${so}"
sbrk_deprecated="1"
+ SYM_PREFIX="_"
;;
*-*-freebsd*)
- CFLAGS="$CFLAGS"
abi="elf"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
+ $as_echo "#define JEMALLOC_SYSCTL_VM_OVERCOMMIT " >>confdefs.h
force_lazy_lock="1"
;;
*-*-dragonfly*)
- CFLAGS="$CFLAGS"
abi="elf"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
-
;;
*-*-openbsd*)
- CFLAGS="$CFLAGS"
abi="elf"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
-
force_tls="0"
;;
*-*-bitrig*)
- CFLAGS="$CFLAGS"
abi="elf"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
+ ;;
+ *-*-linux-android)
+ T_APPEND_V=-D_GNU_SOURCE
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
+ abi="elf"
+ $as_echo "#define JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_HAS_ALLOCA_H 1" >>confdefs.h
+ $as_echo "#define JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_THREADED_INIT " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_C11_ATOMICS 1" >>confdefs.h
+
+ force_tls="0"
+ if test "${LG_SIZEOF_PTR}" = "3"; then
+ default_retain="1"
+ fi
;;
*-*-linux*)
- CFLAGS="$CFLAGS"
- CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+ T_APPEND_V=-D_GNU_SOURCE
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
+ abi="elf"
+ $as_echo "#define JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_HAS_ALLOCA_H 1" >>confdefs.h
+
+ $as_echo "#define JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_THREADED_INIT " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_USE_CXX_THROW " >>confdefs.h
+
+ if test "${LG_SIZEOF_PTR}" = "3"; then
+ default_retain="1"
+ fi
+ ;;
+ *-*-kfreebsd*)
+ T_APPEND_V=-D_GNU_SOURCE
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
abi="elf"
$as_echo "#define JEMALLOC_HAS_ALLOCA_H 1" >>confdefs.h
- $as_echo "#define JEMALLOC_PURGE_MADVISE_DONTNEED " >>confdefs.h
+ $as_echo "#define JEMALLOC_SYSCTL_VM_OVERCOMMIT " >>confdefs.h
$as_echo "#define JEMALLOC_THREADED_INIT " >>confdefs.h
$as_echo "#define JEMALLOC_USE_CXX_THROW " >>confdefs.h
- default_munmap="0"
;;
*-*-netbsd*)
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking ABI" >&5
@@ -4958,27 +7459,36 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- CFLAGS="$CFLAGS"; abi="elf"
+ abi="elf"
else
abi="aout"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $abi" >&5
$as_echo "$abi" >&6; }
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
-
;;
*-*-solaris2*)
- CFLAGS="$CFLAGS"
abi="elf"
- $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
-
RPATH='-Wl,-R,$(1)'
- CPPFLAGS="$CPPFLAGS -D_POSIX_PTHREAD_SEMANTICS"
- LIBS="$LIBS -lposix4 -lsocket -lnsl"
+ T_APPEND_V=-D_POSIX_PTHREAD_SEMANTICS
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
+ T_APPEND_V=-lposix4 -lsocket -lnsl
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
;;
*-ibm-aix*)
- if "$LG_SIZEOF_PTR" = "8"; then
+ if test "${LG_SIZEOF_PTR}" = "3"; then
LD_PRELOAD_VAR="LDR_PRELOAD64"
else
LD_PRELOAD_VAR="LDR_PRELOAD"
@@ -4988,7 +7498,6 @@ $as_echo "$abi" >&6; }
*-*-mingw* | *-*-cygwin*)
abi="pecoff"
force_tls="0"
- force_lazy_lock="1"
maps_coalesce="0"
RPATH=""
so="dll"
@@ -5005,7 +7514,15 @@ $as_echo "$abi" >&6; }
else
importlib="${so}"
DSO_LDFLAGS="-shared"
+ link_whole_archive="1"
fi
+ case "${host}" in
+ *-*-cygwin*)
+ DUMP_SYMS="dumpbin /SYMBOLS"
+ ;;
+ *)
+ ;;
+ esac
a="lib"
libprefix=""
SOREV="${so}"
@@ -5086,6 +7603,74 @@ _ACEOF
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing log" >&5
+$as_echo_n "checking for library containing log... " >&6; }
+if ${ac_cv_search_log+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char log ();
+int
+main ()
+{
+return log ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' m; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_log=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_log+:} false; then :
+ break
+fi
+done
+if ${ac_cv_search_log+:} false; then :
+
+else
+ ac_cv_search_log=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_log" >&5
+$as_echo "$ac_cv_search_log" >&6; }
+ac_res=$ac_cv_search_log
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
+
+else
+ as_fn_error $? "Missing math functions" "$LINENO" 5
+fi
+
+if test "x$ac_cv_search_log" != "xnone required" ; then
+ LM="$ac_cv_search_log"
+else
+ LM=
+fi
+
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether __attribute__ syntax is compilable" >&5
$as_echo_n "checking whether __attribute__ syntax is compilable... " >&6; }
if ${je_cv_attribute+:} false; then :
@@ -5120,12 +7705,21 @@ if test "x${je_cv_attribute}" = "xyes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fvisibility=hidden" >&5
$as_echo_n "checking whether compiler supports -fvisibility=hidden... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-fvisibility=hidden"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-fvisibility=hidden
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -fvisibility=hidden"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -5141,30 +7735,109 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-fvisibility=hidden
+ je_cv_cflags_added=-fvisibility=hidden
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fvisibility=hidden" >&5
+$as_echo_n "checking whether compiler supports -fvisibility=hidden... " >&6; }
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-fvisibility=hidden
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-fvisibility=hidden
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cxxflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
fi
fi
-SAVED_CFLAGS="${CFLAGS}"
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
$as_echo_n "checking whether compiler supports -Werror... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Werror"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -Werror"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -5180,17 +7853,74 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Werror
+ je_cv_cflags_added=-Werror
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5
+$as_echo_n "checking whether compiler supports -herror_on_warning... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-herror_on_warning
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-herror_on_warning
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether tls_model attribute is compilable" >&5
@@ -5222,24 +7952,86 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_tls_model" >&5
$as_echo "$je_cv_tls_model" >&6; }
-CFLAGS="${SAVED_CFLAGS}"
-if test "x${je_cv_tls_model}" = "xyes" ; then
- $as_echo "#define JEMALLOC_TLS_MODEL __attribute__((tls_model(\"initial-exec\")))" >>confdefs.h
-
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- $as_echo "#define JEMALLOC_TLS_MODEL " >>confdefs.h
-
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-SAVED_CFLAGS="${CFLAGS}"
+
+
+
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
$as_echo_n "checking whether compiler supports -Werror... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Werror"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Werror
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
else
- CFLAGS="${CFLAGS} -Werror"
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5
+$as_echo_n "checking whether compiler supports -herror_on_warning... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-herror_on_warning
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -5255,17 +8047,23 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Werror
+ je_cv_cflags_added=-herror_on_warning
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether alloc_size attribute is compilable" >&5
@@ -5295,21 +8093,38 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_alloc_size" >&5
$as_echo "$je_cv_alloc_size" >&6; }
-CFLAGS="${SAVED_CFLAGS}"
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
if test "x${je_cv_alloc_size}" = "xyes" ; then
$as_echo "#define JEMALLOC_HAVE_ATTR_ALLOC_SIZE " >>confdefs.h
fi
-SAVED_CFLAGS="${CFLAGS}"
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
$as_echo_n "checking whether compiler supports -Werror... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Werror"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -Werror"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -5325,17 +8140,74 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Werror
+ je_cv_cflags_added=-Werror
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5
+$as_echo_n "checking whether compiler supports -herror_on_warning... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-herror_on_warning
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-herror_on_warning
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether format(gnu_printf, ...) attribute is compilable" >&5
@@ -5365,21 +8237,89 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_format_gnu_printf" >&5
$as_echo "$je_cv_format_gnu_printf" >&6; }
-CFLAGS="${SAVED_CFLAGS}"
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
if test "x${je_cv_format_gnu_printf}" = "xyes" ; then
$as_echo "#define JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF " >>confdefs.h
fi
-SAVED_CFLAGS="${CFLAGS}"
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
$as_echo_n "checking whether compiler supports -Werror... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-Werror"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Werror
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -Werror"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5
+$as_echo_n "checking whether compiler supports -herror_on_warning... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-herror_on_warning
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -5395,17 +8335,23 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-Werror
+ je_cv_cflags_added=-herror_on_warning
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether format(printf, ...) attribute is compilable" >&5
@@ -5435,7 +8381,14 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_format_printf" >&5
$as_echo "$je_cv_format_printf" >&6; }
-CFLAGS="${SAVED_CFLAGS}"
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
if test "x${je_cv_format_printf}" = "xyes" ; then
$as_echo "#define JEMALLOC_HAVE_ATTR_FORMAT_PRINTF " >>confdefs.h
@@ -5739,165 +8692,132 @@ fi
-public_syms="malloc_conf malloc_message malloc calloc posix_memalign aligned_alloc realloc free mallocx rallocx xallocx sallocx dallocx sdallocx nallocx mallctl mallctlnametomib mallctlbymib malloc_stats_print malloc_usable_size"
-ac_fn_c_check_func "$LINENO" "memalign" "ac_cv_func_memalign"
-if test "x$ac_cv_func_memalign" = xyes; then :
- $as_echo "#define JEMALLOC_OVERRIDE_MEMALIGN " >>confdefs.h
-
- public_syms="${public_syms} memalign"
+# Check whether --with-mangling was given.
+if test "${with_mangling+set}" = set; then :
+ withval=$with_mangling; mangling_map="$with_mangling"
+else
+ mangling_map=""
fi
-ac_fn_c_check_func "$LINENO" "valloc" "ac_cv_func_valloc"
-if test "x$ac_cv_func_valloc" = xyes; then :
- $as_echo "#define JEMALLOC_OVERRIDE_VALLOC " >>confdefs.h
-
- public_syms="${public_syms} valloc"
-fi
-GCOV_FLAGS=
-# Check whether --enable-code-coverage was given.
-if test "${enable_code_coverage+set}" = set; then :
- enableval=$enable_code_coverage; if test "x$enable_code_coverage" = "xno" ; then
- enable_code_coverage="0"
+# Check whether --with-jemalloc_prefix was given.
+if test "${with_jemalloc_prefix+set}" = set; then :
+ withval=$with_jemalloc_prefix; JEMALLOC_PREFIX="$with_jemalloc_prefix"
else
- enable_code_coverage="1"
-fi
-
+ if test "x$abi" != "xmacho" -a "x$abi" != "xpecoff"; then
+ JEMALLOC_PREFIX=""
else
- enable_code_coverage="0"
+ JEMALLOC_PREFIX="je_"
+fi
fi
-if test "x$enable_code_coverage" = "x1" ; then
- deoptimize="no"
- echo "$CFLAGS $EXTRA_CFLAGS" | grep '\-O' >/dev/null || deoptimize="yes"
- if test "x${deoptimize}" = "xyes" ; then
+if test "x$JEMALLOC_PREFIX" = "x" ; then
+ $as_echo "#define JEMALLOC_IS_MALLOC 1" >>confdefs.h
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O0" >&5
-$as_echo_n "checking whether compiler supports -O0... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-O0"
else
- CFLAGS="${CFLAGS} -O0"
-fi
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
+ JEMALLOC_CPREFIX=`echo ${JEMALLOC_PREFIX} | tr "a-z" "A-Z"`
+ cat >>confdefs.h <<_ACEOF
+#define JEMALLOC_PREFIX "$JEMALLOC_PREFIX"
+_ACEOF
+ cat >>confdefs.h <<_ACEOF
+#define JEMALLOC_CPREFIX "$JEMALLOC_CPREFIX"
+_ACEOF
-int
-main ()
-{
+fi
- return 0;
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-O0
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-else
- je_cv_cflags_appended=
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
- fi
+# Check whether --with-export was given.
+if test "${with_export+set}" = set; then :
+ withval=$with_export; if test "x$with_export" = "xno"; then
+ $as_echo "#define JEMALLOC_EXPORT /**/" >>confdefs.h
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fprofile-arcs -ftest-coverage" >&5
-$as_echo_n "checking whether compiler supports -fprofile-arcs -ftest-coverage... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-fprofile-arcs -ftest-coverage"
-else
- CFLAGS="${CFLAGS} -fprofile-arcs -ftest-coverage"
fi
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
+fi
-int
-main ()
-{
-
- return 0;
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-fprofile-arcs -ftest-coverage
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
-$as_echo "yes" >&6; }
-else
- je_cv_cflags_appended=
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+public_syms="aligned_alloc calloc dallocx free mallctl mallctlbymib mallctlnametomib malloc malloc_conf malloc_message malloc_stats_print malloc_usable_size mallocx nallocx posix_memalign rallocx realloc sallocx sdallocx xallocx"
+ac_fn_c_check_func "$LINENO" "memalign" "ac_cv_func_memalign"
+if test "x$ac_cv_func_memalign" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE_MEMALIGN " >>confdefs.h
+ public_syms="${public_syms} memalign"
fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
- EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fprofile-arcs -ftest-coverage"
- $as_echo "#define JEMALLOC_CODE_COVERAGE " >>confdefs.h
+ac_fn_c_check_func "$LINENO" "valloc" "ac_cv_func_valloc"
+if test "x$ac_cv_func_valloc" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE_VALLOC " >>confdefs.h
+ public_syms="${public_syms} valloc"
fi
+wrap_syms=
+if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ ac_fn_c_check_func "$LINENO" "__libc_calloc" "ac_cv_func___libc_calloc"
+if test "x$ac_cv_func___libc_calloc" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_CALLOC " >>confdefs.h
-# Check whether --with-mangling was given.
-if test "${with_mangling+set}" = set; then :
- withval=$with_mangling; mangling_map="$with_mangling"
-else
- mangling_map=""
+ wrap_syms="${wrap_syms} __libc_calloc"
fi
+ ac_fn_c_check_func "$LINENO" "__libc_free" "ac_cv_func___libc_free"
+if test "x$ac_cv_func___libc_free" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_FREE " >>confdefs.h
-
-# Check whether --with-jemalloc_prefix was given.
-if test "${with_jemalloc_prefix+set}" = set; then :
- withval=$with_jemalloc_prefix; JEMALLOC_PREFIX="$with_jemalloc_prefix"
-else
- if test "x$abi" != "xmacho" -a "x$abi" != "xpecoff"; then
- JEMALLOC_PREFIX=""
-else
- JEMALLOC_PREFIX="je_"
+ wrap_syms="${wrap_syms} __libc_free"
fi
+ ac_fn_c_check_func "$LINENO" "__libc_malloc" "ac_cv_func___libc_malloc"
+if test "x$ac_cv_func___libc_malloc" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_MALLOC " >>confdefs.h
+
+ wrap_syms="${wrap_syms} __libc_malloc"
fi
-if test "x$JEMALLOC_PREFIX" != "x" ; then
- JEMALLOC_CPREFIX=`echo ${JEMALLOC_PREFIX} | tr "a-z" "A-Z"`
- cat >>confdefs.h <<_ACEOF
-#define JEMALLOC_PREFIX "$JEMALLOC_PREFIX"
-_ACEOF
+ ac_fn_c_check_func "$LINENO" "__libc_memalign" "ac_cv_func___libc_memalign"
+if test "x$ac_cv_func___libc_memalign" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_MEMALIGN " >>confdefs.h
- cat >>confdefs.h <<_ACEOF
-#define JEMALLOC_CPREFIX "$JEMALLOC_CPREFIX"
-_ACEOF
+ wrap_syms="${wrap_syms} __libc_memalign"
+fi
+ ac_fn_c_check_func "$LINENO" "__libc_realloc" "ac_cv_func___libc_realloc"
+if test "x$ac_cv_func___libc_realloc" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_REALLOC " >>confdefs.h
+
+ wrap_syms="${wrap_syms} __libc_realloc"
fi
+ ac_fn_c_check_func "$LINENO" "__libc_valloc" "ac_cv_func___libc_valloc"
+if test "x$ac_cv_func___libc_valloc" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___LIBC_VALLOC " >>confdefs.h
+ wrap_syms="${wrap_syms} __libc_valloc"
+fi
-# Check whether --with-export was given.
-if test "${with_export+set}" = set; then :
- withval=$with_export; if test "x$with_export" = "xno"; then
- $as_echo "#define JEMALLOC_EXPORT /**/" >>confdefs.h
+ ac_fn_c_check_func "$LINENO" "__posix_memalign" "ac_cv_func___posix_memalign"
+if test "x$ac_cv_func___posix_memalign" = xyes; then :
+ $as_echo "#define JEMALLOC_OVERRIDE___POSIX_MEMALIGN " >>confdefs.h
+ wrap_syms="${wrap_syms} __posix_memalign"
fi
fi
+case "${host}" in
+ *-*-mingw* | *-*-cygwin*)
+ wrap_syms="${wrap_syms} tls_callback"
+ ;;
+ *)
+ ;;
+esac
# Check whether --with-private_namespace was given.
@@ -5927,6 +8847,21 @@ fi
install_suffix="$INSTALL_SUFFIX"
+
+# Check whether --with-malloc_conf was given.
+if test "${with_malloc_conf+set}" = set; then :
+ withval=$with_malloc_conf; JEMALLOC_CONFIG_MALLOC_CONF="$with_malloc_conf"
+else
+ JEMALLOC_CONFIG_MALLOC_CONF=""
+
+fi
+
+config_malloc_conf="$JEMALLOC_CONFIG_MALLOC_CONF"
+cat >>confdefs.h <<_ACEOF
+#define JEMALLOC_CONFIG_MALLOC_CONF "$config_malloc_conf"
+_ACEOF
+
+
je_="je_"
@@ -5938,7 +8873,7 @@ cfgoutputs_in="${cfgoutputs_in} doc/jemalloc.xml.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_macros.h.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_protos.h.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_typedefs.h.in"
-cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_internal.h.in"
+cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_preamble.h.in"
cfgoutputs_in="${cfgoutputs_in} test/test.sh.in"
cfgoutputs_in="${cfgoutputs_in} test/include/test/jemalloc_test.h.in"
@@ -5950,7 +8885,7 @@ cfgoutputs_out="${cfgoutputs_out} doc/jemalloc.xml"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_macros.h"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_protos.h"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_typedefs.h"
-cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_internal.h"
+cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_preamble.h"
cfgoutputs_out="${cfgoutputs_out} test/test.sh"
cfgoutputs_out="${cfgoutputs_out} test/include/test/jemalloc_test.h"
@@ -5962,15 +8897,14 @@ cfgoutputs_tup="${cfgoutputs_tup} doc/jemalloc.xml:doc/jemalloc.xml.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_macros.h:include/jemalloc/jemalloc_macros.h.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_protos.h:include/jemalloc/jemalloc_protos.h.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_typedefs.h:include/jemalloc/jemalloc_typedefs.h.in"
-cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_internal.h"
+cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_preamble.h"
cfgoutputs_tup="${cfgoutputs_tup} test/test.sh:test/test.sh.in"
cfgoutputs_tup="${cfgoutputs_tup} test/include/test/jemalloc_test.h:test/include/test/jemalloc_test.h.in"
cfghdrs_in="include/jemalloc/jemalloc_defs.h.in"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/jemalloc_internal_defs.h.in"
+cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_namespace.sh"
-cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_unnamespace.sh"
-cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.txt"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_namespace.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_unnamespace.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/size_classes.sh"
@@ -5981,8 +8915,8 @@ cfghdrs_in="${cfghdrs_in} test/include/test/jemalloc_test_defs.h.in"
cfghdrs_out="include/jemalloc/jemalloc_defs.h"
cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc${install_suffix}.h"
-cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_namespace.h"
-cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_unnamespace.h"
+cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols.awk"
+cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols_jet.awk"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_symbols.txt"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_namespace.h"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_unnamespace.h"
@@ -5998,24 +8932,6 @@ cfghdrs_tup="include/jemalloc/jemalloc_defs.h:include/jemalloc/jemalloc_defs.h.i
cfghdrs_tup="${cfghdrs_tup} include/jemalloc/internal/jemalloc_internal_defs.h:include/jemalloc/internal/jemalloc_internal_defs.h.in"
cfghdrs_tup="${cfghdrs_tup} test/include/test/jemalloc_test_defs.h:test/include/test/jemalloc_test_defs.h.in"
-# Check whether --enable-cc-silence was given.
-if test "${enable_cc_silence+set}" = set; then :
- enableval=$enable_cc_silence; if test "x$enable_cc_silence" = "xno" ; then
- enable_cc_silence="0"
-else
- enable_cc_silence="1"
-fi
-
-else
- enable_cc_silence="1"
-
-fi
-
-if test "x$enable_cc_silence" = "x1" ; then
- $as_echo "#define JEMALLOC_CC_SILENCE " >>confdefs.h
-
-fi
-
# Check whether --enable-debug was given.
if test "${enable_debug+set}" = set; then :
enableval=$enable_debug; if test "x$enable_debug" = "xno" ; then
@@ -6036,42 +8952,86 @@ fi
if test "x$enable_debug" = "x1" ; then
$as_echo "#define JEMALLOC_DEBUG " >>confdefs.h
- enable_ivsalloc="1"
fi
-# Check whether --enable-ivsalloc was given.
-if test "${enable_ivsalloc+set}" = set; then :
- enableval=$enable_ivsalloc; if test "x$enable_ivsalloc" = "xno" ; then
- enable_ivsalloc="0"
+if test "x$enable_debug" = "x0" ; then
+ if test "x$GCC" = "xyes" ; then
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O3" >&5
+$as_echo_n "checking whether compiler supports -O3... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-O3
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- enable_ivsalloc="1"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
-else
- enable_ivsalloc="0"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-if test "x$enable_ivsalloc" = "x1" ; then
- $as_echo "#define JEMALLOC_IVSALLOC " >>confdefs.h
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-O3
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
-if test "x$enable_debug" = "x0" -a "x$no_CFLAGS" = "xyes" ; then
- optimize="no"
- echo "$CFLAGS $EXTRA_CFLAGS" | grep '\-O' >/dev/null || optimize="yes"
- if test "x${optimize}" = "xyes" ; then
- if test "x$GCC" = "xyes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O3" >&5
$as_echo_n "checking whether compiler supports -O3... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-O3"
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-O3
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -O3"
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -6086,28 +9046,49 @@ main ()
return 0;
}
_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-O3
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-O3
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cxxflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -funroll-loops" >&5
$as_echo_n "checking whether compiler supports -funroll-loops... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-funroll-loops"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-funroll-loops
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -funroll-loops"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -6123,28 +9104,43 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-funroll-loops
+ je_cv_cflags_added=-funroll-loops
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
- elif test "x$je_cv_msvc" = "xyes" ; then
+
+ elif test "x$je_cv_msvc" = "xyes" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O2" >&5
$as_echo_n "checking whether compiler supports -O2... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-O2"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-O2
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -O2"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -6160,28 +9156,106 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-O2
+ je_cv_cflags_added=-O2
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
- else
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O2" >&5
+$as_echo_n "checking whether compiler supports -O2... " >&6; }
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-O2
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-O2
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cxxflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+
+ else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O" >&5
$as_echo_n "checking whether compiler supports -O... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-O"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-O
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- CFLAGS="${CFLAGS} -O"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -6197,19 +9271,87 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-O
+ je_cv_cflags_added=-O
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -O" >&5
+$as_echo_n "checking whether compiler supports -O... " >&6; }
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+T_APPEND_V=-O
+ if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+ je_cv_cxxflags_added=-O
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cxxflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+if test "x${CONFIGURE_CXXFLAGS}" = "x" -o "x${SPECIFIED_CXXFLAGS}" = "x" ; then
+ CXXFLAGS="${CONFIGURE_CXXFLAGS}${SPECIFIED_CXXFLAGS}"
+else
+ CXXFLAGS="${CONFIGURE_CXXFLAGS} ${SPECIFIED_CXXFLAGS}"
+fi
+
- fi
fi
fi
@@ -6333,13 +9475,27 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_unwind_unw_backtrace" >&5
$as_echo "$ac_cv_lib_unwind_unw_backtrace" >&6; }
if test "x$ac_cv_lib_unwind_unw_backtrace" = xyes; then :
- LIBS="$LIBS $LUNWIND"
+ T_APPEND_V=$LUNWIND
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
else
enable_prof_libunwind="0"
fi
else
- LIBS="$LIBS $LUNWIND"
+ T_APPEND_V=$LUNWIND
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
fi
if test "x${enable_prof_libunwind}" = "x1" ; then
backtrace_method="libunwind"
@@ -6377,7 +9533,8 @@ fi
done
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _Unwind_Backtrace in -lgcc" >&5
+ if test "x${enable_prof_libgcc}" = "x1" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for _Unwind_Backtrace in -lgcc" >&5
$as_echo_n "checking for _Unwind_Backtrace in -lgcc... " >&6; }
if ${ac_cv_lib_gcc__Unwind_Backtrace+:} false; then :
$as_echo_n "(cached) " >&6
@@ -6414,11 +9571,19 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gcc__Unwind_Backtrace" >&5
$as_echo "$ac_cv_lib_gcc__Unwind_Backtrace" >&6; }
if test "x$ac_cv_lib_gcc__Unwind_Backtrace" = xyes; then :
- LIBS="$LIBS -lgcc"
+ T_APPEND_V=-lgcc
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
else
enable_prof_libgcc="0"
fi
+ fi
if test "x${enable_prof_libgcc}" = "x1" ; then
backtrace_method="libgcc"
$as_echo "#define JEMALLOC_PROF_LIBGCC " >>confdefs.h
@@ -6446,12 +9611,21 @@ if test "x$backtrace_method" = "x" -a "x$enable_prof_gcc" = "x1" \
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -fno-omit-frame-pointer" >&5
$as_echo_n "checking whether compiler supports -fno-omit-frame-pointer... " >&6; }
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="-fno-omit-frame-pointer"
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-fno-omit-frame-pointer
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- CFLAGS="${CFLAGS} -fno-omit-frame-pointer"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -6467,17 +9641,23 @@ main ()
}
_ACEOF
if ac_fn_c_try_compile "$LINENO"; then :
- je_cv_cflags_appended=-fno-omit-frame-pointer
+ je_cv_cflags_added=-fno-omit-frame-pointer
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
$as_echo "yes" >&6; }
else
- je_cv_cflags_appended=
+ je_cv_cflags_added=
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
$as_echo "no" >&6; }
- CFLAGS="${TCFLAGS}"
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
backtrace_method="gcc intrinsics"
$as_echo "#define JEMALLOC_PROF_GCC " >>confdefs.h
@@ -6495,30 +9675,16 @@ $as_echo_n "checking configured backtracing method... " >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $backtrace_method" >&5
$as_echo "$backtrace_method" >&6; }
if test "x$enable_prof" = "x1" ; then
- if test "x$abi" != "xpecoff"; then
- LIBS="$LIBS -lm"
- fi
-
- $as_echo "#define JEMALLOC_PROF " >>confdefs.h
-
-fi
-
-
-# Check whether --enable-tcache was given.
-if test "${enable_tcache+set}" = set; then :
- enableval=$enable_tcache; if test "x$enable_tcache" = "xno" ; then
- enable_tcache="0"
+ T_APPEND_V=$LM
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
else
- enable_tcache="1"
+ LIBS="${LIBS} ${T_APPEND_V}"
fi
-else
- enable_tcache="1"
-fi
-if test "x$enable_tcache" = "x1" ; then
- $as_echo "#define JEMALLOC_TCACHE " >>confdefs.h
+ $as_echo "#define JEMALLOC_PROF " >>confdefs.h
fi
@@ -6528,25 +9694,11 @@ if test "x${maps_coalesce}" = "x1" ; then
fi
-# Check whether --enable-munmap was given.
-if test "${enable_munmap+set}" = set; then :
- enableval=$enable_munmap; if test "x$enable_munmap" = "xno" ; then
- enable_munmap="0"
-else
- enable_munmap="1"
-fi
-
-else
- enable_munmap="${default_munmap}"
-
-fi
-
-if test "x$enable_munmap" = "x1" ; then
- $as_echo "#define JEMALLOC_MUNMAP " >>confdefs.h
+if test "x$default_retain" = "x1" ; then
+ $as_echo "#define JEMALLOC_RETAIN " >>confdefs.h
fi
-
have_dss="1"
ac_fn_c_check_func "$LINENO" "sbrk" "ac_cv_func_sbrk"
if test "x$ac_cv_func_sbrk" = xyes; then :
@@ -6647,65 +9799,6 @@ if test "x$enable_utrace" = "x1" ; then
fi
-# Check whether --enable-valgrind was given.
-if test "${enable_valgrind+set}" = set; then :
- enableval=$enable_valgrind; if test "x$enable_valgrind" = "xno" ; then
- enable_valgrind="0"
-else
- enable_valgrind="1"
-fi
-
-else
- enable_valgrind="1"
-
-fi
-
-if test "x$enable_valgrind" = "x1" ; then
-
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether valgrind is compilable" >&5
-$as_echo_n "checking whether valgrind is compilable... " >&6; }
-if ${je_cv_valgrind+:} false; then :
- $as_echo_n "(cached) " >&6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-
-#include <valgrind/valgrind.h>
-#include <valgrind/memcheck.h>
-
-#if !defined(VALGRIND_RESIZEINPLACE_BLOCK)
-# error "Incompatible Valgrind version"
-#endif
-
-int
-main ()
-{
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_link "$LINENO"; then :
- je_cv_valgrind=yes
-else
- je_cv_valgrind=no
-fi
-rm -f core conftest.err conftest.$ac_objext \
- conftest$ac_exeext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_valgrind" >&5
-$as_echo "$je_cv_valgrind" >&6; }
-
- if test "x${je_cv_valgrind}" = "xno" ; then
- enable_valgrind="0"
- fi
- if test "x$enable_valgrind" = "x1" ; then
- $as_echo "#define JEMALLOC_VALGRIND " >>confdefs.h
-
- fi
-fi
-
-
# Check whether --enable-xmalloc was given.
if test "${enable_xmalloc+set}" = set; then :
enableval=$enable_xmalloc; if test "x$enable_xmalloc" = "xno" ; then
@@ -6744,6 +9837,70 @@ if test "x$enable_cache_oblivious" = "x1" ; then
fi
+# Check whether --enable-log was given.
+if test "${enable_log+set}" = set; then :
+ enableval=$enable_log; if test "x$enable_log" = "xno" ; then
+ enable_log="0"
+else
+ enable_log="1"
+fi
+
+else
+ enable_log="0"
+
+fi
+
+if test "x$enable_log" = "x1" ; then
+ $as_echo "#define JEMALLOC_LOG " >>confdefs.h
+
+fi
+
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program using __builtin_unreachable is compilable" >&5
+$as_echo_n "checking whether a program using __builtin_unreachable is compilable... " >&6; }
+if ${je_cv_gcc_builtin_unreachable+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+void foo (void) {
+ __builtin_unreachable();
+}
+
+int
+main ()
+{
+
+ {
+ foo();
+ }
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_gcc_builtin_unreachable=yes
+else
+ je_cv_gcc_builtin_unreachable=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_builtin_unreachable" >&5
+$as_echo "$je_cv_gcc_builtin_unreachable" >&6; }
+
+if test "x${je_cv_gcc_builtin_unreachable}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_INTERNAL_UNREACHABLE __builtin_unreachable" >>confdefs.h
+
+else
+ $as_echo "#define JEMALLOC_INTERNAL_UNREACHABLE abort" >>confdefs.h
+
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether a program using __builtin_ffsl is compilable" >&5
$as_echo_n "checking whether a program using __builtin_ffsl is compilable... " >&6; }
@@ -6782,6 +9939,8 @@ fi
$as_echo "$je_cv_gcc_builtin_ffsl" >&6; }
if test "x${je_cv_gcc_builtin_ffsl}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_INTERNAL_FFSLL __builtin_ffsll" >>confdefs.h
+
$as_echo "#define JEMALLOC_INTERNAL_FFSL __builtin_ffsl" >>confdefs.h
$as_echo "#define JEMALLOC_INTERNAL_FFS __builtin_ffs" >>confdefs.h
@@ -6825,6 +9984,8 @@ fi
$as_echo "$je_cv_function_ffsl" >&6; }
if test "x${je_cv_function_ffsl}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_INTERNAL_FFSLL ffsll" >>confdefs.h
+
$as_echo "#define JEMALLOC_INTERNAL_FFSL ffsl" >>confdefs.h
$as_echo "#define JEMALLOC_INTERNAL_FFS ffs" >>confdefs.h
@@ -6835,19 +9996,6 @@ $as_echo "$je_cv_function_ffsl" >&6; }
fi
-# Check whether --with-lg_tiny_min was given.
-if test "${with_lg_tiny_min+set}" = set; then :
- withval=$with_lg_tiny_min; LG_TINY_MIN="$with_lg_tiny_min"
-else
- LG_TINY_MIN="3"
-fi
-
-cat >>confdefs.h <<_ACEOF
-#define LG_TINY_MIN $LG_TINY_MIN
-_ACEOF
-
-
-
# Check whether --with-lg_quantum was given.
if test "${with_lg_quantum+set}" = set; then :
withval=$with_lg_quantum; LG_QUANTA="$with_lg_quantum"
@@ -6913,7 +10061,7 @@ main ()
if (f == NULL) {
return 1;
}
- fprintf(f, "%d\n", result);
+ fprintf(f, "%d", result);
fclose(f);
return 0;
@@ -6948,6 +10096,41 @@ else
fi
+# Check whether --with-lg_hugepage was given.
+if test "${with_lg_hugepage+set}" = set; then :
+ withval=$with_lg_hugepage; je_cv_lg_hugepage="${with_lg_hugepage}"
+else
+ je_cv_lg_hugepage=""
+fi
+
+if test "x${je_cv_lg_hugepage}" = "x" ; then
+ if test -e "/proc/meminfo" ; then
+ hpsk=`cat /proc/meminfo 2>/dev/null | \
+ grep -e '^Hugepagesize:[[:space:]]\+[0-9]\+[[:space:]]kB$' | \
+ awk '{print $2}'`
+ if test "x${hpsk}" != "x" ; then
+ je_cv_lg_hugepage=10
+ while test "${hpsk}" -gt 1 ; do
+ hpsk="$((hpsk / 2))"
+ je_cv_lg_hugepage="$((je_cv_lg_hugepage + 1))"
+ done
+ fi
+ fi
+
+ if test "x${je_cv_lg_hugepage}" = "x" ; then
+ je_cv_lg_hugepage=21
+ fi
+fi
+if test "x${LG_PAGE}" != "xundefined" -a \
+ "${je_cv_lg_hugepage}" -lt "${LG_PAGE}" ; then
+ as_fn_error $? "Huge page size (2^${je_cv_lg_hugepage}) must be at least page size (2^${LG_PAGE})" "$LINENO" 5
+fi
+cat >>confdefs.h <<_ACEOF
+#define LG_HUGEPAGE ${je_cv_lg_hugepage}
+_ACEOF
+
+
+
# Check whether --with-lg_page_sizes was given.
if test "${with_lg_page_sizes+set}" = set; then :
withval=$with_lg_page_sizes; LG_PAGE_SIZES="$with_lg_page_sizes"
@@ -6957,11 +10140,37 @@ fi
-# Check whether --with-lg_size_class_group was given.
-if test "${with_lg_size_class_group+set}" = set; then :
- withval=$with_lg_size_class_group; LG_SIZE_CLASS_GROUP="$with_lg_size_class_group"
+
+# Check whether --with-version was given.
+if test "${with_version+set}" = set; then :
+ withval=$with_version;
+ echo "${with_version}" | grep '^[0-9]\+\.[0-9]\+\.[0-9]\+-[0-9]\+-g[0-9a-f]\+$' 2>&1 1>/dev/null
+ if test $? -eq 0 ; then
+ echo "$with_version" > "${objroot}VERSION"
+ else
+ echo "${with_version}" | grep '^VERSION$' 2>&1 1>/dev/null
+ if test $? -ne 0 ; then
+ as_fn_error $? "${with_version} does not match <major>.<minor>.<bugfix>-<nrev>-g<gid> or VERSION" "$LINENO" 5
+ fi
+ fi
+
else
- LG_SIZE_CLASS_GROUP="2"
+
+ if test "x`test ! \"${srcroot}\" && cd \"${srcroot}\"; git rev-parse --is-inside-work-tree 2>/dev/null`" = "xtrue" ; then
+ for pattern in '[0-9].[0-9].[0-9]' '[0-9].[0-9].[0-9][0-9]' \
+ '[0-9].[0-9][0-9].[0-9]' '[0-9].[0-9][0-9].[0-9][0-9]' \
+ '[0-9][0-9].[0-9].[0-9]' '[0-9][0-9].[0-9].[0-9][0-9]' \
+ '[0-9][0-9].[0-9][0-9].[0-9]' \
+ '[0-9][0-9].[0-9][0-9].[0-9][0-9]'; do
+ (test ! "${srcroot}" && cd "${srcroot}"; git describe --long --abbrev=40 --match="${pattern}") > "${objroot}VERSION.tmp" 2>/dev/null
+ if test $? -eq 0 ; then
+ mv "${objroot}VERSION.tmp" "${objroot}VERSION"
+ break
+ fi
+ done
+ fi
+ rm -f "${objroot}VERSION.tmp"
+
fi
@@ -6989,6 +10198,8 @@ jemalloc_version_gid=`echo ${jemalloc_version} | tr ".g-" " " | awk '{print $5}'
if test "x$abi" != "xpecoff" ; then
+ $as_echo "#define JEMALLOC_HAVE_PTHREAD " >>confdefs.h
+
for ac_header in pthread.h
do :
ac_fn_c_check_header_mongrel "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default"
@@ -7040,7 +10251,14 @@ fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5
$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; }
if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then :
- LIBS="$LIBS -lpthread"
+ T_APPEND_V=-lpthread
+ if test "x${LIBS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ LIBS="${LIBS}${T_APPEND_V}"
+else
+ LIBS="${LIBS} ${T_APPEND_V}"
+fi
+
+
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing pthread_create" >&5
$as_echo_n "checking for library containing pthread_create... " >&6; }
@@ -7102,12 +10320,157 @@ fi
fi
+ wrap_syms="${wrap_syms} pthread_create"
+ have_pthread="1"
+ have_dlsym="1"
+ for ac_header in dlfcn.h
+do :
+ ac_fn_c_check_header_mongrel "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default"
+if test "x$ac_cv_header_dlfcn_h" = xyes; then :
+ cat >>confdefs.h <<_ACEOF
+#define HAVE_DLFCN_H 1
+_ACEOF
+ ac_fn_c_check_func "$LINENO" "dlsym" "ac_cv_func_dlsym"
+if test "x$ac_cv_func_dlsym" = xyes; then :
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlsym in -ldl" >&5
+$as_echo_n "checking for dlsym in -ldl... " >&6; }
+if ${ac_cv_lib_dl_dlsym+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ ac_check_lib_save_LIBS=$LIBS
+LIBS="-ldl $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dlsym ();
+int
+main ()
+{
+return dlsym ();
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_lib_dl_dlsym=yes
+else
+ ac_cv_lib_dl_dlsym=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlsym" >&5
+$as_echo "$ac_cv_lib_dl_dlsym" >&6; }
+if test "x$ac_cv_lib_dl_dlsym" = xyes; then :
+ LIBS="$LIBS -ldl"
+else
+ have_dlsym="0"
+fi
+
+fi
+
+else
+ have_dlsym="0"
fi
-CPPFLAGS="$CPPFLAGS -D_REENTRANT"
+done
+
+ if test "x$have_dlsym" = "x1" ; then
+ $as_echo "#define JEMALLOC_HAVE_DLSYM " >>confdefs.h
+
+ fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthread_atfork(3) is compilable" >&5
+$as_echo_n "checking whether pthread_atfork(3) is compilable... " >&6; }
+if ${je_cv_pthread_atfork+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <pthread.h>
+
+int
+main ()
+{
+
+ pthread_atfork((void *)0, (void *)0, (void *)0);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_pthread_atfork=yes
+else
+ je_cv_pthread_atfork=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_atfork" >&5
+$as_echo "$je_cv_pthread_atfork" >&6; }
+
+ if test "x${je_cv_pthread_atfork}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_PTHREAD_ATFORK " >>confdefs.h
+
+ fi
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether pthread_setname_np(3) is compilable" >&5
+$as_echo_n "checking whether pthread_setname_np(3) is compilable... " >&6; }
+if ${je_cv_pthread_setname_np+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <pthread.h>
+
+int
+main ()
+{
+
+ pthread_setname_np(pthread_self(), "setname_test");
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_pthread_setname_np=yes
+else
+ je_cv_pthread_setname_np=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_pthread_setname_np" >&5
+$as_echo "$je_cv_pthread_setname_np" >&6; }
+
+ if test "x${je_cv_pthread_setname_np}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_PTHREAD_SETNAME_NP " >>confdefs.h
+
+ fi
+fi
+
+T_APPEND_V=-D_REENTRANT
+ if test "x${CPPFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CPPFLAGS="${CPPFLAGS}${T_APPEND_V}"
+else
+ CPPFLAGS="${CPPFLAGS} ${T_APPEND_V}"
+fi
+
+
-SAVED_LIBS="${LIBS}"
-LIBS=
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
$as_echo_n "checking for library containing clock_gettime... " >&6; }
if ${ac_cv_search_clock_gettime+:} false; then :
@@ -7161,182 +10524,493 @@ $as_echo "$ac_cv_search_clock_gettime" >&6; }
ac_res=$ac_cv_search_clock_gettime
if test "$ac_res" != no; then :
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
- TESTLIBS="${LIBS}"
+
fi
-LIBS="${SAVED_LIBS}"
+if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then
+ if test "$ac_cv_search_clock_gettime" != "-lrt"; then
+ SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
-ac_fn_c_check_func "$LINENO" "secure_getenv" "ac_cv_func_secure_getenv"
-if test "x$ac_cv_func_secure_getenv" = xyes; then :
- have_secure_getenv="1"
+
+ unset ac_cv_search_clock_gettime
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -dynamic" >&5
+$as_echo_n "checking whether compiler supports -dynamic... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-dynamic
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
else
- have_secure_getenv="0"
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-if test "x$have_secure_getenv" = "x1" ; then
- $as_echo "#define JEMALLOC_HAVE_SECURE_GETENV " >>confdefs.h
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-dynamic
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-ac_fn_c_check_func "$LINENO" "issetugid" "ac_cv_func_issetugid"
-if test "x$ac_cv_func_issetugid" = xyes; then :
- have_issetugid="1"
+
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing clock_gettime" >&5
+$as_echo_n "checking for library containing clock_gettime... " >&6; }
+if ${ac_cv_search_clock_gettime+:} false; then :
+ $as_echo_n "(cached) " >&6
else
- have_issetugid="0"
+ ac_func_search_save_LIBS=$LIBS
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+/* Override any GCC internal prototype to avoid an error.
+ Use char because int might match the return type of a GCC
+ builtin and then its argument prototype would still apply. */
+#ifdef __cplusplus
+extern "C"
+#endif
+char clock_gettime ();
+int
+main ()
+{
+return clock_gettime ();
+ ;
+ return 0;
+}
+_ACEOF
+for ac_lib in '' rt; do
+ if test -z "$ac_lib"; then
+ ac_res="none required"
+ else
+ ac_res=-l$ac_lib
+ LIBS="-l$ac_lib $ac_func_search_save_LIBS"
+ fi
+ if ac_fn_c_try_link "$LINENO"; then :
+ ac_cv_search_clock_gettime=$ac_res
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext
+ if ${ac_cv_search_clock_gettime+:} false; then :
+ break
fi
+done
+if ${ac_cv_search_clock_gettime+:} false; then :
-if test "x$have_issetugid" = "x1" ; then
- $as_echo "#define JEMALLOC_HAVE_ISSETUGID " >>confdefs.h
+else
+ ac_cv_search_clock_gettime=no
+fi
+rm conftest.$ac_ext
+LIBS=$ac_func_search_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_clock_gettime" >&5
+$as_echo "$ac_cv_search_clock_gettime" >&6; }
+ac_res=$ac_cv_search_clock_gettime
+if test "$ac_res" != no; then :
+ test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
fi
-ac_fn_c_check_func "$LINENO" "_malloc_thread_cleanup" "ac_cv_func__malloc_thread_cleanup"
-if test "x$ac_cv_func__malloc_thread_cleanup" = xyes; then :
- have__malloc_thread_cleanup="1"
+
+ CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- have__malloc_thread_cleanup="0"
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+ fi
fi
-if test "x$have__malloc_thread_cleanup" = "x1" ; then
- $as_echo "#define JEMALLOC_MALLOC_THREAD_CLEANUP " >>confdefs.h
- force_tls="1"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is compilable" >&5
+$as_echo_n "checking whether clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is compilable... " >&6; }
+if ${je_cv_clock_monotonic_coarse+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <time.h>
+
+int
+main ()
+{
+
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_clock_monotonic_coarse=yes
+else
+ je_cv_clock_monotonic_coarse=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_clock_monotonic_coarse" >&5
+$as_echo "$je_cv_clock_monotonic_coarse" >&6; }
-ac_fn_c_check_func "$LINENO" "_pthread_mutex_init_calloc_cb" "ac_cv_func__pthread_mutex_init_calloc_cb"
-if test "x$ac_cv_func__pthread_mutex_init_calloc_cb" = xyes; then :
- have__pthread_mutex_init_calloc_cb="1"
+if test "x${je_cv_clock_monotonic_coarse}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE 1" >>confdefs.h
+
+fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether clock_gettime(CLOCK_MONOTONIC, ...) is compilable" >&5
+$as_echo_n "checking whether clock_gettime(CLOCK_MONOTONIC, ...) is compilable... " >&6; }
+if ${je_cv_clock_monotonic+:} false; then :
+ $as_echo_n "(cached) " >&6
else
- have__pthread_mutex_init_calloc_cb="0"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <unistd.h>
+#include <time.h>
+
+int
+main ()
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+#if !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0
+# error _POSIX_MONOTONIC_CLOCK missing/invalid
+#endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_clock_monotonic=yes
+else
+ je_cv_clock_monotonic=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_clock_monotonic" >&5
+$as_echo "$je_cv_clock_monotonic" >&6; }
-if test "x$have__pthread_mutex_init_calloc_cb" = "x1" ; then
- $as_echo "#define JEMALLOC_MUTEX_INIT_CB 1" >>confdefs.h
+if test "x${je_cv_clock_monotonic}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_CLOCK_MONOTONIC 1" >>confdefs.h
fi
-# Check whether --enable-lazy_lock was given.
-if test "${enable_lazy_lock+set}" = set; then :
- enableval=$enable_lazy_lock; if test "x$enable_lazy_lock" = "xno" ; then
- enable_lazy_lock="0"
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether mach_absolute_time() is compilable" >&5
+$as_echo_n "checking whether mach_absolute_time() is compilable... " >&6; }
+if ${je_cv_mach_absolute_time+:} false; then :
+ $as_echo_n "(cached) " >&6
else
- enable_lazy_lock="1"
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <mach/mach_time.h>
+
+int
+main ()
+{
+
+ mach_absolute_time();
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_mach_absolute_time=yes
+else
+ je_cv_mach_absolute_time=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_mach_absolute_time" >&5
+$as_echo "$je_cv_mach_absolute_time" >&6; }
+
+if test "x${je_cv_mach_absolute_time}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_MACH_ABSOLUTE_TIME 1" >>confdefs.h
+
fi
+# Check whether --enable-syscall was given.
+if test "${enable_syscall+set}" = set; then :
+ enableval=$enable_syscall; if test "x$enable_syscall" = "xno" ; then
+ enable_syscall="0"
else
- enable_lazy_lock=""
+ enable_syscall="1"
+fi
+
+else
+ enable_syscall="1"
fi
-if test "x$enable_lazy_lock" = "x" -a "x${force_lazy_lock}" = "x1" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&5
-$as_echo "Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&6; }
- enable_lazy_lock="1"
+if test "x$enable_syscall" = "x1" ; then
+ SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
+$as_echo_n "checking whether compiler supports -Werror... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
fi
-if test "x$enable_lazy_lock" = "x1" ; then
- if test "x$abi" != "xpecoff" ; then
- for ac_header in dlfcn.h
-do :
- ac_fn_c_check_header_mongrel "$LINENO" "dlfcn.h" "ac_cv_header_dlfcn_h" "$ac_includes_default"
-if test "x$ac_cv_header_dlfcn_h" = xyes; then :
- cat >>confdefs.h <<_ACEOF
-#define HAVE_DLFCN_H 1
-_ACEOF
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- as_fn_error $? "dlfcn.h is missing" "$LINENO" 5
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-done
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
- ac_fn_c_check_func "$LINENO" "dlsym" "ac_cv_func_dlsym"
-if test "x$ac_cv_func_dlsym" = xyes; then :
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Werror
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
else
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dlsym in -ldl" >&5
-$as_echo_n "checking for dlsym in -ldl... " >&6; }
-if ${ac_cv_lib_dl_dlsym+:} false; then :
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether syscall(2) is compilable" >&5
+$as_echo_n "checking whether syscall(2) is compilable... " >&6; }
+if ${je_cv_syscall+:} false; then :
$as_echo_n "(cached) " >&6
else
- ac_check_lib_save_LIBS=$LIBS
-LIBS="-ldl $LIBS"
-cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
-/* Override any GCC internal prototype to avoid an error.
- Use char because int might match the return type of a GCC
- builtin and then its argument prototype would still apply. */
-#ifdef __cplusplus
-extern "C"
-#endif
-char dlsym ();
+#include <sys/syscall.h>
+#include <unistd.h>
+
int
main ()
{
-return dlsym ();
+
+ syscall(SYS_write, 2, "hello", 5);
+
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
- ac_cv_lib_dl_dlsym=yes
+ je_cv_syscall=yes
else
- ac_cv_lib_dl_dlsym=no
+ je_cv_syscall=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
-LIBS=$ac_check_lib_save_LIBS
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_dl_dlsym" >&5
-$as_echo "$ac_cv_lib_dl_dlsym" >&6; }
-if test "x$ac_cv_lib_dl_dlsym" = xyes; then :
- LIBS="$LIBS -ldl"
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_syscall" >&5
+$as_echo "$je_cv_syscall" >&6; }
+
+ CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
else
- as_fn_error $? "libdl is missing" "$LINENO" 5
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
fi
-fi
+ if test "x$je_cv_syscall" = "xyes" ; then
+ $as_echo "#define JEMALLOC_USE_SYSCALL " >>confdefs.h
fi
- $as_echo "#define JEMALLOC_LAZY_LOCK " >>confdefs.h
+fi
+ac_fn_c_check_func "$LINENO" "secure_getenv" "ac_cv_func_secure_getenv"
+if test "x$ac_cv_func_secure_getenv" = xyes; then :
+ have_secure_getenv="1"
else
- enable_lazy_lock="0"
+ have_secure_getenv="0"
+
fi
+if test "x$have_secure_getenv" = "x1" ; then
+ $as_echo "#define JEMALLOC_HAVE_SECURE_GETENV " >>confdefs.h
+
+fi
-# Check whether --enable-tls was given.
-if test "${enable_tls+set}" = set; then :
- enableval=$enable_tls; if test "x$enable_tls" = "xno" ; then
- enable_tls="0"
+ac_fn_c_check_func "$LINENO" "sched_getcpu" "ac_cv_func_sched_getcpu"
+if test "x$ac_cv_func_sched_getcpu" = xyes; then :
+ have_sched_getcpu="1"
else
- enable_tls="1"
+ have_sched_getcpu="0"
+
+fi
+
+if test "x$have_sched_getcpu" = "x1" ; then
+ $as_echo "#define JEMALLOC_HAVE_SCHED_GETCPU " >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "sched_setaffinity" "ac_cv_func_sched_setaffinity"
+if test "x$ac_cv_func_sched_setaffinity" = xyes; then :
+ have_sched_setaffinity="1"
+else
+ have_sched_setaffinity="0"
+
+fi
+
+if test "x$have_sched_setaffinity" = "x1" ; then
+ $as_echo "#define JEMALLOC_HAVE_SCHED_SETAFFINITY " >>confdefs.h
+
fi
+ac_fn_c_check_func "$LINENO" "issetugid" "ac_cv_func_issetugid"
+if test "x$ac_cv_func_issetugid" = xyes; then :
+ have_issetugid="1"
+else
+ have_issetugid="0"
+
+fi
+
+if test "x$have_issetugid" = "x1" ; then
+ $as_echo "#define JEMALLOC_HAVE_ISSETUGID " >>confdefs.h
+
+fi
+
+ac_fn_c_check_func "$LINENO" "_malloc_thread_cleanup" "ac_cv_func__malloc_thread_cleanup"
+if test "x$ac_cv_func__malloc_thread_cleanup" = xyes; then :
+ have__malloc_thread_cleanup="1"
else
- enable_tls=""
+ have__malloc_thread_cleanup="0"
+
+fi
+
+if test "x$have__malloc_thread_cleanup" = "x1" ; then
+ $as_echo "#define JEMALLOC_MALLOC_THREAD_CLEANUP " >>confdefs.h
+
+ wrap_syms="${wrap_syms} _malloc_thread_cleanup"
+ force_tls="1"
+fi
+
+ac_fn_c_check_func "$LINENO" "_pthread_mutex_init_calloc_cb" "ac_cv_func__pthread_mutex_init_calloc_cb"
+if test "x$ac_cv_func__pthread_mutex_init_calloc_cb" = xyes; then :
+ have__pthread_mutex_init_calloc_cb="1"
+else
+ have__pthread_mutex_init_calloc_cb="0"
+
+fi
+
+if test "x$have__pthread_mutex_init_calloc_cb" = "x1" ; then
+ $as_echo "#define JEMALLOC_MUTEX_INIT_CB 1" >>confdefs.h
+
+ wrap_syms="${wrap_syms} _malloc_prefork _malloc_postfork"
+fi
+
+# Check whether --enable-lazy_lock was given.
+if test "${enable_lazy_lock+set}" = set; then :
+ enableval=$enable_lazy_lock; if test "x$enable_lazy_lock" = "xno" ; then
+ enable_lazy_lock="0"
+else
+ enable_lazy_lock="1"
+fi
+
+else
+ enable_lazy_lock=""
+
+fi
+if test "x${enable_lazy_lock}" = "x" ; then
+ if test "x${force_lazy_lock}" = "x1" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&5
+$as_echo "Forcing lazy-lock to avoid allocator/threading bootstrap issues" >&6; }
+ enable_lazy_lock="1"
+ else
+ enable_lazy_lock="0"
+ fi
fi
+if test "x${enable_lazy_lock}" = "x1" -a "x${abi}" = "xpecoff" ; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Forcing no lazy-lock because thread creation monitoring is unimplemented" >&5
+$as_echo "Forcing no lazy-lock because thread creation monitoring is unimplemented" >&6; }
+ enable_lazy_lock="0"
+fi
+if test "x$enable_lazy_lock" = "x1" ; then
+ if test "x$have_dlsym" = "x1" ; then
+ $as_echo "#define JEMALLOC_LAZY_LOCK " >>confdefs.h
-if test "x${enable_tls}" = "x" ; then
- if test "x${force_tls}" = "x1" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Forcing TLS to avoid allocator/threading bootstrap issues" >&5
-$as_echo "Forcing TLS to avoid allocator/threading bootstrap issues" >&6; }
- enable_tls="1"
- elif test "x${force_tls}" = "x0" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Forcing no TLS to avoid allocator/threading bootstrap issues" >&5
-$as_echo "Forcing no TLS to avoid allocator/threading bootstrap issues" >&6; }
- enable_tls="0"
else
- enable_tls="1"
+ as_fn_error $? "Missing dlsym support: lazy-lock cannot be enabled." "$LINENO" 5
fi
fi
+
+
+if test "x${force_tls}" = "x1" ; then
+ enable_tls="1"
+elif test "x${force_tls}" = "x0" ; then
+ enable_tls="0"
+else
+ enable_tls="1"
+fi
if test "x${enable_tls}" = "x1" ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for TLS" >&5
$as_echo_n "checking for TLS... " >&6; }
@@ -7371,24 +11045,17 @@ else
fi
if test "x${enable_tls}" = "x1" ; then
- if test "x${force_tls}" = "x0" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: TLS enabled despite being marked unusable on this platform" >&5
-$as_echo "$as_me: WARNING: TLS enabled despite being marked unusable on this platform" >&2;}
- fi
cat >>confdefs.h <<_ACEOF
#define JEMALLOC_TLS
_ACEOF
-elif test "x${force_tls}" = "x1" ; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: TLS disabled despite being marked critical on this platform" >&5
-$as_echo "$as_me: WARNING: TLS disabled despite being marked critical on this platform" >&2;}
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether C11 atomics is compilable" >&5
$as_echo_n "checking whether C11 atomics is compilable... " >&6; }
-if ${je_cv_c11atomics+:} false; then :
+if ${je_cv_c11_atomics+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
@@ -7409,74 +11076,106 @@ main ()
uint64_t x = 1;
volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
uint64_t r = atomic_fetch_add(a, x) + x;
- return (r == 0);
+ return r == 0;
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
- je_cv_c11atomics=yes
+ je_cv_c11_atomics=yes
else
- je_cv_c11atomics=no
+ je_cv_c11_atomics=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_c11atomics" >&5
-$as_echo "$je_cv_c11atomics" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_c11_atomics" >&5
+$as_echo "$je_cv_c11_atomics" >&6; }
-if test "x${je_cv_c11atomics}" = "xyes" ; then
- $as_echo "#define JEMALLOC_C11ATOMICS 1" >>confdefs.h
+if test "x${je_cv_c11_atomics}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_C11_ATOMICS 1" >>confdefs.h
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether atomic(9) is compilable" >&5
-$as_echo_n "checking whether atomic(9) is compilable... " >&6; }
-if ${je_cv_atomic9+:} false; then :
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether GCC __atomic atomics is compilable" >&5
+$as_echo_n "checking whether GCC __atomic atomics is compilable... " >&6; }
+if ${je_cv_gcc_atomic_atomics+:} false; then :
$as_echo_n "(cached) " >&6
else
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
-#include <sys/types.h>
-#include <machine/atomic.h>
-#include <inttypes.h>
int
main ()
{
- {
- uint32_t x32 = 0;
- volatile uint32_t *x32p = &x32;
- atomic_fetchadd_32(x32p, 1);
- }
- {
- unsigned long xlong = 0;
- volatile unsigned long *xlongp = &xlong;
- atomic_fetchadd_long(xlongp, 1);
- }
+ int x = 0;
+ int val = 1;
+ int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED);
+ int after_add = x;
+ return after_add == 1;
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
- je_cv_atomic9=yes
+ je_cv_gcc_atomic_atomics=yes
else
- je_cv_atomic9=no
+ je_cv_gcc_atomic_atomics=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_atomic9" >&5
-$as_echo "$je_cv_atomic9" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_atomic_atomics" >&5
+$as_echo "$je_cv_gcc_atomic_atomics" >&6; }
-if test "x${je_cv_atomic9}" = "xyes" ; then
- $as_echo "#define JEMALLOC_ATOMIC9 1" >>confdefs.h
+if test "x${je_cv_gcc_atomic_atomics}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_GCC_ATOMIC_ATOMICS 1" >>confdefs.h
+
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether GCC __sync atomics is compilable" >&5
+$as_echo_n "checking whether GCC __sync atomics is compilable... " >&6; }
+if ${je_cv_gcc_sync_atomics+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ int x = 0;
+ int before_add = __sync_fetch_and_add(&x, 1);
+ int after_add = x;
+ return (before_add == 0) && (after_add == 1);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_gcc_sync_atomics=yes
+else
+ je_cv_gcc_sync_atomics=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_gcc_sync_atomics" >&5
+$as_echo "$je_cv_gcc_sync_atomics" >&6; }
+
+if test "x${je_cv_gcc_sync_atomics}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_GCC_SYNC_ATOMICS 1" >>confdefs.h
fi
@@ -7544,9 +11243,7 @@ int
main ()
{
- {
- madvise((void *)0, 0, 0);
- }
+ madvise((void *)0, 0, 0);
;
return 0;
@@ -7566,6 +11263,173 @@ $as_echo "$je_cv_madvise" >&6; }
if test "x${je_cv_madvise}" = "xyes" ; then
$as_echo "#define JEMALLOC_HAVE_MADVISE " >>confdefs.h
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_FREE) is compilable" >&5
+$as_echo_n "checking whether madvise(..., MADV_FREE) is compilable... " >&6; }
+if ${je_cv_madv_free+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <sys/mman.h>
+
+int
+main ()
+{
+
+ madvise((void *)0, 0, MADV_FREE);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_madv_free=yes
+else
+ je_cv_madv_free=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_free" >&5
+$as_echo "$je_cv_madv_free" >&6; }
+
+ if test "x${je_cv_madv_free}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
+
+ elif test "x${je_cv_madvise}" = "xyes" ; then
+ case "${host_cpu}" in i686|x86_64)
+ case "${host}" in *-*-linux*)
+ $as_echo "#define JEMALLOC_PURGE_MADVISE_FREE " >>confdefs.h
+
+ $as_echo "#define JEMALLOC_DEFINE_MADVISE_FREE " >>confdefs.h
+
+ ;;
+ esac
+ ;;
+ esac
+ fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_DONTNEED) is compilable" >&5
+$as_echo_n "checking whether madvise(..., MADV_DONTNEED) is compilable... " >&6; }
+if ${je_cv_madv_dontneed+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <sys/mman.h>
+
+int
+main ()
+{
+
+ madvise((void *)0, 0, MADV_DONTNEED);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_madv_dontneed=yes
+else
+ je_cv_madv_dontneed=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_dontneed" >&5
+$as_echo "$je_cv_madv_dontneed" >&6; }
+
+ if test "x${je_cv_madv_dontneed}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_PURGE_MADVISE_DONTNEED " >>confdefs.h
+
+ fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_DO[NT]DUMP) is compilable" >&5
+$as_echo_n "checking whether madvise(..., MADV_DO[NT]DUMP) is compilable... " >&6; }
+if ${je_cv_madv_dontdump+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <sys/mman.h>
+
+int
+main ()
+{
+
+ madvise((void *)0, 0, MADV_DONTDUMP);
+ madvise((void *)0, 0, MADV_DODUMP);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_madv_dontdump=yes
+else
+ je_cv_madv_dontdump=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_madv_dontdump" >&5
+$as_echo "$je_cv_madv_dontdump" >&6; }
+
+ if test "x${je_cv_madv_dontdump}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_MADVISE_DONTDUMP " >>confdefs.h
+
+ fi
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether madvise(..., MADV_[NO]HUGEPAGE) is compilable" >&5
+$as_echo_n "checking whether madvise(..., MADV_[NO]HUGEPAGE) is compilable... " >&6; }
+if ${je_cv_thp+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <sys/mman.h>
+
+int
+main ()
+{
+
+ madvise((void *)0, 0, MADV_HUGEPAGE);
+ madvise((void *)0, 0, MADV_NOHUGEPAGE);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_thp=yes
+else
+ je_cv_thp=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_thp" >&5
+$as_echo "$je_cv_thp" >&6; }
+
+case "${host_cpu}" in
+ arm*)
+ ;;
+ *)
+ if test "x${je_cv_thp}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_HAVE_MADVISE_HUGE " >>confdefs.h
+
+ fi
+ ;;
+esac
fi
@@ -7708,6 +11572,51 @@ fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether Darwin os_unfair_lock_*() is compilable" >&5
+$as_echo_n "checking whether Darwin os_unfair_lock_*() is compilable... " >&6; }
+if ${je_cv_os_unfair_lock+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <os/lock.h>
+#include <AvailabilityMacros.h>
+
+int
+main ()
+{
+
+ #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200
+ #error "os_unfair_lock is not supported"
+ #else
+ os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
+ os_unfair_lock_lock(&lock);
+ os_unfair_lock_unlock(&lock);
+ #endif
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_os_unfair_lock=yes
+else
+ je_cv_os_unfair_lock=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_os_unfair_lock" >&5
+$as_echo "$je_cv_os_unfair_lock" >&6; }
+
+if test "x${je_cv_os_unfair_lock}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_OS_UNFAIR_LOCK " >>confdefs.h
+
+fi
+
+
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether Darwin OSSpin*() is compilable" >&5
$as_echo_n "checking whether Darwin OSSpin*() is compilable... " >&6; }
if ${je_cv_osspin+:} false; then :
@@ -7772,155 +11681,37 @@ if test "x${enable_zone_allocator}" = "x1" ; then
fi
$as_echo "#define JEMALLOC_ZONE " >>confdefs.h
+fi
- { $as_echo "$as_me:${as_lineno-$LINENO}: checking malloc zone version" >&5
-$as_echo_n "checking malloc zone version... " >&6; }
-
-
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_zone_t) == sizeof(void *) * 14 ? 1 : -1]
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=3
+# Check whether --enable-initial-exec-tls was given.
+if test "${enable_initial_exec_tls+set}" = set; then :
+ enableval=$enable_initial_exec_tls; if test "x$enable_initial_exec_tls" = "xno" ; then
+ enable_initial_exec_tls="0"
else
+ enable_initial_exec_tls="1"
+fi
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_zone_t) == sizeof(void *) * 15 ? 1 : -1]
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=5
else
+ enable_initial_exec_tls="1"
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_zone_t) == sizeof(void *) * 16 ? 1 : -1]
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
+fi
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_introspection_t) == sizeof(void *) * 9 ? 1 : -1]
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=6
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_introspection_t) == sizeof(void *) * 13 ? 1 : -1]
+if test "x${je_cv_tls_model}" = "xyes" -a \
+ "x${enable_initial_exec_tls}" = "x1" ; then
+ $as_echo "#define JEMALLOC_TLS_MODEL __attribute__((tls_model(\"initial-exec\")))" >>confdefs.h
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=7
else
- JEMALLOC_ZONE_VERSION=
+ $as_echo "#define JEMALLOC_TLS_MODEL " >>confdefs.h
fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_zone_t) == sizeof(void *) * 17 ? 1 : -1]
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=8
-else
- cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h. */
-#include <malloc/malloc.h>
-int
-main ()
-{
-static int foo[sizeof(malloc_zone_t) > sizeof(void *) * 17 ? 1 : -1]
-
- ;
- return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
- JEMALLOC_ZONE_VERSION=9
-else
- JEMALLOC_ZONE_VERSION=
-
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
- if test "x${JEMALLOC_ZONE_VERSION}" = "x"; then
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5
-$as_echo "unsupported" >&6; }
- as_fn_error $? "Unsupported malloc zone version" "$LINENO" 5
- fi
- if test "${JEMALLOC_ZONE_VERSION}" = 9; then
- JEMALLOC_ZONE_VERSION=8
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: > 8" >&5
-$as_echo "> 8" >&6; }
- else
- { $as_echo "$as_me:${as_lineno-$LINENO}: result: $JEMALLOC_ZONE_VERSION" >&5
-$as_echo "$JEMALLOC_ZONE_VERSION" >&6; }
- fi
- cat >>confdefs.h <<_ACEOF
-#define JEMALLOC_ZONE_VERSION $JEMALLOC_ZONE_VERSION
-_ACEOF
+if test "x${have_pthread}" = "x1" -a "x${have_dlsym}" = "x1" \
+ -a "x${je_cv_os_unfair_lock}" != "xyes" \
+ -a "x${je_cv_osspin}" != "xyes" ; then
+ $as_echo "#define JEMALLOC_BACKGROUND_THREAD 1" >>confdefs.h
fi
@@ -7965,8 +11756,11 @@ fi
$as_echo "$je_cv_glibc_malloc_hook" >&6; }
if test "x${je_cv_glibc_malloc_hook}" = "xyes" ; then
- $as_echo "#define JEMALLOC_GLIBC_MALLOC_HOOK " >>confdefs.h
+ if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ $as_echo "#define JEMALLOC_GLIBC_MALLOC_HOOK " >>confdefs.h
+ wrap_syms="${wrap_syms} __free_hook __malloc_hook __realloc_hook"
+ fi
fi
@@ -8005,8 +11799,11 @@ fi
$as_echo "$je_cv_glibc_memalign_hook" >&6; }
if test "x${je_cv_glibc_memalign_hook}" = "xyes" ; then
- $as_echo "#define JEMALLOC_GLIBC_MEMALIGN_HOOK " >>confdefs.h
+ if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ $as_echo "#define JEMALLOC_GLIBC_MEMALIGN_HOOK " >>confdefs.h
+ wrap_syms="${wrap_syms} __memalign_hook"
+ fi
fi
@@ -8049,6 +11846,211 @@ if test "x${je_cv_pthread_mutex_adaptive_np}" = "xyes" ; then
fi
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -D_GNU_SOURCE" >&5
+$as_echo_n "checking whether compiler supports -D_GNU_SOURCE... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-D_GNU_SOURCE
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-D_GNU_SOURCE
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -Werror" >&5
+$as_echo_n "checking whether compiler supports -Werror... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-Werror
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-Werror
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether compiler supports -herror_on_warning" >&5
+$as_echo_n "checking whether compiler supports -herror_on_warning... " >&6; }
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+T_APPEND_V=-herror_on_warning
+ if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${T_APPEND_V}" = "x" ; then
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}${T_APPEND_V}"
+else
+ CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS} ${T_APPEND_V}"
+fi
+
+
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+
+int
+main ()
+{
+
+ return 0;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ je_cv_cflags_added=-herror_on_warning
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+else
+ je_cv_cflags_added=
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+ CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether strerror_r returns char with gnu source is compilable" >&5
+$as_echo_n "checking whether strerror_r returns char with gnu source is compilable... " >&6; }
+if ${je_cv_strerror_r_returns_char_with_gnu_source+:} false; then :
+ $as_echo_n "(cached) " >&6
+else
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+int
+main ()
+{
+
+ char *buffer = (char *) malloc(100);
+ char *error = strerror_r(EINVAL, buffer, 100);
+ printf("%s\n", error);
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+ je_cv_strerror_r_returns_char_with_gnu_source=yes
+else
+ je_cv_strerror_r_returns_char_with_gnu_source=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+ conftest$ac_exeext conftest.$ac_ext
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $je_cv_strerror_r_returns_char_with_gnu_source" >&5
+$as_echo "$je_cv_strerror_r_returns_char_with_gnu_source" >&6; }
+
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+if test "x${CONFIGURE_CFLAGS}" = "x" -o "x${SPECIFIED_CFLAGS}" = "x" ; then
+ CFLAGS="${CONFIGURE_CFLAGS}${SPECIFIED_CFLAGS}"
+else
+ CFLAGS="${CONFIGURE_CFLAGS} ${SPECIFIED_CFLAGS}"
+fi
+
+
+if test "x${je_cv_strerror_r_returns_char_with_gnu_source}" = "xyes" ; then
+ $as_echo "#define JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE " >>confdefs.h
+
+fi
+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for stdbool.h that conforms to C99" >&5
$as_echo_n "checking for stdbool.h that conforms to C99... " >&6; }
if ${ac_cv_header_stdbool_h+:} false; then :
@@ -8144,11 +12146,11 @@ fi
-ac_config_commands="$ac_config_commands include/jemalloc/internal/private_namespace.h"
+ac_config_commands="$ac_config_commands include/jemalloc/internal/public_symbols.txt"
-ac_config_commands="$ac_config_commands include/jemalloc/internal/private_unnamespace.h"
+ac_config_commands="$ac_config_commands include/jemalloc/internal/private_symbols.awk"
-ac_config_commands="$ac_config_commands include/jemalloc/internal/public_symbols.txt"
+ac_config_commands="$ac_config_commands include/jemalloc/internal/private_symbols_jet.awk"
ac_config_commands="$ac_config_commands include/jemalloc/internal/public_namespace.h"
@@ -8761,6 +12763,7 @@ gives unlimited permission to copy, distribute and modify it."
ac_pwd='$ac_pwd'
srcdir='$srcdir'
INSTALL='$INSTALL'
+AWK='$AWK'
test -n "\$AWK" || AWK=awk
_ACEOF
@@ -8870,17 +12873,24 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
srcdir="${srcdir}"
objroot="${objroot}"
+ mangling_map="${mangling_map}"
+ public_syms="${public_syms}"
+ JEMALLOC_PREFIX="${JEMALLOC_PREFIX}"
srcdir="${srcdir}"
objroot="${objroot}"
+ public_syms="${public_syms}"
+ wrap_syms="${wrap_syms}"
+ SYM_PREFIX="${SYM_PREFIX}"
+ JEMALLOC_PREFIX="${JEMALLOC_PREFIX}"
srcdir="${srcdir}"
objroot="${objroot}"
- mangling_map="${mangling_map}"
public_syms="${public_syms}"
- JEMALLOC_PREFIX="${JEMALLOC_PREFIX}"
+ wrap_syms="${wrap_syms}"
+ SYM_PREFIX="${SYM_PREFIX}"
srcdir="${srcdir}"
@@ -8895,9 +12905,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
srcdir="${srcdir}"
objroot="${objroot}"
LG_QUANTA="${LG_QUANTA}"
- LG_TINY_MIN=${LG_TINY_MIN}
LG_PAGE_SIZES="${LG_PAGE_SIZES}"
- LG_SIZE_CLASS_GROUP=${LG_SIZE_CLASS_GROUP}
srcdir="${srcdir}"
@@ -8929,9 +12937,9 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1
for ac_config_target in $ac_config_targets
do
case $ac_config_target in
- "include/jemalloc/internal/private_namespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_namespace.h" ;;
- "include/jemalloc/internal/private_unnamespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_unnamespace.h" ;;
"include/jemalloc/internal/public_symbols.txt") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_symbols.txt" ;;
+ "include/jemalloc/internal/private_symbols.awk") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_symbols.awk" ;;
+ "include/jemalloc/internal/private_symbols_jet.awk") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/private_symbols_jet.awk" ;;
"include/jemalloc/internal/public_namespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_namespace.h" ;;
"include/jemalloc/internal/public_unnamespace.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/public_unnamespace.h" ;;
"include/jemalloc/internal/size_classes.h") CONFIG_COMMANDS="$CONFIG_COMMANDS include/jemalloc/internal/size_classes.h" ;;
@@ -9501,14 +13509,6 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
case $ac_file$ac_mode in
- "include/jemalloc/internal/private_namespace.h":C)
- mkdir -p "${objroot}include/jemalloc/internal"
- "${srcdir}/include/jemalloc/internal/private_namespace.sh" "${srcdir}/include/jemalloc/internal/private_symbols.txt" > "${objroot}include/jemalloc/internal/private_namespace.h"
- ;;
- "include/jemalloc/internal/private_unnamespace.h":C)
- mkdir -p "${objroot}include/jemalloc/internal"
- "${srcdir}/include/jemalloc/internal/private_unnamespace.sh" "${srcdir}/include/jemalloc/internal/private_symbols.txt" > "${objroot}include/jemalloc/internal/private_unnamespace.h"
- ;;
"include/jemalloc/internal/public_symbols.txt":C)
f="${objroot}include/jemalloc/internal/public_symbols.txt"
mkdir -p "${objroot}include/jemalloc/internal"
@@ -9525,6 +13525,18 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
echo "${n}:${m}" >> "${f}"
done
;;
+ "include/jemalloc/internal/private_symbols.awk":C)
+ f="${objroot}include/jemalloc/internal/private_symbols.awk"
+ mkdir -p "${objroot}include/jemalloc/internal"
+ export_syms=`for sym in ${public_syms}; do echo "${JEMALLOC_PREFIX}${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;`
+ "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols.awk"
+ ;;
+ "include/jemalloc/internal/private_symbols_jet.awk":C)
+ f="${objroot}include/jemalloc/internal/private_symbols_jet.awk"
+ mkdir -p "${objroot}include/jemalloc/internal"
+ export_syms=`for sym in ${public_syms}; do echo "jet_${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;`
+ "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols_jet.awk"
+ ;;
"include/jemalloc/internal/public_namespace.h":C)
mkdir -p "${objroot}include/jemalloc/internal"
"${srcdir}/include/jemalloc/internal/public_namespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_namespace.h"
@@ -9535,7 +13547,7 @@ $as_echo "$as_me: executing $ac_file commands" >&6;}
;;
"include/jemalloc/internal/size_classes.h":C)
mkdir -p "${objroot}include/jemalloc/internal"
- "${SHELL}" "${srcdir}/include/jemalloc/internal/size_classes.sh" "${LG_QUANTA}" ${LG_TINY_MIN} "${LG_PAGE_SIZES}" ${LG_SIZE_CLASS_GROUP} > "${objroot}include/jemalloc/internal/size_classes.h"
+ "${SHELL}" "${srcdir}/include/jemalloc/internal/size_classes.sh" "${LG_QUANTA}" 3 "${LG_PAGE_SIZES}" 2 > "${objroot}include/jemalloc/internal/size_classes.h"
;;
"include/jemalloc/jemalloc_protos_jet.h":C)
mkdir -p "${objroot}include/jemalloc"
@@ -9608,18 +13620,30 @@ $as_echo "" >&6; }
$as_echo "CONFIG : ${CONFIG}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CC : ${CC}" >&5
$as_echo "CC : ${CC}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CFLAGS : ${CFLAGS}" >&5
-$as_echo "CFLAGS : ${CFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}" >&5
+$as_echo "CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}" >&5
+$as_echo "SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: EXTRA_CFLAGS : ${EXTRA_CFLAGS}" >&5
+$as_echo "EXTRA_CFLAGS : ${EXTRA_CFLAGS}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CPPFLAGS : ${CPPFLAGS}" >&5
$as_echo "CPPFLAGS : ${CPPFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CXX : ${CXX}" >&5
+$as_echo "CXX : ${CXX}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}" >&5
+$as_echo "CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}" >&5
+$as_echo "SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}" >&5
+$as_echo "EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: LDFLAGS : ${LDFLAGS}" >&5
$as_echo "LDFLAGS : ${LDFLAGS}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}" >&5
$as_echo "EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: DSO_LDFLAGS : ${DSO_LDFLAGS}" >&5
+$as_echo "DSO_LDFLAGS : ${DSO_LDFLAGS}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: LIBS : ${LIBS}" >&5
$as_echo "LIBS : ${LIBS}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: TESTLIBS : ${TESTLIBS}" >&5
-$as_echo "TESTLIBS : ${TESTLIBS}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: RPATH_EXTRA : ${RPATH_EXTRA}" >&5
$as_echo "RPATH_EXTRA : ${RPATH_EXTRA}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: " >&5
@@ -9662,14 +13686,12 @@ $as_echo "JEMALLOC_PRIVATE_NAMESPACE" >&6; }
$as_echo " : ${JEMALLOC_PRIVATE_NAMESPACE}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: install_suffix : ${install_suffix}" >&5
$as_echo "install_suffix : ${install_suffix}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: malloc_conf : ${config_malloc_conf}" >&5
+$as_echo "malloc_conf : ${config_malloc_conf}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: autogen : ${enable_autogen}" >&5
$as_echo "autogen : ${enable_autogen}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: cc-silence : ${enable_cc_silence}" >&5
-$as_echo "cc-silence : ${enable_cc_silence}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: debug : ${enable_debug}" >&5
$as_echo "debug : ${enable_debug}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: code-coverage : ${enable_code_coverage}" >&5
-$as_echo "code-coverage : ${enable_code_coverage}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: stats : ${enable_stats}" >&5
$as_echo "stats : ${enable_stats}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: prof : ${enable_prof}" >&5
@@ -9680,23 +13702,19 @@ $as_echo "prof-libunwind : ${enable_prof_libunwind}" >&6; }
$as_echo "prof-libgcc : ${enable_prof_libgcc}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: prof-gcc : ${enable_prof_gcc}" >&5
$as_echo "prof-gcc : ${enable_prof_gcc}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: tcache : ${enable_tcache}" >&5
-$as_echo "tcache : ${enable_tcache}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: fill : ${enable_fill}" >&5
$as_echo "fill : ${enable_fill}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: utrace : ${enable_utrace}" >&5
$as_echo "utrace : ${enable_utrace}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: valgrind : ${enable_valgrind}" >&5
-$as_echo "valgrind : ${enable_valgrind}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: xmalloc : ${enable_xmalloc}" >&5
$as_echo "xmalloc : ${enable_xmalloc}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: munmap : ${enable_munmap}" >&5
-$as_echo "munmap : ${enable_munmap}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: log : ${enable_log}" >&5
+$as_echo "log : ${enable_log}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: lazy_lock : ${enable_lazy_lock}" >&5
$as_echo "lazy_lock : ${enable_lazy_lock}" >&6; }
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: tls : ${enable_tls}" >&5
-$as_echo "tls : ${enable_tls}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: cache-oblivious : ${enable_cache_oblivious}" >&5
$as_echo "cache-oblivious : ${enable_cache_oblivious}" >&6; }
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: cxx : ${enable_cxx}" >&5
+$as_echo "cxx : ${enable_cxx}" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ===============================================================================" >&5
$as_echo "===============================================================================" >&6; }
diff --git a/deps/jemalloc/configure.ac b/deps/jemalloc/configure.ac
index 7a1290e0d..a6a08db08 100644
--- a/deps/jemalloc/configure.ac
+++ b/deps/jemalloc/configure.ac
@@ -1,34 +1,99 @@
dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.68)
AC_INIT([Makefile.in])
+AC_CONFIG_AUX_DIR([build-aux])
+
dnl ============================================================================
dnl Custom macro definitions.
-dnl JE_CFLAGS_APPEND(cflag)
-AC_DEFUN([JE_CFLAGS_APPEND],
-[
-AC_MSG_CHECKING([whether compiler supports $1])
-TCFLAGS="${CFLAGS}"
-if test "x${CFLAGS}" = "x" ; then
- CFLAGS="$1"
+dnl JE_CONCAT_VVV(r, a, b)
+dnl
+dnl Set $r to the concatenation of $a and $b, with a space separating them iff
+dnl both $a and $b are non-empty.
+AC_DEFUN([JE_CONCAT_VVV],
+if test "x[$]{$2}" = "x" -o "x[$]{$3}" = "x" ; then
+ $1="[$]{$2}[$]{$3}"
else
- CFLAGS="${CFLAGS} $1"
+ $1="[$]{$2} [$]{$3}"
fi
+)
+
+dnl JE_APPEND_VS(a, b)
+dnl
+dnl Set $a to the concatenation of $a and b, with a space separating them iff
+dnl both $a and b are non-empty.
+AC_DEFUN([JE_APPEND_VS],
+ T_APPEND_V=$2
+ JE_CONCAT_VVV($1, $1, T_APPEND_V)
+)
+
+CONFIGURE_CFLAGS=
+SPECIFIED_CFLAGS="${CFLAGS}"
+dnl JE_CFLAGS_ADD(cflag)
+dnl
+dnl CFLAGS is the concatenation of CONFIGURE_CFLAGS and SPECIFIED_CFLAGS
+dnl (ignoring EXTRA_CFLAGS, which does not impact configure tests. This macro
+dnl appends to CONFIGURE_CFLAGS and regenerates CFLAGS.
+AC_DEFUN([JE_CFLAGS_ADD],
+[
+AC_MSG_CHECKING([whether compiler supports $1])
+T_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+JE_APPEND_VS(CONFIGURE_CFLAGS, $1)
+JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
[[
]], [[
return 0;
]])],
- [je_cv_cflags_appended=$1]
+ [je_cv_cflags_added=$1]
AC_MSG_RESULT([yes]),
- [je_cv_cflags_appended=]
+ [je_cv_cflags_added=]
AC_MSG_RESULT([no])
- [CFLAGS="${TCFLAGS}"]
+ [CONFIGURE_CFLAGS="${T_CONFIGURE_CFLAGS}"]
)
+JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS)
+])
+
+dnl JE_CFLAGS_SAVE()
+dnl JE_CFLAGS_RESTORE()
+dnl
+dnl Save/restore CFLAGS. Nesting is not supported.
+AC_DEFUN([JE_CFLAGS_SAVE],
+SAVED_CONFIGURE_CFLAGS="${CONFIGURE_CFLAGS}"
+)
+AC_DEFUN([JE_CFLAGS_RESTORE],
+CONFIGURE_CFLAGS="${SAVED_CONFIGURE_CFLAGS}"
+JE_CONCAT_VVV(CFLAGS, CONFIGURE_CFLAGS, SPECIFIED_CFLAGS)
+)
+
+CONFIGURE_CXXFLAGS=
+SPECIFIED_CXXFLAGS="${CXXFLAGS}"
+dnl JE_CXXFLAGS_ADD(cxxflag)
+AC_DEFUN([JE_CXXFLAGS_ADD],
+[
+AC_MSG_CHECKING([whether compiler supports $1])
+T_CONFIGURE_CXXFLAGS="${CONFIGURE_CXXFLAGS}"
+JE_APPEND_VS(CONFIGURE_CXXFLAGS, $1)
+JE_CONCAT_VVV(CXXFLAGS, CONFIGURE_CXXFLAGS, SPECIFIED_CXXFLAGS)
+AC_LANG_PUSH([C++])
+AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
+[[
+]], [[
+ return 0;
+]])],
+ [je_cv_cxxflags_added=$1]
+ AC_MSG_RESULT([yes]),
+ [je_cv_cxxflags_added=]
+ AC_MSG_RESULT([no])
+ [CONFIGURE_CXXFLAGS="${T_CONFIGURE_CXXFLAGS}"]
+)
+AC_LANG_POP([C++])
+JE_CONCAT_VVV(CXXFLAGS, CONFIGURE_CXXFLAGS, SPECIFIED_CXXFLAGS)
])
dnl JE_COMPILABLE(label, hcode, mcode, rvar)
-dnl
+dnl
dnl Use AC_LINK_IFELSE() rather than AC_COMPILE_IFELSE() so that linker errors
dnl cause failure.
AC_DEFUN([JE_COMPILABLE],
@@ -116,6 +181,7 @@ dnl If CFLAGS isn't defined, set CFLAGS to something reasonable. Otherwise,
dnl just prevent autoconf from molesting CFLAGS.
CFLAGS=$CFLAGS
AC_PROG_CC
+
if test "x$GCC" != "xyes" ; then
AC_CACHE_CHECK([whether compiler is MSVC],
[je_cv_msvc],
@@ -129,31 +195,122 @@ if test "x$GCC" != "xyes" ; then
[je_cv_msvc=no])])
fi
-if test "x$CFLAGS" = "x" ; then
- no_CFLAGS="yes"
- if test "x$GCC" = "xyes" ; then
- JE_CFLAGS_APPEND([-std=gnu99])
- if test "x$je_cv_cflags_appended" = "x-std=gnu99" ; then
+dnl check if a cray prgenv wrapper compiler is being used
+je_cv_cray_prgenv_wrapper=""
+if test "x${PE_ENV}" != "x" ; then
+ case "${CC}" in
+ CC|cc)
+ je_cv_cray_prgenv_wrapper="yes"
+ ;;
+ *)
+ ;;
+ esac
+fi
+
+AC_CACHE_CHECK([whether compiler is cray],
+ [je_cv_cray],
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+ [
+#ifndef _CRAYC
+ int fail[-1];
+#endif
+])],
+ [je_cv_cray=yes],
+ [je_cv_cray=no])])
+
+if test "x${je_cv_cray}" = "xyes" ; then
+ AC_CACHE_CHECK([whether cray compiler version is 8.4],
+ [je_cv_cray_84],
+ [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+ [
+#if !(_RELEASE_MAJOR == 8 && _RELEASE_MINOR == 4)
+ int fail[-1];
+#endif
+])],
+ [je_cv_cray_84=yes],
+ [je_cv_cray_84=no])])
+fi
+
+if test "x$GCC" = "xyes" ; then
+ JE_CFLAGS_ADD([-std=gnu11])
+ if test "x$je_cv_cflags_added" = "x-std=gnu11" ; then
+ AC_DEFINE_UNQUOTED([JEMALLOC_HAS_RESTRICT])
+ else
+ JE_CFLAGS_ADD([-std=gnu99])
+ if test "x$je_cv_cflags_added" = "x-std=gnu99" ; then
AC_DEFINE_UNQUOTED([JEMALLOC_HAS_RESTRICT])
fi
- JE_CFLAGS_APPEND([-Wall])
- JE_CFLAGS_APPEND([-Werror=declaration-after-statement])
- JE_CFLAGS_APPEND([-pipe])
- JE_CFLAGS_APPEND([-g3])
- elif test "x$je_cv_msvc" = "xyes" ; then
- CC="$CC -nologo"
- JE_CFLAGS_APPEND([-Zi])
- JE_CFLAGS_APPEND([-MT])
- JE_CFLAGS_APPEND([-W3])
- JE_CFLAGS_APPEND([-FS])
- CPPFLAGS="$CPPFLAGS -I${srcdir}/include/msvc_compat"
fi
+ JE_CFLAGS_ADD([-Wall])
+ JE_CFLAGS_ADD([-Wshorten-64-to-32])
+ JE_CFLAGS_ADD([-Wsign-compare])
+ JE_CFLAGS_ADD([-Wundef])
+ JE_CFLAGS_ADD([-Wno-format-zero-length])
+ JE_CFLAGS_ADD([-pipe])
+ JE_CFLAGS_ADD([-g3])
+elif test "x$je_cv_msvc" = "xyes" ; then
+ CC="$CC -nologo"
+ JE_CFLAGS_ADD([-Zi])
+ JE_CFLAGS_ADD([-MT])
+ JE_CFLAGS_ADD([-W3])
+ JE_CFLAGS_ADD([-FS])
+ JE_APPEND_VS(CPPFLAGS, -I${srcdir}/include/msvc_compat)
+fi
+if test "x$je_cv_cray" = "xyes" ; then
+ dnl cray compiler 8.4 has an inlining bug
+ if test "x$je_cv_cray_84" = "xyes" ; then
+ JE_CFLAGS_ADD([-hipa2])
+ JE_CFLAGS_ADD([-hnognu])
+ fi
+ dnl ignore unreachable code warning
+ JE_CFLAGS_ADD([-hnomessage=128])
+ dnl ignore redefinition of "malloc", "free", etc warning
+ JE_CFLAGS_ADD([-hnomessage=1357])
+fi
+AC_SUBST([CONFIGURE_CFLAGS])
+AC_SUBST([SPECIFIED_CFLAGS])
+AC_SUBST([EXTRA_CFLAGS])
+AC_PROG_CPP
+
+AC_ARG_ENABLE([cxx],
+ [AS_HELP_STRING([--disable-cxx], [Disable C++ integration])],
+if test "x$enable_cxx" = "xno" ; then
+ enable_cxx="0"
+else
+ enable_cxx="1"
fi
-dnl Append EXTRA_CFLAGS to CFLAGS, if defined.
-if test "x$EXTRA_CFLAGS" != "x" ; then
- JE_CFLAGS_APPEND([$EXTRA_CFLAGS])
+,
+enable_cxx="1"
+)
+if test "x$enable_cxx" = "x1" ; then
+ dnl Require at least c++14, which is the first version to support sized
+ dnl deallocation. C++ support is not compiled otherwise.
+ m4_include([m4/ax_cxx_compile_stdcxx.m4])
+ AX_CXX_COMPILE_STDCXX([14], [noext], [optional])
+ if test "x${HAVE_CXX14}" = "x1" ; then
+ JE_CXXFLAGS_ADD([-Wall])
+ JE_CXXFLAGS_ADD([-g3])
+
+ SAVED_LIBS="${LIBS}"
+ JE_APPEND_VS(LIBS, -lstdc++)
+ JE_COMPILABLE([libstdc++ linkage], [
+#include <stdlib.h>
+], [[
+ int *arr = (int *)malloc(sizeof(int) * 42);
+ if (arr == NULL)
+ return 1;
+]], [je_cv_libstdcxx])
+ if test "x${je_cv_libstdcxx}" = "xno" ; then
+ LIBS="${SAVED_LIBS}"
+ fi
+ else
+ enable_cxx="0"
+ fi
fi
-AC_PROG_CPP
+AC_SUBST([enable_cxx])
+AC_SUBST([CONFIGURE_CXXFLAGS])
+AC_SUBST([SPECIFIED_CXXFLAGS])
+AC_SUBST([EXTRA_CXXFLAGS])
AC_C_BIGENDIAN([ac_cv_big_endian=1], [ac_cv_big_endian=0])
if test "x${ac_cv_big_endian}" = "x1" ; then
@@ -161,16 +318,21 @@ if test "x${ac_cv_big_endian}" = "x1" ; then
fi
if test "x${je_cv_msvc}" = "xyes" -a "x${ac_cv_header_inttypes_h}" = "xno"; then
- CPPFLAGS="$CPPFLAGS -I${srcdir}/include/msvc_compat/C99"
+ JE_APPEND_VS(CPPFLAGS, -I${srcdir}/include/msvc_compat/C99)
fi
-AC_CHECK_SIZEOF([void *])
-if test "x${ac_cv_sizeof_void_p}" = "x8" ; then
- LG_SIZEOF_PTR=3
-elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then
- LG_SIZEOF_PTR=2
+if test "x${je_cv_msvc}" = "xyes" ; then
+ LG_SIZEOF_PTR=LG_SIZEOF_PTR_WIN
+ AC_MSG_RESULT([Using a predefined value for sizeof(void *): 4 for 32-bit, 8 for 64-bit])
else
- AC_MSG_ERROR([Unsupported pointer size: ${ac_cv_sizeof_void_p}])
+ AC_CHECK_SIZEOF([void *])
+ if test "x${ac_cv_sizeof_void_p}" = "x8" ; then
+ LG_SIZEOF_PTR=3
+ elif test "x${ac_cv_sizeof_void_p}" = "x4" ; then
+ LG_SIZEOF_PTR=2
+ else
+ AC_MSG_ERROR([Unsupported pointer size: ${ac_cv_sizeof_void_p}])
+ fi
fi
AC_DEFINE_UNQUOTED([LG_SIZEOF_PTR], [$LG_SIZEOF_PTR])
@@ -194,6 +356,16 @@ else
fi
AC_DEFINE_UNQUOTED([LG_SIZEOF_LONG], [$LG_SIZEOF_LONG])
+AC_CHECK_SIZEOF([long long])
+if test "x${ac_cv_sizeof_long_long}" = "x8" ; then
+ LG_SIZEOF_LONG_LONG=3
+elif test "x${ac_cv_sizeof_long_long}" = "x4" ; then
+ LG_SIZEOF_LONG_LONG=2
+else
+ AC_MSG_ERROR([Unsupported long long size: ${ac_cv_sizeof_long_long}])
+fi
+AC_DEFINE_UNQUOTED([LG_SIZEOF_LONG_LONG], [$LG_SIZEOF_LONG_LONG])
+
AC_CHECK_SIZEOF([intmax_t])
if test "x${ac_cv_sizeof_intmax_t}" = "x16" ; then
LG_SIZEOF_INTMAX_T=4
@@ -211,22 +383,119 @@ dnl CPU-specific settings.
CPU_SPINWAIT=""
case "${host_cpu}" in
i686|x86_64)
- AC_CACHE_VAL([je_cv_pause],
- [JE_COMPILABLE([pause instruction], [],
- [[__asm__ volatile("pause"); return 0;]],
- [je_cv_pause])])
- if test "x${je_cv_pause}" = "xyes" ; then
- CPU_SPINWAIT='__asm__ volatile("pause")'
+ HAVE_CPU_SPINWAIT=1
+ if test "x${je_cv_msvc}" = "xyes" ; then
+ AC_CACHE_VAL([je_cv_pause_msvc],
+ [JE_COMPILABLE([pause instruction MSVC], [],
+ [[_mm_pause(); return 0;]],
+ [je_cv_pause_msvc])])
+ if test "x${je_cv_pause_msvc}" = "xyes" ; then
+ CPU_SPINWAIT='_mm_pause()'
+ fi
+ else
+ AC_CACHE_VAL([je_cv_pause],
+ [JE_COMPILABLE([pause instruction], [],
+ [[__asm__ volatile("pause"); return 0;]],
+ [je_cv_pause])])
+ if test "x${je_cv_pause}" = "xyes" ; then
+ CPU_SPINWAIT='__asm__ volatile("pause")'
+ fi
fi
;;
- powerpc)
- AC_DEFINE_UNQUOTED([HAVE_ALTIVEC], [ ])
- ;;
*)
+ HAVE_CPU_SPINWAIT=0
;;
esac
+AC_DEFINE_UNQUOTED([HAVE_CPU_SPINWAIT], [$HAVE_CPU_SPINWAIT])
AC_DEFINE_UNQUOTED([CPU_SPINWAIT], [$CPU_SPINWAIT])
+AC_ARG_WITH([lg_vaddr],
+ [AS_HELP_STRING([--with-lg-vaddr=<lg-vaddr>], [Number of significant virtual address bits])],
+ [LG_VADDR="$with_lg_vaddr"], [LG_VADDR="detect"])
+
+case "${host_cpu}" in
+ aarch64)
+ if test "x$LG_VADDR" = "xdetect"; then
+ AC_MSG_CHECKING([number of significant virtual address bits])
+ if test "x${LG_SIZEOF_PTR}" = "x2" ; then
+ #aarch64 ILP32
+ LG_VADDR=32
+ else
+ #aarch64 LP64
+ LG_VADDR=48
+ fi
+ AC_MSG_RESULT([$LG_VADDR])
+ fi
+ ;;
+ x86_64)
+ if test "x$LG_VADDR" = "xdetect"; then
+ AC_CACHE_CHECK([number of significant virtual address bits],
+ [je_cv_lg_vaddr],
+ AC_RUN_IFELSE([AC_LANG_PROGRAM(
+[[
+#include <stdio.h>
+#ifdef _WIN32
+#include <limits.h>
+#include <intrin.h>
+typedef unsigned __int32 uint32_t;
+#else
+#include <stdint.h>
+#endif
+]], [[
+ uint32_t r[[4]];
+ uint32_t eax_in = 0x80000008U;
+#ifdef _WIN32
+ __cpuid((int *)r, (int)eax_in);
+#else
+ asm volatile ("cpuid"
+ : "=a" (r[[0]]), "=b" (r[[1]]), "=c" (r[[2]]), "=d" (r[[3]])
+ : "a" (eax_in), "c" (0)
+ );
+#endif
+ uint32_t eax_out = r[[0]];
+ uint32_t vaddr = ((eax_out & 0x0000ff00U) >> 8);
+ FILE *f = fopen("conftest.out", "w");
+ if (f == NULL) {
+ return 1;
+ }
+ if (vaddr > (sizeof(void *) << 3)) {
+ vaddr = sizeof(void *) << 3;
+ }
+ fprintf(f, "%u", vaddr);
+ fclose(f);
+ return 0;
+]])],
+ [je_cv_lg_vaddr=`cat conftest.out`],
+ [je_cv_lg_vaddr=error],
+ [je_cv_lg_vaddr=57]))
+ if test "x${je_cv_lg_vaddr}" != "x" ; then
+ LG_VADDR="${je_cv_lg_vaddr}"
+ fi
+ if test "x${LG_VADDR}" != "xerror" ; then
+ AC_DEFINE_UNQUOTED([LG_VADDR], [$LG_VADDR])
+ else
+ AC_MSG_ERROR([cannot determine number of significant virtual address bits])
+ fi
+ fi
+ ;;
+ *)
+ if test "x$LG_VADDR" = "xdetect"; then
+ AC_MSG_CHECKING([number of significant virtual address bits])
+ if test "x${LG_SIZEOF_PTR}" = "x3" ; then
+ LG_VADDR=64
+ elif test "x${LG_SIZEOF_PTR}" = "x2" ; then
+ LG_VADDR=32
+ elif test "x${LG_SIZEOF_PTR}" = "xLG_SIZEOF_PTR_WIN" ; then
+ LG_VADDR="(1U << (LG_SIZEOF_PTR_WIN+3))"
+ else
+ AC_MSG_ERROR([Unsupported lg(pointer size): ${LG_SIZEOF_PTR}])
+ fi
+ AC_MSG_RESULT([$LG_VADDR])
+ fi
+ ;;
+esac
+AC_DEFINE_UNQUOTED([LG_VADDR], [$LG_VADDR])
+
LD_PRELOAD_VAR="LD_PRELOAD"
so="so"
importlib="${so}"
@@ -234,36 +503,53 @@ o="$ac_objext"
a="a"
exe="$ac_exeext"
libprefix="lib"
+link_whole_archive="0"
DSO_LDFLAGS='-shared -Wl,-soname,$(@F)'
RPATH='-Wl,-rpath,$(1)'
SOREV="${so}.${rev}"
PIC_CFLAGS='-fPIC -DPIC'
CTARGET='-o $@'
LDTARGET='-o $@'
+TEST_LD_MODE=
EXTRA_LDFLAGS=
ARFLAGS='crus'
AROUT=' $@'
CC_MM=1
+if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then
+ TEST_LD_MODE='-dynamic'
+fi
+
+if test "x${je_cv_cray}" = "xyes" ; then
+ CC_MM=
+fi
+
AN_MAKEVAR([AR], [AC_PROG_AR])
AN_PROGRAM([ar], [AC_PROG_AR])
AC_DEFUN([AC_PROG_AR], [AC_CHECK_TOOL(AR, ar, :)])
AC_PROG_AR
+AN_MAKEVAR([NM], [AC_PROG_NM])
+AN_PROGRAM([nm], [AC_PROG_NM])
+AC_DEFUN([AC_PROG_NM], [AC_CHECK_TOOL(NM, nm, :)])
+AC_PROG_NM
+
+AC_PROG_AWK
+
dnl Platform-specific settings. abi and RPATH can probably be determined
dnl programmatically, but doing so is error-prone, which makes it generally
dnl not worth the trouble.
-dnl
+dnl
dnl Define cpp macros in CPPFLAGS, rather than doing AC_DEFINE(macro), since the
dnl definitions need to be seen before any headers are included, which is a pain
dnl to make happen otherwise.
-default_munmap="1"
+default_retain="0"
maps_coalesce="1"
+DUMP_SYMS="${NM} -a"
+SYM_PREFIX=""
case "${host}" in
*-*-darwin* | *-*-ios*)
- CFLAGS="$CFLAGS"
abi="macho"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
RPATH=""
LD_PRELOAD_VAR="DYLD_INSERT_LIBRARIES"
so="dylib"
@@ -272,38 +558,58 @@ case "${host}" in
DSO_LDFLAGS='-shared -Wl,-install_name,$(LIBDIR)/$(@F)'
SOREV="${rev}.${so}"
sbrk_deprecated="1"
+ SYM_PREFIX="_"
;;
*-*-freebsd*)
- CFLAGS="$CFLAGS"
abi="elf"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
+ AC_DEFINE([JEMALLOC_SYSCTL_VM_OVERCOMMIT], [ ])
force_lazy_lock="1"
;;
*-*-dragonfly*)
- CFLAGS="$CFLAGS"
abi="elf"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
;;
*-*-openbsd*)
- CFLAGS="$CFLAGS"
abi="elf"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
force_tls="0"
;;
*-*-bitrig*)
- CFLAGS="$CFLAGS"
abi="elf"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
+ ;;
+ *-*-linux-android)
+ dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE.
+ JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE)
+ abi="elf"
+ AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS], [ ])
+ AC_DEFINE([JEMALLOC_HAS_ALLOCA_H])
+ AC_DEFINE([JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY], [ ])
+ AC_DEFINE([JEMALLOC_THREADED_INIT], [ ])
+ AC_DEFINE([JEMALLOC_C11_ATOMICS])
+ force_tls="0"
+ if test "${LG_SIZEOF_PTR}" = "3"; then
+ default_retain="1"
+ fi
;;
*-*-linux*)
- CFLAGS="$CFLAGS"
- CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+ dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE.
+ JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE)
abi="elf"
+ AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS], [ ])
AC_DEFINE([JEMALLOC_HAS_ALLOCA_H])
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED], [ ])
+ AC_DEFINE([JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY], [ ])
+ AC_DEFINE([JEMALLOC_THREADED_INIT], [ ])
+ AC_DEFINE([JEMALLOC_USE_CXX_THROW], [ ])
+ if test "${LG_SIZEOF_PTR}" = "3"; then
+ default_retain="1"
+ fi
+ ;;
+ *-*-kfreebsd*)
+ dnl syscall(2) and secure_getenv(3) are exposed by _GNU_SOURCE.
+ JE_APPEND_VS(CPPFLAGS, -D_GNU_SOURCE)
+ abi="elf"
+ AC_DEFINE([JEMALLOC_HAS_ALLOCA_H])
+ AC_DEFINE([JEMALLOC_SYSCTL_VM_OVERCOMMIT], [ ])
AC_DEFINE([JEMALLOC_THREADED_INIT], [ ])
AC_DEFINE([JEMALLOC_USE_CXX_THROW], [ ])
- default_munmap="0"
;;
*-*-netbsd*)
AC_MSG_CHECKING([ABI])
@@ -314,22 +620,19 @@ case "${host}" in
#error aout
#endif
]])],
- [CFLAGS="$CFLAGS"; abi="elf"],
+ [abi="elf"],
[abi="aout"])
AC_MSG_RESULT([$abi])
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
;;
*-*-solaris2*)
- CFLAGS="$CFLAGS"
abi="elf"
- AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
RPATH='-Wl,-R,$(1)'
dnl Solaris needs this for sigwait().
- CPPFLAGS="$CPPFLAGS -D_POSIX_PTHREAD_SEMANTICS"
- LIBS="$LIBS -lposix4 -lsocket -lnsl"
+ JE_APPEND_VS(CPPFLAGS, -D_POSIX_PTHREAD_SEMANTICS)
+ JE_APPEND_VS(LIBS, -lposix4 -lsocket -lnsl)
;;
*-ibm-aix*)
- if "$LG_SIZEOF_PTR" = "8"; then
+ if test "${LG_SIZEOF_PTR}" = "3"; then
dnl 64bit AIX
LD_PRELOAD_VAR="LDR_PRELOAD64"
else
@@ -341,7 +644,6 @@ case "${host}" in
*-*-mingw* | *-*-cygwin*)
abi="pecoff"
force_tls="0"
- force_lazy_lock="1"
maps_coalesce="0"
RPATH=""
so="dll"
@@ -358,7 +660,15 @@ case "${host}" in
else
importlib="${so}"
DSO_LDFLAGS="-shared"
+ link_whole_archive="1"
fi
+ case "${host}" in
+ *-*-cygwin*)
+ DUMP_SYMS="dumpbin /SYMBOLS"
+ ;;
+ *)
+ ;;
+ esac
a="lib"
libprefix=""
SOREV="${so}"
@@ -395,17 +705,29 @@ AC_SUBST([o])
AC_SUBST([a])
AC_SUBST([exe])
AC_SUBST([libprefix])
+AC_SUBST([link_whole_archive])
AC_SUBST([DSO_LDFLAGS])
AC_SUBST([EXTRA_LDFLAGS])
AC_SUBST([SOREV])
AC_SUBST([PIC_CFLAGS])
AC_SUBST([CTARGET])
AC_SUBST([LDTARGET])
+AC_SUBST([TEST_LD_MODE])
AC_SUBST([MKLIB])
AC_SUBST([ARFLAGS])
AC_SUBST([AROUT])
+AC_SUBST([DUMP_SYMS])
AC_SUBST([CC_MM])
+dnl Determine whether libm must be linked to use e.g. log(3).
+AC_SEARCH_LIBS([log], [m], , [AC_MSG_ERROR([Missing math functions])])
+if test "x$ac_cv_search_log" != "xnone required" ; then
+ LM="$ac_cv_search_log"
+else
+ LM=
+fi
+AC_SUBST(LM)
+
JE_COMPILABLE([__attribute__ syntax],
[static __attribute__((unused)) void foo(void){}],
[],
@@ -413,51 +735,53 @@ JE_COMPILABLE([__attribute__ syntax],
if test "x${je_cv_attribute}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_ATTR], [ ])
if test "x${GCC}" = "xyes" -a "x${abi}" = "xelf"; then
- JE_CFLAGS_APPEND([-fvisibility=hidden])
+ JE_CFLAGS_ADD([-fvisibility=hidden])
+ JE_CXXFLAGS_ADD([-fvisibility=hidden])
fi
fi
dnl Check for tls_model attribute support (clang 3.0 still lacks support).
-SAVED_CFLAGS="${CFLAGS}"
-JE_CFLAGS_APPEND([-Werror])
+JE_CFLAGS_SAVE()
+JE_CFLAGS_ADD([-Werror])
+JE_CFLAGS_ADD([-herror_on_warning])
JE_COMPILABLE([tls_model attribute], [],
[static __thread int
__attribute__((tls_model("initial-exec"), unused)) foo;
foo = 0;],
[je_cv_tls_model])
-CFLAGS="${SAVED_CFLAGS}"
-if test "x${je_cv_tls_model}" = "xyes" ; then
- AC_DEFINE([JEMALLOC_TLS_MODEL],
- [__attribute__((tls_model("initial-exec")))])
-else
- AC_DEFINE([JEMALLOC_TLS_MODEL], [ ])
-fi
+JE_CFLAGS_RESTORE()
+dnl (Setting of JEMALLOC_TLS_MODEL is done later, after we've checked for
+dnl --disable-initial-exec-tls)
+
dnl Check for alloc_size attribute support.
-SAVED_CFLAGS="${CFLAGS}"
-JE_CFLAGS_APPEND([-Werror])
+JE_CFLAGS_SAVE()
+JE_CFLAGS_ADD([-Werror])
+JE_CFLAGS_ADD([-herror_on_warning])
JE_COMPILABLE([alloc_size attribute], [#include <stdlib.h>],
[void *foo(size_t size) __attribute__((alloc_size(1)));],
[je_cv_alloc_size])
-CFLAGS="${SAVED_CFLAGS}"
+JE_CFLAGS_RESTORE()
if test "x${je_cv_alloc_size}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_ATTR_ALLOC_SIZE], [ ])
fi
dnl Check for format(gnu_printf, ...) attribute support.
-SAVED_CFLAGS="${CFLAGS}"
-JE_CFLAGS_APPEND([-Werror])
+JE_CFLAGS_SAVE()
+JE_CFLAGS_ADD([-Werror])
+JE_CFLAGS_ADD([-herror_on_warning])
JE_COMPILABLE([format(gnu_printf, ...) attribute], [#include <stdlib.h>],
[void *foo(const char *format, ...) __attribute__((format(gnu_printf, 1, 2)));],
[je_cv_format_gnu_printf])
-CFLAGS="${SAVED_CFLAGS}"
+JE_CFLAGS_RESTORE()
if test "x${je_cv_format_gnu_printf}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF], [ ])
fi
dnl Check for format(printf, ...) attribute support.
-SAVED_CFLAGS="${CFLAGS}"
-JE_CFLAGS_APPEND([-Werror])
+JE_CFLAGS_SAVE()
+JE_CFLAGS_ADD([-Werror])
+JE_CFLAGS_ADD([-herror_on_warning])
JE_COMPILABLE([format(printf, ...) attribute], [#include <stdlib.h>],
[void *foo(const char *format, ...) __attribute__((format(printf, 1, 2)));],
[je_cv_format_printf])
-CFLAGS="${SAVED_CFLAGS}"
+JE_CFLAGS_RESTORE()
if test "x${je_cv_format_printf}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_ATTR_FORMAT_PRINTF], [ ])
fi
@@ -492,41 +816,6 @@ AC_PROG_RANLIB
AC_PATH_PROG([LD], [ld], [false], [$PATH])
AC_PATH_PROG([AUTOCONF], [autoconf], [false], [$PATH])
-public_syms="malloc_conf malloc_message malloc calloc posix_memalign aligned_alloc realloc free mallocx rallocx xallocx sallocx dallocx sdallocx nallocx mallctl mallctlnametomib mallctlbymib malloc_stats_print malloc_usable_size"
-
-dnl Check for allocator-related functions that should be wrapped.
-AC_CHECK_FUNC([memalign],
- [AC_DEFINE([JEMALLOC_OVERRIDE_MEMALIGN], [ ])
- public_syms="${public_syms} memalign"])
-AC_CHECK_FUNC([valloc],
- [AC_DEFINE([JEMALLOC_OVERRIDE_VALLOC], [ ])
- public_syms="${public_syms} valloc"])
-
-dnl Do not compute test code coverage by default.
-GCOV_FLAGS=
-AC_ARG_ENABLE([code-coverage],
- [AS_HELP_STRING([--enable-code-coverage],
- [Enable code coverage])],
-[if test "x$enable_code_coverage" = "xno" ; then
- enable_code_coverage="0"
-else
- enable_code_coverage="1"
-fi
-],
-[enable_code_coverage="0"]
-)
-if test "x$enable_code_coverage" = "x1" ; then
- deoptimize="no"
- echo "$CFLAGS $EXTRA_CFLAGS" | grep '\-O' >/dev/null || deoptimize="yes"
- if test "x${deoptimize}" = "xyes" ; then
- JE_CFLAGS_APPEND([-O0])
- fi
- JE_CFLAGS_APPEND([-fprofile-arcs -ftest-coverage])
- EXTRA_LDFLAGS="$EXTRA_LDFLAGS -fprofile-arcs -ftest-coverage"
- AC_DEFINE([JEMALLOC_CODE_COVERAGE], [ ])
-fi
-AC_SUBST([enable_code_coverage])
-
dnl Perform no name mangling by default.
AC_ARG_WITH([mangling],
[AS_HELP_STRING([--with-mangling=<map>], [Mangle symbols in <map>])],
@@ -542,11 +831,14 @@ else
JEMALLOC_PREFIX="je_"
fi]
)
-if test "x$JEMALLOC_PREFIX" != "x" ; then
+if test "x$JEMALLOC_PREFIX" = "x" ; then
+ AC_DEFINE([JEMALLOC_IS_MALLOC])
+else
JEMALLOC_CPREFIX=`echo ${JEMALLOC_PREFIX} | tr "a-z" "A-Z"`
AC_DEFINE_UNQUOTED([JEMALLOC_PREFIX], ["$JEMALLOC_PREFIX"])
AC_DEFINE_UNQUOTED([JEMALLOC_CPREFIX], ["$JEMALLOC_CPREFIX"])
fi
+AC_SUBST([JEMALLOC_PREFIX])
AC_SUBST([JEMALLOC_CPREFIX])
AC_ARG_WITH([export],
@@ -556,6 +848,49 @@ AC_ARG_WITH([export],
fi]
)
+public_syms="aligned_alloc calloc dallocx free mallctl mallctlbymib mallctlnametomib malloc malloc_conf malloc_message malloc_stats_print malloc_usable_size mallocx nallocx posix_memalign rallocx realloc sallocx sdallocx xallocx"
+dnl Check for additional platform-specific public API functions.
+AC_CHECK_FUNC([memalign],
+ [AC_DEFINE([JEMALLOC_OVERRIDE_MEMALIGN], [ ])
+ public_syms="${public_syms} memalign"])
+AC_CHECK_FUNC([valloc],
+ [AC_DEFINE([JEMALLOC_OVERRIDE_VALLOC], [ ])
+ public_syms="${public_syms} valloc"])
+
+dnl Check for allocator-related functions that should be wrapped.
+wrap_syms=
+if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ AC_CHECK_FUNC([__libc_calloc],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_CALLOC], [ ])
+ wrap_syms="${wrap_syms} __libc_calloc"])
+ AC_CHECK_FUNC([__libc_free],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_FREE], [ ])
+ wrap_syms="${wrap_syms} __libc_free"])
+ AC_CHECK_FUNC([__libc_malloc],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_MALLOC], [ ])
+ wrap_syms="${wrap_syms} __libc_malloc"])
+ AC_CHECK_FUNC([__libc_memalign],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_MEMALIGN], [ ])
+ wrap_syms="${wrap_syms} __libc_memalign"])
+ AC_CHECK_FUNC([__libc_realloc],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_REALLOC], [ ])
+ wrap_syms="${wrap_syms} __libc_realloc"])
+ AC_CHECK_FUNC([__libc_valloc],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___LIBC_VALLOC], [ ])
+ wrap_syms="${wrap_syms} __libc_valloc"])
+ AC_CHECK_FUNC([__posix_memalign],
+ [AC_DEFINE([JEMALLOC_OVERRIDE___POSIX_MEMALIGN], [ ])
+ wrap_syms="${wrap_syms} __posix_memalign"])
+fi
+
+case "${host}" in
+ *-*-mingw* | *-*-cygwin*)
+ wrap_syms="${wrap_syms} tls_callback"
+ ;;
+ *)
+ ;;
+esac
+
dnl Mangle library-private APIs.
AC_ARG_WITH([private_namespace],
[AS_HELP_STRING([--with-private-namespace=<prefix>], [Prefix to prepend to all library-private APIs])],
@@ -575,6 +910,15 @@ AC_ARG_WITH([install_suffix],
install_suffix="$INSTALL_SUFFIX"
AC_SUBST([install_suffix])
+dnl Specify default malloc_conf.
+AC_ARG_WITH([malloc_conf],
+ [AS_HELP_STRING([--with-malloc-conf=<malloc_conf>], [config.malloc_conf options string])],
+ [JEMALLOC_CONFIG_MALLOC_CONF="$with_malloc_conf"],
+ [JEMALLOC_CONFIG_MALLOC_CONF=""]
+)
+config_malloc_conf="$JEMALLOC_CONFIG_MALLOC_CONF"
+AC_DEFINE_UNQUOTED([JEMALLOC_CONFIG_MALLOC_CONF], ["$config_malloc_conf"])
+
dnl Substitute @je_@ in jemalloc_protos.h.in, primarily to make generation of
dnl jemalloc_protos_jet.h easy.
je_="je_"
@@ -588,7 +932,7 @@ cfgoutputs_in="${cfgoutputs_in} doc/jemalloc.xml.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_macros.h.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_protos.h.in"
cfgoutputs_in="${cfgoutputs_in} include/jemalloc/jemalloc_typedefs.h.in"
-cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_internal.h.in"
+cfgoutputs_in="${cfgoutputs_in} include/jemalloc/internal/jemalloc_preamble.h.in"
cfgoutputs_in="${cfgoutputs_in} test/test.sh.in"
cfgoutputs_in="${cfgoutputs_in} test/include/test/jemalloc_test.h.in"
@@ -600,7 +944,7 @@ cfgoutputs_out="${cfgoutputs_out} doc/jemalloc.xml"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_macros.h"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_protos.h"
cfgoutputs_out="${cfgoutputs_out} include/jemalloc/jemalloc_typedefs.h"
-cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_internal.h"
+cfgoutputs_out="${cfgoutputs_out} include/jemalloc/internal/jemalloc_preamble.h"
cfgoutputs_out="${cfgoutputs_out} test/test.sh"
cfgoutputs_out="${cfgoutputs_out} test/include/test/jemalloc_test.h"
@@ -612,15 +956,14 @@ cfgoutputs_tup="${cfgoutputs_tup} doc/jemalloc.xml:doc/jemalloc.xml.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_macros.h:include/jemalloc/jemalloc_macros.h.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_protos.h:include/jemalloc/jemalloc_protos.h.in"
cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/jemalloc_typedefs.h:include/jemalloc/jemalloc_typedefs.h.in"
-cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_internal.h"
+cfgoutputs_tup="${cfgoutputs_tup} include/jemalloc/internal/jemalloc_preamble.h"
cfgoutputs_tup="${cfgoutputs_tup} test/test.sh:test/test.sh.in"
cfgoutputs_tup="${cfgoutputs_tup} test/include/test/jemalloc_test.h:test/include/test/jemalloc_test.h.in"
cfghdrs_in="include/jemalloc/jemalloc_defs.h.in"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/jemalloc_internal_defs.h.in"
+cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_namespace.sh"
-cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_unnamespace.sh"
-cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/private_symbols.txt"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_namespace.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/public_unnamespace.sh"
cfghdrs_in="${cfghdrs_in} include/jemalloc/internal/size_classes.sh"
@@ -631,8 +974,8 @@ cfghdrs_in="${cfghdrs_in} test/include/test/jemalloc_test_defs.h.in"
cfghdrs_out="include/jemalloc/jemalloc_defs.h"
cfghdrs_out="${cfghdrs_out} include/jemalloc/jemalloc${install_suffix}.h"
-cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_namespace.h"
-cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_unnamespace.h"
+cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols.awk"
+cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/private_symbols_jet.awk"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_symbols.txt"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_namespace.h"
cfghdrs_out="${cfghdrs_out} include/jemalloc/internal/public_unnamespace.h"
@@ -648,26 +991,10 @@ cfghdrs_tup="include/jemalloc/jemalloc_defs.h:include/jemalloc/jemalloc_defs.h.i
cfghdrs_tup="${cfghdrs_tup} include/jemalloc/internal/jemalloc_internal_defs.h:include/jemalloc/internal/jemalloc_internal_defs.h.in"
cfghdrs_tup="${cfghdrs_tup} test/include/test/jemalloc_test_defs.h:test/include/test/jemalloc_test_defs.h.in"
-dnl Silence irrelevant compiler warnings by default.
-AC_ARG_ENABLE([cc-silence],
- [AS_HELP_STRING([--disable-cc-silence],
- [Do not silence irrelevant compiler warnings])],
-[if test "x$enable_cc_silence" = "xno" ; then
- enable_cc_silence="0"
-else
- enable_cc_silence="1"
-fi
-],
-[enable_cc_silence="1"]
-)
-if test "x$enable_cc_silence" = "x1" ; then
- AC_DEFINE([JEMALLOC_CC_SILENCE], [ ])
-fi
-
dnl Do not compile with debugging by default.
AC_ARG_ENABLE([debug],
[AS_HELP_STRING([--enable-debug],
- [Build debugging code (implies --enable-ivsalloc)])],
+ [Build debugging code])],
[if test "x$enable_debug" = "xno" ; then
enable_debug="0"
else
@@ -681,40 +1008,21 @@ if test "x$enable_debug" = "x1" ; then
fi
if test "x$enable_debug" = "x1" ; then
AC_DEFINE([JEMALLOC_DEBUG], [ ])
- enable_ivsalloc="1"
fi
AC_SUBST([enable_debug])
-dnl Do not validate pointers by default.
-AC_ARG_ENABLE([ivsalloc],
- [AS_HELP_STRING([--enable-ivsalloc],
- [Validate pointers passed through the public API])],
-[if test "x$enable_ivsalloc" = "xno" ; then
- enable_ivsalloc="0"
-else
- enable_ivsalloc="1"
-fi
-],
-[enable_ivsalloc="0"]
-)
-if test "x$enable_ivsalloc" = "x1" ; then
- AC_DEFINE([JEMALLOC_IVSALLOC], [ ])
-fi
-
dnl Only optimize if not debugging.
-if test "x$enable_debug" = "x0" -a "x$no_CFLAGS" = "xyes" ; then
- dnl Make sure that an optimization flag was not specified in EXTRA_CFLAGS.
- optimize="no"
- echo "$CFLAGS $EXTRA_CFLAGS" | grep '\-O' >/dev/null || optimize="yes"
- if test "x${optimize}" = "xyes" ; then
- if test "x$GCC" = "xyes" ; then
- JE_CFLAGS_APPEND([-O3])
- JE_CFLAGS_APPEND([-funroll-loops])
- elif test "x$je_cv_msvc" = "xyes" ; then
- JE_CFLAGS_APPEND([-O2])
- else
- JE_CFLAGS_APPEND([-O])
- fi
+if test "x$enable_debug" = "x0" ; then
+ if test "x$GCC" = "xyes" ; then
+ JE_CFLAGS_ADD([-O3])
+ JE_CXXFLAGS_ADD([-O3])
+ JE_CFLAGS_ADD([-funroll-loops])
+ elif test "x$je_cv_msvc" = "xyes" ; then
+ JE_CFLAGS_ADD([-O2])
+ JE_CXXFLAGS_ADD([-O2])
+ else
+ JE_CFLAGS_ADD([-O])
+ JE_CXXFLAGS_ADD([-O])
fi
fi
@@ -778,10 +1086,10 @@ fi,
if test "x$backtrace_method" = "x" -a "x$enable_prof_libunwind" = "x1" ; then
AC_CHECK_HEADERS([libunwind.h], , [enable_prof_libunwind="0"])
if test "x$LUNWIND" = "x-lunwind" ; then
- AC_CHECK_LIB([unwind], [unw_backtrace], [LIBS="$LIBS $LUNWIND"],
+ AC_CHECK_LIB([unwind], [unw_backtrace], [JE_APPEND_VS(LIBS, $LUNWIND)],
[enable_prof_libunwind="0"])
else
- LIBS="$LIBS $LUNWIND"
+ JE_APPEND_VS(LIBS, $LUNWIND)
fi
if test "x${enable_prof_libunwind}" = "x1" ; then
backtrace_method="libunwind"
@@ -803,7 +1111,9 @@ fi
if test "x$backtrace_method" = "x" -a "x$enable_prof_libgcc" = "x1" \
-a "x$GCC" = "xyes" ; then
AC_CHECK_HEADERS([unwind.h], , [enable_prof_libgcc="0"])
- AC_CHECK_LIB([gcc], [_Unwind_Backtrace], [LIBS="$LIBS -lgcc"], [enable_prof_libgcc="0"])
+ if test "x${enable_prof_libgcc}" = "x1" ; then
+ AC_CHECK_LIB([gcc], [_Unwind_Backtrace], [JE_APPEND_VS(LIBS, -lgcc)], [enable_prof_libgcc="0"])
+ fi
if test "x${enable_prof_libgcc}" = "x1" ; then
backtrace_method="libgcc"
AC_DEFINE([JEMALLOC_PROF_LIBGCC], [ ])
@@ -825,7 +1135,7 @@ fi
)
if test "x$backtrace_method" = "x" -a "x$enable_prof_gcc" = "x1" \
-a "x$GCC" = "xyes" ; then
- JE_CFLAGS_APPEND([-fno-omit-frame-pointer])
+ JE_CFLAGS_ADD([-fno-omit-frame-pointer])
backtrace_method="gcc intrinsics"
AC_DEFINE([JEMALLOC_PROF_GCC], [ ])
else
@@ -839,52 +1149,23 @@ fi
AC_MSG_CHECKING([configured backtracing method])
AC_MSG_RESULT([$backtrace_method])
if test "x$enable_prof" = "x1" ; then
- if test "x$abi" != "xpecoff"; then
- dnl Heap profiling uses the log(3) function.
- LIBS="$LIBS -lm"
- fi
+ dnl Heap profiling uses the log(3) function.
+ JE_APPEND_VS(LIBS, $LM)
AC_DEFINE([JEMALLOC_PROF], [ ])
fi
AC_SUBST([enable_prof])
-dnl Enable thread-specific caching by default.
-AC_ARG_ENABLE([tcache],
- [AS_HELP_STRING([--disable-tcache], [Disable per thread caches])],
-[if test "x$enable_tcache" = "xno" ; then
- enable_tcache="0"
-else
- enable_tcache="1"
-fi
-],
-[enable_tcache="1"]
-)
-if test "x$enable_tcache" = "x1" ; then
- AC_DEFINE([JEMALLOC_TCACHE], [ ])
-fi
-AC_SUBST([enable_tcache])
-
dnl Indicate whether adjacent virtual memory mappings automatically coalesce
dnl (and fragment on demand).
if test "x${maps_coalesce}" = "x1" ; then
AC_DEFINE([JEMALLOC_MAPS_COALESCE], [ ])
fi
-dnl Enable VM deallocation via munmap() by default.
-AC_ARG_ENABLE([munmap],
- [AS_HELP_STRING([--disable-munmap], [Disable VM deallocation via munmap(2)])],
-[if test "x$enable_munmap" = "xno" ; then
- enable_munmap="0"
-else
- enable_munmap="1"
+dnl Indicate whether to retain memory (rather than using munmap()) by default.
+if test "x$default_retain" = "x1" ; then
+ AC_DEFINE([JEMALLOC_RETAIN], [ ])
fi
-],
-[enable_munmap="${default_munmap}"]
-)
-if test "x$enable_munmap" = "x1" ; then
- AC_DEFINE([JEMALLOC_MUNMAP], [ ])
-fi
-AC_SUBST([enable_munmap])
dnl Enable allocation from DSS if supported by the OS.
have_dss="1"
@@ -905,8 +1186,7 @@ fi
dnl Support the junk/zero filling option by default.
AC_ARG_ENABLE([fill],
- [AS_HELP_STRING([--disable-fill],
- [Disable support for junk/zero filling, quarantine, and redzones])],
+ [AS_HELP_STRING([--disable-fill], [Disable support for junk/zero filling])],
[if test "x$enable_fill" = "xno" ; then
enable_fill="0"
else
@@ -948,35 +1228,6 @@ if test "x$enable_utrace" = "x1" ; then
fi
AC_SUBST([enable_utrace])
-dnl Support Valgrind by default.
-AC_ARG_ENABLE([valgrind],
- [AS_HELP_STRING([--disable-valgrind], [Disable support for Valgrind])],
-[if test "x$enable_valgrind" = "xno" ; then
- enable_valgrind="0"
-else
- enable_valgrind="1"
-fi
-],
-[enable_valgrind="1"]
-)
-if test "x$enable_valgrind" = "x1" ; then
- JE_COMPILABLE([valgrind], [
-#include <valgrind/valgrind.h>
-#include <valgrind/memcheck.h>
-
-#if !defined(VALGRIND_RESIZEINPLACE_BLOCK)
-# error "Incompatible Valgrind version"
-#endif
-], [], [je_cv_valgrind])
- if test "x${je_cv_valgrind}" = "xno" ; then
- enable_valgrind="0"
- fi
- if test "x$enable_valgrind" = "x1" ; then
- AC_DEFINE([JEMALLOC_VALGRIND], [ ])
- fi
-fi
-AC_SUBST([enable_valgrind])
-
dnl Do not support the xmalloc option by default.
AC_ARG_ENABLE([xmalloc],
[AS_HELP_STRING([--enable-xmalloc], [Support xmalloc option])],
@@ -1010,11 +1261,43 @@ if test "x$enable_cache_oblivious" = "x1" ; then
fi
AC_SUBST([enable_cache_oblivious])
+dnl Do not log by default.
+AC_ARG_ENABLE([log],
+ [AS_HELP_STRING([--enable-log], [Support debug logging])],
+[if test "x$enable_log" = "xno" ; then
+ enable_log="0"
+else
+ enable_log="1"
+fi
+],
+[enable_log="0"]
+)
+if test "x$enable_log" = "x1" ; then
+ AC_DEFINE([JEMALLOC_LOG], [ ])
+fi
+AC_SUBST([enable_log])
+
+
+JE_COMPILABLE([a program using __builtin_unreachable], [
+void foo (void) {
+ __builtin_unreachable();
+}
+], [
+ {
+ foo();
+ }
+], [je_cv_gcc_builtin_unreachable])
+if test "x${je_cv_gcc_builtin_unreachable}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_INTERNAL_UNREACHABLE], [__builtin_unreachable])
+else
+ AC_DEFINE([JEMALLOC_INTERNAL_UNREACHABLE], [abort])
+fi
+
dnl ============================================================================
dnl Check for __builtin_ffsl(), then ffsl(3), and fail if neither are found.
dnl One of those two functions should (theoretically) exist on all platforms
dnl that jemalloc currently has a chance of functioning on without modification.
-dnl We additionally assume ffs() or __builtin_ffs() are defined if
+dnl We additionally assume ffs[ll]() or __builtin_ffs[ll]() are defined if
dnl ffsl() or __builtin_ffsl() are defined, respectively.
JE_COMPILABLE([a program using __builtin_ffsl], [
#include <stdio.h>
@@ -1027,6 +1310,7 @@ JE_COMPILABLE([a program using __builtin_ffsl], [
}
], [je_cv_gcc_builtin_ffsl])
if test "x${je_cv_gcc_builtin_ffsl}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_INTERNAL_FFSLL], [__builtin_ffsll])
AC_DEFINE([JEMALLOC_INTERNAL_FFSL], [__builtin_ffsl])
AC_DEFINE([JEMALLOC_INTERNAL_FFS], [__builtin_ffs])
else
@@ -1041,6 +1325,7 @@ else
}
], [je_cv_function_ffsl])
if test "x${je_cv_function_ffsl}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_INTERNAL_FFSLL], [ffsll])
AC_DEFINE([JEMALLOC_INTERNAL_FFSL], [ffsl])
AC_DEFINE([JEMALLOC_INTERNAL_FFS], [ffs])
else
@@ -1048,13 +1333,6 @@ else
fi
fi
-AC_ARG_WITH([lg_tiny_min],
- [AS_HELP_STRING([--with-lg-tiny-min=<lg-tiny-min>],
- [Base 2 log of minimum tiny size class to support])],
- [LG_TINY_MIN="$with_lg_tiny_min"],
- [LG_TINY_MIN="3"])
-AC_DEFINE_UNQUOTED([LG_TINY_MIN], [$LG_TINY_MIN])
-
AC_ARG_WITH([lg_quantum],
[AS_HELP_STRING([--with-lg-quantum=<lg-quantum>],
[Base 2 log of minimum allocation alignment])],
@@ -1100,7 +1378,7 @@ if test "x$LG_PAGE" = "xdetect"; then
if (f == NULL) {
return 1;
}
- fprintf(f, "%d\n", result);
+ fprintf(f, "%d", result);
fclose(f);
return 0;
@@ -1118,42 +1396,83 @@ else
AC_MSG_ERROR([cannot determine value for LG_PAGE])
fi
+AC_ARG_WITH([lg_hugepage],
+ [AS_HELP_STRING([--with-lg-hugepage=<lg-hugepage>],
+ [Base 2 log of system huge page size])],
+ [je_cv_lg_hugepage="${with_lg_hugepage}"],
+ [je_cv_lg_hugepage=""])
+if test "x${je_cv_lg_hugepage}" = "x" ; then
+ dnl Look in /proc/meminfo (Linux-specific) for information on the default huge
+ dnl page size, if any. The relevant line looks like:
+ dnl
+ dnl Hugepagesize: 2048 kB
+ if test -e "/proc/meminfo" ; then
+ hpsk=[`cat /proc/meminfo 2>/dev/null | \
+ grep -e '^Hugepagesize:[[:space:]]\+[0-9]\+[[:space:]]kB$' | \
+ awk '{print $2}'`]
+ if test "x${hpsk}" != "x" ; then
+ je_cv_lg_hugepage=10
+ while test "${hpsk}" -gt 1 ; do
+ hpsk="$((hpsk / 2))"
+ je_cv_lg_hugepage="$((je_cv_lg_hugepage + 1))"
+ done
+ fi
+ fi
+
+ dnl Set default if unable to automatically configure.
+ if test "x${je_cv_lg_hugepage}" = "x" ; then
+ je_cv_lg_hugepage=21
+ fi
+fi
+if test "x${LG_PAGE}" != "xundefined" -a \
+ "${je_cv_lg_hugepage}" -lt "${LG_PAGE}" ; then
+ AC_MSG_ERROR([Huge page size (2^${je_cv_lg_hugepage}) must be at least page size (2^${LG_PAGE})])
+fi
+AC_DEFINE_UNQUOTED([LG_HUGEPAGE], [${je_cv_lg_hugepage}])
+
AC_ARG_WITH([lg_page_sizes],
[AS_HELP_STRING([--with-lg-page-sizes=<lg-page-sizes>],
[Base 2 logs of system page sizes to support])],
[LG_PAGE_SIZES="$with_lg_page_sizes"], [LG_PAGE_SIZES="$LG_PAGE"])
-AC_ARG_WITH([lg_size_class_group],
- [AS_HELP_STRING([--with-lg-size-class-group=<lg-size-class-group>],
- [Base 2 log of size classes per doubling])],
- [LG_SIZE_CLASS_GROUP="$with_lg_size_class_group"],
- [LG_SIZE_CLASS_GROUP="2"])
-
dnl ============================================================================
dnl jemalloc configuration.
-dnl
-
-dnl Set VERSION if source directory is inside a git repository.
-if test "x`test ! \"${srcroot}\" && cd \"${srcroot}\"; git rev-parse --is-inside-work-tree 2>/dev/null`" = "xtrue" ; then
- dnl Pattern globs aren't powerful enough to match both single- and
- dnl double-digit version numbers, so iterate over patterns to support up to
- dnl version 99.99.99 without any accidental matches.
- rm -f "${objroot}VERSION"
- for pattern in ['[0-9].[0-9].[0-9]' '[0-9].[0-9].[0-9][0-9]' \
- '[0-9].[0-9][0-9].[0-9]' '[0-9].[0-9][0-9].[0-9][0-9]' \
- '[0-9][0-9].[0-9].[0-9]' '[0-9][0-9].[0-9].[0-9][0-9]' \
- '[0-9][0-9].[0-9][0-9].[0-9]' \
- '[0-9][0-9].[0-9][0-9].[0-9][0-9]']; do
- if test ! -e "${objroot}VERSION" ; then
- (test ! "${srcroot}" && cd "${srcroot}"; git describe --long --abbrev=40 --match="${pattern}") > "${objroot}VERSION.tmp" 2>/dev/null
- if test $? -eq 0 ; then
- mv "${objroot}VERSION.tmp" "${objroot}VERSION"
- break
+dnl
+
+AC_ARG_WITH([version],
+ [AS_HELP_STRING([--with-version=<major>.<minor>.<bugfix>-<nrev>-g<gid>],
+ [Version string])],
+ [
+ echo "${with_version}" | grep ['^[0-9]\+\.[0-9]\+\.[0-9]\+-[0-9]\+-g[0-9a-f]\+$'] 2>&1 1>/dev/null
+ if test $? -eq 0 ; then
+ echo "$with_version" > "${objroot}VERSION"
+ else
+ echo "${with_version}" | grep ['^VERSION$'] 2>&1 1>/dev/null
+ if test $? -ne 0 ; then
+ AC_MSG_ERROR([${with_version} does not match <major>.<minor>.<bugfix>-<nrev>-g<gid> or VERSION])
fi
fi
- done
-fi
-rm -f "${objroot}VERSION.tmp"
+ ], [
+ dnl Set VERSION if source directory is inside a git repository.
+ if test "x`test ! \"${srcroot}\" && cd \"${srcroot}\"; git rev-parse --is-inside-work-tree 2>/dev/null`" = "xtrue" ; then
+ dnl Pattern globs aren't powerful enough to match both single- and
+ dnl double-digit version numbers, so iterate over patterns to support up
+ dnl to version 99.99.99 without any accidental matches.
+ for pattern in ['[0-9].[0-9].[0-9]' '[0-9].[0-9].[0-9][0-9]' \
+ '[0-9].[0-9][0-9].[0-9]' '[0-9].[0-9][0-9].[0-9][0-9]' \
+ '[0-9][0-9].[0-9].[0-9]' '[0-9][0-9].[0-9].[0-9][0-9]' \
+ '[0-9][0-9].[0-9][0-9].[0-9]' \
+ '[0-9][0-9].[0-9][0-9].[0-9][0-9]']; do
+ (test ! "${srcroot}" && cd "${srcroot}"; git describe --long --abbrev=40 --match="${pattern}") > "${objroot}VERSION.tmp" 2>/dev/null
+ if test $? -eq 0 ; then
+ mv "${objroot}VERSION.tmp" "${objroot}VERSION"
+ break
+ fi
+ done
+ fi
+ rm -f "${objroot}VERSION.tmp"
+ ])
+
if test ! -e "${objroot}VERSION" ; then
if test ! -e "${srcroot}VERSION" ; then
AC_MSG_RESULT(
@@ -1180,23 +1499,128 @@ dnl ============================================================================
dnl Configure pthreads.
if test "x$abi" != "xpecoff" ; then
+ AC_DEFINE([JEMALLOC_HAVE_PTHREAD], [ ])
AC_CHECK_HEADERS([pthread.h], , [AC_MSG_ERROR([pthread.h is missing])])
dnl Some systems may embed pthreads functionality in libc; check for libpthread
dnl first, but try libc too before failing.
- AC_CHECK_LIB([pthread], [pthread_create], [LIBS="$LIBS -lpthread"],
+ AC_CHECK_LIB([pthread], [pthread_create], [JE_APPEND_VS(LIBS, -lpthread)],
[AC_SEARCH_LIBS([pthread_create], , ,
AC_MSG_ERROR([libpthread is missing]))])
+ wrap_syms="${wrap_syms} pthread_create"
+ have_pthread="1"
+ dnl Check if we have dlsym support.
+ have_dlsym="1"
+ AC_CHECK_HEADERS([dlfcn.h],
+ AC_CHECK_FUNC([dlsym], [],
+ [AC_CHECK_LIB([dl], [dlsym], [LIBS="$LIBS -ldl"], [have_dlsym="0"])]),
+ [have_dlsym="0"])
+ if test "x$have_dlsym" = "x1" ; then
+ AC_DEFINE([JEMALLOC_HAVE_DLSYM], [ ])
+ fi
+ JE_COMPILABLE([pthread_atfork(3)], [
+#include <pthread.h>
+], [
+ pthread_atfork((void *)0, (void *)0, (void *)0);
+], [je_cv_pthread_atfork])
+ if test "x${je_cv_pthread_atfork}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_PTHREAD_ATFORK], [ ])
+ fi
+ dnl Check if pthread_setname_np is available with the expected API.
+ JE_COMPILABLE([pthread_setname_np(3)], [
+#include <pthread.h>
+], [
+ pthread_setname_np(pthread_self(), "setname_test");
+], [je_cv_pthread_setname_np])
+ if test "x${je_cv_pthread_setname_np}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_PTHREAD_SETNAME_NP], [ ])
+ fi
+fi
+
+JE_APPEND_VS(CPPFLAGS, -D_REENTRANT)
+
+dnl Check whether clock_gettime(2) is in libc or librt.
+AC_SEARCH_LIBS([clock_gettime], [rt])
+
+dnl Cray wrapper compiler often adds `-lrt` when using `-static`. Check with
+dnl `-dynamic` as well in case a user tries to dynamically link in jemalloc
+if test "x$je_cv_cray_prgenv_wrapper" = "xyes" ; then
+ if test "$ac_cv_search_clock_gettime" != "-lrt"; then
+ JE_CFLAGS_SAVE()
+
+ unset ac_cv_search_clock_gettime
+ JE_CFLAGS_ADD([-dynamic])
+ AC_SEARCH_LIBS([clock_gettime], [rt])
+
+ JE_CFLAGS_RESTORE()
+ fi
+fi
+
+dnl check for CLOCK_MONOTONIC_COARSE (Linux-specific).
+JE_COMPILABLE([clock_gettime(CLOCK_MONOTONIC_COARSE, ...)], [
+#include <time.h>
+], [
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+], [je_cv_clock_monotonic_coarse])
+if test "x${je_cv_clock_monotonic_coarse}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE])
+fi
+
+dnl check for CLOCK_MONOTONIC.
+JE_COMPILABLE([clock_gettime(CLOCK_MONOTONIC, ...)], [
+#include <unistd.h>
+#include <time.h>
+], [
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+#if !defined(_POSIX_MONOTONIC_CLOCK) || _POSIX_MONOTONIC_CLOCK < 0
+# error _POSIX_MONOTONIC_CLOCK missing/invalid
+#endif
+], [je_cv_clock_monotonic])
+if test "x${je_cv_clock_monotonic}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_CLOCK_MONOTONIC])
fi
-CPPFLAGS="$CPPFLAGS -D_REENTRANT"
+dnl Check for mach_absolute_time().
+JE_COMPILABLE([mach_absolute_time()], [
+#include <mach/mach_time.h>
+], [
+ mach_absolute_time();
+], [je_cv_mach_absolute_time])
+if test "x${je_cv_mach_absolute_time}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_MACH_ABSOLUTE_TIME])
+fi
-dnl Check whether clock_gettime(2) is in libc or librt. This function is only
-dnl used in test code, so save the result to TESTLIBS to avoid poluting LIBS.
-SAVED_LIBS="${LIBS}"
-LIBS=
-AC_SEARCH_LIBS([clock_gettime], [rt], [TESTLIBS="${LIBS}"])
-AC_SUBST([TESTLIBS])
-LIBS="${SAVED_LIBS}"
+dnl Use syscall(2) (if available) by default.
+AC_ARG_ENABLE([syscall],
+ [AS_HELP_STRING([--disable-syscall], [Disable use of syscall(2)])],
+[if test "x$enable_syscall" = "xno" ; then
+ enable_syscall="0"
+else
+ enable_syscall="1"
+fi
+],
+[enable_syscall="1"]
+)
+if test "x$enable_syscall" = "x1" ; then
+ dnl Check if syscall(2) is usable. Treat warnings as errors, so that e.g. OS
+ dnl X 10.12's deprecation warning prevents use.
+ JE_CFLAGS_SAVE()
+ JE_CFLAGS_ADD([-Werror])
+ JE_COMPILABLE([syscall(2)], [
+#include <sys/syscall.h>
+#include <unistd.h>
+], [
+ syscall(SYS_write, 2, "hello", 5);
+],
+ [je_cv_syscall])
+ JE_CFLAGS_RESTORE()
+ if test "x$je_cv_syscall" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_USE_SYSCALL], [ ])
+ fi
+fi
dnl Check if the GNU-specific secure_getenv function exists.
AC_CHECK_FUNC([secure_getenv],
@@ -1207,6 +1631,24 @@ if test "x$have_secure_getenv" = "x1" ; then
AC_DEFINE([JEMALLOC_HAVE_SECURE_GETENV], [ ])
fi
+dnl Check if the GNU-specific sched_getcpu function exists.
+AC_CHECK_FUNC([sched_getcpu],
+ [have_sched_getcpu="1"],
+ [have_sched_getcpu="0"]
+ )
+if test "x$have_sched_getcpu" = "x1" ; then
+ AC_DEFINE([JEMALLOC_HAVE_SCHED_GETCPU], [ ])
+fi
+
+dnl Check if the GNU-specific sched_setaffinity function exists.
+AC_CHECK_FUNC([sched_setaffinity],
+ [have_sched_setaffinity="1"],
+ [have_sched_setaffinity="0"]
+ )
+if test "x$have_sched_setaffinity" = "x1" ; then
+ AC_DEFINE([JEMALLOC_HAVE_SCHED_SETAFFINITY], [ ])
+fi
+
dnl Check if the Solaris/BSD issetugid function exists.
AC_CHECK_FUNC([issetugid],
[have_issetugid="1"],
@@ -1226,6 +1668,7 @@ AC_CHECK_FUNC([_malloc_thread_cleanup],
)
if test "x$have__malloc_thread_cleanup" = "x1" ; then
AC_DEFINE([JEMALLOC_MALLOC_THREAD_CLEANUP], [ ])
+ wrap_syms="${wrap_syms} _malloc_thread_cleanup"
force_tls="1"
fi
@@ -1238,6 +1681,7 @@ AC_CHECK_FUNC([_pthread_mutex_init_calloc_cb],
)
if test "x$have__pthread_mutex_init_calloc_cb" = "x1" ; then
AC_DEFINE([JEMALLOC_MUTEX_INIT_CB])
+ wrap_syms="${wrap_syms} _malloc_prefork _malloc_postfork"
fi
dnl Disable lazy locking by default.
@@ -1252,45 +1696,35 @@ fi
],
[enable_lazy_lock=""]
)
-if test "x$enable_lazy_lock" = "x" -a "x${force_lazy_lock}" = "x1" ; then
- AC_MSG_RESULT([Forcing lazy-lock to avoid allocator/threading bootstrap issues])
- enable_lazy_lock="1"
+if test "x${enable_lazy_lock}" = "x" ; then
+ if test "x${force_lazy_lock}" = "x1" ; then
+ AC_MSG_RESULT([Forcing lazy-lock to avoid allocator/threading bootstrap issues])
+ enable_lazy_lock="1"
+ else
+ enable_lazy_lock="0"
+ fi
+fi
+if test "x${enable_lazy_lock}" = "x1" -a "x${abi}" = "xpecoff" ; then
+ AC_MSG_RESULT([Forcing no lazy-lock because thread creation monitoring is unimplemented])
+ enable_lazy_lock="0"
fi
if test "x$enable_lazy_lock" = "x1" ; then
- if test "x$abi" != "xpecoff" ; then
- AC_CHECK_HEADERS([dlfcn.h], , [AC_MSG_ERROR([dlfcn.h is missing])])
- AC_CHECK_FUNC([dlsym], [],
- [AC_CHECK_LIB([dl], [dlsym], [LIBS="$LIBS -ldl"],
- [AC_MSG_ERROR([libdl is missing])])
- ])
+ if test "x$have_dlsym" = "x1" ; then
+ AC_DEFINE([JEMALLOC_LAZY_LOCK], [ ])
+ else
+ AC_MSG_ERROR([Missing dlsym support: lazy-lock cannot be enabled.])
fi
- AC_DEFINE([JEMALLOC_LAZY_LOCK], [ ])
-else
- enable_lazy_lock="0"
fi
AC_SUBST([enable_lazy_lock])
-AC_ARG_ENABLE([tls],
- [AS_HELP_STRING([--disable-tls], [Disable thread-local storage (__thread keyword)])],
-if test "x$enable_tls" = "xno" ; then
+dnl Automatically configure TLS.
+if test "x${force_tls}" = "x1" ; then
+ enable_tls="1"
+elif test "x${force_tls}" = "x0" ; then
enable_tls="0"
else
enable_tls="1"
fi
-,
-enable_tls=""
-)
-if test "x${enable_tls}" = "x" ; then
- if test "x${force_tls}" = "x1" ; then
- AC_MSG_RESULT([Forcing TLS to avoid allocator/threading bootstrap issues])
- enable_tls="1"
- elif test "x${force_tls}" = "x0" ; then
- AC_MSG_RESULT([Forcing no TLS to avoid allocator/threading bootstrap issues])
- enable_tls="0"
- else
- enable_tls="1"
- fi
-fi
if test "x${enable_tls}" = "x1" ; then
AC_MSG_CHECKING([for TLS])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM(
@@ -1309,12 +1743,7 @@ else
fi
AC_SUBST([enable_tls])
if test "x${enable_tls}" = "x1" ; then
- if test "x${force_tls}" = "x0" ; then
- AC_MSG_WARN([TLS enabled despite being marked unusable on this platform])
- fi
AC_DEFINE_UNQUOTED([JEMALLOC_TLS], [ ])
-elif test "x${force_tls}" = "x1" ; then
- AC_MSG_WARN([TLS disabled despite being marked critical on this platform])
fi
dnl ============================================================================
@@ -1332,37 +1761,45 @@ JE_COMPILABLE([C11 atomics], [
uint64_t x = 1;
volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
uint64_t r = atomic_fetch_add(a, x) + x;
- return (r == 0);
-], [je_cv_c11atomics])
-if test "x${je_cv_c11atomics}" = "xyes" ; then
- AC_DEFINE([JEMALLOC_C11ATOMICS])
+ return r == 0;
+], [je_cv_c11_atomics])
+if test "x${je_cv_c11_atomics}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_C11_ATOMICS])
fi
dnl ============================================================================
-dnl Check for atomic(9) operations as provided on FreeBSD.
+dnl Check for GCC-style __atomic atomics.
-JE_COMPILABLE([atomic(9)], [
-#include <sys/types.h>
-#include <machine/atomic.h>
-#include <inttypes.h>
+JE_COMPILABLE([GCC __atomic atomics], [
], [
- {
- uint32_t x32 = 0;
- volatile uint32_t *x32p = &x32;
- atomic_fetchadd_32(x32p, 1);
- }
- {
- unsigned long xlong = 0;
- volatile unsigned long *xlongp = &xlong;
- atomic_fetchadd_long(xlongp, 1);
- }
-], [je_cv_atomic9])
-if test "x${je_cv_atomic9}" = "xyes" ; then
- AC_DEFINE([JEMALLOC_ATOMIC9])
+ int x = 0;
+ int val = 1;
+ int y = __atomic_fetch_add(&x, val, __ATOMIC_RELAXED);
+ int after_add = x;
+ return after_add == 1;
+], [je_cv_gcc_atomic_atomics])
+if test "x${je_cv_gcc_atomic_atomics}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_GCC_ATOMIC_ATOMICS])
+fi
+
+dnl ============================================================================
+dnl Check for GCC-style __sync atomics.
+
+JE_COMPILABLE([GCC __sync atomics], [
+], [
+ int x = 0;
+ int before_add = __sync_fetch_and_add(&x, 1);
+ int after_add = x;
+ return (before_add == 0) && (after_add == 1);
+], [je_cv_gcc_sync_atomics])
+if test "x${je_cv_gcc_sync_atomics}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_GCC_SYNC_ATOMICS])
fi
dnl ============================================================================
dnl Check for atomic(3) operations as provided on Darwin.
+dnl We need this not for the atomic operations (which are provided above), but
+dnl rather for the OSSpinLock type it exposes.
JE_COMPILABLE([Darwin OSAtomic*()], [
#include <libkern/OSAtomic.h>
@@ -1389,12 +1826,67 @@ dnl Check for madvise(2).
JE_COMPILABLE([madvise(2)], [
#include <sys/mman.h>
], [
- {
- madvise((void *)0, 0, 0);
- }
+ madvise((void *)0, 0, 0);
], [je_cv_madvise])
if test "x${je_cv_madvise}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_MADVISE], [ ])
+
+ dnl Check for madvise(..., MADV_FREE).
+ JE_COMPILABLE([madvise(..., MADV_FREE)], [
+#include <sys/mman.h>
+], [
+ madvise((void *)0, 0, MADV_FREE);
+], [je_cv_madv_free])
+ if test "x${je_cv_madv_free}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
+ elif test "x${je_cv_madvise}" = "xyes" ; then
+ case "${host_cpu}" in i686|x86_64)
+ case "${host}" in *-*-linux*)
+ AC_DEFINE([JEMALLOC_PURGE_MADVISE_FREE], [ ])
+ AC_DEFINE([JEMALLOC_DEFINE_MADVISE_FREE], [ ])
+ ;;
+ esac
+ ;;
+ esac
+ fi
+
+ dnl Check for madvise(..., MADV_DONTNEED).
+ JE_COMPILABLE([madvise(..., MADV_DONTNEED)], [
+#include <sys/mman.h>
+], [
+ madvise((void *)0, 0, MADV_DONTNEED);
+], [je_cv_madv_dontneed])
+ if test "x${je_cv_madv_dontneed}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_PURGE_MADVISE_DONTNEED], [ ])
+ fi
+
+ dnl Check for madvise(..., MADV_DO[NT]DUMP).
+ JE_COMPILABLE([madvise(..., MADV_DO[[NT]]DUMP)], [
+#include <sys/mman.h>
+], [
+ madvise((void *)0, 0, MADV_DONTDUMP);
+ madvise((void *)0, 0, MADV_DODUMP);
+], [je_cv_madv_dontdump])
+ if test "x${je_cv_madv_dontdump}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_MADVISE_DONTDUMP], [ ])
+ fi
+
+ dnl Check for madvise(..., MADV_[NO]HUGEPAGE).
+ JE_COMPILABLE([madvise(..., MADV_[[NO]]HUGEPAGE)], [
+#include <sys/mman.h>
+], [
+ madvise((void *)0, 0, MADV_HUGEPAGE);
+ madvise((void *)0, 0, MADV_NOHUGEPAGE);
+], [je_cv_thp])
+case "${host_cpu}" in
+ arm*)
+ ;;
+ *)
+ if test "x${je_cv_thp}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_HAVE_MADVISE_HUGE], [ ])
+ fi
+ ;;
+esac
fi
dnl ============================================================================
@@ -1455,6 +1947,25 @@ if test "x${je_cv_builtin_clz}" = "xyes" ; then
fi
dnl ============================================================================
+dnl Check for os_unfair_lock operations as provided on Darwin.
+
+JE_COMPILABLE([Darwin os_unfair_lock_*()], [
+#include <os/lock.h>
+#include <AvailabilityMacros.h>
+], [
+ #if MAC_OS_X_VERSION_MIN_REQUIRED < 101200
+ #error "os_unfair_lock is not supported"
+ #else
+ os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
+ os_unfair_lock_lock(&lock);
+ os_unfair_lock_unlock(&lock);
+ #endif
+], [je_cv_os_unfair_lock])
+if test "x${je_cv_os_unfair_lock}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_OS_UNFAIR_LOCK], [ ])
+fi
+
+dnl ============================================================================
dnl Check for spinlock(3) operations as provided on Darwin.
JE_COMPILABLE([Darwin OSSpin*()], [
@@ -1493,37 +2004,38 @@ if test "x${enable_zone_allocator}" = "x1" ; then
AC_MSG_ERROR([--enable-zone-allocator is only supported on Darwin])
fi
AC_DEFINE([JEMALLOC_ZONE], [ ])
+fi
- dnl The szone version jumped from 3 to 6 between the OS X 10.5.x and 10.6
- dnl releases. malloc_zone_t and malloc_introspection_t have new fields in
- dnl 10.6, which is the only source-level indication of the change.
- AC_MSG_CHECKING([malloc zone version])
- AC_DEFUN([JE_ZONE_PROGRAM],
- [AC_LANG_PROGRAM(
- [#include <malloc/malloc.h>],
- [static int foo[[sizeof($1) $2 sizeof(void *) * $3 ? 1 : -1]]]
- )])
-
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_zone_t,==,14)],[JEMALLOC_ZONE_VERSION=3],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_zone_t,==,15)],[JEMALLOC_ZONE_VERSION=5],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_zone_t,==,16)],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_introspection_t,==,9)],[JEMALLOC_ZONE_VERSION=6],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_introspection_t,==,13)],[JEMALLOC_ZONE_VERSION=7],[JEMALLOC_ZONE_VERSION=]
- )])],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_zone_t,==,17)],[JEMALLOC_ZONE_VERSION=8],[
- AC_COMPILE_IFELSE([JE_ZONE_PROGRAM(malloc_zone_t,>,17)],[JEMALLOC_ZONE_VERSION=9],[JEMALLOC_ZONE_VERSION=]
- )])])])])
- if test "x${JEMALLOC_ZONE_VERSION}" = "x"; then
- AC_MSG_RESULT([unsupported])
- AC_MSG_ERROR([Unsupported malloc zone version])
- fi
- if test "${JEMALLOC_ZONE_VERSION}" = 9; then
- JEMALLOC_ZONE_VERSION=8
- AC_MSG_RESULT([> 8])
- else
- AC_MSG_RESULT([$JEMALLOC_ZONE_VERSION])
- fi
- AC_DEFINE_UNQUOTED(JEMALLOC_ZONE_VERSION, [$JEMALLOC_ZONE_VERSION])
+dnl ============================================================================
+dnl Use initial-exec TLS by default.
+AC_ARG_ENABLE([initial-exec-tls],
+ [AS_HELP_STRING([--disable-initial-exec-tls],
+ [Disable the initial-exec tls model])],
+[if test "x$enable_initial_exec_tls" = "xno" ; then
+ enable_initial_exec_tls="0"
+else
+ enable_initial_exec_tls="1"
+fi
+],
+[enable_initial_exec_tls="1"]
+)
+AC_SUBST([enable_initial_exec_tls])
+
+if test "x${je_cv_tls_model}" = "xyes" -a \
+ "x${enable_initial_exec_tls}" = "x1" ; then
+ AC_DEFINE([JEMALLOC_TLS_MODEL],
+ [__attribute__((tls_model("initial-exec")))])
+else
+ AC_DEFINE([JEMALLOC_TLS_MODEL], [ ])
+fi
+
+dnl ============================================================================
+dnl Enable background threads if possible.
+
+if test "x${have_pthread}" = "x1" -a "x${have_dlsym}" = "x1" \
+ -a "x${je_cv_os_unfair_lock}" != "xyes" \
+ -a "x${je_cv_osspin}" != "xyes" ; then
+ AC_DEFINE([JEMALLOC_BACKGROUND_THREAD])
fi
dnl ============================================================================
@@ -1542,7 +2054,10 @@ extern void *(* __realloc_hook)(void *ptr, size_t size);
if (__free_hook && ptr) __free_hook(ptr);
], [je_cv_glibc_malloc_hook])
if test "x${je_cv_glibc_malloc_hook}" = "xyes" ; then
- AC_DEFINE([JEMALLOC_GLIBC_MALLOC_HOOK], [ ])
+ if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ AC_DEFINE([JEMALLOC_GLIBC_MALLOC_HOOK], [ ])
+ wrap_syms="${wrap_syms} __free_hook __malloc_hook __realloc_hook"
+ fi
fi
JE_COMPILABLE([glibc memalign hook], [
@@ -1554,7 +2069,10 @@ extern void *(* __memalign_hook)(size_t alignment, size_t size);
if (__memalign_hook) ptr = __memalign_hook(16, 7);
], [je_cv_glibc_memalign_hook])
if test "x${je_cv_glibc_memalign_hook}" = "xyes" ; then
- AC_DEFINE([JEMALLOC_GLIBC_MEMALIGN_HOOK], [ ])
+ if test "x${JEMALLOC_PREFIX}" = "x" ; then
+ AC_DEFINE([JEMALLOC_GLIBC_MEMALIGN_HOOK], [ ])
+ wrap_syms="${wrap_syms} __memalign_hook"
+ fi
fi
JE_COMPILABLE([pthreads adaptive mutexes], [
@@ -1569,6 +2087,25 @@ if test "x${je_cv_pthread_mutex_adaptive_np}" = "xyes" ; then
AC_DEFINE([JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP], [ ])
fi
+JE_CFLAGS_SAVE()
+JE_CFLAGS_ADD([-D_GNU_SOURCE])
+JE_CFLAGS_ADD([-Werror])
+JE_CFLAGS_ADD([-herror_on_warning])
+JE_COMPILABLE([strerror_r returns char with gnu source], [
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+], [
+ char *buffer = (char *) malloc(100);
+ char *error = strerror_r(EINVAL, buffer, 100);
+ printf("%s\n", error);
+], [je_cv_strerror_r_returns_char_with_gnu_source])
+JE_CFLAGS_RESTORE()
+if test "x${je_cv_strerror_r_returns_char_with_gnu_source}" = "xyes" ; then
+ AC_DEFINE([JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE], [ ])
+fi
+
dnl ============================================================================
dnl Check for typedefs, structures, and compiler characteristics.
AC_HEADER_STDBOOL
@@ -1576,20 +2113,6 @@ AC_HEADER_STDBOOL
dnl ============================================================================
dnl Define commands that generate output files.
-AC_CONFIG_COMMANDS([include/jemalloc/internal/private_namespace.h], [
- mkdir -p "${objroot}include/jemalloc/internal"
- "${srcdir}/include/jemalloc/internal/private_namespace.sh" "${srcdir}/include/jemalloc/internal/private_symbols.txt" > "${objroot}include/jemalloc/internal/private_namespace.h"
-], [
- srcdir="${srcdir}"
- objroot="${objroot}"
-])
-AC_CONFIG_COMMANDS([include/jemalloc/internal/private_unnamespace.h], [
- mkdir -p "${objroot}include/jemalloc/internal"
- "${srcdir}/include/jemalloc/internal/private_unnamespace.sh" "${srcdir}/include/jemalloc/internal/private_symbols.txt" > "${objroot}include/jemalloc/internal/private_unnamespace.h"
-], [
- srcdir="${srcdir}"
- objroot="${objroot}"
-])
AC_CONFIG_COMMANDS([include/jemalloc/internal/public_symbols.txt], [
f="${objroot}include/jemalloc/internal/public_symbols.txt"
mkdir -p "${objroot}include/jemalloc/internal"
@@ -1613,6 +2136,31 @@ AC_CONFIG_COMMANDS([include/jemalloc/internal/public_symbols.txt], [
public_syms="${public_syms}"
JEMALLOC_PREFIX="${JEMALLOC_PREFIX}"
])
+AC_CONFIG_COMMANDS([include/jemalloc/internal/private_symbols.awk], [
+ f="${objroot}include/jemalloc/internal/private_symbols.awk"
+ mkdir -p "${objroot}include/jemalloc/internal"
+ export_syms=`for sym in ${public_syms}; do echo "${JEMALLOC_PREFIX}${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;`
+ "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols.awk"
+], [
+ srcdir="${srcdir}"
+ objroot="${objroot}"
+ public_syms="${public_syms}"
+ wrap_syms="${wrap_syms}"
+ SYM_PREFIX="${SYM_PREFIX}"
+ JEMALLOC_PREFIX="${JEMALLOC_PREFIX}"
+])
+AC_CONFIG_COMMANDS([include/jemalloc/internal/private_symbols_jet.awk], [
+ f="${objroot}include/jemalloc/internal/private_symbols_jet.awk"
+ mkdir -p "${objroot}include/jemalloc/internal"
+ export_syms=`for sym in ${public_syms}; do echo "jet_${sym}"; done; for sym in ${wrap_syms}; do echo "${sym}"; done;`
+ "${srcdir}/include/jemalloc/internal/private_symbols.sh" "${SYM_PREFIX}" ${export_syms} > "${objroot}include/jemalloc/internal/private_symbols_jet.awk"
+], [
+ srcdir="${srcdir}"
+ objroot="${objroot}"
+ public_syms="${public_syms}"
+ wrap_syms="${wrap_syms}"
+ SYM_PREFIX="${SYM_PREFIX}"
+])
AC_CONFIG_COMMANDS([include/jemalloc/internal/public_namespace.h], [
mkdir -p "${objroot}include/jemalloc/internal"
"${srcdir}/include/jemalloc/internal/public_namespace.sh" "${objroot}include/jemalloc/internal/public_symbols.txt" > "${objroot}include/jemalloc/internal/public_namespace.h"
@@ -1629,15 +2177,13 @@ AC_CONFIG_COMMANDS([include/jemalloc/internal/public_unnamespace.h], [
])
AC_CONFIG_COMMANDS([include/jemalloc/internal/size_classes.h], [
mkdir -p "${objroot}include/jemalloc/internal"
- "${SHELL}" "${srcdir}/include/jemalloc/internal/size_classes.sh" "${LG_QUANTA}" ${LG_TINY_MIN} "${LG_PAGE_SIZES}" ${LG_SIZE_CLASS_GROUP} > "${objroot}include/jemalloc/internal/size_classes.h"
+ "${SHELL}" "${srcdir}/include/jemalloc/internal/size_classes.sh" "${LG_QUANTA}" 3 "${LG_PAGE_SIZES}" 2 > "${objroot}include/jemalloc/internal/size_classes.h"
], [
SHELL="${SHELL}"
srcdir="${srcdir}"
objroot="${objroot}"
LG_QUANTA="${LG_QUANTA}"
- LG_TINY_MIN=${LG_TINY_MIN}
LG_PAGE_SIZES="${LG_PAGE_SIZES}"
- LG_SIZE_CLASS_GROUP=${LG_SIZE_CLASS_GROUP}
])
AC_CONFIG_COMMANDS([include/jemalloc/jemalloc_protos_jet.h], [
mkdir -p "${objroot}include/jemalloc"
@@ -1697,12 +2243,18 @@ AC_MSG_RESULT([library revision : ${rev}])
AC_MSG_RESULT([])
AC_MSG_RESULT([CONFIG : ${CONFIG}])
AC_MSG_RESULT([CC : ${CC}])
-AC_MSG_RESULT([CFLAGS : ${CFLAGS}])
+AC_MSG_RESULT([CONFIGURE_CFLAGS : ${CONFIGURE_CFLAGS}])
+AC_MSG_RESULT([SPECIFIED_CFLAGS : ${SPECIFIED_CFLAGS}])
+AC_MSG_RESULT([EXTRA_CFLAGS : ${EXTRA_CFLAGS}])
AC_MSG_RESULT([CPPFLAGS : ${CPPFLAGS}])
+AC_MSG_RESULT([CXX : ${CXX}])
+AC_MSG_RESULT([CONFIGURE_CXXFLAGS : ${CONFIGURE_CXXFLAGS}])
+AC_MSG_RESULT([SPECIFIED_CXXFLAGS : ${SPECIFIED_CXXFLAGS}])
+AC_MSG_RESULT([EXTRA_CXXFLAGS : ${EXTRA_CXXFLAGS}])
AC_MSG_RESULT([LDFLAGS : ${LDFLAGS}])
AC_MSG_RESULT([EXTRA_LDFLAGS : ${EXTRA_LDFLAGS}])
+AC_MSG_RESULT([DSO_LDFLAGS : ${DSO_LDFLAGS}])
AC_MSG_RESULT([LIBS : ${LIBS}])
-AC_MSG_RESULT([TESTLIBS : ${TESTLIBS}])
AC_MSG_RESULT([RPATH_EXTRA : ${RPATH_EXTRA}])
AC_MSG_RESULT([])
AC_MSG_RESULT([XSLTPROC : ${XSLTPROC}])
@@ -1724,22 +2276,19 @@ AC_MSG_RESULT([JEMALLOC_PREFIX : ${JEMALLOC_PREFIX}])
AC_MSG_RESULT([JEMALLOC_PRIVATE_NAMESPACE])
AC_MSG_RESULT([ : ${JEMALLOC_PRIVATE_NAMESPACE}])
AC_MSG_RESULT([install_suffix : ${install_suffix}])
+AC_MSG_RESULT([malloc_conf : ${config_malloc_conf}])
AC_MSG_RESULT([autogen : ${enable_autogen}])
-AC_MSG_RESULT([cc-silence : ${enable_cc_silence}])
AC_MSG_RESULT([debug : ${enable_debug}])
-AC_MSG_RESULT([code-coverage : ${enable_code_coverage}])
AC_MSG_RESULT([stats : ${enable_stats}])
AC_MSG_RESULT([prof : ${enable_prof}])
AC_MSG_RESULT([prof-libunwind : ${enable_prof_libunwind}])
AC_MSG_RESULT([prof-libgcc : ${enable_prof_libgcc}])
AC_MSG_RESULT([prof-gcc : ${enable_prof_gcc}])
-AC_MSG_RESULT([tcache : ${enable_tcache}])
AC_MSG_RESULT([fill : ${enable_fill}])
AC_MSG_RESULT([utrace : ${enable_utrace}])
-AC_MSG_RESULT([valgrind : ${enable_valgrind}])
AC_MSG_RESULT([xmalloc : ${enable_xmalloc}])
-AC_MSG_RESULT([munmap : ${enable_munmap}])
+AC_MSG_RESULT([log : ${enable_log}])
AC_MSG_RESULT([lazy_lock : ${enable_lazy_lock}])
-AC_MSG_RESULT([tls : ${enable_tls}])
AC_MSG_RESULT([cache-oblivious : ${enable_cache_oblivious}])
+AC_MSG_RESULT([cxx : ${enable_cxx}])
AC_MSG_RESULT([===============================================================================])
diff --git a/deps/jemalloc/coverage.sh b/deps/jemalloc/coverage.sh
deleted file mode 100755
index 6d1362a8c..000000000
--- a/deps/jemalloc/coverage.sh
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/bin/sh
-
-set -e
-
-objdir=$1
-suffix=$2
-shift 2
-objs=$@
-
-gcov -b -p -f -o "${objdir}" ${objs}
-
-# Move gcov outputs so that subsequent gcov invocations won't clobber results
-# for the same sources with different compilation flags.
-for f in `find . -maxdepth 1 -type f -name '*.gcov'` ; do
- mv "${f}" "${f}.${suffix}"
-done
diff --git a/deps/jemalloc/doc/html.xsl.in b/deps/jemalloc/doc/html.xsl.in
index a91d9746f..ec4fa6552 100644
--- a/deps/jemalloc/doc/html.xsl.in
+++ b/deps/jemalloc/doc/html.xsl.in
@@ -1,4 +1,5 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:import href="@XSLROOT@/html/docbook.xsl"/>
<xsl:import href="@abs_srcroot@doc/stylesheet.xsl"/>
+ <xsl:output method="xml" encoding="utf-8"/>
</xsl:stylesheet>
diff --git a/deps/jemalloc/doc/jemalloc.3 b/deps/jemalloc/doc/jemalloc.3
deleted file mode 100644
index 2e6b2c0e8..000000000
--- a/deps/jemalloc/doc/jemalloc.3
+++ /dev/null
@@ -1,1961 +0,0 @@
-'\" t
-.\" Title: JEMALLOC
-.\" Author: Jason Evans
-.\" Generator: DocBook XSL Stylesheets v1.78.1 <http://docbook.sf.net/>
-.\" Date: 09/24/2015
-.\" Manual: User Manual
-.\" Source: jemalloc 4.0.3-0-ge9192eacf8935e29fc62fddc2701f7942b1cc02c
-.\" Language: English
-.\"
-.TH "JEMALLOC" "3" "09/24/2015" "jemalloc 4.0.3-0-ge9192eacf893" "User Manual"
-.\" -----------------------------------------------------------------
-.\" * Define some portability stuff
-.\" -----------------------------------------------------------------
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.\" http://bugs.debian.org/507673
-.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
-.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.ie \n(.g .ds Aq \(aq
-.el .ds Aq '
-.\" -----------------------------------------------------------------
-.\" * set default formatting
-.\" -----------------------------------------------------------------
-.\" disable hyphenation
-.nh
-.\" disable justification (adjust text to left margin only)
-.ad l
-.\" -----------------------------------------------------------------
-.\" * MAIN CONTENT STARTS HERE *
-.\" -----------------------------------------------------------------
-.SH "NAME"
-jemalloc \- general purpose memory allocation functions
-.SH "LIBRARY"
-.PP
-This manual describes jemalloc 4\&.0\&.3\-0\-ge9192eacf8935e29fc62fddc2701f7942b1cc02c\&. More information can be found at the
-\m[blue]\fBjemalloc website\fR\m[]\&\s-2\u[1]\d\s+2\&.
-.SH "SYNOPSIS"
-.sp
-.ft B
-.nf
-#include <jemalloc/jemalloc\&.h>
-.fi
-.ft
-.SS "Standard API"
-.HP \w'void\ *malloc('u
-.BI "void *malloc(size_t\ " "size" ");"
-.HP \w'void\ *calloc('u
-.BI "void *calloc(size_t\ " "number" ", size_t\ " "size" ");"
-.HP \w'int\ posix_memalign('u
-.BI "int posix_memalign(void\ **" "ptr" ", size_t\ " "alignment" ", size_t\ " "size" ");"
-.HP \w'void\ *aligned_alloc('u
-.BI "void *aligned_alloc(size_t\ " "alignment" ", size_t\ " "size" ");"
-.HP \w'void\ *realloc('u
-.BI "void *realloc(void\ *" "ptr" ", size_t\ " "size" ");"
-.HP \w'void\ free('u
-.BI "void free(void\ *" "ptr" ");"
-.SS "Non\-standard API"
-.HP \w'void\ *mallocx('u
-.BI "void *mallocx(size_t\ " "size" ", int\ " "flags" ");"
-.HP \w'void\ *rallocx('u
-.BI "void *rallocx(void\ *" "ptr" ", size_t\ " "size" ", int\ " "flags" ");"
-.HP \w'size_t\ xallocx('u
-.BI "size_t xallocx(void\ *" "ptr" ", size_t\ " "size" ", size_t\ " "extra" ", int\ " "flags" ");"
-.HP \w'size_t\ sallocx('u
-.BI "size_t sallocx(void\ *" "ptr" ", int\ " "flags" ");"
-.HP \w'void\ dallocx('u
-.BI "void dallocx(void\ *" "ptr" ", int\ " "flags" ");"
-.HP \w'void\ sdallocx('u
-.BI "void sdallocx(void\ *" "ptr" ", size_t\ " "size" ", int\ " "flags" ");"
-.HP \w'size_t\ nallocx('u
-.BI "size_t nallocx(size_t\ " "size" ", int\ " "flags" ");"
-.HP \w'int\ mallctl('u
-.BI "int mallctl(const\ char\ *" "name" ", void\ *" "oldp" ", size_t\ *" "oldlenp" ", void\ *" "newp" ", size_t\ " "newlen" ");"
-.HP \w'int\ mallctlnametomib('u
-.BI "int mallctlnametomib(const\ char\ *" "name" ", size_t\ *" "mibp" ", size_t\ *" "miblenp" ");"
-.HP \w'int\ mallctlbymib('u
-.BI "int mallctlbymib(const\ size_t\ *" "mib" ", size_t\ " "miblen" ", void\ *" "oldp" ", size_t\ *" "oldlenp" ", void\ *" "newp" ", size_t\ " "newlen" ");"
-.HP \w'void\ malloc_stats_print('u
-.BI "void malloc_stats_print(void\ " "(*write_cb)" "\ (void\ *,\ const\ char\ *), void\ *" "cbopaque" ", const\ char\ *" "opts" ");"
-.HP \w'size_t\ malloc_usable_size('u
-.BI "size_t malloc_usable_size(const\ void\ *" "ptr" ");"
-.HP \w'void\ (*malloc_message)('u
-.BI "void (*malloc_message)(void\ *" "cbopaque" ", const\ char\ *" "s" ");"
-.PP
-const char *\fImalloc_conf\fR;
-.SH "DESCRIPTION"
-.SS "Standard API"
-.PP
-The
-\fBmalloc\fR\fB\fR
-function allocates
-\fIsize\fR
-bytes of uninitialized memory\&. The allocated space is suitably aligned (after possible pointer coercion) for storage of any type of object\&.
-.PP
-The
-\fBcalloc\fR\fB\fR
-function allocates space for
-\fInumber\fR
-objects, each
-\fIsize\fR
-bytes in length\&. The result is identical to calling
-\fBmalloc\fR\fB\fR
-with an argument of
-\fInumber\fR
-*
-\fIsize\fR, with the exception that the allocated memory is explicitly initialized to zero bytes\&.
-.PP
-The
-\fBposix_memalign\fR\fB\fR
-function allocates
-\fIsize\fR
-bytes of memory such that the allocation\*(Aqs base address is a multiple of
-\fIalignment\fR, and returns the allocation in the value pointed to by
-\fIptr\fR\&. The requested
-\fIalignment\fR
-must be a power of 2 at least as large as
-sizeof(\fBvoid *\fR)\&.
-.PP
-The
-\fBaligned_alloc\fR\fB\fR
-function allocates
-\fIsize\fR
-bytes of memory such that the allocation\*(Aqs base address is a multiple of
-\fIalignment\fR\&. The requested
-\fIalignment\fR
-must be a power of 2\&. Behavior is undefined if
-\fIsize\fR
-is not an integral multiple of
-\fIalignment\fR\&.
-.PP
-The
-\fBrealloc\fR\fB\fR
-function changes the size of the previously allocated memory referenced by
-\fIptr\fR
-to
-\fIsize\fR
-bytes\&. The contents of the memory are unchanged up to the lesser of the new and old sizes\&. If the new size is larger, the contents of the newly allocated portion of the memory are undefined\&. Upon success, the memory referenced by
-\fIptr\fR
-is freed and a pointer to the newly allocated memory is returned\&. Note that
-\fBrealloc\fR\fB\fR
-may move the memory allocation, resulting in a different return value than
-\fIptr\fR\&. If
-\fIptr\fR
-is
-\fBNULL\fR, the
-\fBrealloc\fR\fB\fR
-function behaves identically to
-\fBmalloc\fR\fB\fR
-for the specified size\&.
-.PP
-The
-\fBfree\fR\fB\fR
-function causes the allocated memory referenced by
-\fIptr\fR
-to be made available for future allocations\&. If
-\fIptr\fR
-is
-\fBNULL\fR, no action occurs\&.
-.SS "Non\-standard API"
-.PP
-The
-\fBmallocx\fR\fB\fR,
-\fBrallocx\fR\fB\fR,
-\fBxallocx\fR\fB\fR,
-\fBsallocx\fR\fB\fR,
-\fBdallocx\fR\fB\fR,
-\fBsdallocx\fR\fB\fR, and
-\fBnallocx\fR\fB\fR
-functions all have a
-\fIflags\fR
-argument that can be used to specify options\&. The functions only check the options that are contextually relevant\&. Use bitwise or (|) operations to specify one or more of the following:
-.PP
-\fBMALLOCX_LG_ALIGN(\fR\fB\fIla\fR\fR\fB) \fR
-.RS 4
-Align the memory allocation to start at an address that is a multiple of
-(1 << \fIla\fR)\&. This macro does not validate that
-\fIla\fR
-is within the valid range\&.
-.RE
-.PP
-\fBMALLOCX_ALIGN(\fR\fB\fIa\fR\fR\fB) \fR
-.RS 4
-Align the memory allocation to start at an address that is a multiple of
-\fIa\fR, where
-\fIa\fR
-is a power of two\&. This macro does not validate that
-\fIa\fR
-is a power of 2\&.
-.RE
-.PP
-\fBMALLOCX_ZERO\fR
-.RS 4
-Initialize newly allocated memory to contain zero bytes\&. In the growing reallocation case, the real size prior to reallocation defines the boundary between untouched bytes and those that are initialized to contain zero bytes\&. If this macro is absent, newly allocated memory is uninitialized\&.
-.RE
-.PP
-\fBMALLOCX_TCACHE(\fR\fB\fItc\fR\fR\fB) \fR
-.RS 4
-Use the thread\-specific cache (tcache) specified by the identifier
-\fItc\fR, which must have been acquired via the
-"tcache\&.create"
-mallctl\&. This macro does not validate that
-\fItc\fR
-specifies a valid identifier\&.
-.RE
-.PP
-\fBMALLOCX_TCACHE_NONE\fR
-.RS 4
-Do not use a thread\-specific cache (tcache)\&. Unless
-\fBMALLOCX_TCACHE(\fR\fB\fItc\fR\fR\fB)\fR
-or
-\fBMALLOCX_TCACHE_NONE\fR
-is specified, an automatically managed tcache will be used under many circumstances\&. This macro cannot be used in the same
-\fIflags\fR
-argument as
-\fBMALLOCX_TCACHE(\fR\fB\fItc\fR\fR\fB)\fR\&.
-.RE
-.PP
-\fBMALLOCX_ARENA(\fR\fB\fIa\fR\fR\fB) \fR
-.RS 4
-Use the arena specified by the index
-\fIa\fR\&. This macro has no effect for regions that were allocated via an arena other than the one specified\&. This macro does not validate that
-\fIa\fR
-specifies an arena index in the valid range\&.
-.RE
-.PP
-The
-\fBmallocx\fR\fB\fR
-function allocates at least
-\fIsize\fR
-bytes of memory, and returns a pointer to the base address of the allocation\&. Behavior is undefined if
-\fIsize\fR
-is
-\fB0\fR, or if request size overflows due to size class and/or alignment constraints\&.
-.PP
-The
-\fBrallocx\fR\fB\fR
-function resizes the allocation at
-\fIptr\fR
-to be at least
-\fIsize\fR
-bytes, and returns a pointer to the base address of the resulting allocation, which may or may not have moved from its original location\&. Behavior is undefined if
-\fIsize\fR
-is
-\fB0\fR, or if request size overflows due to size class and/or alignment constraints\&.
-.PP
-The
-\fBxallocx\fR\fB\fR
-function resizes the allocation at
-\fIptr\fR
-in place to be at least
-\fIsize\fR
-bytes, and returns the real size of the allocation\&. If
-\fIextra\fR
-is non\-zero, an attempt is made to resize the allocation to be at least
-(\fIsize\fR + \fIextra\fR)
-bytes, though inability to allocate the extra byte(s) will not by itself result in failure to resize\&. Behavior is undefined if
-\fIsize\fR
-is
-\fB0\fR, or if
-(\fIsize\fR + \fIextra\fR > \fBSIZE_T_MAX\fR)\&.
-.PP
-The
-\fBsallocx\fR\fB\fR
-function returns the real size of the allocation at
-\fIptr\fR\&.
-.PP
-The
-\fBdallocx\fR\fB\fR
-function causes the memory referenced by
-\fIptr\fR
-to be made available for future allocations\&.
-.PP
-The
-\fBsdallocx\fR\fB\fR
-function is an extension of
-\fBdallocx\fR\fB\fR
-with a
-\fIsize\fR
-parameter to allow the caller to pass in the allocation size as an optimization\&. The minimum valid input size is the original requested size of the allocation, and the maximum valid input size is the corresponding value returned by
-\fBnallocx\fR\fB\fR
-or
-\fBsallocx\fR\fB\fR\&.
-.PP
-The
-\fBnallocx\fR\fB\fR
-function allocates no memory, but it performs the same size computation as the
-\fBmallocx\fR\fB\fR
-function, and returns the real size of the allocation that would result from the equivalent
-\fBmallocx\fR\fB\fR
-function call\&. Behavior is undefined if
-\fIsize\fR
-is
-\fB0\fR, or if request size overflows due to size class and/or alignment constraints\&.
-.PP
-The
-\fBmallctl\fR\fB\fR
-function provides a general interface for introspecting the memory allocator, as well as setting modifiable parameters and triggering actions\&. The period\-separated
-\fIname\fR
-argument specifies a location in a tree\-structured namespace; see the
-MALLCTL NAMESPACE
-section for documentation on the tree contents\&. To read a value, pass a pointer via
-\fIoldp\fR
-to adequate space to contain the value, and a pointer to its length via
-\fIoldlenp\fR; otherwise pass
-\fBNULL\fR
-and
-\fBNULL\fR\&. Similarly, to write a value, pass a pointer to the value via
-\fInewp\fR, and its length via
-\fInewlen\fR; otherwise pass
-\fBNULL\fR
-and
-\fB0\fR\&.
-.PP
-The
-\fBmallctlnametomib\fR\fB\fR
-function provides a way to avoid repeated name lookups for applications that repeatedly query the same portion of the namespace, by translating a name to a \(lqManagement Information Base\(rq (MIB) that can be passed repeatedly to
-\fBmallctlbymib\fR\fB\fR\&. Upon successful return from
-\fBmallctlnametomib\fR\fB\fR,
-\fImibp\fR
-contains an array of
-\fI*miblenp\fR
-integers, where
-\fI*miblenp\fR
-is the lesser of the number of components in
-\fIname\fR
-and the input value of
-\fI*miblenp\fR\&. Thus it is possible to pass a
-\fI*miblenp\fR
-that is smaller than the number of period\-separated name components, which results in a partial MIB that can be used as the basis for constructing a complete MIB\&. For name components that are integers (e\&.g\&. the 2 in
-"arenas\&.bin\&.2\&.size"), the corresponding MIB component will always be that integer\&. Therefore, it is legitimate to construct code like the following:
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-unsigned nbins, i;
-size_t mib[4];
-size_t len, miblen;
-
-len = sizeof(nbins);
-mallctl("arenas\&.nbins", &nbins, &len, NULL, 0);
-
-miblen = 4;
-mallctlnametomib("arenas\&.bin\&.0\&.size", mib, &miblen);
-for (i = 0; i < nbins; i++) {
- size_t bin_size;
-
- mib[2] = i;
- len = sizeof(bin_size);
- mallctlbymib(mib, miblen, &bin_size, &len, NULL, 0);
- /* Do something with bin_size\&.\&.\&. */
-}
-.fi
-.if n \{\
-.RE
-.\}
-.PP
-The
-\fBmalloc_stats_print\fR\fB\fR
-function writes human\-readable summary statistics via the
-\fIwrite_cb\fR
-callback function pointer and
-\fIcbopaque\fR
-data passed to
-\fIwrite_cb\fR, or
-\fBmalloc_message\fR\fB\fR
-if
-\fIwrite_cb\fR
-is
-\fBNULL\fR\&. This function can be called repeatedly\&. General information that never changes during execution can be omitted by specifying "g" as a character within the
-\fIopts\fR
-string\&. Note that
-\fBmalloc_message\fR\fB\fR
-uses the
-\fBmallctl*\fR\fB\fR
-functions internally, so inconsistent statistics can be reported if multiple threads use these functions simultaneously\&. If
-\fB\-\-enable\-stats\fR
-is specified during configuration, \(lqm\(rq and \(lqa\(rq can be specified to omit merged arena and per arena statistics, respectively; \(lqb\(rq, \(lql\(rq, and \(lqh\(rq can be specified to omit per size class statistics for bins, large objects, and huge objects, respectively\&. Unrecognized characters are silently ignored\&. Note that thread caching may prevent some statistics from being completely up to date, since extra locking would be required to merge counters that track thread cache operations\&.
-.PP
-The
-\fBmalloc_usable_size\fR\fB\fR
-function returns the usable size of the allocation pointed to by
-\fIptr\fR\&. The return value may be larger than the size that was requested during allocation\&. The
-\fBmalloc_usable_size\fR\fB\fR
-function is not a mechanism for in\-place
-\fBrealloc\fR\fB\fR; rather it is provided solely as a tool for introspection purposes\&. Any discrepancy between the requested allocation size and the size reported by
-\fBmalloc_usable_size\fR\fB\fR
-should not be depended on, since such behavior is entirely implementation\-dependent\&.
-.SH "TUNING"
-.PP
-Once, when the first call is made to one of the memory allocation routines, the allocator initializes its internals based in part on various options that can be specified at compile\- or run\-time\&.
-.PP
-The string pointed to by the global variable
-\fImalloc_conf\fR, the \(lqname\(rq of the file referenced by the symbolic link named
-/etc/malloc\&.conf, and the value of the environment variable
-\fBMALLOC_CONF\fR, will be interpreted, in that order, from left to right as options\&. Note that
-\fImalloc_conf\fR
-may be read before
-\fBmain\fR\fB\fR
-is entered, so the declaration of
-\fImalloc_conf\fR
-should specify an initializer that contains the final value to be read by jemalloc\&.
-\fImalloc_conf\fR
-is a compile\-time setting, whereas
-/etc/malloc\&.conf
-and
-\fBMALLOC_CONF\fR
-can be safely set any time prior to program invocation\&.
-.PP
-An options string is a comma\-separated list of option:value pairs\&. There is one key corresponding to each
-"opt\&.*"
-mallctl (see the
-MALLCTL NAMESPACE
-section for options documentation)\&. For example,
-abort:true,narenas:1
-sets the
-"opt\&.abort"
-and
-"opt\&.narenas"
-options\&. Some options have boolean values (true/false), others have integer values (base 8, 10, or 16, depending on prefix), and yet others have raw string values\&.
-.SH "IMPLEMENTATION NOTES"
-.PP
-Traditionally, allocators have used
-\fBsbrk\fR(2)
-to obtain memory, which is suboptimal for several reasons, including race conditions, increased fragmentation, and artificial limitations on maximum usable memory\&. If
-\fBsbrk\fR(2)
-is supported by the operating system, this allocator uses both
-\fBmmap\fR(2)
-and
-\fBsbrk\fR(2), in that order of preference; otherwise only
-\fBmmap\fR(2)
-is used\&.
-.PP
-This allocator uses multiple arenas in order to reduce lock contention for threaded programs on multi\-processor systems\&. This works well with regard to threading scalability, but incurs some costs\&. There is a small fixed per\-arena overhead, and additionally, arenas manage memory completely independently of each other, which means a small fixed increase in overall memory fragmentation\&. These overheads are not generally an issue, given the number of arenas normally used\&. Note that using substantially more arenas than the default is not likely to improve performance, mainly due to reduced cache performance\&. However, it may make sense to reduce the number of arenas if an application does not make much use of the allocation functions\&.
-.PP
-In addition to multiple arenas, unless
-\fB\-\-disable\-tcache\fR
-is specified during configuration, this allocator supports thread\-specific caching for small and large objects, in order to make it possible to completely avoid synchronization for most allocation requests\&. Such caching allows very fast allocation in the common case, but it increases memory usage and fragmentation, since a bounded number of objects can remain allocated in each thread cache\&.
-.PP
-Memory is conceptually broken into equal\-sized chunks, where the chunk size is a power of two that is greater than the page size\&. Chunks are always aligned to multiples of the chunk size\&. This alignment makes it possible to find metadata for user objects very quickly\&.
-.PP
-User objects are broken into three categories according to size: small, large, and huge\&. Small and large objects are managed entirely by arenas; huge objects are additionally aggregated in a single data structure that is shared by all threads\&. Huge objects are typically used by applications infrequently enough that this single data structure is not a scalability issue\&.
-.PP
-Each chunk that is managed by an arena tracks its contents as runs of contiguous pages (unused, backing a set of small objects, or backing one large object)\&. The combination of chunk alignment and chunk page maps makes it possible to determine all metadata regarding small and large allocations in constant time\&.
-.PP
-Small objects are managed in groups by page runs\&. Each run maintains a bitmap to track which regions are in use\&. Allocation requests that are no more than half the quantum (8 or 16, depending on architecture) are rounded up to the nearest power of two that is at least
-sizeof(\fBdouble\fR)\&. All other object size classes are multiples of the quantum, spaced such that there are four size classes for each doubling in size, which limits internal fragmentation to approximately 20% for all but the smallest size classes\&. Small size classes are smaller than four times the page size, large size classes are smaller than the chunk size (see the
-"opt\&.lg_chunk"
-option), and huge size classes extend from the chunk size up to one size class less than the full address space size\&.
-.PP
-Allocations are packed tightly together, which can be an issue for multi\-threaded applications\&. If you need to assure that allocations do not suffer from cacheline sharing, round your allocation requests up to the nearest multiple of the cacheline size, or specify cacheline alignment when allocating\&.
-.PP
-The
-\fBrealloc\fR\fB\fR,
-\fBrallocx\fR\fB\fR, and
-\fBxallocx\fR\fB\fR
-functions may resize allocations without moving them under limited circumstances\&. Unlike the
-\fB*allocx\fR\fB\fR
-API, the standard API does not officially round up the usable size of an allocation to the nearest size class, so technically it is necessary to call
-\fBrealloc\fR\fB\fR
-to grow e\&.g\&. a 9\-byte allocation to 16 bytes, or shrink a 16\-byte allocation to 9 bytes\&. Growth and shrinkage trivially succeeds in place as long as the pre\-size and post\-size both round up to the same size class\&. No other API guarantees are made regarding in\-place resizing, but the current implementation also tries to resize large and huge allocations in place, as long as the pre\-size and post\-size are both large or both huge\&. In such cases shrinkage always succeeds for large size classes, but for huge size classes the chunk allocator must support splitting (see
-"arena\&.<i>\&.chunk_hooks")\&. Growth only succeeds if the trailing memory is currently available, and additionally for huge size classes the chunk allocator must support merging\&.
-.PP
-Assuming 2 MiB chunks, 4 KiB pages, and a 16\-byte quantum on a 64\-bit system, the size classes in each category are as shown in
-Table 1\&.
-.sp
-.it 1 an-trap
-.nr an-no-space-flag 1
-.nr an-break-flag 1
-.br
-.B Table\ \&1.\ \&Size classes
-.TS
-allbox tab(:);
-lB rB lB.
-T{
-Category
-T}:T{
-Spacing
-T}:T{
-Size
-T}
-.T&
-l r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-l r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-l r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l
-^ r l.
-T{
-Small
-T}:T{
-lg
-T}:T{
-[8]
-T}
-:T{
-16
-T}:T{
-[16, 32, 48, 64, 80, 96, 112, 128]
-T}
-:T{
-32
-T}:T{
-[160, 192, 224, 256]
-T}
-:T{
-64
-T}:T{
-[320, 384, 448, 512]
-T}
-:T{
-128
-T}:T{
-[640, 768, 896, 1024]
-T}
-:T{
-256
-T}:T{
-[1280, 1536, 1792, 2048]
-T}
-:T{
-512
-T}:T{
-[2560, 3072, 3584, 4096]
-T}
-:T{
-1 KiB
-T}:T{
-[5 KiB, 6 KiB, 7 KiB, 8 KiB]
-T}
-:T{
-2 KiB
-T}:T{
-[10 KiB, 12 KiB, 14 KiB]
-T}
-T{
-Large
-T}:T{
-2 KiB
-T}:T{
-[16 KiB]
-T}
-:T{
-4 KiB
-T}:T{
-[20 KiB, 24 KiB, 28 KiB, 32 KiB]
-T}
-:T{
-8 KiB
-T}:T{
-[40 KiB, 48 KiB, 54 KiB, 64 KiB]
-T}
-:T{
-16 KiB
-T}:T{
-[80 KiB, 96 KiB, 112 KiB, 128 KiB]
-T}
-:T{
-32 KiB
-T}:T{
-[160 KiB, 192 KiB, 224 KiB, 256 KiB]
-T}
-:T{
-64 KiB
-T}:T{
-[320 KiB, 384 KiB, 448 KiB, 512 KiB]
-T}
-:T{
-128 KiB
-T}:T{
-[640 KiB, 768 KiB, 896 KiB, 1 MiB]
-T}
-:T{
-256 KiB
-T}:T{
-[1280 KiB, 1536 KiB, 1792 KiB]
-T}
-T{
-Huge
-T}:T{
-256 KiB
-T}:T{
-[2 MiB]
-T}
-:T{
-512 KiB
-T}:T{
-[2560 KiB, 3 MiB, 3584 KiB, 4 MiB]
-T}
-:T{
-1 MiB
-T}:T{
-[5 MiB, 6 MiB, 7 MiB, 8 MiB]
-T}
-:T{
-2 MiB
-T}:T{
-[10 MiB, 12 MiB, 14 MiB, 16 MiB]
-T}
-:T{
-4 MiB
-T}:T{
-[20 MiB, 24 MiB, 28 MiB, 32 MiB]
-T}
-:T{
-8 MiB
-T}:T{
-[40 MiB, 48 MiB, 56 MiB, 64 MiB]
-T}
-:T{
-\&.\&.\&.
-T}:T{
-\&.\&.\&.
-T}
-.TE
-.sp 1
-.SH "MALLCTL NAMESPACE"
-.PP
-The following names are defined in the namespace accessible via the
-\fBmallctl*\fR\fB\fR
-functions\&. Value types are specified in parentheses, their readable/writable statuses are encoded as
-rw,
-r\-,
-\-w, or
-\-\-, and required build configuration flags follow, if any\&. A name element encoded as
-<i>
-or
-<j>
-indicates an integer component, where the integer varies from 0 to some upper value that must be determined via introspection\&. In the case of
-"stats\&.arenas\&.<i>\&.*",
-<i>
-equal to
-"arenas\&.narenas"
-can be used to access the summation of statistics from all arenas\&. Take special note of the
-"epoch"
-mallctl, which controls refreshing of cached dynamic statistics\&.
-.PP
-"version" (\fBconst char *\fR) r\-
-.RS 4
-Return the jemalloc version string\&.
-.RE
-.PP
-"epoch" (\fBuint64_t\fR) rw
-.RS 4
-If a value is passed in, refresh the data from which the
-\fBmallctl*\fR\fB\fR
-functions report values, and increment the epoch\&. Return the current epoch\&. This is useful for detecting whether another thread caused a refresh\&.
-.RE
-.PP
-"config\&.cache_oblivious" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-cache\-oblivious\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.debug" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-debug\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.fill" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-fill\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.lazy_lock" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-lazy\-lock\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.munmap" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-munmap\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.prof" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-prof\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.prof_libgcc" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-disable\-prof\-libgcc\fR
-was not specified during build configuration\&.
-.RE
-.PP
-"config\&.prof_libunwind" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-prof\-libunwind\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.stats" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-stats\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.tcache" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-disable\-tcache\fR
-was not specified during build configuration\&.
-.RE
-.PP
-"config\&.tls" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-disable\-tls\fR
-was not specified during build configuration\&.
-.RE
-.PP
-"config\&.utrace" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-utrace\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.valgrind" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-valgrind\fR
-was specified during build configuration\&.
-.RE
-.PP
-"config\&.xmalloc" (\fBbool\fR) r\-
-.RS 4
-\fB\-\-enable\-xmalloc\fR
-was specified during build configuration\&.
-.RE
-.PP
-"opt\&.abort" (\fBbool\fR) r\-
-.RS 4
-Abort\-on\-warning enabled/disabled\&. If true, most warnings are fatal\&. The process will call
-\fBabort\fR(3)
-in these cases\&. This option is disabled by default unless
-\fB\-\-enable\-debug\fR
-is specified during configuration, in which case it is enabled by default\&.
-.RE
-.PP
-"opt\&.dss" (\fBconst char *\fR) r\-
-.RS 4
-dss (\fBsbrk\fR(2)) allocation precedence as related to
-\fBmmap\fR(2)
-allocation\&. The following settings are supported if
-\fBsbrk\fR(2)
-is supported by the operating system: \(lqdisabled\(rq, \(lqprimary\(rq, and \(lqsecondary\(rq; otherwise only \(lqdisabled\(rq is supported\&. The default is \(lqsecondary\(rq if
-\fBsbrk\fR(2)
-is supported by the operating system; \(lqdisabled\(rq otherwise\&.
-.RE
-.PP
-"opt\&.lg_chunk" (\fBsize_t\fR) r\-
-.RS 4
-Virtual memory chunk size (log base 2)\&. If a chunk size outside the supported size range is specified, the size is silently clipped to the minimum/maximum supported size\&. The default chunk size is 2 MiB (2^21)\&.
-.RE
-.PP
-"opt\&.narenas" (\fBsize_t\fR) r\-
-.RS 4
-Maximum number of arenas to use for automatic multiplexing of threads and arenas\&. The default is four times the number of CPUs, or one if there is a single CPU\&.
-.RE
-.PP
-"opt\&.lg_dirty_mult" (\fBssize_t\fR) r\-
-.RS 4
-Per\-arena minimum ratio (log base 2) of active to dirty pages\&. Some dirty unused pages may be allowed to accumulate, within the limit set by the ratio (or one chunk worth of dirty pages, whichever is greater), before informing the kernel about some of those pages via
-\fBmadvise\fR(2)
-or a similar system call\&. This provides the kernel with sufficient information to recycle dirty pages if physical memory becomes scarce and the pages remain unused\&. The default minimum ratio is 8:1 (2^3:1); an option value of \-1 will disable dirty page purging\&. See
-"arenas\&.lg_dirty_mult"
-and
-"arena\&.<i>\&.lg_dirty_mult"
-for related dynamic control options\&.
-.RE
-.PP
-"opt\&.stats_print" (\fBbool\fR) r\-
-.RS 4
-Enable/disable statistics printing at exit\&. If enabled, the
-\fBmalloc_stats_print\fR\fB\fR
-function is called at program exit via an
-\fBatexit\fR(3)
-function\&. If
-\fB\-\-enable\-stats\fR
-is specified during configuration, this has the potential to cause deadlock for a multi\-threaded process that exits while one or more threads are executing in the memory allocation functions\&. Furthermore,
-\fBatexit\fR\fB\fR
-may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls
-\fBatexit\fR\fB\fR, so this option is not univerally usable (though the application can register its own
-\fBatexit\fR\fB\fR
-function with equivalent functionality)\&. Therefore, this option should only be used with care; it is primarily intended as a performance tuning aid during application development\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.junk" (\fBconst char *\fR) r\- [\fB\-\-enable\-fill\fR]
-.RS 4
-Junk filling\&. If set to "alloc", each byte of uninitialized allocated memory will be initialized to
-0xa5\&. If set to "free", all deallocated memory will be initialized to
-0x5a\&. If set to "true", both allocated and deallocated memory will be initialized, and if set to "false", junk filling be disabled entirely\&. This is intended for debugging and will impact performance negatively\&. This option is "false" by default unless
-\fB\-\-enable\-debug\fR
-is specified during configuration, in which case it is "true" by default unless running inside
-\m[blue]\fBValgrind\fR\m[]\&\s-2\u[2]\d\s+2\&.
-.RE
-.PP
-"opt\&.quarantine" (\fBsize_t\fR) r\- [\fB\-\-enable\-fill\fR]
-.RS 4
-Per thread quarantine size in bytes\&. If non\-zero, each thread maintains a FIFO object quarantine that stores up to the specified number of bytes of memory\&. The quarantined memory is not freed until it is released from quarantine, though it is immediately junk\-filled if the
-"opt\&.junk"
-option is enabled\&. This feature is of particular use in combination with
-\m[blue]\fBValgrind\fR\m[]\&\s-2\u[2]\d\s+2, which can detect attempts to access quarantined objects\&. This is intended for debugging and will impact performance negatively\&. The default quarantine size is 0 unless running inside Valgrind, in which case the default is 16 MiB\&.
-.RE
-.PP
-"opt\&.redzone" (\fBbool\fR) r\- [\fB\-\-enable\-fill\fR]
-.RS 4
-Redzones enabled/disabled\&. If enabled, small allocations have redzones before and after them\&. Furthermore, if the
-"opt\&.junk"
-option is enabled, the redzones are checked for corruption during deallocation\&. However, the primary intended purpose of this feature is to be used in combination with
-\m[blue]\fBValgrind\fR\m[]\&\s-2\u[2]\d\s+2, which needs redzones in order to do effective buffer overflow/underflow detection\&. This option is intended for debugging and will impact performance negatively\&. This option is disabled by default unless running inside Valgrind\&.
-.RE
-.PP
-"opt\&.zero" (\fBbool\fR) r\- [\fB\-\-enable\-fill\fR]
-.RS 4
-Zero filling enabled/disabled\&. If enabled, each byte of uninitialized allocated memory will be initialized to 0\&. Note that this initialization only happens once for each byte, so
-\fBrealloc\fR\fB\fR
-and
-\fBrallocx\fR\fB\fR
-calls do not zero memory that was previously allocated\&. This is intended for debugging and will impact performance negatively\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.utrace" (\fBbool\fR) r\- [\fB\-\-enable\-utrace\fR]
-.RS 4
-Allocation tracing based on
-\fButrace\fR(2)
-enabled/disabled\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.xmalloc" (\fBbool\fR) r\- [\fB\-\-enable\-xmalloc\fR]
-.RS 4
-Abort\-on\-out\-of\-memory enabled/disabled\&. If enabled, rather than returning failure for any allocation function, display a diagnostic message on
-\fBSTDERR_FILENO\fR
-and cause the program to drop core (using
-\fBabort\fR(3))\&. If an application is designed to depend on this behavior, set the option at compile time by including the following in the source code:
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-malloc_conf = "xmalloc:true";
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-This option is disabled by default\&.
-.RE
-.PP
-"opt\&.tcache" (\fBbool\fR) r\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Thread\-specific caching (tcache) enabled/disabled\&. When there are multiple threads, each thread uses a tcache for objects up to a certain size\&. Thread\-specific caching allows many allocations to be satisfied without performing any thread synchronization, at the cost of increased memory use\&. See the
-"opt\&.lg_tcache_max"
-option for related tuning information\&. This option is enabled by default unless running inside
-\m[blue]\fBValgrind\fR\m[]\&\s-2\u[2]\d\s+2, in which case it is forcefully disabled\&.
-.RE
-.PP
-"opt\&.lg_tcache_max" (\fBsize_t\fR) r\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Maximum size class (log base 2) to cache in the thread\-specific cache (tcache)\&. At a minimum, all small size classes are cached, and at a maximum all large size classes are cached\&. The default maximum is 32 KiB (2^15)\&.
-.RE
-.PP
-"opt\&.prof" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Memory profiling enabled/disabled\&. If enabled, profile memory allocation activity\&. See the
-"opt\&.prof_active"
-option for on\-the\-fly activation/deactivation\&. See the
-"opt\&.lg_prof_sample"
-option for probabilistic sampling control\&. See the
-"opt\&.prof_accum"
-option for control of cumulative sample reporting\&. See the
-"opt\&.lg_prof_interval"
-option for information on interval\-triggered profile dumping, the
-"opt\&.prof_gdump"
-option for information on high\-water\-triggered profile dumping, and the
-"opt\&.prof_final"
-option for final profile dumping\&. Profile output is compatible with the
-\fBjeprof\fR
-command, which is based on the
-\fBpprof\fR
-that is developed as part of the
-\m[blue]\fBgperftools package\fR\m[]\&\s-2\u[3]\d\s+2\&.
-.RE
-.PP
-"opt\&.prof_prefix" (\fBconst char *\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Filename prefix for profile dumps\&. If the prefix is set to the empty string, no automatic dumps will occur; this is primarily useful for disabling the automatic final heap dump (which also disables leak reporting, if enabled)\&. The default prefix is
-jeprof\&.
-.RE
-.PP
-"opt\&.prof_active" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Profiling activated/deactivated\&. This is a secondary control mechanism that makes it possible to start the application with profiling enabled (see the
-"opt\&.prof"
-option) but inactive, then toggle profiling at any time during program execution with the
-"prof\&.active"
-mallctl\&. This option is enabled by default\&.
-.RE
-.PP
-"opt\&.prof_thread_active_init" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Initial setting for
-"thread\&.prof\&.active"
-in newly created threads\&. The initial setting for newly created threads can also be changed during execution via the
-"prof\&.thread_active_init"
-mallctl\&. This option is enabled by default\&.
-.RE
-.PP
-"opt\&.lg_prof_sample" (\fBsize_t\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Average interval (log base 2) between allocation samples, as measured in bytes of allocation activity\&. Increasing the sampling interval decreases profile fidelity, but also decreases the computational overhead\&. The default sample interval is 512 KiB (2^19 B)\&.
-.RE
-.PP
-"opt\&.prof_accum" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Reporting of cumulative object/byte counts in profile dumps enabled/disabled\&. If this option is enabled, every unique backtrace must be stored for the duration of execution\&. Depending on the application, this can impose a large memory overhead, and the cumulative counts are not always of interest\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.lg_prof_interval" (\fBssize_t\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Average interval (log base 2) between memory profile dumps, as measured in bytes of allocation activity\&. The actual interval between dumps may be sporadic because decentralized allocation counters are used to avoid synchronization bottlenecks\&. Profiles are dumped to files named according to the pattern
-<prefix>\&.<pid>\&.<seq>\&.i<iseq>\&.heap, where
-<prefix>
-is controlled by the
-"opt\&.prof_prefix"
-option\&. By default, interval\-triggered profile dumping is disabled (encoded as \-1)\&.
-.RE
-.PP
-"opt\&.prof_gdump" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Set the initial state of
-"prof\&.gdump", which when enabled triggers a memory profile dump every time the total virtual memory exceeds the previous maximum\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.prof_final" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Use an
-\fBatexit\fR(3)
-function to dump final memory usage to a file named according to the pattern
-<prefix>\&.<pid>\&.<seq>\&.f\&.heap, where
-<prefix>
-is controlled by the
-"opt\&.prof_prefix"
-option\&. Note that
-\fBatexit\fR\fB\fR
-may allocate memory during application initialization and then deadlock internally when jemalloc in turn calls
-\fBatexit\fR\fB\fR, so this option is not univerally usable (though the application can register its own
-\fBatexit\fR\fB\fR
-function with equivalent functionality)\&. This option is disabled by default\&.
-.RE
-.PP
-"opt\&.prof_leak" (\fBbool\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Leak reporting enabled/disabled\&. If enabled, use an
-\fBatexit\fR(3)
-function to report memory leaks detected by allocation sampling\&. See the
-"opt\&.prof"
-option for information on analyzing heap profile output\&. This option is disabled by default\&.
-.RE
-.PP
-"thread\&.arena" (\fBunsigned\fR) rw
-.RS 4
-Get or set the arena associated with the calling thread\&. If the specified arena was not initialized beforehand (see the
-"arenas\&.initialized"
-mallctl), it will be automatically initialized as a side effect of calling this interface\&.
-.RE
-.PP
-"thread\&.allocated" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Get the total number of bytes ever allocated by the calling thread\&. This counter has the potential to wrap around; it is up to the application to appropriately interpret the counter in such cases\&.
-.RE
-.PP
-"thread\&.allocatedp" (\fBuint64_t *\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Get a pointer to the the value that is returned by the
-"thread\&.allocated"
-mallctl\&. This is useful for avoiding the overhead of repeated
-\fBmallctl*\fR\fB\fR
-calls\&.
-.RE
-.PP
-"thread\&.deallocated" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Get the total number of bytes ever deallocated by the calling thread\&. This counter has the potential to wrap around; it is up to the application to appropriately interpret the counter in such cases\&.
-.RE
-.PP
-"thread\&.deallocatedp" (\fBuint64_t *\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Get a pointer to the the value that is returned by the
-"thread\&.deallocated"
-mallctl\&. This is useful for avoiding the overhead of repeated
-\fBmallctl*\fR\fB\fR
-calls\&.
-.RE
-.PP
-"thread\&.tcache\&.enabled" (\fBbool\fR) rw [\fB\-\-enable\-tcache\fR]
-.RS 4
-Enable/disable calling thread\*(Aqs tcache\&. The tcache is implicitly flushed as a side effect of becoming disabled (see
-"thread\&.tcache\&.flush")\&.
-.RE
-.PP
-"thread\&.tcache\&.flush" (\fBvoid\fR) \-\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Flush calling thread\*(Aqs thread\-specific cache (tcache)\&. This interface releases all cached objects and internal data structures associated with the calling thread\*(Aqs tcache\&. Ordinarily, this interface need not be called, since automatic periodic incremental garbage collection occurs, and the thread cache is automatically discarded when a thread exits\&. However, garbage collection is triggered by allocation activity, so it is possible for a thread that stops allocating/deallocating to retain its cache indefinitely, in which case the developer may find manual flushing useful\&.
-.RE
-.PP
-"thread\&.prof\&.name" (\fBconst char *\fR) r\- or \-w [\fB\-\-enable\-prof\fR]
-.RS 4
-Get/set the descriptive name associated with the calling thread in memory profile dumps\&. An internal copy of the name string is created, so the input string need not be maintained after this interface completes execution\&. The output string of this interface should be copied for non\-ephemeral uses, because multiple implementation details can cause asynchronous string deallocation\&. Furthermore, each invocation of this interface can only read or write; simultaneous read/write is not supported due to string lifetime limitations\&. The name string must nil\-terminated and comprised only of characters in the sets recognized by
-\fBisgraph\fR(3)
-and
-\fBisblank\fR(3)\&.
-.RE
-.PP
-"thread\&.prof\&.active" (\fBbool\fR) rw [\fB\-\-enable\-prof\fR]
-.RS 4
-Control whether sampling is currently active for the calling thread\&. This is an activation mechanism in addition to
-"prof\&.active"; both must be active for the calling thread to sample\&. This flag is enabled by default\&.
-.RE
-.PP
-"tcache\&.create" (\fBunsigned\fR) r\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Create an explicit thread\-specific cache (tcache) and return an identifier that can be passed to the
-\fBMALLOCX_TCACHE(\fR\fB\fItc\fR\fR\fB)\fR
-macro to explicitly use the specified cache rather than the automatically managed one that is used by default\&. Each explicit cache can be used by only one thread at a time; the application must assure that this constraint holds\&.
-.RE
-.PP
-"tcache\&.flush" (\fBunsigned\fR) \-w [\fB\-\-enable\-tcache\fR]
-.RS 4
-Flush the specified thread\-specific cache (tcache)\&. The same considerations apply to this interface as to
-"thread\&.tcache\&.flush", except that the tcache will never be automatically be discarded\&.
-.RE
-.PP
-"tcache\&.destroy" (\fBunsigned\fR) \-w [\fB\-\-enable\-tcache\fR]
-.RS 4
-Flush the specified thread\-specific cache (tcache) and make the identifier available for use during a future tcache creation\&.
-.RE
-.PP
-"arena\&.<i>\&.purge" (\fBvoid\fR) \-\-
-.RS 4
-Purge unused dirty pages for arena <i>, or for all arenas if <i> equals
-"arenas\&.narenas"\&.
-.RE
-.PP
-"arena\&.<i>\&.dss" (\fBconst char *\fR) rw
-.RS 4
-Set the precedence of dss allocation as related to mmap allocation for arena <i>, or for all arenas if <i> equals
-"arenas\&.narenas"\&. See
-"opt\&.dss"
-for supported settings\&.
-.RE
-.PP
-"arena\&.<i>\&.lg_dirty_mult" (\fBssize_t\fR) rw
-.RS 4
-Current per\-arena minimum ratio (log base 2) of active to dirty pages for arena <i>\&. Each time this interface is set and the ratio is increased, pages are synchronously purged as necessary to impose the new ratio\&. See
-"opt\&.lg_dirty_mult"
-for additional information\&.
-.RE
-.PP
-"arena\&.<i>\&.chunk_hooks" (\fBchunk_hooks_t\fR) rw
-.RS 4
-Get or set the chunk management hook functions for arena <i>\&. The functions must be capable of operating on all extant chunks associated with arena <i>, usually by passing unknown chunks to the replaced functions\&. In practice, it is feasible to control allocation for arenas created via
-"arenas\&.extend"
-such that all chunks originate from an application\-supplied chunk allocator (by setting custom chunk hook functions just after arena creation), but the automatically created arenas may have already created chunks prior to the application having an opportunity to take over chunk allocation\&.
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-typedef struct {
- chunk_alloc_t *alloc;
- chunk_dalloc_t *dalloc;
- chunk_commit_t *commit;
- chunk_decommit_t *decommit;
- chunk_purge_t *purge;
- chunk_split_t *split;
- chunk_merge_t *merge;
-} chunk_hooks_t;
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-The
-\fBchunk_hooks_t\fR
-structure comprises function pointers which are described individually below\&. jemalloc uses these functions to manage chunk lifetime, which starts off with allocation of mapped committed memory, in the simplest case followed by deallocation\&. However, there are performance and platform reasons to retain chunks for later reuse\&. Cleanup attempts cascade from deallocation to decommit to purging, which gives the chunk management functions opportunities to reject the most permanent cleanup operations in favor of less permanent (and often less costly) operations\&. The chunk splitting and merging operations can also be opted out of, but this is mainly intended to support platforms on which virtual memory mappings provided by the operating system kernel do not automatically coalesce and split, e\&.g\&. Windows\&.
-.HP \w'typedef\ void\ *(chunk_alloc_t)('u
-.BI "typedef void *(chunk_alloc_t)(void\ *" "chunk" ", size_t\ " "size" ", size_t\ " "alignment" ", bool\ *" "zero" ", bool\ *" "commit" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk allocation function conforms to the
-\fBchunk_alloc_t\fR
-type and upon success returns a pointer to
-\fIsize\fR
-bytes of mapped memory on behalf of arena
-\fIarena_ind\fR
-such that the chunk\*(Aqs base address is a multiple of
-\fIalignment\fR, as well as setting
-\fI*zero\fR
-to indicate whether the chunk is zeroed and
-\fI*commit\fR
-to indicate whether the chunk is committed\&. Upon error the function returns
-\fBNULL\fR
-and leaves
-\fI*zero\fR
-and
-\fI*commit\fR
-unmodified\&. The
-\fIsize\fR
-parameter is always a multiple of the chunk size\&. The
-\fIalignment\fR
-parameter is always a power of two at least as large as the chunk size\&. Zeroing is mandatory if
-\fI*zero\fR
-is true upon function entry\&. Committing is mandatory if
-\fI*commit\fR
-is true upon function entry\&. If
-\fIchunk\fR
-is not
-\fBNULL\fR, the returned pointer must be
-\fIchunk\fR
-on success or
-\fBNULL\fR
-on error\&. Committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults\&. Note that replacing the default chunk allocation function makes the arena\*(Aqs
-"arena\&.<i>\&.dss"
-setting irrelevant\&.
-.HP \w'typedef\ bool\ (chunk_dalloc_t)('u
-.BI "typedef bool (chunk_dalloc_t)(void\ *" "chunk" ", size_t\ " "size" ", bool\ " "committed" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk deallocation function conforms to the
-\fBchunk_dalloc_t\fR
-type and deallocates a
-\fIchunk\fR
-of given
-\fIsize\fR
-with
-\fIcommitted\fR/decommited memory as indicated, on behalf of arena
-\fIarena_ind\fR, returning false upon success\&. If the function returns true, this indicates opt\-out from deallocation; the virtual memory mapping associated with the chunk remains mapped, in the same commit state, and available for future use, in which case it will be automatically retained for later reuse\&.
-.HP \w'typedef\ bool\ (chunk_commit_t)('u
-.BI "typedef bool (chunk_commit_t)(void\ *" "chunk" ", size_t\ " "size" ", size_t\ " "offset" ", size_t\ " "length" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk commit function conforms to the
-\fBchunk_commit_t\fR
-type and commits zeroed physical memory to back pages within a
-\fIchunk\fR
-of given
-\fIsize\fR
-at
-\fIoffset\fR
-bytes, extending for
-\fIlength\fR
-on behalf of arena
-\fIarena_ind\fR, returning false upon success\&. Committed memory may be committed in absolute terms as on a system that does not overcommit, or in implicit terms as on a system that overcommits and satisfies physical memory needs on demand via soft page faults\&. If the function returns true, this indicates insufficient physical memory to satisfy the request\&.
-.HP \w'typedef\ bool\ (chunk_decommit_t)('u
-.BI "typedef bool (chunk_decommit_t)(void\ *" "chunk" ", size_t\ " "size" ", size_t\ " "offset" ", size_t\ " "length" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk decommit function conforms to the
-\fBchunk_decommit_t\fR
-type and decommits any physical memory that is backing pages within a
-\fIchunk\fR
-of given
-\fIsize\fR
-at
-\fIoffset\fR
-bytes, extending for
-\fIlength\fR
-on behalf of arena
-\fIarena_ind\fR, returning false upon success, in which case the pages will be committed via the chunk commit function before being reused\&. If the function returns true, this indicates opt\-out from decommit; the memory remains committed and available for future use, in which case it will be automatically retained for later reuse\&.
-.HP \w'typedef\ bool\ (chunk_purge_t)('u
-.BI "typedef bool (chunk_purge_t)(void\ *" "chunk" ", size_t" "size" ", size_t\ " "offset" ", size_t\ " "length" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk purge function conforms to the
-\fBchunk_purge_t\fR
-type and optionally discards physical pages within the virtual memory mapping associated with
-\fIchunk\fR
-of given
-\fIsize\fR
-at
-\fIoffset\fR
-bytes, extending for
-\fIlength\fR
-on behalf of arena
-\fIarena_ind\fR, returning false if pages within the purged virtual memory range will be zero\-filled the next time they are accessed\&.
-.HP \w'typedef\ bool\ (chunk_split_t)('u
-.BI "typedef bool (chunk_split_t)(void\ *" "chunk" ", size_t\ " "size" ", size_t\ " "size_a" ", size_t\ " "size_b" ", bool\ " "committed" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk split function conforms to the
-\fBchunk_split_t\fR
-type and optionally splits
-\fIchunk\fR
-of given
-\fIsize\fR
-into two adjacent chunks, the first of
-\fIsize_a\fR
-bytes, and the second of
-\fIsize_b\fR
-bytes, operating on
-\fIcommitted\fR/decommitted memory as indicated, on behalf of arena
-\fIarena_ind\fR, returning false upon success\&. If the function returns true, this indicates that the chunk remains unsplit and therefore should continue to be operated on as a whole\&.
-.HP \w'typedef\ bool\ (chunk_merge_t)('u
-.BI "typedef bool (chunk_merge_t)(void\ *" "chunk_a" ", size_t\ " "size_a" ", void\ *" "chunk_b" ", size_t\ " "size_b" ", bool\ " "committed" ", unsigned\ " "arena_ind" ");"
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-.fi
-.if n \{\
-.RE
-.\}
-.sp
-A chunk merge function conforms to the
-\fBchunk_merge_t\fR
-type and optionally merges adjacent chunks,
-\fIchunk_a\fR
-of given
-\fIsize_a\fR
-and
-\fIchunk_b\fR
-of given
-\fIsize_b\fR
-into one contiguous chunk, operating on
-\fIcommitted\fR/decommitted memory as indicated, on behalf of arena
-\fIarena_ind\fR, returning false upon success\&. If the function returns true, this indicates that the chunks remain distinct mappings and therefore should continue to be operated on independently\&.
-.RE
-.PP
-"arenas\&.narenas" (\fBunsigned\fR) r\-
-.RS 4
-Current limit on number of arenas\&.
-.RE
-.PP
-"arenas\&.initialized" (\fBbool *\fR) r\-
-.RS 4
-An array of
-"arenas\&.narenas"
-booleans\&. Each boolean indicates whether the corresponding arena is initialized\&.
-.RE
-.PP
-"arenas\&.lg_dirty_mult" (\fBssize_t\fR) rw
-.RS 4
-Current default per\-arena minimum ratio (log base 2) of active to dirty pages, used to initialize
-"arena\&.<i>\&.lg_dirty_mult"
-during arena creation\&. See
-"opt\&.lg_dirty_mult"
-for additional information\&.
-.RE
-.PP
-"arenas\&.quantum" (\fBsize_t\fR) r\-
-.RS 4
-Quantum size\&.
-.RE
-.PP
-"arenas\&.page" (\fBsize_t\fR) r\-
-.RS 4
-Page size\&.
-.RE
-.PP
-"arenas\&.tcache_max" (\fBsize_t\fR) r\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Maximum thread\-cached size class\&.
-.RE
-.PP
-"arenas\&.nbins" (\fBunsigned\fR) r\-
-.RS 4
-Number of bin size classes\&.
-.RE
-.PP
-"arenas\&.nhbins" (\fBunsigned\fR) r\- [\fB\-\-enable\-tcache\fR]
-.RS 4
-Total number of thread cache bin size classes\&.
-.RE
-.PP
-"arenas\&.bin\&.<i>\&.size" (\fBsize_t\fR) r\-
-.RS 4
-Maximum size supported by size class\&.
-.RE
-.PP
-"arenas\&.bin\&.<i>\&.nregs" (\fBuint32_t\fR) r\-
-.RS 4
-Number of regions per page run\&.
-.RE
-.PP
-"arenas\&.bin\&.<i>\&.run_size" (\fBsize_t\fR) r\-
-.RS 4
-Number of bytes per page run\&.
-.RE
-.PP
-"arenas\&.nlruns" (\fBunsigned\fR) r\-
-.RS 4
-Total number of large size classes\&.
-.RE
-.PP
-"arenas\&.lrun\&.<i>\&.size" (\fBsize_t\fR) r\-
-.RS 4
-Maximum size supported by this large size class\&.
-.RE
-.PP
-"arenas\&.nhchunks" (\fBunsigned\fR) r\-
-.RS 4
-Total number of huge size classes\&.
-.RE
-.PP
-"arenas\&.hchunk\&.<i>\&.size" (\fBsize_t\fR) r\-
-.RS 4
-Maximum size supported by this huge size class\&.
-.RE
-.PP
-"arenas\&.extend" (\fBunsigned\fR) r\-
-.RS 4
-Extend the array of arenas by appending a new arena, and returning the new arena index\&.
-.RE
-.PP
-"prof\&.thread_active_init" (\fBbool\fR) rw [\fB\-\-enable\-prof\fR]
-.RS 4
-Control the initial setting for
-"thread\&.prof\&.active"
-in newly created threads\&. See the
-"opt\&.prof_thread_active_init"
-option for additional information\&.
-.RE
-.PP
-"prof\&.active" (\fBbool\fR) rw [\fB\-\-enable\-prof\fR]
-.RS 4
-Control whether sampling is currently active\&. See the
-"opt\&.prof_active"
-option for additional information, as well as the interrelated
-"thread\&.prof\&.active"
-mallctl\&.
-.RE
-.PP
-"prof\&.dump" (\fBconst char *\fR) \-w [\fB\-\-enable\-prof\fR]
-.RS 4
-Dump a memory profile to the specified file, or if NULL is specified, to a file according to the pattern
-<prefix>\&.<pid>\&.<seq>\&.m<mseq>\&.heap, where
-<prefix>
-is controlled by the
-"opt\&.prof_prefix"
-option\&.
-.RE
-.PP
-"prof\&.gdump" (\fBbool\fR) rw [\fB\-\-enable\-prof\fR]
-.RS 4
-When enabled, trigger a memory profile dump every time the total virtual memory exceeds the previous maximum\&. Profiles are dumped to files named according to the pattern
-<prefix>\&.<pid>\&.<seq>\&.u<useq>\&.heap, where
-<prefix>
-is controlled by the
-"opt\&.prof_prefix"
-option\&.
-.RE
-.PP
-"prof\&.reset" (\fBsize_t\fR) \-w [\fB\-\-enable\-prof\fR]
-.RS 4
-Reset all memory profile statistics, and optionally update the sample rate (see
-"opt\&.lg_prof_sample"
-and
-"prof\&.lg_sample")\&.
-.RE
-.PP
-"prof\&.lg_sample" (\fBsize_t\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Get the current sample rate (see
-"opt\&.lg_prof_sample")\&.
-.RE
-.PP
-"prof\&.interval" (\fBuint64_t\fR) r\- [\fB\-\-enable\-prof\fR]
-.RS 4
-Average number of bytes allocated between inverval\-based profile dumps\&. See the
-"opt\&.lg_prof_interval"
-option for additional information\&.
-.RE
-.PP
-"stats\&.cactive" (\fBsize_t *\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Pointer to a counter that contains an approximate count of the current number of bytes in active pages\&. The estimate may be high, but never low, because each arena rounds up when computing its contribution to the counter\&. Note that the
-"epoch"
-mallctl has no bearing on this counter\&. Furthermore, counter consistency is maintained via atomic operations, so it is necessary to use an atomic operation in order to guarantee a consistent read when dereferencing the pointer\&.
-.RE
-.PP
-"stats\&.allocated" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Total number of bytes allocated by the application\&.
-.RE
-.PP
-"stats\&.active" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Total number of bytes in active pages allocated by the application\&. This is a multiple of the page size, and greater than or equal to
-"stats\&.allocated"\&. This does not include
-"stats\&.arenas\&.<i>\&.pdirty", nor pages entirely devoted to allocator metadata\&.
-.RE
-.PP
-"stats\&.metadata" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Total number of bytes dedicated to metadata, which comprise base allocations used for bootstrap\-sensitive internal allocator data structures, arena chunk headers (see
-"stats\&.arenas\&.<i>\&.metadata\&.mapped"), and internal allocations (see
-"stats\&.arenas\&.<i>\&.metadata\&.allocated")\&.
-.RE
-.PP
-"stats\&.resident" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Maximum number of bytes in physically resident data pages mapped by the allocator, comprising all pages dedicated to allocator metadata, pages backing active allocations, and unused dirty pages\&. This is a maximum rather than precise because pages may not actually be physically resident if they correspond to demand\-zeroed virtual memory that has not yet been touched\&. This is a multiple of the page size, and is larger than
-"stats\&.active"\&.
-.RE
-.PP
-"stats\&.mapped" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Total number of bytes in active chunks mapped by the allocator\&. This is a multiple of the chunk size, and is larger than
-"stats\&.active"\&. This does not include inactive chunks, even those that contain unused dirty pages, which means that there is no strict ordering between this and
-"stats\&.resident"\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.dss" (\fBconst char *\fR) r\-
-.RS 4
-dss (\fBsbrk\fR(2)) allocation precedence as related to
-\fBmmap\fR(2)
-allocation\&. See
-"opt\&.dss"
-for details\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.lg_dirty_mult" (\fBssize_t\fR) r\-
-.RS 4
-Minimum ratio (log base 2) of active to dirty pages\&. See
-"opt\&.lg_dirty_mult"
-for details\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.nthreads" (\fBunsigned\fR) r\-
-.RS 4
-Number of threads currently assigned to arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.pactive" (\fBsize_t\fR) r\-
-.RS 4
-Number of pages in active runs\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.pdirty" (\fBsize_t\fR) r\-
-.RS 4
-Number of pages within unused runs that are potentially dirty, and for which
-\fBmadvise\fR\fB\fI\&.\&.\&.\fR\fR\fB \fR\fB\fI\fBMADV_DONTNEED\fR\fR\fR
-or similar has not been called\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.mapped" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of mapped bytes\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.metadata\&.mapped" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of mapped bytes in arena chunk headers, which track the states of the non\-metadata pages\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.metadata\&.allocated" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of bytes dedicated to internal allocations\&. Internal allocations differ from application\-originated allocations in that they are for internal use, and that they are omitted from heap profiles\&. This statistic is reported separately from
-"stats\&.metadata"
-and
-"stats\&.arenas\&.<i>\&.metadata\&.mapped"
-because it overlaps with e\&.g\&. the
-"stats\&.allocated"
-and
-"stats\&.active"
-statistics, whereas the other metadata statistics do not\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.npurge" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of dirty page purge sweeps performed\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.nmadvise" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of
-\fBmadvise\fR\fB\fI\&.\&.\&.\fR\fR\fB \fR\fB\fI\fBMADV_DONTNEED\fR\fR\fR
-or similar calls made to purge dirty pages\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.purged" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of pages purged\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.small\&.allocated" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of bytes currently allocated by small objects\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.small\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests served by small bins\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.small\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of small objects returned to bins\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.small\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of small allocation requests\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.large\&.allocated" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of bytes currently allocated by large objects\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.large\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of large allocation requests served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.large\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of large deallocation requests served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.large\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of large allocation requests\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.huge\&.allocated" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Number of bytes currently allocated by huge objects\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.huge\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of huge allocation requests served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.huge\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of huge deallocation requests served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.huge\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of huge allocation requests\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocations served by bin\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocations returned to bin\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.curregs" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Current number of regions for this size class\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nfills" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR \fB\-\-enable\-tcache\fR]
-.RS 4
-Cumulative number of tcache fills\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nflushes" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR \fB\-\-enable\-tcache\fR]
-.RS 4
-Cumulative number of tcache flushes\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nruns" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of runs created\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.nreruns" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of times the current run from which to allocate changed\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.bins\&.<j>\&.curruns" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Current number of runs\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.lruns\&.<j>\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests for this size class served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.lruns\&.<j>\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of deallocation requests for this size class served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.lruns\&.<j>\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests for this size class\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.lruns\&.<j>\&.curruns" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Current number of runs for this size class\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.hchunks\&.<j>\&.nmalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests for this size class served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.hchunks\&.<j>\&.ndalloc" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of deallocation requests for this size class served directly by the arena\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.hchunks\&.<j>\&.nrequests" (\fBuint64_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Cumulative number of allocation requests for this size class\&.
-.RE
-.PP
-"stats\&.arenas\&.<i>\&.hchunks\&.<j>\&.curhchunks" (\fBsize_t\fR) r\- [\fB\-\-enable\-stats\fR]
-.RS 4
-Current number of huge allocations for this size class\&.
-.RE
-.SH "DEBUGGING MALLOC PROBLEMS"
-.PP
-When debugging, it is a good idea to configure/build jemalloc with the
-\fB\-\-enable\-debug\fR
-and
-\fB\-\-enable\-fill\fR
-options, and recompile the program with suitable options and symbols for debugger support\&. When so configured, jemalloc incorporates a wide variety of run\-time assertions that catch application errors such as double\-free, write\-after\-free, etc\&.
-.PP
-Programs often accidentally depend on \(lquninitialized\(rq memory actually being filled with zero bytes\&. Junk filling (see the
-"opt\&.junk"
-option) tends to expose such bugs in the form of obviously incorrect results and/or coredumps\&. Conversely, zero filling (see the
-"opt\&.zero"
-option) eliminates the symptoms of such bugs\&. Between these two options, it is usually possible to quickly detect, diagnose, and eliminate such bugs\&.
-.PP
-This implementation does not provide much detail about the problems it detects, because the performance impact for storing such information would be prohibitive\&. However, jemalloc does integrate with the most excellent
-\m[blue]\fBValgrind\fR\m[]\&\s-2\u[2]\d\s+2
-tool if the
-\fB\-\-enable\-valgrind\fR
-configuration option is enabled\&.
-.SH "DIAGNOSTIC MESSAGES"
-.PP
-If any of the memory allocation/deallocation functions detect an error or warning condition, a message will be printed to file descriptor
-\fBSTDERR_FILENO\fR\&. Errors will result in the process dumping core\&. If the
-"opt\&.abort"
-option is set, most warnings are treated as errors\&.
-.PP
-The
-\fImalloc_message\fR
-variable allows the programmer to override the function which emits the text strings forming the errors and warnings if for some reason the
-\fBSTDERR_FILENO\fR
-file descriptor is not suitable for this\&.
-\fBmalloc_message\fR\fB\fR
-takes the
-\fIcbopaque\fR
-pointer argument that is
-\fBNULL\fR
-unless overridden by the arguments in a call to
-\fBmalloc_stats_print\fR\fB\fR, followed by a string pointer\&. Please note that doing anything which tries to allocate memory in this function is likely to result in a crash or deadlock\&.
-.PP
-All messages are prefixed by \(lq<jemalloc>:\(rq\&.
-.SH "RETURN VALUES"
-.SS "Standard API"
-.PP
-The
-\fBmalloc\fR\fB\fR
-and
-\fBcalloc\fR\fB\fR
-functions return a pointer to the allocated memory if successful; otherwise a
-\fBNULL\fR
-pointer is returned and
-\fIerrno\fR
-is set to
-ENOMEM\&.
-.PP
-The
-\fBposix_memalign\fR\fB\fR
-function returns the value 0 if successful; otherwise it returns an error value\&. The
-\fBposix_memalign\fR\fB\fR
-function will fail if:
-.PP
-EINVAL
-.RS 4
-The
-\fIalignment\fR
-parameter is not a power of 2 at least as large as
-sizeof(\fBvoid *\fR)\&.
-.RE
-.PP
-ENOMEM
-.RS 4
-Memory allocation error\&.
-.RE
-.PP
-The
-\fBaligned_alloc\fR\fB\fR
-function returns a pointer to the allocated memory if successful; otherwise a
-\fBNULL\fR
-pointer is returned and
-\fIerrno\fR
-is set\&. The
-\fBaligned_alloc\fR\fB\fR
-function will fail if:
-.PP
-EINVAL
-.RS 4
-The
-\fIalignment\fR
-parameter is not a power of 2\&.
-.RE
-.PP
-ENOMEM
-.RS 4
-Memory allocation error\&.
-.RE
-.PP
-The
-\fBrealloc\fR\fB\fR
-function returns a pointer, possibly identical to
-\fIptr\fR, to the allocated memory if successful; otherwise a
-\fBNULL\fR
-pointer is returned, and
-\fIerrno\fR
-is set to
-ENOMEM
-if the error was the result of an allocation failure\&. The
-\fBrealloc\fR\fB\fR
-function always leaves the original buffer intact when an error occurs\&.
-.PP
-The
-\fBfree\fR\fB\fR
-function returns no value\&.
-.SS "Non\-standard API"
-.PP
-The
-\fBmallocx\fR\fB\fR
-and
-\fBrallocx\fR\fB\fR
-functions return a pointer to the allocated memory if successful; otherwise a
-\fBNULL\fR
-pointer is returned to indicate insufficient contiguous memory was available to service the allocation request\&.
-.PP
-The
-\fBxallocx\fR\fB\fR
-function returns the real size of the resulting resized allocation pointed to by
-\fIptr\fR, which is a value less than
-\fIsize\fR
-if the allocation could not be adequately grown in place\&.
-.PP
-The
-\fBsallocx\fR\fB\fR
-function returns the real size of the allocation pointed to by
-\fIptr\fR\&.
-.PP
-The
-\fBnallocx\fR\fB\fR
-returns the real size that would result from a successful equivalent
-\fBmallocx\fR\fB\fR
-function call, or zero if insufficient memory is available to perform the size computation\&.
-.PP
-The
-\fBmallctl\fR\fB\fR,
-\fBmallctlnametomib\fR\fB\fR, and
-\fBmallctlbymib\fR\fB\fR
-functions return 0 on success; otherwise they return an error value\&. The functions will fail if:
-.PP
-EINVAL
-.RS 4
-\fInewp\fR
-is not
-\fBNULL\fR, and
-\fInewlen\fR
-is too large or too small\&. Alternatively,
-\fI*oldlenp\fR
-is too large or too small; in this case as much data as possible are read despite the error\&.
-.RE
-.PP
-ENOENT
-.RS 4
-\fIname\fR
-or
-\fImib\fR
-specifies an unknown/invalid value\&.
-.RE
-.PP
-EPERM
-.RS 4
-Attempt to read or write void value, or attempt to write read\-only value\&.
-.RE
-.PP
-EAGAIN
-.RS 4
-A memory allocation failure occurred\&.
-.RE
-.PP
-EFAULT
-.RS 4
-An interface with side effects failed in some way not directly related to
-\fBmallctl*\fR\fB\fR
-read/write processing\&.
-.RE
-.PP
-The
-\fBmalloc_usable_size\fR\fB\fR
-function returns the usable size of the allocation pointed to by
-\fIptr\fR\&.
-.SH "ENVIRONMENT"
-.PP
-The following environment variable affects the execution of the allocation functions:
-.PP
-\fBMALLOC_CONF\fR
-.RS 4
-If the environment variable
-\fBMALLOC_CONF\fR
-is set, the characters it contains will be interpreted as options\&.
-.RE
-.SH "EXAMPLES"
-.PP
-To dump core whenever a problem occurs:
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-ln \-s \*(Aqabort:true\*(Aq /etc/malloc\&.conf
-.fi
-.if n \{\
-.RE
-.\}
-.PP
-To specify in the source a chunk size that is 16 MiB:
-.sp
-.if n \{\
-.RS 4
-.\}
-.nf
-malloc_conf = "lg_chunk:24";
-.fi
-.if n \{\
-.RE
-.\}
-.SH "SEE ALSO"
-.PP
-\fBmadvise\fR(2),
-\fBmmap\fR(2),
-\fBsbrk\fR(2),
-\fButrace\fR(2),
-\fBalloca\fR(3),
-\fBatexit\fR(3),
-\fBgetpagesize\fR(3)
-.SH "STANDARDS"
-.PP
-The
-\fBmalloc\fR\fB\fR,
-\fBcalloc\fR\fB\fR,
-\fBrealloc\fR\fB\fR, and
-\fBfree\fR\fB\fR
-functions conform to ISO/IEC 9899:1990 (\(lqISO C90\(rq)\&.
-.PP
-The
-\fBposix_memalign\fR\fB\fR
-function conforms to IEEE Std 1003\&.1\-2001 (\(lqPOSIX\&.1\(rq)\&.
-.SH "AUTHOR"
-.PP
-\fBJason Evans\fR
-.RS 4
-.RE
-.SH "NOTES"
-.IP " 1." 4
-jemalloc website
-.RS 4
-\%http://www.canonware.com/jemalloc/
-.RE
-.IP " 2." 4
-Valgrind
-.RS 4
-\%http://valgrind.org/
-.RE
-.IP " 3." 4
-gperftools package
-.RS 4
-\%http://code.google.com/p/gperftools/
-.RE
diff --git a/deps/jemalloc/doc/jemalloc.html b/deps/jemalloc/doc/jemalloc.html
deleted file mode 100644
index 7b8e2be8c..000000000
--- a/deps/jemalloc/doc/jemalloc.html
+++ /dev/null
@@ -1,1825 +0,0 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>JEMALLOC</title><meta name="generator" content="DocBook XSL Stylesheets V1.78.1"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="refentry"><a name="idp45223136"></a><div class="titlepage"></div><div class="refnamediv"><h2>Name</h2><p>jemalloc &#8212; general purpose memory allocation functions</p></div><div class="refsect1"><a name="library"></a><h2>LIBRARY</h2><p>This manual describes jemalloc 4.0.3-0-ge9192eacf8935e29fc62fddc2701f7942b1cc02c. More information
- can be found at the <a class="ulink" href="http://www.canonware.com/jemalloc/" target="_top">jemalloc website</a>.</p></div><div class="refsynopsisdiv"><h2>SYNOPSIS</h2><div class="funcsynopsis"><pre class="funcsynopsisinfo">#include &lt;<code class="filename">jemalloc/jemalloc.h</code>&gt;</pre><div class="refsect2"><a name="idp44244480"></a><h3>Standard API</h3><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">malloc</b>(</code></td><td>size_t <var class="pdparam">size</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">calloc</b>(</code></td><td>size_t <var class="pdparam">number</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">int <b class="fsfunc">posix_memalign</b>(</code></td><td>void **<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">alignment</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">aligned_alloc</b>(</code></td><td>size_t <var class="pdparam">alignment</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">realloc</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void <b class="fsfunc">free</b>(</code></td><td>void *<var class="pdparam">ptr</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="refsect2"><a name="idp46062768"></a><h3>Non-standard API</h3><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">mallocx</b>(</code></td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void *<b class="fsfunc">rallocx</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">size_t <b class="fsfunc">xallocx</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">extra</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">size_t <b class="fsfunc">sallocx</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void <b class="fsfunc">dallocx</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void <b class="fsfunc">sdallocx</b>(</code></td><td>void *<var class="pdparam">ptr</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">size_t <b class="fsfunc">nallocx</b>(</code></td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>int <var class="pdparam">flags</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">int <b class="fsfunc">mallctl</b>(</code></td><td>const char *<var class="pdparam">name</var>, </td></tr><tr><td> </td><td>void *<var class="pdparam">oldp</var>, </td></tr><tr><td> </td><td>size_t *<var class="pdparam">oldlenp</var>, </td></tr><tr><td> </td><td>void *<var class="pdparam">newp</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">newlen</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">int <b class="fsfunc">mallctlnametomib</b>(</code></td><td>const char *<var class="pdparam">name</var>, </td></tr><tr><td> </td><td>size_t *<var class="pdparam">mibp</var>, </td></tr><tr><td> </td><td>size_t *<var class="pdparam">miblenp</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">int <b class="fsfunc">mallctlbymib</b>(</code></td><td>const size_t *<var class="pdparam">mib</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">miblen</var>, </td></tr><tr><td> </td><td>void *<var class="pdparam">oldp</var>, </td></tr><tr><td> </td><td>size_t *<var class="pdparam">oldlenp</var>, </td></tr><tr><td> </td><td>void *<var class="pdparam">newp</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">newlen</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void <b class="fsfunc">malloc_stats_print</b>(</code></td><td>void <var class="pdparam">(*write_cb)</var>
- <code>(</code>void *, const char *<code>)</code>
- , </td></tr><tr><td> </td><td>void *<var class="pdparam">cbopaque</var>, </td></tr><tr><td> </td><td>const char *<var class="pdparam">opts</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">size_t <b class="fsfunc">malloc_usable_size</b>(</code></td><td>const void *<var class="pdparam">ptr</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">void <b class="fsfunc">(*malloc_message)</b>(</code></td><td>void *<var class="pdparam">cbopaque</var>, </td></tr><tr><td> </td><td>const char *<var class="pdparam">s</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div><p><span class="type">const char *</span><code class="varname">malloc_conf</code>;</p></div></div></div><div class="refsect1"><a name="description"></a><h2>DESCRIPTION</h2><div class="refsect2"><a name="idp46115952"></a><h3>Standard API</h3><p>The <code class="function">malloc</code>(<em class="parameter"><code></code></em>) function allocates
- <em class="parameter"><code>size</code></em> bytes of uninitialized memory. The allocated
- space is suitably aligned (after possible pointer coercion) for storage
- of any type of object.</p><p>The <code class="function">calloc</code>(<em class="parameter"><code></code></em>) function allocates
- space for <em class="parameter"><code>number</code></em> objects, each
- <em class="parameter"><code>size</code></em> bytes in length. The result is identical to
- calling <code class="function">malloc</code>(<em class="parameter"><code></code></em>) with an argument of
- <em class="parameter"><code>number</code></em> * <em class="parameter"><code>size</code></em>, with the
- exception that the allocated memory is explicitly initialized to zero
- bytes.</p><p>The <code class="function">posix_memalign</code>(<em class="parameter"><code></code></em>) function
- allocates <em class="parameter"><code>size</code></em> bytes of memory such that the
- allocation's base address is a multiple of
- <em class="parameter"><code>alignment</code></em>, and returns the allocation in the value
- pointed to by <em class="parameter"><code>ptr</code></em>. The requested
- <em class="parameter"><code>alignment</code></em> must be a power of 2 at least as large as
- <code class="code">sizeof(<span class="type">void *</span>)</code>.</p><p>The <code class="function">aligned_alloc</code>(<em class="parameter"><code></code></em>) function
- allocates <em class="parameter"><code>size</code></em> bytes of memory such that the
- allocation's base address is a multiple of
- <em class="parameter"><code>alignment</code></em>. The requested
- <em class="parameter"><code>alignment</code></em> must be a power of 2. Behavior is
- undefined if <em class="parameter"><code>size</code></em> is not an integral multiple of
- <em class="parameter"><code>alignment</code></em>.</p><p>The <code class="function">realloc</code>(<em class="parameter"><code></code></em>) function changes the
- size of the previously allocated memory referenced by
- <em class="parameter"><code>ptr</code></em> to <em class="parameter"><code>size</code></em> bytes. The
- contents of the memory are unchanged up to the lesser of the new and old
- sizes. If the new size is larger, the contents of the newly allocated
- portion of the memory are undefined. Upon success, the memory referenced
- by <em class="parameter"><code>ptr</code></em> is freed and a pointer to the newly
- allocated memory is returned. Note that
- <code class="function">realloc</code>(<em class="parameter"><code></code></em>) may move the memory allocation,
- resulting in a different return value than <em class="parameter"><code>ptr</code></em>.
- If <em class="parameter"><code>ptr</code></em> is <code class="constant">NULL</code>, the
- <code class="function">realloc</code>(<em class="parameter"><code></code></em>) function behaves identically to
- <code class="function">malloc</code>(<em class="parameter"><code></code></em>) for the specified size.</p><p>The <code class="function">free</code>(<em class="parameter"><code></code></em>) function causes the
- allocated memory referenced by <em class="parameter"><code>ptr</code></em> to be made
- available for future allocations. If <em class="parameter"><code>ptr</code></em> is
- <code class="constant">NULL</code>, no action occurs.</p></div><div class="refsect2"><a name="idp46144704"></a><h3>Non-standard API</h3><p>The <code class="function">mallocx</code>(<em class="parameter"><code></code></em>),
- <code class="function">rallocx</code>(<em class="parameter"><code></code></em>),
- <code class="function">xallocx</code>(<em class="parameter"><code></code></em>),
- <code class="function">sallocx</code>(<em class="parameter"><code></code></em>),
- <code class="function">dallocx</code>(<em class="parameter"><code></code></em>),
- <code class="function">sdallocx</code>(<em class="parameter"><code></code></em>), and
- <code class="function">nallocx</code>(<em class="parameter"><code></code></em>) functions all have a
- <em class="parameter"><code>flags</code></em> argument that can be used to specify
- options. The functions only check the options that are contextually
- relevant. Use bitwise or (<code class="code">|</code>) operations to
- specify one or more of the following:
- </p><div class="variablelist"><dl class="variablelist"><dt><a name="MALLOCX_LG_ALIGN"></a><span class="term"><code class="constant">MALLOCX_LG_ALIGN(<em class="parameter"><code>la</code></em>)
- </code></span></dt><dd><p>Align the memory allocation to start at an address
- that is a multiple of <code class="code">(1 &lt;&lt;
- <em class="parameter"><code>la</code></em>)</code>. This macro does not validate
- that <em class="parameter"><code>la</code></em> is within the valid
- range.</p></dd><dt><a name="MALLOCX_ALIGN"></a><span class="term"><code class="constant">MALLOCX_ALIGN(<em class="parameter"><code>a</code></em>)
- </code></span></dt><dd><p>Align the memory allocation to start at an address
- that is a multiple of <em class="parameter"><code>a</code></em>, where
- <em class="parameter"><code>a</code></em> is a power of two. This macro does not
- validate that <em class="parameter"><code>a</code></em> is a power of 2.
- </p></dd><dt><a name="MALLOCX_ZERO"></a><span class="term"><code class="constant">MALLOCX_ZERO</code></span></dt><dd><p>Initialize newly allocated memory to contain zero
- bytes. In the growing reallocation case, the real size prior to
- reallocation defines the boundary between untouched bytes and those
- that are initialized to contain zero bytes. If this macro is
- absent, newly allocated memory is uninitialized.</p></dd><dt><a name="MALLOCX_TCACHE"></a><span class="term"><code class="constant">MALLOCX_TCACHE(<em class="parameter"><code>tc</code></em>)
- </code></span></dt><dd><p>Use the thread-specific cache (tcache) specified by
- the identifier <em class="parameter"><code>tc</code></em>, which must have been
- acquired via the <a class="link" href="#tcache.create">
- "<code class="mallctl">tcache.create</code>"
- </a>
- mallctl. This macro does not validate that
- <em class="parameter"><code>tc</code></em> specifies a valid
- identifier.</p></dd><dt><a name="MALLOC_TCACHE_NONE"></a><span class="term"><code class="constant">MALLOCX_TCACHE_NONE</code></span></dt><dd><p>Do not use a thread-specific cache (tcache). Unless
- <code class="constant">MALLOCX_TCACHE(<em class="parameter"><code>tc</code></em>)</code> or
- <code class="constant">MALLOCX_TCACHE_NONE</code> is specified, an
- automatically managed tcache will be used under many circumstances.
- This macro cannot be used in the same <em class="parameter"><code>flags</code></em>
- argument as
- <code class="constant">MALLOCX_TCACHE(<em class="parameter"><code>tc</code></em>)</code>.</p></dd><dt><a name="MALLOCX_ARENA"></a><span class="term"><code class="constant">MALLOCX_ARENA(<em class="parameter"><code>a</code></em>)
- </code></span></dt><dd><p>Use the arena specified by the index
- <em class="parameter"><code>a</code></em>. This macro has no effect for regions that
- were allocated via an arena other than the one specified. This
- macro does not validate that <em class="parameter"><code>a</code></em> specifies an
- arena index in the valid range.</p></dd></dl></div><p>
- </p><p>The <code class="function">mallocx</code>(<em class="parameter"><code></code></em>) function allocates at
- least <em class="parameter"><code>size</code></em> bytes of memory, and returns a pointer
- to the base address of the allocation. Behavior is undefined if
- <em class="parameter"><code>size</code></em> is <code class="constant">0</code>, or if request size
- overflows due to size class and/or alignment constraints.</p><p>The <code class="function">rallocx</code>(<em class="parameter"><code></code></em>) function resizes the
- allocation at <em class="parameter"><code>ptr</code></em> to be at least
- <em class="parameter"><code>size</code></em> bytes, and returns a pointer to the base
- address of the resulting allocation, which may or may not have moved from
- its original location. Behavior is undefined if
- <em class="parameter"><code>size</code></em> is <code class="constant">0</code>, or if request size
- overflows due to size class and/or alignment constraints.</p><p>The <code class="function">xallocx</code>(<em class="parameter"><code></code></em>) function resizes the
- allocation at <em class="parameter"><code>ptr</code></em> in place to be at least
- <em class="parameter"><code>size</code></em> bytes, and returns the real size of the
- allocation. If <em class="parameter"><code>extra</code></em> is non-zero, an attempt is
- made to resize the allocation to be at least <code class="code">(<em class="parameter"><code>size</code></em> +
- <em class="parameter"><code>extra</code></em>)</code> bytes, though inability to allocate
- the extra byte(s) will not by itself result in failure to resize.
- Behavior is undefined if <em class="parameter"><code>size</code></em> is
- <code class="constant">0</code>, or if <code class="code">(<em class="parameter"><code>size</code></em> + <em class="parameter"><code>extra</code></em>
- &gt; <code class="constant">SIZE_T_MAX</code>)</code>.</p><p>The <code class="function">sallocx</code>(<em class="parameter"><code></code></em>) function returns the
- real size of the allocation at <em class="parameter"><code>ptr</code></em>.</p><p>The <code class="function">dallocx</code>(<em class="parameter"><code></code></em>) function causes the
- memory referenced by <em class="parameter"><code>ptr</code></em> to be made available for
- future allocations.</p><p>The <code class="function">sdallocx</code>(<em class="parameter"><code></code></em>) function is an
- extension of <code class="function">dallocx</code>(<em class="parameter"><code></code></em>) with a
- <em class="parameter"><code>size</code></em> parameter to allow the caller to pass in the
- allocation size as an optimization. The minimum valid input size is the
- original requested size of the allocation, and the maximum valid input
- size is the corresponding value returned by
- <code class="function">nallocx</code>(<em class="parameter"><code></code></em>) or
- <code class="function">sallocx</code>(<em class="parameter"><code></code></em>).</p><p>The <code class="function">nallocx</code>(<em class="parameter"><code></code></em>) function allocates no
- memory, but it performs the same size computation as the
- <code class="function">mallocx</code>(<em class="parameter"><code></code></em>) function, and returns the real
- size of the allocation that would result from the equivalent
- <code class="function">mallocx</code>(<em class="parameter"><code></code></em>) function call. Behavior is
- undefined if <em class="parameter"><code>size</code></em> is <code class="constant">0</code>, or if
- request size overflows due to size class and/or alignment
- constraints.</p><p>The <code class="function">mallctl</code>(<em class="parameter"><code></code></em>) function provides a
- general interface for introspecting the memory allocator, as well as
- setting modifiable parameters and triggering actions. The
- period-separated <em class="parameter"><code>name</code></em> argument specifies a
- location in a tree-structured namespace; see the <a class="xref" href="#mallctl_namespace" title="MALLCTL NAMESPACE">MALLCTL NAMESPACE</a> section for
- documentation on the tree contents. To read a value, pass a pointer via
- <em class="parameter"><code>oldp</code></em> to adequate space to contain the value, and a
- pointer to its length via <em class="parameter"><code>oldlenp</code></em>; otherwise pass
- <code class="constant">NULL</code> and <code class="constant">NULL</code>. Similarly, to
- write a value, pass a pointer to the value via
- <em class="parameter"><code>newp</code></em>, and its length via
- <em class="parameter"><code>newlen</code></em>; otherwise pass <code class="constant">NULL</code>
- and <code class="constant">0</code>.</p><p>The <code class="function">mallctlnametomib</code>(<em class="parameter"><code></code></em>) function
- provides a way to avoid repeated name lookups for applications that
- repeatedly query the same portion of the namespace, by translating a name
- to a &#8220;Management Information Base&#8221; (MIB) that can be passed
- repeatedly to <code class="function">mallctlbymib</code>(<em class="parameter"><code></code></em>). Upon
- successful return from <code class="function">mallctlnametomib</code>(<em class="parameter"><code></code></em>),
- <em class="parameter"><code>mibp</code></em> contains an array of
- <em class="parameter"><code>*miblenp</code></em> integers, where
- <em class="parameter"><code>*miblenp</code></em> is the lesser of the number of components
- in <em class="parameter"><code>name</code></em> and the input value of
- <em class="parameter"><code>*miblenp</code></em>. Thus it is possible to pass a
- <em class="parameter"><code>*miblenp</code></em> that is smaller than the number of
- period-separated name components, which results in a partial MIB that can
- be used as the basis for constructing a complete MIB. For name
- components that are integers (e.g. the 2 in
- <a class="link" href="#arenas.bin.i.size">
- "<code class="mallctl">arenas.bin.2.size</code>"
- </a>),
- the corresponding MIB component will always be that integer. Therefore,
- it is legitimate to construct code like the following: </p><pre class="programlisting">
-unsigned nbins, i;
-size_t mib[4];
-size_t len, miblen;
-
-len = sizeof(nbins);
-mallctl("arenas.nbins", &amp;nbins, &amp;len, NULL, 0);
-
-miblen = 4;
-mallctlnametomib("arenas.bin.0.size", mib, &amp;miblen);
-for (i = 0; i &lt; nbins; i++) {
- size_t bin_size;
-
- mib[2] = i;
- len = sizeof(bin_size);
- mallctlbymib(mib, miblen, &amp;bin_size, &amp;len, NULL, 0);
- /* Do something with bin_size... */
-}</pre><p>The <code class="function">malloc_stats_print</code>(<em class="parameter"><code></code></em>) function
- writes human-readable summary statistics via the
- <em class="parameter"><code>write_cb</code></em> callback function pointer and
- <em class="parameter"><code>cbopaque</code></em> data passed to
- <em class="parameter"><code>write_cb</code></em>, or
- <code class="function">malloc_message</code>(<em class="parameter"><code></code></em>) if
- <em class="parameter"><code>write_cb</code></em> is <code class="constant">NULL</code>. This
- function can be called repeatedly. General information that never
- changes during execution can be omitted by specifying "g" as a character
- within the <em class="parameter"><code>opts</code></em> string. Note that
- <code class="function">malloc_message</code>(<em class="parameter"><code></code></em>) uses the
- <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>) functions internally, so
- inconsistent statistics can be reported if multiple threads use these
- functions simultaneously. If <code class="option">--enable-stats</code> is
- specified during configuration, &#8220;m&#8221; and &#8220;a&#8221; can
- be specified to omit merged arena and per arena statistics, respectively;
- &#8220;b&#8221;, &#8220;l&#8221;, and &#8220;h&#8221; can be specified to
- omit per size class statistics for bins, large objects, and huge objects,
- respectively. Unrecognized characters are silently ignored. Note that
- thread caching may prevent some statistics from being completely up to
- date, since extra locking would be required to merge counters that track
- thread cache operations.
- </p><p>The <code class="function">malloc_usable_size</code>(<em class="parameter"><code></code></em>) function
- returns the usable size of the allocation pointed to by
- <em class="parameter"><code>ptr</code></em>. The return value may be larger than the size
- that was requested during allocation. The
- <code class="function">malloc_usable_size</code>(<em class="parameter"><code></code></em>) function is not a
- mechanism for in-place <code class="function">realloc</code>(<em class="parameter"><code></code></em>); rather
- it is provided solely as a tool for introspection purposes. Any
- discrepancy between the requested allocation size and the size reported
- by <code class="function">malloc_usable_size</code>(<em class="parameter"><code></code></em>) should not be
- depended on, since such behavior is entirely implementation-dependent.
- </p></div></div><div class="refsect1"><a name="tuning"></a><h2>TUNING</h2><p>Once, when the first call is made to one of the memory allocation
- routines, the allocator initializes its internals based in part on various
- options that can be specified at compile- or run-time.</p><p>The string pointed to by the global variable
- <code class="varname">malloc_conf</code>, the &#8220;name&#8221; of the file
- referenced by the symbolic link named <code class="filename">/etc/malloc.conf</code>, and the value of the
- environment variable <code class="envar">MALLOC_CONF</code>, will be interpreted, in
- that order, from left to right as options. Note that
- <code class="varname">malloc_conf</code> may be read before
- <code class="function">main</code>(<em class="parameter"><code></code></em>) is entered, so the declaration of
- <code class="varname">malloc_conf</code> should specify an initializer that contains
- the final value to be read by jemalloc. <code class="varname">malloc_conf</code> is
- a compile-time setting, whereas <code class="filename">/etc/malloc.conf</code> and <code class="envar">MALLOC_CONF</code>
- can be safely set any time prior to program invocation.</p><p>An options string is a comma-separated list of option:value pairs.
- There is one key corresponding to each <a class="link" href="#opt.abort">
- "<code class="mallctl">opt.*</code>"
- </a> mallctl (see the <a class="xref" href="#mallctl_namespace" title="MALLCTL NAMESPACE">MALLCTL NAMESPACE</a> section for options
- documentation). For example, <code class="literal">abort:true,narenas:1</code> sets
- the <a class="link" href="#opt.abort">
- "<code class="mallctl">opt.abort</code>"
- </a> and <a class="link" href="#opt.narenas">
- "<code class="mallctl">opt.narenas</code>"
- </a> options. Some
- options have boolean values (true/false), others have integer values (base
- 8, 10, or 16, depending on prefix), and yet others have raw string
- values.</p></div><div class="refsect1"><a name="implementation_notes"></a><h2>IMPLEMENTATION NOTES</h2><p>Traditionally, allocators have used
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span> to obtain memory, which is
- suboptimal for several reasons, including race conditions, increased
- fragmentation, and artificial limitations on maximum usable memory. If
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span> is supported by the operating
- system, this allocator uses both
- <span class="citerefentry"><span class="refentrytitle">mmap</span>(2)</span> and
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span>, in that order of preference;
- otherwise only <span class="citerefentry"><span class="refentrytitle">mmap</span>(2)</span> is used.</p><p>This allocator uses multiple arenas in order to reduce lock
- contention for threaded programs on multi-processor systems. This works
- well with regard to threading scalability, but incurs some costs. There is
- a small fixed per-arena overhead, and additionally, arenas manage memory
- completely independently of each other, which means a small fixed increase
- in overall memory fragmentation. These overheads are not generally an
- issue, given the number of arenas normally used. Note that using
- substantially more arenas than the default is not likely to improve
- performance, mainly due to reduced cache performance. However, it may make
- sense to reduce the number of arenas if an application does not make much
- use of the allocation functions.</p><p>In addition to multiple arenas, unless
- <code class="option">--disable-tcache</code> is specified during configuration, this
- allocator supports thread-specific caching for small and large objects, in
- order to make it possible to completely avoid synchronization for most
- allocation requests. Such caching allows very fast allocation in the
- common case, but it increases memory usage and fragmentation, since a
- bounded number of objects can remain allocated in each thread cache.</p><p>Memory is conceptually broken into equal-sized chunks, where the
- chunk size is a power of two that is greater than the page size. Chunks
- are always aligned to multiples of the chunk size. This alignment makes it
- possible to find metadata for user objects very quickly.</p><p>User objects are broken into three categories according to size:
- small, large, and huge. Small and large objects are managed entirely by
- arenas; huge objects are additionally aggregated in a single data structure
- that is shared by all threads. Huge objects are typically used by
- applications infrequently enough that this single data structure is not a
- scalability issue.</p><p>Each chunk that is managed by an arena tracks its contents as runs of
- contiguous pages (unused, backing a set of small objects, or backing one
- large object). The combination of chunk alignment and chunk page maps
- makes it possible to determine all metadata regarding small and large
- allocations in constant time.</p><p>Small objects are managed in groups by page runs. Each run maintains
- a bitmap to track which regions are in use. Allocation requests that are no
- more than half the quantum (8 or 16, depending on architecture) are rounded
- up to the nearest power of two that is at least <code class="code">sizeof(<span class="type">double</span>)</code>. All other object size
- classes are multiples of the quantum, spaced such that there are four size
- classes for each doubling in size, which limits internal fragmentation to
- approximately 20% for all but the smallest size classes. Small size classes
- are smaller than four times the page size, large size classes are smaller
- than the chunk size (see the <a class="link" href="#opt.lg_chunk">
- "<code class="mallctl">opt.lg_chunk</code>"
- </a> option), and
- huge size classes extend from the chunk size up to one size class less than
- the full address space size.</p><p>Allocations are packed tightly together, which can be an issue for
- multi-threaded applications. If you need to assure that allocations do not
- suffer from cacheline sharing, round your allocation requests up to the
- nearest multiple of the cacheline size, or specify cacheline alignment when
- allocating.</p><p>The <code class="function">realloc</code>(<em class="parameter"><code></code></em>),
- <code class="function">rallocx</code>(<em class="parameter"><code></code></em>), and
- <code class="function">xallocx</code>(<em class="parameter"><code></code></em>) functions may resize allocations
- without moving them under limited circumstances. Unlike the
- <code class="function">*allocx</code>(<em class="parameter"><code></code></em>) API, the standard API does not
- officially round up the usable size of an allocation to the nearest size
- class, so technically it is necessary to call
- <code class="function">realloc</code>(<em class="parameter"><code></code></em>) to grow e.g. a 9-byte allocation to
- 16 bytes, or shrink a 16-byte allocation to 9 bytes. Growth and shrinkage
- trivially succeeds in place as long as the pre-size and post-size both round
- up to the same size class. No other API guarantees are made regarding
- in-place resizing, but the current implementation also tries to resize large
- and huge allocations in place, as long as the pre-size and post-size are
- both large or both huge. In such cases shrinkage always succeeds for large
- size classes, but for huge size classes the chunk allocator must support
- splitting (see <a class="link" href="#arena.i.chunk_hooks">
- "<code class="mallctl">arena.&lt;i&gt;.chunk_hooks</code>"
- </a>).
- Growth only succeeds if the trailing memory is currently available, and
- additionally for huge size classes the chunk allocator must support
- merging.</p><p>Assuming 2 MiB chunks, 4 KiB pages, and a 16-byte quantum on a
- 64-bit system, the size classes in each category are as shown in <a class="xref" href="#size_classes" title="Table 1. Size classes">Table 1</a>.</p><div class="table"><a name="size_classes"></a><p class="title"><b>Table 1. Size classes</b></p><div class="table-contents"><table summary="Size classes" border="1"><colgroup><col align="left" class="c1"><col align="right" class="c2"><col align="left" class="c3"></colgroup><thead><tr><th align="left">Category</th><th align="right">Spacing</th><th align="left">Size</th></tr></thead><tbody><tr><td rowspan="9" align="left">Small</td><td align="right">lg</td><td align="left">[8]</td></tr><tr><td align="right">16</td><td align="left">[16, 32, 48, 64, 80, 96, 112, 128]</td></tr><tr><td align="right">32</td><td align="left">[160, 192, 224, 256]</td></tr><tr><td align="right">64</td><td align="left">[320, 384, 448, 512]</td></tr><tr><td align="right">128</td><td align="left">[640, 768, 896, 1024]</td></tr><tr><td align="right">256</td><td align="left">[1280, 1536, 1792, 2048]</td></tr><tr><td align="right">512</td><td align="left">[2560, 3072, 3584, 4096]</td></tr><tr><td align="right">1 KiB</td><td align="left">[5 KiB, 6 KiB, 7 KiB, 8 KiB]</td></tr><tr><td align="right">2 KiB</td><td align="left">[10 KiB, 12 KiB, 14 KiB]</td></tr><tr><td rowspan="8" align="left">Large</td><td align="right">2 KiB</td><td align="left">[16 KiB]</td></tr><tr><td align="right">4 KiB</td><td align="left">[20 KiB, 24 KiB, 28 KiB, 32 KiB]</td></tr><tr><td align="right">8 KiB</td><td align="left">[40 KiB, 48 KiB, 54 KiB, 64 KiB]</td></tr><tr><td align="right">16 KiB</td><td align="left">[80 KiB, 96 KiB, 112 KiB, 128 KiB]</td></tr><tr><td align="right">32 KiB</td><td align="left">[160 KiB, 192 KiB, 224 KiB, 256 KiB]</td></tr><tr><td align="right">64 KiB</td><td align="left">[320 KiB, 384 KiB, 448 KiB, 512 KiB]</td></tr><tr><td align="right">128 KiB</td><td align="left">[640 KiB, 768 KiB, 896 KiB, 1 MiB]</td></tr><tr><td align="right">256 KiB</td><td align="left">[1280 KiB, 1536 KiB, 1792 KiB]</td></tr><tr><td rowspan="7" align="left">Huge</td><td align="right">256 KiB</td><td align="left">[2 MiB]</td></tr><tr><td align="right">512 KiB</td><td align="left">[2560 KiB, 3 MiB, 3584 KiB, 4 MiB]</td></tr><tr><td align="right">1 MiB</td><td align="left">[5 MiB, 6 MiB, 7 MiB, 8 MiB]</td></tr><tr><td align="right">2 MiB</td><td align="left">[10 MiB, 12 MiB, 14 MiB, 16 MiB]</td></tr><tr><td align="right">4 MiB</td><td align="left">[20 MiB, 24 MiB, 28 MiB, 32 MiB]</td></tr><tr><td align="right">8 MiB</td><td align="left">[40 MiB, 48 MiB, 56 MiB, 64 MiB]</td></tr><tr><td align="right">...</td><td align="left">...</td></tr></tbody></table></div></div><br class="table-break"></div><div class="refsect1"><a name="mallctl_namespace"></a><h2>MALLCTL NAMESPACE</h2><p>The following names are defined in the namespace accessible via the
- <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>) functions. Value types are
- specified in parentheses, their readable/writable statuses are encoded as
- <code class="literal">rw</code>, <code class="literal">r-</code>, <code class="literal">-w</code>, or
- <code class="literal">--</code>, and required build configuration flags follow, if
- any. A name element encoded as <code class="literal">&lt;i&gt;</code> or
- <code class="literal">&lt;j&gt;</code> indicates an integer component, where the
- integer varies from 0 to some upper value that must be determined via
- introspection. In the case of
- "<code class="mallctl">stats.arenas.&lt;i&gt;.*</code>"
- ,
- <code class="literal">&lt;i&gt;</code> equal to <a class="link" href="#arenas.narenas">
- "<code class="mallctl">arenas.narenas</code>"
- </a> can be
- used to access the summation of statistics from all arenas. Take special
- note of the <a class="link" href="#epoch">
- "<code class="mallctl">epoch</code>"
- </a> mallctl,
- which controls refreshing of cached dynamic statistics.</p><div class="variablelist"><dl class="variablelist"><dt><a name="version"></a><span class="term">
-
- "<code class="mallctl">version</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Return the jemalloc version string.</p></dd><dt><a name="epoch"></a><span class="term">
-
- "<code class="mallctl">epoch</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>If a value is passed in, refresh the data from which
- the <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>) functions report values,
- and increment the epoch. Return the current epoch. This is useful for
- detecting whether another thread caused a refresh.</p></dd><dt><a name="config.cache_oblivious"></a><span class="term">
-
- "<code class="mallctl">config.cache_oblivious</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-cache-oblivious</code> was specified
- during build configuration.</p></dd><dt><a name="config.debug"></a><span class="term">
-
- "<code class="mallctl">config.debug</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-debug</code> was specified during
- build configuration.</p></dd><dt><a name="config.fill"></a><span class="term">
-
- "<code class="mallctl">config.fill</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-fill</code> was specified during
- build configuration.</p></dd><dt><a name="config.lazy_lock"></a><span class="term">
-
- "<code class="mallctl">config.lazy_lock</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-lazy-lock</code> was specified
- during build configuration.</p></dd><dt><a name="config.munmap"></a><span class="term">
-
- "<code class="mallctl">config.munmap</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-munmap</code> was specified during
- build configuration.</p></dd><dt><a name="config.prof"></a><span class="term">
-
- "<code class="mallctl">config.prof</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-prof</code> was specified during
- build configuration.</p></dd><dt><a name="config.prof_libgcc"></a><span class="term">
-
- "<code class="mallctl">config.prof_libgcc</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--disable-prof-libgcc</code> was not
- specified during build configuration.</p></dd><dt><a name="config.prof_libunwind"></a><span class="term">
-
- "<code class="mallctl">config.prof_libunwind</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-prof-libunwind</code> was specified
- during build configuration.</p></dd><dt><a name="config.stats"></a><span class="term">
-
- "<code class="mallctl">config.stats</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-stats</code> was specified during
- build configuration.</p></dd><dt><a name="config.tcache"></a><span class="term">
-
- "<code class="mallctl">config.tcache</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--disable-tcache</code> was not specified
- during build configuration.</p></dd><dt><a name="config.tls"></a><span class="term">
-
- "<code class="mallctl">config.tls</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--disable-tls</code> was not specified during
- build configuration.</p></dd><dt><a name="config.utrace"></a><span class="term">
-
- "<code class="mallctl">config.utrace</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-utrace</code> was specified during
- build configuration.</p></dd><dt><a name="config.valgrind"></a><span class="term">
-
- "<code class="mallctl">config.valgrind</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-valgrind</code> was specified during
- build configuration.</p></dd><dt><a name="config.xmalloc"></a><span class="term">
-
- "<code class="mallctl">config.xmalloc</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p><code class="option">--enable-xmalloc</code> was specified during
- build configuration.</p></dd><dt><a name="opt.abort"></a><span class="term">
-
- "<code class="mallctl">opt.abort</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Abort-on-warning enabled/disabled. If true, most
- warnings are fatal. The process will call
- <span class="citerefentry"><span class="refentrytitle">abort</span>(3)</span> in these cases. This option is
- disabled by default unless <code class="option">--enable-debug</code> is
- specified during configuration, in which case it is enabled by default.
- </p></dd><dt><a name="opt.dss"></a><span class="term">
-
- "<code class="mallctl">opt.dss</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>dss (<span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span>) allocation precedence as
- related to <span class="citerefentry"><span class="refentrytitle">mmap</span>(2)</span> allocation. The following
- settings are supported if
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span> is supported by the operating
- system: &#8220;disabled&#8221;, &#8220;primary&#8221;, and
- &#8220;secondary&#8221;; otherwise only &#8220;disabled&#8221; is
- supported. The default is &#8220;secondary&#8221; if
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span> is supported by the operating
- system; &#8220;disabled&#8221; otherwise.
- </p></dd><dt><a name="opt.lg_chunk"></a><span class="term">
-
- "<code class="mallctl">opt.lg_chunk</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Virtual memory chunk size (log base 2). If a chunk
- size outside the supported size range is specified, the size is
- silently clipped to the minimum/maximum supported size. The default
- chunk size is 2 MiB (2^21).
- </p></dd><dt><a name="opt.narenas"></a><span class="term">
-
- "<code class="mallctl">opt.narenas</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Maximum number of arenas to use for automatic
- multiplexing of threads and arenas. The default is four times the
- number of CPUs, or one if there is a single CPU.</p></dd><dt><a name="opt.lg_dirty_mult"></a><span class="term">
-
- "<code class="mallctl">opt.lg_dirty_mult</code>"
-
- (<span class="type">ssize_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Per-arena minimum ratio (log base 2) of active to dirty
- pages. Some dirty unused pages may be allowed to accumulate, within
- the limit set by the ratio (or one chunk worth of dirty pages,
- whichever is greater), before informing the kernel about some of those
- pages via <span class="citerefentry"><span class="refentrytitle">madvise</span>(2)</span> or a similar system call. This
- provides the kernel with sufficient information to recycle dirty pages
- if physical memory becomes scarce and the pages remain unused. The
- default minimum ratio is 8:1 (2^3:1); an option value of -1 will
- disable dirty page purging. See <a class="link" href="#arenas.lg_dirty_mult">
- "<code class="mallctl">arenas.lg_dirty_mult</code>"
- </a>
- and <a class="link" href="#arena.i.lg_dirty_mult">
- "<code class="mallctl">arena.&lt;i&gt;.lg_dirty_mult</code>"
- </a>
- for related dynamic control options.</p></dd><dt><a name="opt.stats_print"></a><span class="term">
-
- "<code class="mallctl">opt.stats_print</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Enable/disable statistics printing at exit. If
- enabled, the <code class="function">malloc_stats_print</code>(<em class="parameter"><code></code></em>)
- function is called at program exit via an
- <span class="citerefentry"><span class="refentrytitle">atexit</span>(3)</span> function. If
- <code class="option">--enable-stats</code> is specified during configuration, this
- has the potential to cause deadlock for a multi-threaded process that
- exits while one or more threads are executing in the memory allocation
- functions. Furthermore, <code class="function">atexit</code>(<em class="parameter"><code></code></em>) may
- allocate memory during application initialization and then deadlock
- internally when jemalloc in turn calls
- <code class="function">atexit</code>(<em class="parameter"><code></code></em>), so this option is not
- univerally usable (though the application can register its own
- <code class="function">atexit</code>(<em class="parameter"><code></code></em>) function with equivalent
- functionality). Therefore, this option should only be used with care;
- it is primarily intended as a performance tuning aid during application
- development. This option is disabled by default.</p></dd><dt><a name="opt.junk"></a><span class="term">
-
- "<code class="mallctl">opt.junk</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-fill</code>]
- </span></dt><dd><p>Junk filling. If set to "alloc", each byte of
- uninitialized allocated memory will be initialized to
- <code class="literal">0xa5</code>. If set to "free", all deallocated memory will
- be initialized to <code class="literal">0x5a</code>. If set to "true", both
- allocated and deallocated memory will be initialized, and if set to
- "false", junk filling be disabled entirely. This is intended for
- debugging and will impact performance negatively. This option is
- "false" by default unless <code class="option">--enable-debug</code> is specified
- during configuration, in which case it is "true" by default unless
- running inside <a class="ulink" href="http://valgrind.org/" target="_top">Valgrind</a>.</p></dd><dt><a name="opt.quarantine"></a><span class="term">
-
- "<code class="mallctl">opt.quarantine</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-fill</code>]
- </span></dt><dd><p>Per thread quarantine size in bytes. If non-zero, each
- thread maintains a FIFO object quarantine that stores up to the
- specified number of bytes of memory. The quarantined memory is not
- freed until it is released from quarantine, though it is immediately
- junk-filled if the <a class="link" href="#opt.junk">
- "<code class="mallctl">opt.junk</code>"
- </a> option is
- enabled. This feature is of particular use in combination with <a class="ulink" href="http://valgrind.org/" target="_top">Valgrind</a>, which can detect attempts
- to access quarantined objects. This is intended for debugging and will
- impact performance negatively. The default quarantine size is 0 unless
- running inside Valgrind, in which case the default is 16
- MiB.</p></dd><dt><a name="opt.redzone"></a><span class="term">
-
- "<code class="mallctl">opt.redzone</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-fill</code>]
- </span></dt><dd><p>Redzones enabled/disabled. If enabled, small
- allocations have redzones before and after them. Furthermore, if the
- <a class="link" href="#opt.junk">
- "<code class="mallctl">opt.junk</code>"
- </a> option is
- enabled, the redzones are checked for corruption during deallocation.
- However, the primary intended purpose of this feature is to be used in
- combination with <a class="ulink" href="http://valgrind.org/" target="_top">Valgrind</a>,
- which needs redzones in order to do effective buffer overflow/underflow
- detection. This option is intended for debugging and will impact
- performance negatively. This option is disabled by
- default unless running inside Valgrind.</p></dd><dt><a name="opt.zero"></a><span class="term">
-
- "<code class="mallctl">opt.zero</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-fill</code>]
- </span></dt><dd><p>Zero filling enabled/disabled. If enabled, each byte
- of uninitialized allocated memory will be initialized to 0. Note that
- this initialization only happens once for each byte, so
- <code class="function">realloc</code>(<em class="parameter"><code></code></em>) and
- <code class="function">rallocx</code>(<em class="parameter"><code></code></em>) calls do not zero memory that
- was previously allocated. This is intended for debugging and will
- impact performance negatively. This option is disabled by default.
- </p></dd><dt><a name="opt.utrace"></a><span class="term">
-
- "<code class="mallctl">opt.utrace</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-utrace</code>]
- </span></dt><dd><p>Allocation tracing based on
- <span class="citerefentry"><span class="refentrytitle">utrace</span>(2)</span> enabled/disabled. This option
- is disabled by default.</p></dd><dt><a name="opt.xmalloc"></a><span class="term">
-
- "<code class="mallctl">opt.xmalloc</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-xmalloc</code>]
- </span></dt><dd><p>Abort-on-out-of-memory enabled/disabled. If enabled,
- rather than returning failure for any allocation function, display a
- diagnostic message on <code class="constant">STDERR_FILENO</code> and cause the
- program to drop core (using
- <span class="citerefentry"><span class="refentrytitle">abort</span>(3)</span>). If an application is
- designed to depend on this behavior, set the option at compile time by
- including the following in the source code:
- </p><pre class="programlisting">
-malloc_conf = "xmalloc:true";</pre><p>
- This option is disabled by default.</p></dd><dt><a name="opt.tcache"></a><span class="term">
-
- "<code class="mallctl">opt.tcache</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Thread-specific caching (tcache) enabled/disabled. When
- there are multiple threads, each thread uses a tcache for objects up to
- a certain size. Thread-specific caching allows many allocations to be
- satisfied without performing any thread synchronization, at the cost of
- increased memory use. See the <a class="link" href="#opt.lg_tcache_max">
- "<code class="mallctl">opt.lg_tcache_max</code>"
- </a>
- option for related tuning information. This option is enabled by
- default unless running inside <a class="ulink" href="http://valgrind.org/" target="_top">Valgrind</a>, in which case it is
- forcefully disabled.</p></dd><dt><a name="opt.lg_tcache_max"></a><span class="term">
-
- "<code class="mallctl">opt.lg_tcache_max</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Maximum size class (log base 2) to cache in the
- thread-specific cache (tcache). At a minimum, all small size classes
- are cached, and at a maximum all large size classes are cached. The
- default maximum is 32 KiB (2^15).</p></dd><dt><a name="opt.prof"></a><span class="term">
-
- "<code class="mallctl">opt.prof</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Memory profiling enabled/disabled. If enabled, profile
- memory allocation activity. See the <a class="link" href="#opt.prof_active">
- "<code class="mallctl">opt.prof_active</code>"
- </a>
- option for on-the-fly activation/deactivation. See the <a class="link" href="#opt.lg_prof_sample">
- "<code class="mallctl">opt.lg_prof_sample</code>"
- </a>
- option for probabilistic sampling control. See the <a class="link" href="#opt.prof_accum">
- "<code class="mallctl">opt.prof_accum</code>"
- </a>
- option for control of cumulative sample reporting. See the <a class="link" href="#opt.lg_prof_interval">
- "<code class="mallctl">opt.lg_prof_interval</code>"
- </a>
- option for information on interval-triggered profile dumping, the <a class="link" href="#opt.prof_gdump">
- "<code class="mallctl">opt.prof_gdump</code>"
- </a>
- option for information on high-water-triggered profile dumping, and the
- <a class="link" href="#opt.prof_final">
- "<code class="mallctl">opt.prof_final</code>"
- </a>
- option for final profile dumping. Profile output is compatible with
- the <span class="command"><strong>jeprof</strong></span> command, which is based on the
- <span class="command"><strong>pprof</strong></span> that is developed as part of the <a class="ulink" href="http://code.google.com/p/gperftools/" target="_top">gperftools
- package</a>.</p></dd><dt><a name="opt.prof_prefix"></a><span class="term">
-
- "<code class="mallctl">opt.prof_prefix</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Filename prefix for profile dumps. If the prefix is
- set to the empty string, no automatic dumps will occur; this is
- primarily useful for disabling the automatic final heap dump (which
- also disables leak reporting, if enabled). The default prefix is
- <code class="filename">jeprof</code>.</p></dd><dt><a name="opt.prof_active"></a><span class="term">
-
- "<code class="mallctl">opt.prof_active</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Profiling activated/deactivated. This is a secondary
- control mechanism that makes it possible to start the application with
- profiling enabled (see the <a class="link" href="#opt.prof">
- "<code class="mallctl">opt.prof</code>"
- </a> option) but
- inactive, then toggle profiling at any time during program execution
- with the <a class="link" href="#prof.active">
- "<code class="mallctl">prof.active</code>"
- </a> mallctl.
- This option is enabled by default.</p></dd><dt><a name="opt.prof_thread_active_init"></a><span class="term">
-
- "<code class="mallctl">opt.prof_thread_active_init</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Initial setting for <a class="link" href="#thread.prof.active">
- "<code class="mallctl">thread.prof.active</code>"
- </a>
- in newly created threads. The initial setting for newly created threads
- can also be changed during execution via the <a class="link" href="#prof.thread_active_init">
- "<code class="mallctl">prof.thread_active_init</code>"
- </a>
- mallctl. This option is enabled by default.</p></dd><dt><a name="opt.lg_prof_sample"></a><span class="term">
-
- "<code class="mallctl">opt.lg_prof_sample</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Average interval (log base 2) between allocation
- samples, as measured in bytes of allocation activity. Increasing the
- sampling interval decreases profile fidelity, but also decreases the
- computational overhead. The default sample interval is 512 KiB (2^19
- B).</p></dd><dt><a name="opt.prof_accum"></a><span class="term">
-
- "<code class="mallctl">opt.prof_accum</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Reporting of cumulative object/byte counts in profile
- dumps enabled/disabled. If this option is enabled, every unique
- backtrace must be stored for the duration of execution. Depending on
- the application, this can impose a large memory overhead, and the
- cumulative counts are not always of interest. This option is disabled
- by default.</p></dd><dt><a name="opt.lg_prof_interval"></a><span class="term">
-
- "<code class="mallctl">opt.lg_prof_interval</code>"
-
- (<span class="type">ssize_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Average interval (log base 2) between memory profile
- dumps, as measured in bytes of allocation activity. The actual
- interval between dumps may be sporadic because decentralized allocation
- counters are used to avoid synchronization bottlenecks. Profiles are
- dumped to files named according to the pattern
- <code class="filename">&lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.i&lt;iseq&gt;.heap</code>,
- where <code class="literal">&lt;prefix&gt;</code> is controlled by the
- <a class="link" href="#opt.prof_prefix">
- "<code class="mallctl">opt.prof_prefix</code>"
- </a>
- option. By default, interval-triggered profile dumping is disabled
- (encoded as -1).
- </p></dd><dt><a name="opt.prof_gdump"></a><span class="term">
-
- "<code class="mallctl">opt.prof_gdump</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Set the initial state of <a class="link" href="#prof.gdump">
- "<code class="mallctl">prof.gdump</code>"
- </a>, which when
- enabled triggers a memory profile dump every time the total virtual
- memory exceeds the previous maximum. This option is disabled by
- default.</p></dd><dt><a name="opt.prof_final"></a><span class="term">
-
- "<code class="mallctl">opt.prof_final</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Use an
- <span class="citerefentry"><span class="refentrytitle">atexit</span>(3)</span> function to dump final memory
- usage to a file named according to the pattern
- <code class="filename">&lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.f.heap</code>,
- where <code class="literal">&lt;prefix&gt;</code> is controlled by the <a class="link" href="#opt.prof_prefix">
- "<code class="mallctl">opt.prof_prefix</code>"
- </a>
- option. Note that <code class="function">atexit</code>(<em class="parameter"><code></code></em>) may allocate
- memory during application initialization and then deadlock internally
- when jemalloc in turn calls <code class="function">atexit</code>(<em class="parameter"><code></code></em>), so
- this option is not univerally usable (though the application can
- register its own <code class="function">atexit</code>(<em class="parameter"><code></code></em>) function with
- equivalent functionality). This option is disabled by
- default.</p></dd><dt><a name="opt.prof_leak"></a><span class="term">
-
- "<code class="mallctl">opt.prof_leak</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Leak reporting enabled/disabled. If enabled, use an
- <span class="citerefentry"><span class="refentrytitle">atexit</span>(3)</span> function to report memory leaks
- detected by allocation sampling. See the
- <a class="link" href="#opt.prof">
- "<code class="mallctl">opt.prof</code>"
- </a> option for
- information on analyzing heap profile output. This option is disabled
- by default.</p></dd><dt><a name="thread.arena"></a><span class="term">
-
- "<code class="mallctl">thread.arena</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>Get or set the arena associated with the calling
- thread. If the specified arena was not initialized beforehand (see the
- <a class="link" href="#arenas.initialized">
- "<code class="mallctl">arenas.initialized</code>"
- </a>
- mallctl), it will be automatically initialized as a side effect of
- calling this interface.</p></dd><dt><a name="thread.allocated"></a><span class="term">
-
- "<code class="mallctl">thread.allocated</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Get the total number of bytes ever allocated by the
- calling thread. This counter has the potential to wrap around; it is
- up to the application to appropriately interpret the counter in such
- cases.</p></dd><dt><a name="thread.allocatedp"></a><span class="term">
-
- "<code class="mallctl">thread.allocatedp</code>"
-
- (<span class="type">uint64_t *</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Get a pointer to the the value that is returned by the
- <a class="link" href="#thread.allocated">
- "<code class="mallctl">thread.allocated</code>"
- </a>
- mallctl. This is useful for avoiding the overhead of repeated
- <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>) calls.</p></dd><dt><a name="thread.deallocated"></a><span class="term">
-
- "<code class="mallctl">thread.deallocated</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Get the total number of bytes ever deallocated by the
- calling thread. This counter has the potential to wrap around; it is
- up to the application to appropriately interpret the counter in such
- cases.</p></dd><dt><a name="thread.deallocatedp"></a><span class="term">
-
- "<code class="mallctl">thread.deallocatedp</code>"
-
- (<span class="type">uint64_t *</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Get a pointer to the the value that is returned by the
- <a class="link" href="#thread.deallocated">
- "<code class="mallctl">thread.deallocated</code>"
- </a>
- mallctl. This is useful for avoiding the overhead of repeated
- <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>) calls.</p></dd><dt><a name="thread.tcache.enabled"></a><span class="term">
-
- "<code class="mallctl">thread.tcache.enabled</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">rw</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Enable/disable calling thread's tcache. The tcache is
- implicitly flushed as a side effect of becoming
- disabled (see <a class="link" href="#thread.tcache.flush">
- "<code class="mallctl">thread.tcache.flush</code>"
- </a>).
- </p></dd><dt><a name="thread.tcache.flush"></a><span class="term">
-
- "<code class="mallctl">thread.tcache.flush</code>"
-
- (<span class="type">void</span>)
- <code class="literal">--</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Flush calling thread's thread-specific cache (tcache).
- This interface releases all cached objects and internal data structures
- associated with the calling thread's tcache. Ordinarily, this interface
- need not be called, since automatic periodic incremental garbage
- collection occurs, and the thread cache is automatically discarded when
- a thread exits. However, garbage collection is triggered by allocation
- activity, so it is possible for a thread that stops
- allocating/deallocating to retain its cache indefinitely, in which case
- the developer may find manual flushing useful.</p></dd><dt><a name="thread.prof.name"></a><span class="term">
-
- "<code class="mallctl">thread.prof.name</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code> or
- <code class="literal">-w</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Get/set the descriptive name associated with the calling
- thread in memory profile dumps. An internal copy of the name string is
- created, so the input string need not be maintained after this interface
- completes execution. The output string of this interface should be
- copied for non-ephemeral uses, because multiple implementation details
- can cause asynchronous string deallocation. Furthermore, each
- invocation of this interface can only read or write; simultaneous
- read/write is not supported due to string lifetime limitations. The
- name string must nil-terminated and comprised only of characters in the
- sets recognized
- by <span class="citerefentry"><span class="refentrytitle">isgraph</span>(3)</span> and
- <span class="citerefentry"><span class="refentrytitle">isblank</span>(3)</span>.</p></dd><dt><a name="thread.prof.active"></a><span class="term">
-
- "<code class="mallctl">thread.prof.active</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">rw</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Control whether sampling is currently active for the
- calling thread. This is an activation mechanism in addition to <a class="link" href="#prof.active">
- "<code class="mallctl">prof.active</code>"
- </a>; both must
- be active for the calling thread to sample. This flag is enabled by
- default.</p></dd><dt><a name="tcache.create"></a><span class="term">
-
- "<code class="mallctl">tcache.create</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Create an explicit thread-specific cache (tcache) and
- return an identifier that can be passed to the <a class="link" href="#MALLOCX_TCACHE"><code class="constant">MALLOCX_TCACHE(<em class="parameter"><code>tc</code></em>)</code></a>
- macro to explicitly use the specified cache rather than the
- automatically managed one that is used by default. Each explicit cache
- can be used by only one thread at a time; the application must assure
- that this constraint holds.
- </p></dd><dt><a name="tcache.flush"></a><span class="term">
-
- "<code class="mallctl">tcache.flush</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">-w</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Flush the specified thread-specific cache (tcache). The
- same considerations apply to this interface as to <a class="link" href="#thread.tcache.flush">
- "<code class="mallctl">thread.tcache.flush</code>"
- </a>,
- except that the tcache will never be automatically be discarded.
- </p></dd><dt><a name="tcache.destroy"></a><span class="term">
-
- "<code class="mallctl">tcache.destroy</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">-w</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Flush the specified thread-specific cache (tcache) and
- make the identifier available for use during a future tcache creation.
- </p></dd><dt><a name="arena.i.purge"></a><span class="term">
-
- "<code class="mallctl">arena.&lt;i&gt;.purge</code>"
-
- (<span class="type">void</span>)
- <code class="literal">--</code>
- </span></dt><dd><p>Purge unused dirty pages for arena &lt;i&gt;, or for
- all arenas if &lt;i&gt; equals <a class="link" href="#arenas.narenas">
- "<code class="mallctl">arenas.narenas</code>"
- </a>.
- </p></dd><dt><a name="arena.i.dss"></a><span class="term">
-
- "<code class="mallctl">arena.&lt;i&gt;.dss</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>Set the precedence of dss allocation as related to mmap
- allocation for arena &lt;i&gt;, or for all arenas if &lt;i&gt; equals
- <a class="link" href="#arenas.narenas">
- "<code class="mallctl">arenas.narenas</code>"
- </a>. See
- <a class="link" href="#opt.dss">
- "<code class="mallctl">opt.dss</code>"
- </a> for supported
- settings.</p></dd><dt><a name="arena.i.lg_dirty_mult"></a><span class="term">
-
- "<code class="mallctl">arena.&lt;i&gt;.lg_dirty_mult</code>"
-
- (<span class="type">ssize_t</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>Current per-arena minimum ratio (log base 2) of active
- to dirty pages for arena &lt;i&gt;. Each time this interface is set and
- the ratio is increased, pages are synchronously purged as necessary to
- impose the new ratio. See <a class="link" href="#opt.lg_dirty_mult">
- "<code class="mallctl">opt.lg_dirty_mult</code>"
- </a>
- for additional information.</p></dd><dt><a name="arena.i.chunk_hooks"></a><span class="term">
-
- "<code class="mallctl">arena.&lt;i&gt;.chunk_hooks</code>"
-
- (<span class="type">chunk_hooks_t</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>Get or set the chunk management hook functions for arena
- &lt;i&gt;. The functions must be capable of operating on all extant
- chunks associated with arena &lt;i&gt;, usually by passing unknown
- chunks to the replaced functions. In practice, it is feasible to
- control allocation for arenas created via <a class="link" href="#arenas.extend">
- "<code class="mallctl">arenas.extend</code>"
- </a> such
- that all chunks originate from an application-supplied chunk allocator
- (by setting custom chunk hook functions just after arena creation), but
- the automatically created arenas may have already created chunks prior
- to the application having an opportunity to take over chunk
- allocation.</p><pre class="programlisting">
-typedef struct {
- chunk_alloc_t *alloc;
- chunk_dalloc_t *dalloc;
- chunk_commit_t *commit;
- chunk_decommit_t *decommit;
- chunk_purge_t *purge;
- chunk_split_t *split;
- chunk_merge_t *merge;
-} chunk_hooks_t;</pre><p>The <span class="type">chunk_hooks_t</span> structure comprises function
- pointers which are described individually below. jemalloc uses these
- functions to manage chunk lifetime, which starts off with allocation of
- mapped committed memory, in the simplest case followed by deallocation.
- However, there are performance and platform reasons to retain chunks for
- later reuse. Cleanup attempts cascade from deallocation to decommit to
- purging, which gives the chunk management functions opportunities to
- reject the most permanent cleanup operations in favor of less permanent
- (and often less costly) operations. The chunk splitting and merging
- operations can also be opted out of, but this is mainly intended to
- support platforms on which virtual memory mappings provided by the
- operating system kernel do not automatically coalesce and split, e.g.
- Windows.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef void *<b class="fsfunc">(chunk_alloc_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">alignment</var>, </td></tr><tr><td> </td><td>bool *<var class="pdparam">zero</var>, </td></tr><tr><td> </td><td>bool *<var class="pdparam">commit</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk allocation function conforms to the
- <span class="type">chunk_alloc_t</span> type and upon success returns a pointer to
- <em class="parameter"><code>size</code></em> bytes of mapped memory on behalf of arena
- <em class="parameter"><code>arena_ind</code></em> such that the chunk's base address is a
- multiple of <em class="parameter"><code>alignment</code></em>, as well as setting
- <em class="parameter"><code>*zero</code></em> to indicate whether the chunk is zeroed and
- <em class="parameter"><code>*commit</code></em> to indicate whether the chunk is
- committed. Upon error the function returns <code class="constant">NULL</code>
- and leaves <em class="parameter"><code>*zero</code></em> and
- <em class="parameter"><code>*commit</code></em> unmodified. The
- <em class="parameter"><code>size</code></em> parameter is always a multiple of the chunk
- size. The <em class="parameter"><code>alignment</code></em> parameter is always a power
- of two at least as large as the chunk size. Zeroing is mandatory if
- <em class="parameter"><code>*zero</code></em> is true upon function entry. Committing is
- mandatory if <em class="parameter"><code>*commit</code></em> is true upon function entry.
- If <em class="parameter"><code>chunk</code></em> is not <code class="constant">NULL</code>, the
- returned pointer must be <em class="parameter"><code>chunk</code></em> on success or
- <code class="constant">NULL</code> on error. Committed memory may be committed
- in absolute terms as on a system that does not overcommit, or in
- implicit terms as on a system that overcommits and satisfies physical
- memory needs on demand via soft page faults. Note that replacing the
- default chunk allocation function makes the arena's <a class="link" href="#arena.i.dss">
- "<code class="mallctl">arena.&lt;i&gt;.dss</code>"
- </a>
- setting irrelevant.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_dalloc_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>bool <var class="pdparam">committed</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>
- A chunk deallocation function conforms to the
- <span class="type">chunk_dalloc_t</span> type and deallocates a
- <em class="parameter"><code>chunk</code></em> of given <em class="parameter"><code>size</code></em> with
- <em class="parameter"><code>committed</code></em>/decommited memory as indicated, on
- behalf of arena <em class="parameter"><code>arena_ind</code></em>, returning false upon
- success. If the function returns true, this indicates opt-out from
- deallocation; the virtual memory mapping associated with the chunk
- remains mapped, in the same commit state, and available for future use,
- in which case it will be automatically retained for later reuse.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_commit_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">offset</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">length</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk commit function conforms to the
- <span class="type">chunk_commit_t</span> type and commits zeroed physical memory to
- back pages within a <em class="parameter"><code>chunk</code></em> of given
- <em class="parameter"><code>size</code></em> at <em class="parameter"><code>offset</code></em> bytes,
- extending for <em class="parameter"><code>length</code></em> on behalf of arena
- <em class="parameter"><code>arena_ind</code></em>, returning false upon success.
- Committed memory may be committed in absolute terms as on a system that
- does not overcommit, or in implicit terms as on a system that
- overcommits and satisfies physical memory needs on demand via soft page
- faults. If the function returns true, this indicates insufficient
- physical memory to satisfy the request.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_decommit_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">offset</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">length</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk decommit function conforms to the
- <span class="type">chunk_decommit_t</span> type and decommits any physical memory
- that is backing pages within a <em class="parameter"><code>chunk</code></em> of given
- <em class="parameter"><code>size</code></em> at <em class="parameter"><code>offset</code></em> bytes,
- extending for <em class="parameter"><code>length</code></em> on behalf of arena
- <em class="parameter"><code>arena_ind</code></em>, returning false upon success, in which
- case the pages will be committed via the chunk commit function before
- being reused. If the function returns true, this indicates opt-out from
- decommit; the memory remains committed and available for future use, in
- which case it will be automatically retained for later reuse.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_purge_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t<var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">offset</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">length</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk purge function conforms to the <span class="type">chunk_purge_t</span>
- type and optionally discards physical pages within the virtual memory
- mapping associated with <em class="parameter"><code>chunk</code></em> of given
- <em class="parameter"><code>size</code></em> at <em class="parameter"><code>offset</code></em> bytes,
- extending for <em class="parameter"><code>length</code></em> on behalf of arena
- <em class="parameter"><code>arena_ind</code></em>, returning false if pages within the
- purged virtual memory range will be zero-filled the next time they are
- accessed.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_split_t)</b>(</code></td><td>void *<var class="pdparam">chunk</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size_a</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size_b</var>, </td></tr><tr><td> </td><td>bool <var class="pdparam">committed</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk split function conforms to the <span class="type">chunk_split_t</span>
- type and optionally splits <em class="parameter"><code>chunk</code></em> of given
- <em class="parameter"><code>size</code></em> into two adjacent chunks, the first of
- <em class="parameter"><code>size_a</code></em> bytes, and the second of
- <em class="parameter"><code>size_b</code></em> bytes, operating on
- <em class="parameter"><code>committed</code></em>/decommitted memory as indicated, on
- behalf of arena <em class="parameter"><code>arena_ind</code></em>, returning false upon
- success. If the function returns true, this indicates that the chunk
- remains unsplit and therefore should continue to be operated on as a
- whole.</p><div class="funcsynopsis"><table border="0" class="funcprototype-table" summary="Function synopsis" style="cellspacing: 0; cellpadding: 0;"><tr><td><code class="funcdef">typedef bool <b class="fsfunc">(chunk_merge_t)</b>(</code></td><td>void *<var class="pdparam">chunk_a</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size_a</var>, </td></tr><tr><td> </td><td>void *<var class="pdparam">chunk_b</var>, </td></tr><tr><td> </td><td>size_t <var class="pdparam">size_b</var>, </td></tr><tr><td> </td><td>bool <var class="pdparam">committed</var>, </td></tr><tr><td> </td><td>unsigned <var class="pdparam">arena_ind</var><code>)</code>;</td></tr></table><div class="funcprototype-spacer"> </div></div><div class="literallayout"><p></p></div><p>A chunk merge function conforms to the <span class="type">chunk_merge_t</span>
- type and optionally merges adjacent chunks,
- <em class="parameter"><code>chunk_a</code></em> of given <em class="parameter"><code>size_a</code></em>
- and <em class="parameter"><code>chunk_b</code></em> of given
- <em class="parameter"><code>size_b</code></em> into one contiguous chunk, operating on
- <em class="parameter"><code>committed</code></em>/decommitted memory as indicated, on
- behalf of arena <em class="parameter"><code>arena_ind</code></em>, returning false upon
- success. If the function returns true, this indicates that the chunks
- remain distinct mappings and therefore should continue to be operated on
- independently.</p></dd><dt><a name="arenas.narenas"></a><span class="term">
-
- "<code class="mallctl">arenas.narenas</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Current limit on number of arenas.</p></dd><dt><a name="arenas.initialized"></a><span class="term">
-
- "<code class="mallctl">arenas.initialized</code>"
-
- (<span class="type">bool *</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>An array of <a class="link" href="#arenas.narenas">
- "<code class="mallctl">arenas.narenas</code>"
- </a>
- booleans. Each boolean indicates whether the corresponding arena is
- initialized.</p></dd><dt><a name="arenas.lg_dirty_mult"></a><span class="term">
-
- "<code class="mallctl">arenas.lg_dirty_mult</code>"
-
- (<span class="type">ssize_t</span>)
- <code class="literal">rw</code>
- </span></dt><dd><p>Current default per-arena minimum ratio (log base 2) of
- active to dirty pages, used to initialize <a class="link" href="#arena.i.lg_dirty_mult">
- "<code class="mallctl">arena.&lt;i&gt;.lg_dirty_mult</code>"
- </a>
- during arena creation. See <a class="link" href="#opt.lg_dirty_mult">
- "<code class="mallctl">opt.lg_dirty_mult</code>"
- </a>
- for additional information.</p></dd><dt><a name="arenas.quantum"></a><span class="term">
-
- "<code class="mallctl">arenas.quantum</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Quantum size.</p></dd><dt><a name="arenas.page"></a><span class="term">
-
- "<code class="mallctl">arenas.page</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Page size.</p></dd><dt><a name="arenas.tcache_max"></a><span class="term">
-
- "<code class="mallctl">arenas.tcache_max</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Maximum thread-cached size class.</p></dd><dt><a name="arenas.nbins"></a><span class="term">
-
- "<code class="mallctl">arenas.nbins</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of bin size classes.</p></dd><dt><a name="arenas.nhbins"></a><span class="term">
-
- "<code class="mallctl">arenas.nhbins</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Total number of thread cache bin size
- classes.</p></dd><dt><a name="arenas.bin.i.size"></a><span class="term">
-
- "<code class="mallctl">arenas.bin.&lt;i&gt;.size</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Maximum size supported by size class.</p></dd><dt><a name="arenas.bin.i.nregs"></a><span class="term">
-
- "<code class="mallctl">arenas.bin.&lt;i&gt;.nregs</code>"
-
- (<span class="type">uint32_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of regions per page run.</p></dd><dt><a name="arenas.bin.i.run_size"></a><span class="term">
-
- "<code class="mallctl">arenas.bin.&lt;i&gt;.run_size</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of bytes per page run.</p></dd><dt><a name="arenas.nlruns"></a><span class="term">
-
- "<code class="mallctl">arenas.nlruns</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Total number of large size classes.</p></dd><dt><a name="arenas.lrun.i.size"></a><span class="term">
-
- "<code class="mallctl">arenas.lrun.&lt;i&gt;.size</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Maximum size supported by this large size
- class.</p></dd><dt><a name="arenas.nhchunks"></a><span class="term">
-
- "<code class="mallctl">arenas.nhchunks</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Total number of huge size classes.</p></dd><dt><a name="arenas.hchunk.i.size"></a><span class="term">
-
- "<code class="mallctl">arenas.hchunk.&lt;i&gt;.size</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Maximum size supported by this huge size
- class.</p></dd><dt><a name="arenas.extend"></a><span class="term">
-
- "<code class="mallctl">arenas.extend</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Extend the array of arenas by appending a new arena,
- and returning the new arena index.</p></dd><dt><a name="prof.thread_active_init"></a><span class="term">
-
- "<code class="mallctl">prof.thread_active_init</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">rw</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Control the initial setting for <a class="link" href="#thread.prof.active">
- "<code class="mallctl">thread.prof.active</code>"
- </a>
- in newly created threads. See the <a class="link" href="#opt.prof_thread_active_init">
- "<code class="mallctl">opt.prof_thread_active_init</code>"
- </a>
- option for additional information.</p></dd><dt><a name="prof.active"></a><span class="term">
-
- "<code class="mallctl">prof.active</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">rw</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Control whether sampling is currently active. See the
- <a class="link" href="#opt.prof_active">
- "<code class="mallctl">opt.prof_active</code>"
- </a>
- option for additional information, as well as the interrelated <a class="link" href="#thread.prof.active">
- "<code class="mallctl">thread.prof.active</code>"
- </a>
- mallctl.</p></dd><dt><a name="prof.dump"></a><span class="term">
-
- "<code class="mallctl">prof.dump</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">-w</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Dump a memory profile to the specified file, or if NULL
- is specified, to a file according to the pattern
- <code class="filename">&lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.m&lt;mseq&gt;.heap</code>,
- where <code class="literal">&lt;prefix&gt;</code> is controlled by the
- <a class="link" href="#opt.prof_prefix">
- "<code class="mallctl">opt.prof_prefix</code>"
- </a>
- option.</p></dd><dt><a name="prof.gdump"></a><span class="term">
-
- "<code class="mallctl">prof.gdump</code>"
-
- (<span class="type">bool</span>)
- <code class="literal">rw</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>When enabled, trigger a memory profile dump every time
- the total virtual memory exceeds the previous maximum. Profiles are
- dumped to files named according to the pattern
- <code class="filename">&lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.u&lt;useq&gt;.heap</code>,
- where <code class="literal">&lt;prefix&gt;</code> is controlled by the <a class="link" href="#opt.prof_prefix">
- "<code class="mallctl">opt.prof_prefix</code>"
- </a>
- option.</p></dd><dt><a name="prof.reset"></a><span class="term">
-
- "<code class="mallctl">prof.reset</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">-w</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Reset all memory profile statistics, and optionally
- update the sample rate (see <a class="link" href="#opt.lg_prof_sample">
- "<code class="mallctl">opt.lg_prof_sample</code>"
- </a>
- and <a class="link" href="#prof.lg_sample">
- "<code class="mallctl">prof.lg_sample</code>"
- </a>).
- </p></dd><dt><a name="prof.lg_sample"></a><span class="term">
-
- "<code class="mallctl">prof.lg_sample</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Get the current sample rate (see <a class="link" href="#opt.lg_prof_sample">
- "<code class="mallctl">opt.lg_prof_sample</code>"
- </a>).
- </p></dd><dt><a name="prof.interval"></a><span class="term">
-
- "<code class="mallctl">prof.interval</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-prof</code>]
- </span></dt><dd><p>Average number of bytes allocated between
- inverval-based profile dumps. See the
- <a class="link" href="#opt.lg_prof_interval">
- "<code class="mallctl">opt.lg_prof_interval</code>"
- </a>
- option for additional information.</p></dd><dt><a name="stats.cactive"></a><span class="term">
-
- "<code class="mallctl">stats.cactive</code>"
-
- (<span class="type">size_t *</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Pointer to a counter that contains an approximate count
- of the current number of bytes in active pages. The estimate may be
- high, but never low, because each arena rounds up when computing its
- contribution to the counter. Note that the <a class="link" href="#epoch">
- "<code class="mallctl">epoch</code>"
- </a> mallctl has no bearing
- on this counter. Furthermore, counter consistency is maintained via
- atomic operations, so it is necessary to use an atomic operation in
- order to guarantee a consistent read when dereferencing the pointer.
- </p></dd><dt><a name="stats.allocated"></a><span class="term">
-
- "<code class="mallctl">stats.allocated</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Total number of bytes allocated by the
- application.</p></dd><dt><a name="stats.active"></a><span class="term">
-
- "<code class="mallctl">stats.active</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Total number of bytes in active pages allocated by the
- application. This is a multiple of the page size, and greater than or
- equal to <a class="link" href="#stats.allocated">
- "<code class="mallctl">stats.allocated</code>"
- </a>.
- This does not include <a class="link" href="#stats.arenas.i.pdirty">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.pdirty</code>"
- </a>, nor pages
- entirely devoted to allocator metadata.</p></dd><dt><a name="stats.metadata"></a><span class="term">
-
- "<code class="mallctl">stats.metadata</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Total number of bytes dedicated to metadata, which
- comprise base allocations used for bootstrap-sensitive internal
- allocator data structures, arena chunk headers (see <a class="link" href="#stats.arenas.i.metadata.mapped">
- "<code class="mallctl">stats.arenas.&lt;i&gt;.metadata.mapped</code>"
- </a>),
- and internal allocations (see <a class="link" href="#stats.arenas.i.metadata.allocated">
- "<code class="mallctl">stats.arenas.&lt;i&gt;.metadata.allocated</code>"
- </a>).</p></dd><dt><a name="stats.resident"></a><span class="term">
-
- "<code class="mallctl">stats.resident</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Maximum number of bytes in physically resident data
- pages mapped by the allocator, comprising all pages dedicated to
- allocator metadata, pages backing active allocations, and unused dirty
- pages. This is a maximum rather than precise because pages may not
- actually be physically resident if they correspond to demand-zeroed
- virtual memory that has not yet been touched. This is a multiple of the
- page size, and is larger than <a class="link" href="#stats.active">
- "<code class="mallctl">stats.active</code>"
- </a>.</p></dd><dt><a name="stats.mapped"></a><span class="term">
-
- "<code class="mallctl">stats.mapped</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Total number of bytes in active chunks mapped by the
- allocator. This is a multiple of the chunk size, and is larger than
- <a class="link" href="#stats.active">
- "<code class="mallctl">stats.active</code>"
- </a>.
- This does not include inactive chunks, even those that contain unused
- dirty pages, which means that there is no strict ordering between this
- and <a class="link" href="#stats.resident">
- "<code class="mallctl">stats.resident</code>"
- </a>.</p></dd><dt><a name="stats.arenas.i.dss"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.dss</code>"
-
- (<span class="type">const char *</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>dss (<span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span>) allocation precedence as
- related to <span class="citerefentry"><span class="refentrytitle">mmap</span>(2)</span> allocation. See <a class="link" href="#opt.dss">
- "<code class="mallctl">opt.dss</code>"
- </a> for details.
- </p></dd><dt><a name="stats.arenas.i.lg_dirty_mult"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.lg_dirty_mult</code>"
-
- (<span class="type">ssize_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Minimum ratio (log base 2) of active to dirty pages.
- See <a class="link" href="#opt.lg_dirty_mult">
- "<code class="mallctl">opt.lg_dirty_mult</code>"
- </a>
- for details.</p></dd><dt><a name="stats.arenas.i.nthreads"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.nthreads</code>"
-
- (<span class="type">unsigned</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of threads currently assigned to
- arena.</p></dd><dt><a name="stats.arenas.i.pactive"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.pactive</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of pages in active runs.</p></dd><dt><a name="stats.arenas.i.pdirty"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.pdirty</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- </span></dt><dd><p>Number of pages within unused runs that are potentially
- dirty, and for which <code class="function">madvise</code>(<em class="parameter"><code>...</code></em>,
- <em class="parameter"><code><code class="constant">MADV_DONTNEED</code></code></em>) or
- similar has not been called.</p></dd><dt><a name="stats.arenas.i.mapped"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.mapped</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of mapped bytes.</p></dd><dt><a name="stats.arenas.i.metadata.mapped"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.metadata.mapped</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of mapped bytes in arena chunk headers, which
- track the states of the non-metadata pages.</p></dd><dt><a name="stats.arenas.i.metadata.allocated"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.metadata.allocated</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of bytes dedicated to internal allocations.
- Internal allocations differ from application-originated allocations in
- that they are for internal use, and that they are omitted from heap
- profiles. This statistic is reported separately from <a class="link" href="#stats.metadata">
- "<code class="mallctl">stats.metadata</code>"
- </a> and
- <a class="link" href="#stats.arenas.i.metadata.mapped">
- "<code class="mallctl">stats.arenas.&lt;i&gt;.metadata.mapped</code>"
- </a>
- because it overlaps with e.g. the <a class="link" href="#stats.allocated">
- "<code class="mallctl">stats.allocated</code>"
- </a> and
- <a class="link" href="#stats.active">
- "<code class="mallctl">stats.active</code>"
- </a>
- statistics, whereas the other metadata statistics do
- not.</p></dd><dt><a name="stats.arenas.i.npurge"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.npurge</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of dirty page purge sweeps performed.
- </p></dd><dt><a name="stats.arenas.i.nmadvise"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.nmadvise</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of <code class="function">madvise</code>(<em class="parameter"><code>...</code></em>,
- <em class="parameter"><code><code class="constant">MADV_DONTNEED</code></code></em>) or
- similar calls made to purge dirty pages.</p></dd><dt><a name="stats.arenas.i.purged"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.purged</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of pages purged.</p></dd><dt><a name="stats.arenas.i.small.allocated"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.small.allocated</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of bytes currently allocated by small objects.
- </p></dd><dt><a name="stats.arenas.i.small.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.small.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation requests served by
- small bins.</p></dd><dt><a name="stats.arenas.i.small.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.small.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of small objects returned to bins.
- </p></dd><dt><a name="stats.arenas.i.small.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.small.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of small allocation requests.
- </p></dd><dt><a name="stats.arenas.i.large.allocated"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.large.allocated</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of bytes currently allocated by large objects.
- </p></dd><dt><a name="stats.arenas.i.large.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.large.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of large allocation requests served
- directly by the arena.</p></dd><dt><a name="stats.arenas.i.large.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.large.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of large deallocation requests served
- directly by the arena.</p></dd><dt><a name="stats.arenas.i.large.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.large.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of large allocation requests.
- </p></dd><dt><a name="stats.arenas.i.huge.allocated"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.huge.allocated</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Number of bytes currently allocated by huge objects.
- </p></dd><dt><a name="stats.arenas.i.huge.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.huge.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of huge allocation requests served
- directly by the arena.</p></dd><dt><a name="stats.arenas.i.huge.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.huge.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of huge deallocation requests served
- directly by the arena.</p></dd><dt><a name="stats.arenas.i.huge.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.huge.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of huge allocation requests.
- </p></dd><dt><a name="stats.arenas.i.bins.j.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocations served by bin.
- </p></dd><dt><a name="stats.arenas.i.bins.j.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocations returned to bin.
- </p></dd><dt><a name="stats.arenas.i.bins.j.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation
- requests.</p></dd><dt><a name="stats.arenas.i.bins.j.curregs"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.curregs</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Current number of regions for this size
- class.</p></dd><dt><a name="stats.arenas.i.bins.j.nfills"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nfills</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code> <code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Cumulative number of tcache fills.</p></dd><dt><a name="stats.arenas.i.bins.j.nflushes"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nflushes</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code> <code class="option">--enable-tcache</code>]
- </span></dt><dd><p>Cumulative number of tcache flushes.</p></dd><dt><a name="stats.arenas.i.bins.j.nruns"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nruns</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of runs created.</p></dd><dt><a name="stats.arenas.i.bins.j.nreruns"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nreruns</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of times the current run from which
- to allocate changed.</p></dd><dt><a name="stats.arenas.i.bins.j.curruns"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.curruns</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Current number of runs.</p></dd><dt><a name="stats.arenas.i.lruns.j.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation requests for this size
- class served directly by the arena.</p></dd><dt><a name="stats.arenas.i.lruns.j.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of deallocation requests for this
- size class served directly by the arena.</p></dd><dt><a name="stats.arenas.i.lruns.j.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation requests for this size
- class.</p></dd><dt><a name="stats.arenas.i.lruns.j.curruns"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.curruns</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Current number of runs for this size class.
- </p></dd><dt><a name="stats.arenas.i.hchunks.j.nmalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.nmalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation requests for this size
- class served directly by the arena.</p></dd><dt><a name="stats.arenas.i.hchunks.j.ndalloc"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.ndalloc</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of deallocation requests for this
- size class served directly by the arena.</p></dd><dt><a name="stats.arenas.i.hchunks.j.nrequests"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.nrequests</code>"
-
- (<span class="type">uint64_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Cumulative number of allocation requests for this size
- class.</p></dd><dt><a name="stats.arenas.i.hchunks.j.curhchunks"></a><span class="term">
-
- "<code class="mallctl">stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.curhchunks</code>"
-
- (<span class="type">size_t</span>)
- <code class="literal">r-</code>
- [<code class="option">--enable-stats</code>]
- </span></dt><dd><p>Current number of huge allocations for this size class.
- </p></dd></dl></div></div><div class="refsect1"><a name="debugging_malloc_problems"></a><h2>DEBUGGING MALLOC PROBLEMS</h2><p>When debugging, it is a good idea to configure/build jemalloc with
- the <code class="option">--enable-debug</code> and <code class="option">--enable-fill</code>
- options, and recompile the program with suitable options and symbols for
- debugger support. When so configured, jemalloc incorporates a wide variety
- of run-time assertions that catch application errors such as double-free,
- write-after-free, etc.</p><p>Programs often accidentally depend on &#8220;uninitialized&#8221;
- memory actually being filled with zero bytes. Junk filling
- (see the <a class="link" href="#opt.junk">
- "<code class="mallctl">opt.junk</code>"
- </a>
- option) tends to expose such bugs in the form of obviously incorrect
- results and/or coredumps. Conversely, zero
- filling (see the <a class="link" href="#opt.zero">
- "<code class="mallctl">opt.zero</code>"
- </a> option) eliminates
- the symptoms of such bugs. Between these two options, it is usually
- possible to quickly detect, diagnose, and eliminate such bugs.</p><p>This implementation does not provide much detail about the problems
- it detects, because the performance impact for storing such information
- would be prohibitive. However, jemalloc does integrate with the most
- excellent <a class="ulink" href="http://valgrind.org/" target="_top">Valgrind</a> tool if the
- <code class="option">--enable-valgrind</code> configuration option is enabled.</p></div><div class="refsect1"><a name="diagnostic_messages"></a><h2>DIAGNOSTIC MESSAGES</h2><p>If any of the memory allocation/deallocation functions detect an
- error or warning condition, a message will be printed to file descriptor
- <code class="constant">STDERR_FILENO</code>. Errors will result in the process
- dumping core. If the <a class="link" href="#opt.abort">
- "<code class="mallctl">opt.abort</code>"
- </a> option is set, most
- warnings are treated as errors.</p><p>The <code class="varname">malloc_message</code> variable allows the programmer
- to override the function which emits the text strings forming the errors
- and warnings if for some reason the <code class="constant">STDERR_FILENO</code> file
- descriptor is not suitable for this.
- <code class="function">malloc_message</code>(<em class="parameter"><code></code></em>) takes the
- <em class="parameter"><code>cbopaque</code></em> pointer argument that is
- <code class="constant">NULL</code> unless overridden by the arguments in a call to
- <code class="function">malloc_stats_print</code>(<em class="parameter"><code></code></em>), followed by a string
- pointer. Please note that doing anything which tries to allocate memory in
- this function is likely to result in a crash or deadlock.</p><p>All messages are prefixed by
- &#8220;<code class="computeroutput">&lt;jemalloc&gt;: </code>&#8221;.</p></div><div class="refsect1"><a name="return_values"></a><h2>RETURN VALUES</h2><div class="refsect2"><a name="idp46949776"></a><h3>Standard API</h3><p>The <code class="function">malloc</code>(<em class="parameter"><code></code></em>) and
- <code class="function">calloc</code>(<em class="parameter"><code></code></em>) functions return a pointer to the
- allocated memory if successful; otherwise a <code class="constant">NULL</code>
- pointer is returned and <code class="varname">errno</code> is set to
- <span class="errorname">ENOMEM</span>.</p><p>The <code class="function">posix_memalign</code>(<em class="parameter"><code></code></em>) function
- returns the value 0 if successful; otherwise it returns an error value.
- The <code class="function">posix_memalign</code>(<em class="parameter"><code></code></em>) function will fail
- if:
- </p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="errorname">EINVAL</span></span></dt><dd><p>The <em class="parameter"><code>alignment</code></em> parameter is
- not a power of 2 at least as large as
- <code class="code">sizeof(<span class="type">void *</span>)</code>.
- </p></dd><dt><span class="term"><span class="errorname">ENOMEM</span></span></dt><dd><p>Memory allocation error.</p></dd></dl></div><p>
- </p><p>The <code class="function">aligned_alloc</code>(<em class="parameter"><code></code></em>) function returns
- a pointer to the allocated memory if successful; otherwise a
- <code class="constant">NULL</code> pointer is returned and
- <code class="varname">errno</code> is set. The
- <code class="function">aligned_alloc</code>(<em class="parameter"><code></code></em>) function will fail if:
- </p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="errorname">EINVAL</span></span></dt><dd><p>The <em class="parameter"><code>alignment</code></em> parameter is
- not a power of 2.
- </p></dd><dt><span class="term"><span class="errorname">ENOMEM</span></span></dt><dd><p>Memory allocation error.</p></dd></dl></div><p>
- </p><p>The <code class="function">realloc</code>(<em class="parameter"><code></code></em>) function returns a
- pointer, possibly identical to <em class="parameter"><code>ptr</code></em>, to the
- allocated memory if successful; otherwise a <code class="constant">NULL</code>
- pointer is returned, and <code class="varname">errno</code> is set to
- <span class="errorname">ENOMEM</span> if the error was the result of an
- allocation failure. The <code class="function">realloc</code>(<em class="parameter"><code></code></em>)
- function always leaves the original buffer intact when an error occurs.
- </p><p>The <code class="function">free</code>(<em class="parameter"><code></code></em>) function returns no
- value.</p></div><div class="refsect2"><a name="idp46974576"></a><h3>Non-standard API</h3><p>The <code class="function">mallocx</code>(<em class="parameter"><code></code></em>) and
- <code class="function">rallocx</code>(<em class="parameter"><code></code></em>) functions return a pointer to
- the allocated memory if successful; otherwise a <code class="constant">NULL</code>
- pointer is returned to indicate insufficient contiguous memory was
- available to service the allocation request. </p><p>The <code class="function">xallocx</code>(<em class="parameter"><code></code></em>) function returns the
- real size of the resulting resized allocation pointed to by
- <em class="parameter"><code>ptr</code></em>, which is a value less than
- <em class="parameter"><code>size</code></em> if the allocation could not be adequately
- grown in place. </p><p>The <code class="function">sallocx</code>(<em class="parameter"><code></code></em>) function returns the
- real size of the allocation pointed to by <em class="parameter"><code>ptr</code></em>.
- </p><p>The <code class="function">nallocx</code>(<em class="parameter"><code></code></em>) returns the real size
- that would result from a successful equivalent
- <code class="function">mallocx</code>(<em class="parameter"><code></code></em>) function call, or zero if
- insufficient memory is available to perform the size computation. </p><p>The <code class="function">mallctl</code>(<em class="parameter"><code></code></em>),
- <code class="function">mallctlnametomib</code>(<em class="parameter"><code></code></em>), and
- <code class="function">mallctlbymib</code>(<em class="parameter"><code></code></em>) functions return 0 on
- success; otherwise they return an error value. The functions will fail
- if:
- </p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><span class="errorname">EINVAL</span></span></dt><dd><p><em class="parameter"><code>newp</code></em> is not
- <code class="constant">NULL</code>, and <em class="parameter"><code>newlen</code></em> is too
- large or too small. Alternatively, <em class="parameter"><code>*oldlenp</code></em>
- is too large or too small; in this case as much data as possible
- are read despite the error.</p></dd><dt><span class="term"><span class="errorname">ENOENT</span></span></dt><dd><p><em class="parameter"><code>name</code></em> or
- <em class="parameter"><code>mib</code></em> specifies an unknown/invalid
- value.</p></dd><dt><span class="term"><span class="errorname">EPERM</span></span></dt><dd><p>Attempt to read or write void value, or attempt to
- write read-only value.</p></dd><dt><span class="term"><span class="errorname">EAGAIN</span></span></dt><dd><p>A memory allocation failure
- occurred.</p></dd><dt><span class="term"><span class="errorname">EFAULT</span></span></dt><dd><p>An interface with side effects failed in some way
- not directly related to <code class="function">mallctl*</code>(<em class="parameter"><code></code></em>)
- read/write processing.</p></dd></dl></div><p>
- </p><p>The <code class="function">malloc_usable_size</code>(<em class="parameter"><code></code></em>) function
- returns the usable size of the allocation pointed to by
- <em class="parameter"><code>ptr</code></em>. </p></div></div><div class="refsect1"><a name="environment"></a><h2>ENVIRONMENT</h2><p>The following environment variable affects the execution of the
- allocation functions:
- </p><div class="variablelist"><dl class="variablelist"><dt><span class="term"><code class="envar">MALLOC_CONF</code></span></dt><dd><p>If the environment variable
- <code class="envar">MALLOC_CONF</code> is set, the characters it contains
- will be interpreted as options.</p></dd></dl></div><p>
- </p></div><div class="refsect1"><a name="examples"></a><h2>EXAMPLES</h2><p>To dump core whenever a problem occurs:
- </p><pre class="screen">ln -s 'abort:true' /etc/malloc.conf</pre><p>
- </p><p>To specify in the source a chunk size that is 16 MiB:
- </p><pre class="programlisting">
-malloc_conf = "lg_chunk:24";</pre></div><div class="refsect1"><a name="see_also"></a><h2>SEE ALSO</h2><p><span class="citerefentry"><span class="refentrytitle">madvise</span>(2)</span>,
- <span class="citerefentry"><span class="refentrytitle">mmap</span>(2)</span>,
- <span class="citerefentry"><span class="refentrytitle">sbrk</span>(2)</span>,
- <span class="citerefentry"><span class="refentrytitle">utrace</span>(2)</span>,
- <span class="citerefentry"><span class="refentrytitle">alloca</span>(3)</span>,
- <span class="citerefentry"><span class="refentrytitle">atexit</span>(3)</span>,
- <span class="citerefentry"><span class="refentrytitle">getpagesize</span>(3)</span></p></div><div class="refsect1"><a name="standards"></a><h2>STANDARDS</h2><p>The <code class="function">malloc</code>(<em class="parameter"><code></code></em>),
- <code class="function">calloc</code>(<em class="parameter"><code></code></em>),
- <code class="function">realloc</code>(<em class="parameter"><code></code></em>), and
- <code class="function">free</code>(<em class="parameter"><code></code></em>) functions conform to ISO/IEC
- 9899:1990 (&#8220;ISO C90&#8221;).</p><p>The <code class="function">posix_memalign</code>(<em class="parameter"><code></code></em>) function conforms
- to IEEE Std 1003.1-2001 (&#8220;POSIX.1&#8221;).</p></div></div></body></html>
diff --git a/deps/jemalloc/doc/jemalloc.xml.in b/deps/jemalloc/doc/jemalloc.xml.in
index 8fc774b18..1e12fd3a8 100644
--- a/deps/jemalloc/doc/jemalloc.xml.in
+++ b/deps/jemalloc/doc/jemalloc.xml.in
@@ -52,7 +52,7 @@
<title>LIBRARY</title>
<para>This manual describes jemalloc @jemalloc_version@. More information
can be found at the <ulink
- url="http://www.canonware.com/jemalloc/">jemalloc website</ulink>.</para>
+ url="http://jemalloc.net/">jemalloc website</ulink>.</para>
</refsect1>
<refsynopsisdiv>
<title>SYNOPSIS</title>
@@ -180,20 +180,20 @@
<refsect2>
<title>Standard API</title>
- <para>The <function>malloc<parameter/></function> function allocates
+ <para>The <function>malloc()</function> function allocates
<parameter>size</parameter> bytes of uninitialized memory. The allocated
space is suitably aligned (after possible pointer coercion) for storage
of any type of object.</para>
- <para>The <function>calloc<parameter/></function> function allocates
+ <para>The <function>calloc()</function> function allocates
space for <parameter>number</parameter> objects, each
<parameter>size</parameter> bytes in length. The result is identical to
- calling <function>malloc<parameter/></function> with an argument of
+ calling <function>malloc()</function> with an argument of
<parameter>number</parameter> * <parameter>size</parameter>, with the
exception that the allocated memory is explicitly initialized to zero
bytes.</para>
- <para>The <function>posix_memalign<parameter/></function> function
+ <para>The <function>posix_memalign()</function> function
allocates <parameter>size</parameter> bytes of memory such that the
allocation's base address is a multiple of
<parameter>alignment</parameter>, and returns the allocation in the value
@@ -201,7 +201,7 @@
<parameter>alignment</parameter> must be a power of 2 at least as large as
<code language="C">sizeof(<type>void *</type>)</code>.</para>
- <para>The <function>aligned_alloc<parameter/></function> function
+ <para>The <function>aligned_alloc()</function> function
allocates <parameter>size</parameter> bytes of memory such that the
allocation's base address is a multiple of
<parameter>alignment</parameter>. The requested
@@ -209,7 +209,7 @@
undefined if <parameter>size</parameter> is not an integral multiple of
<parameter>alignment</parameter>.</para>
- <para>The <function>realloc<parameter/></function> function changes the
+ <para>The <function>realloc()</function> function changes the
size of the previously allocated memory referenced by
<parameter>ptr</parameter> to <parameter>size</parameter> bytes. The
contents of the memory are unchanged up to the lesser of the new and old
@@ -217,26 +217,26 @@
portion of the memory are undefined. Upon success, the memory referenced
by <parameter>ptr</parameter> is freed and a pointer to the newly
allocated memory is returned. Note that
- <function>realloc<parameter/></function> may move the memory allocation,
+ <function>realloc()</function> may move the memory allocation,
resulting in a different return value than <parameter>ptr</parameter>.
If <parameter>ptr</parameter> is <constant>NULL</constant>, the
- <function>realloc<parameter/></function> function behaves identically to
- <function>malloc<parameter/></function> for the specified size.</para>
+ <function>realloc()</function> function behaves identically to
+ <function>malloc()</function> for the specified size.</para>
- <para>The <function>free<parameter/></function> function causes the
+ <para>The <function>free()</function> function causes the
allocated memory referenced by <parameter>ptr</parameter> to be made
available for future allocations. If <parameter>ptr</parameter> is
<constant>NULL</constant>, no action occurs.</para>
</refsect2>
<refsect2>
<title>Non-standard API</title>
- <para>The <function>mallocx<parameter/></function>,
- <function>rallocx<parameter/></function>,
- <function>xallocx<parameter/></function>,
- <function>sallocx<parameter/></function>,
- <function>dallocx<parameter/></function>,
- <function>sdallocx<parameter/></function>, and
- <function>nallocx<parameter/></function> functions all have a
+ <para>The <function>mallocx()</function>,
+ <function>rallocx()</function>,
+ <function>xallocx()</function>,
+ <function>sallocx()</function>,
+ <function>dallocx()</function>,
+ <function>sdallocx()</function>, and
+ <function>nallocx()</function> functions all have a
<parameter>flags</parameter> argument that can be used to specify
options. The functions only check the options that are contextually
relevant. Use bitwise or (<code language="C">|</code>) operations to
@@ -307,21 +307,19 @@
</variablelist>
</para>
- <para>The <function>mallocx<parameter/></function> function allocates at
+ <para>The <function>mallocx()</function> function allocates at
least <parameter>size</parameter> bytes of memory, and returns a pointer
to the base address of the allocation. Behavior is undefined if
- <parameter>size</parameter> is <constant>0</constant>, or if request size
- overflows due to size class and/or alignment constraints.</para>
+ <parameter>size</parameter> is <constant>0</constant>.</para>
- <para>The <function>rallocx<parameter/></function> function resizes the
+ <para>The <function>rallocx()</function> function resizes the
allocation at <parameter>ptr</parameter> to be at least
<parameter>size</parameter> bytes, and returns a pointer to the base
address of the resulting allocation, which may or may not have moved from
its original location. Behavior is undefined if
- <parameter>size</parameter> is <constant>0</constant>, or if request size
- overflows due to size class and/or alignment constraints.</para>
+ <parameter>size</parameter> is <constant>0</constant>.</para>
- <para>The <function>xallocx<parameter/></function> function resizes the
+ <para>The <function>xallocx()</function> function resizes the
allocation at <parameter>ptr</parameter> in place to be at least
<parameter>size</parameter> bytes, and returns the real size of the
allocation. If <parameter>extra</parameter> is non-zero, an attempt is
@@ -334,32 +332,32 @@
language="C">(<parameter>size</parameter> + <parameter>extra</parameter>
&gt; <constant>SIZE_T_MAX</constant>)</code>.</para>
- <para>The <function>sallocx<parameter/></function> function returns the
+ <para>The <function>sallocx()</function> function returns the
real size of the allocation at <parameter>ptr</parameter>.</para>
- <para>The <function>dallocx<parameter/></function> function causes the
+ <para>The <function>dallocx()</function> function causes the
memory referenced by <parameter>ptr</parameter> to be made available for
future allocations.</para>
- <para>The <function>sdallocx<parameter/></function> function is an
- extension of <function>dallocx<parameter/></function> with a
+ <para>The <function>sdallocx()</function> function is an
+ extension of <function>dallocx()</function> with a
<parameter>size</parameter> parameter to allow the caller to pass in the
allocation size as an optimization. The minimum valid input size is the
original requested size of the allocation, and the maximum valid input
size is the corresponding value returned by
- <function>nallocx<parameter/></function> or
- <function>sallocx<parameter/></function>.</para>
+ <function>nallocx()</function> or
+ <function>sallocx()</function>.</para>
- <para>The <function>nallocx<parameter/></function> function allocates no
+ <para>The <function>nallocx()</function> function allocates no
memory, but it performs the same size computation as the
- <function>mallocx<parameter/></function> function, and returns the real
+ <function>mallocx()</function> function, and returns the real
size of the allocation that would result from the equivalent
- <function>mallocx<parameter/></function> function call. Behavior is
- undefined if <parameter>size</parameter> is <constant>0</constant>, or if
- request size overflows due to size class and/or alignment
- constraints.</para>
+ <function>mallocx()</function> function call, or
+ <constant>0</constant> if the inputs exceed the maximum supported size
+ class and/or alignment. Behavior is undefined if
+ <parameter>size</parameter> is <constant>0</constant>.</para>
- <para>The <function>mallctl<parameter/></function> function provides a
+ <para>The <function>mallctl()</function> function provides a
general interface for introspecting the memory allocator, as well as
setting modifiable parameters and triggering actions. The
period-separated <parameter>name</parameter> argument specifies a
@@ -374,12 +372,12 @@
<parameter>newlen</parameter>; otherwise pass <constant>NULL</constant>
and <constant>0</constant>.</para>
- <para>The <function>mallctlnametomib<parameter/></function> function
+ <para>The <function>mallctlnametomib()</function> function
provides a way to avoid repeated name lookups for applications that
repeatedly query the same portion of the namespace, by translating a name
- to a &ldquo;Management Information Base&rdquo; (MIB) that can be passed
- repeatedly to <function>mallctlbymib<parameter/></function>. Upon
- successful return from <function>mallctlnametomib<parameter/></function>,
+ to a <quote>Management Information Base</quote> (MIB) that can be passed
+ repeatedly to <function>mallctlbymib()</function>. Upon
+ successful return from <function>mallctlnametomib()</function>,
<parameter>mibp</parameter> contains an array of
<parameter>*miblenp</parameter> integers, where
<parameter>*miblenp</parameter> is the lesser of the number of components
@@ -408,43 +406,47 @@ for (i = 0; i < nbins; i++) {
mib[2] = i;
len = sizeof(bin_size);
- mallctlbymib(mib, miblen, &bin_size, &len, NULL, 0);
+ mallctlbymib(mib, miblen, (void *)&bin_size, &len, NULL, 0);
/* Do something with bin_size... */
}]]></programlisting></para>
- <para>The <function>malloc_stats_print<parameter/></function> function
- writes human-readable summary statistics via the
- <parameter>write_cb</parameter> callback function pointer and
- <parameter>cbopaque</parameter> data passed to
- <parameter>write_cb</parameter>, or
- <function>malloc_message<parameter/></function> if
- <parameter>write_cb</parameter> is <constant>NULL</constant>. This
- function can be called repeatedly. General information that never
- changes during execution can be omitted by specifying "g" as a character
+ <varlistentry id="malloc_stats_print_opts">
+ </varlistentry>
+ <para>The <function>malloc_stats_print()</function> function writes
+ summary statistics via the <parameter>write_cb</parameter> callback
+ function pointer and <parameter>cbopaque</parameter> data passed to
+ <parameter>write_cb</parameter>, or <function>malloc_message()</function>
+ if <parameter>write_cb</parameter> is <constant>NULL</constant>. The
+ statistics are presented in human-readable form unless <quote>J</quote> is
+ specified as a character within the <parameter>opts</parameter> string, in
+ which case the statistics are presented in <ulink
+ url="http://www.json.org/">JSON format</ulink>. This function can be
+ called repeatedly. General information that never changes during
+ execution can be omitted by specifying <quote>g</quote> as a character
within the <parameter>opts</parameter> string. Note that
- <function>malloc_message<parameter/></function> uses the
- <function>mallctl*<parameter/></function> functions internally, so
- inconsistent statistics can be reported if multiple threads use these
- functions simultaneously. If <option>--enable-stats</option> is
- specified during configuration, &ldquo;m&rdquo; and &ldquo;a&rdquo; can
- be specified to omit merged arena and per arena statistics, respectively;
- &ldquo;b&rdquo;, &ldquo;l&rdquo;, and &ldquo;h&rdquo; can be specified to
- omit per size class statistics for bins, large objects, and huge objects,
- respectively. Unrecognized characters are silently ignored. Note that
+ <function>malloc_message()</function> uses the
+ <function>mallctl*()</function> functions internally, so inconsistent
+ statistics can be reported if multiple threads use these functions
+ simultaneously. If <option>--enable-stats</option> is specified during
+ configuration, <quote>m</quote>, <quote>d</quote>, and <quote>a</quote>
+ can be specified to omit merged arena, destroyed merged arena, and per
+ arena statistics, respectively; <quote>b</quote> and <quote>l</quote> can
+ be specified to omit per size class statistics for bins and large objects,
+ respectively; <quote>x</quote> can be specified to omit all mutex
+ statistics. Unrecognized characters are silently ignored. Note that
thread caching may prevent some statistics from being completely up to
date, since extra locking would be required to merge counters that track
- thread cache operations.
- </para>
+ thread cache operations.</para>
- <para>The <function>malloc_usable_size<parameter/></function> function
+ <para>The <function>malloc_usable_size()</function> function
returns the usable size of the allocation pointed to by
<parameter>ptr</parameter>. The return value may be larger than the size
that was requested during allocation. The
- <function>malloc_usable_size<parameter/></function> function is not a
- mechanism for in-place <function>realloc<parameter/></function>; rather
+ <function>malloc_usable_size()</function> function is not a
+ mechanism for in-place <function>realloc()</function>; rather
it is provided solely as a tool for introspection purposes. Any
discrepancy between the requested allocation size and the size reported
- by <function>malloc_usable_size<parameter/></function> should not be
+ by <function>malloc_usable_size()</function> should not be
depended on, since such behavior is entirely implementation-dependent.
</para>
</refsect2>
@@ -455,19 +457,20 @@ for (i = 0; i < nbins; i++) {
routines, the allocator initializes its internals based in part on various
options that can be specified at compile- or run-time.</para>
- <para>The string pointed to by the global variable
- <varname>malloc_conf</varname>, the &ldquo;name&rdquo; of the file
- referenced by the symbolic link named <filename
- class="symlink">/etc/malloc.conf</filename>, and the value of the
+ <para>The string specified via <option>--with-malloc-conf</option>, the
+ string pointed to by the global variable <varname>malloc_conf</varname>, the
+ <quote>name</quote> of the file referenced by the symbolic link named
+ <filename class="symlink">/etc/malloc.conf</filename>, and the value of the
environment variable <envar>MALLOC_CONF</envar>, will be interpreted, in
that order, from left to right as options. Note that
<varname>malloc_conf</varname> may be read before
- <function>main<parameter/></function> is entered, so the declaration of
+ <function>main()</function> is entered, so the declaration of
<varname>malloc_conf</varname> should specify an initializer that contains
- the final value to be read by jemalloc. <varname>malloc_conf</varname> is
- a compile-time setting, whereas <filename
- class="symlink">/etc/malloc.conf</filename> and <envar>MALLOC_CONF</envar>
- can be safely set any time prior to program invocation.</para>
+ the final value to be read by jemalloc. <option>--with-malloc-conf</option>
+ and <varname>malloc_conf</varname> are compile-time mechanisms, whereas
+ <filename class="symlink">/etc/malloc.conf</filename> and
+ <envar>MALLOC_CONF</envar> can be safely set any time prior to program
+ invocation.</para>
<para>An options string is a comma-separated list of option:value pairs.
There is one key corresponding to each <link
@@ -509,33 +512,21 @@ for (i = 0; i < nbins; i++) {
sense to reduce the number of arenas if an application does not make much
use of the allocation functions.</para>
- <para>In addition to multiple arenas, unless
- <option>--disable-tcache</option> is specified during configuration, this
- allocator supports thread-specific caching for small and large objects, in
- order to make it possible to completely avoid synchronization for most
- allocation requests. Such caching allows very fast allocation in the
- common case, but it increases memory usage and fragmentation, since a
- bounded number of objects can remain allocated in each thread cache.</para>
-
- <para>Memory is conceptually broken into equal-sized chunks, where the
- chunk size is a power of two that is greater than the page size. Chunks
- are always aligned to multiples of the chunk size. This alignment makes it
- possible to find metadata for user objects very quickly.</para>
-
- <para>User objects are broken into three categories according to size:
- small, large, and huge. Small and large objects are managed entirely by
- arenas; huge objects are additionally aggregated in a single data structure
- that is shared by all threads. Huge objects are typically used by
- applications infrequently enough that this single data structure is not a
- scalability issue.</para>
-
- <para>Each chunk that is managed by an arena tracks its contents as runs of
- contiguous pages (unused, backing a set of small objects, or backing one
- large object). The combination of chunk alignment and chunk page maps
- makes it possible to determine all metadata regarding small and large
- allocations in constant time.</para>
-
- <para>Small objects are managed in groups by page runs. Each run maintains
+ <para>In addition to multiple arenas, this allocator supports
+ thread-specific caching, in order to make it possible to completely avoid
+ synchronization for most allocation requests. Such caching allows very fast
+ allocation in the common case, but it increases memory usage and
+ fragmentation, since a bounded number of objects can remain allocated in
+ each thread cache.</para>
+
+ <para>Memory is conceptually broken into extents. Extents are always
+ aligned to multiples of the page size. This alignment makes it possible to
+ find metadata for user objects quickly. User objects are broken into two
+ categories according to size: small and large. Contiguous small objects
+ comprise a slab, which resides within a single extent, whereas large objects
+ each have their own extents backing them.</para>
+
+ <para>Small objects are managed in groups by slabs. Each slab maintains
a bitmap to track which regions are in use. Allocation requests that are no
more than half the quantum (8 or 16, depending on architecture) are rounded
up to the nearest power of two that is at least <code
@@ -543,11 +534,9 @@ for (i = 0; i < nbins; i++) {
classes are multiples of the quantum, spaced such that there are four size
classes for each doubling in size, which limits internal fragmentation to
approximately 20% for all but the smallest size classes. Small size classes
- are smaller than four times the page size, large size classes are smaller
- than the chunk size (see the <link
- linkend="opt.lg_chunk"><mallctl>opt.lg_chunk</mallctl></link> option), and
- huge size classes extend from the chunk size up to one size class less than
- the full address space size.</para>
+ are smaller than four times the page size, and large size classes extend
+ from four times the page size up to the largest size class that does not
+ exceed <constant>PTRDIFF_MAX</constant>.</para>
<para>Allocations are packed tightly together, which can be an issue for
multi-threaded applications. If you need to assure that allocations do not
@@ -555,30 +544,28 @@ for (i = 0; i < nbins; i++) {
nearest multiple of the cacheline size, or specify cacheline alignment when
allocating.</para>
- <para>The <function>realloc<parameter/></function>,
- <function>rallocx<parameter/></function>, and
- <function>xallocx<parameter/></function> functions may resize allocations
+ <para>The <function>realloc()</function>,
+ <function>rallocx()</function>, and
+ <function>xallocx()</function> functions may resize allocations
without moving them under limited circumstances. Unlike the
- <function>*allocx<parameter/></function> API, the standard API does not
+ <function>*allocx()</function> API, the standard API does not
officially round up the usable size of an allocation to the nearest size
class, so technically it is necessary to call
- <function>realloc<parameter/></function> to grow e.g. a 9-byte allocation to
+ <function>realloc()</function> to grow e.g. a 9-byte allocation to
16 bytes, or shrink a 16-byte allocation to 9 bytes. Growth and shrinkage
trivially succeeds in place as long as the pre-size and post-size both round
up to the same size class. No other API guarantees are made regarding
in-place resizing, but the current implementation also tries to resize large
- and huge allocations in place, as long as the pre-size and post-size are
- both large or both huge. In such cases shrinkage always succeeds for large
- size classes, but for huge size classes the chunk allocator must support
- splitting (see <link
- linkend="arena.i.chunk_hooks"><mallctl>arena.&lt;i&gt;.chunk_hooks</mallctl></link>).
- Growth only succeeds if the trailing memory is currently available, and
- additionally for huge size classes the chunk allocator must support
- merging.</para>
-
- <para>Assuming 2 MiB chunks, 4 KiB pages, and a 16-byte quantum on a
- 64-bit system, the size classes in each category are as shown in <xref
- linkend="size_classes" xrefstyle="template:Table %n"/>.</para>
+ allocations in place, as long as the pre-size and post-size are both large.
+ For shrinkage to succeed, the extent allocator must support splitting (see
+ <link
+ linkend="arena.i.extent_hooks"><mallctl>arena.&lt;i&gt;.extent_hooks</mallctl></link>).
+ Growth only succeeds if the trailing memory is currently available, and the
+ extent allocator supports merging.</para>
+
+ <para>Assuming 4 KiB pages and a 16-byte quantum on a 64-bit system, the
+ size classes in each category are as shown in <xref linkend="size_classes"
+ xrefstyle="template:Table %n"/>.</para>
<table xml:id="size_classes" frame="all">
<title>Size classes</title>
@@ -632,7 +619,7 @@ for (i = 0; i < nbins; i++) {
<entry>[10 KiB, 12 KiB, 14 KiB]</entry>
</row>
<row>
- <entry morerows="7">Large</entry>
+ <entry morerows="15">Large</entry>
<entry>2 KiB</entry>
<entry>[16 KiB]</entry>
</row>
@@ -662,12 +649,7 @@ for (i = 0; i < nbins; i++) {
</row>
<row>
<entry>256 KiB</entry>
- <entry>[1280 KiB, 1536 KiB, 1792 KiB]</entry>
- </row>
- <row>
- <entry morerows="6">Huge</entry>
- <entry>256 KiB</entry>
- <entry>[2 MiB]</entry>
+ <entry>[1280 KiB, 1536 KiB, 1792 KiB, 2 MiB]</entry>
</row>
<row>
<entry>512 KiB</entry>
@@ -693,6 +675,14 @@ for (i = 0; i < nbins; i++) {
<entry>...</entry>
<entry>...</entry>
</row>
+ <row>
+ <entry>512 PiB</entry>
+ <entry>[2560 PiB, 3 EiB, 3584 PiB, 4 EiB]</entry>
+ </row>
+ <row>
+ <entry>1 EiB</entry>
+ <entry>[5 EiB, 6 EiB, 7 EiB]</entry>
+ </row>
</tbody>
</tgroup>
</table>
@@ -700,19 +690,32 @@ for (i = 0; i < nbins; i++) {
<refsect1 id="mallctl_namespace">
<title>MALLCTL NAMESPACE</title>
<para>The following names are defined in the namespace accessible via the
- <function>mallctl*<parameter/></function> functions. Value types are
- specified in parentheses, their readable/writable statuses are encoded as
+ <function>mallctl*()</function> functions. Value types are specified in
+ parentheses, their readable/writable statuses are encoded as
<literal>rw</literal>, <literal>r-</literal>, <literal>-w</literal>, or
<literal>--</literal>, and required build configuration flags follow, if
any. A name element encoded as <literal>&lt;i&gt;</literal> or
<literal>&lt;j&gt;</literal> indicates an integer component, where the
integer varies from 0 to some upper value that must be determined via
- introspection. In the case of <mallctl>stats.arenas.&lt;i&gt;.*</mallctl>,
- <literal>&lt;i&gt;</literal> equal to <link
- linkend="arenas.narenas"><mallctl>arenas.narenas</mallctl></link> can be
- used to access the summation of statistics from all arenas. Take special
- note of the <link linkend="epoch"><mallctl>epoch</mallctl></link> mallctl,
- which controls refreshing of cached dynamic statistics.</para>
+ introspection. In the case of <mallctl>stats.arenas.&lt;i&gt;.*</mallctl>
+ and <mallctl>arena.&lt;i&gt;.{initialized,purge,decay,dss}</mallctl>,
+ <literal>&lt;i&gt;</literal> equal to
+ <constant>MALLCTL_ARENAS_ALL</constant> can be used to operate on all arenas
+ or access the summation of statistics from all arenas; similarly
+ <literal>&lt;i&gt;</literal> equal to
+ <constant>MALLCTL_ARENAS_DESTROYED</constant> can be used to access the
+ summation of statistics from all destroyed arenas. These constants can be
+ utilized either via <function>mallctlnametomib()</function> followed by
+ <function>mallctlbymib()</function>, or via code such as the following:
+ <programlisting language="C"><![CDATA[
+#define STRINGIFY_HELPER(x) #x
+#define STRINGIFY(x) STRINGIFY_HELPER(x)
+
+mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".decay",
+ NULL, NULL, NULL, 0);]]></programlisting>
+ Take special note of the <link
+ linkend="epoch"><mallctl>epoch</mallctl></link> mallctl, which controls
+ refreshing of cached dynamic statistics.</para>
<variablelist>
<varlistentry id="version">
@@ -731,11 +734,45 @@ for (i = 0; i < nbins; i++) {
<literal>rw</literal>
</term>
<listitem><para>If a value is passed in, refresh the data from which
- the <function>mallctl*<parameter/></function> functions report values,
+ the <function>mallctl*()</function> functions report values,
and increment the epoch. Return the current epoch. This is useful for
detecting whether another thread caused a refresh.</para></listitem>
</varlistentry>
+ <varlistentry id="background_thread">
+ <term>
+ <mallctl>background_thread</mallctl>
+ (<type>bool</type>)
+ <literal>rw</literal>
+ </term>
+ <listitem><para>Enable/disable internal background worker threads. When
+ set to true, background threads are created on demand (the number of
+ background threads will be no more than the number of CPUs or active
+ arenas). Threads run periodically, and handle <link
+ linkend="arena.i.decay">purging</link> asynchronously. When switching
+ off, background threads are terminated synchronously. Note that after
+ <citerefentry><refentrytitle>fork</refentrytitle><manvolnum>2</manvolnum></citerefentry>
+ function, the state in the child process will be disabled regardless
+ the state in parent process. See <link
+ linkend="stats.background_thread.num_threads"><mallctl>stats.background_thread</mallctl></link>
+ for related stats. <link
+ linkend="opt.background_thread"><mallctl>opt.background_thread</mallctl></link>
+ can be used to set the default option. This option is only available on
+ selected pthread-based platforms.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="max_background_threads">
+ <term>
+ <mallctl>max_background_threads</mallctl>
+ (<type>size_t</type>)
+ <literal>rw</literal>
+ </term>
+ <listitem><para>Maximum number of background worker threads that will
+ be created. This value is capped at <link
+ linkend="opt.max_background_threads"><mallctl>opt.max_background_threads</mallctl></link> at
+ startup.</para></listitem>
+ </varlistentry>
+
<varlistentry id="config.cache_oblivious">
<term>
<mallctl>config.cache_oblivious</mallctl>
@@ -776,14 +813,15 @@ for (i = 0; i < nbins; i++) {
during build configuration.</para></listitem>
</varlistentry>
- <varlistentry id="config.munmap">
+ <varlistentry id="config.malloc_conf">
<term>
- <mallctl>config.munmap</mallctl>
- (<type>bool</type>)
+ <mallctl>config.malloc_conf</mallctl>
+ (<type>const char *</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--enable-munmap</option> was specified during
- build configuration.</para></listitem>
+ <listitem><para>Embedded configure-time-specified run-time options
+ string, empty unless <option>--with-malloc-conf</option> was specified
+ during build configuration.</para></listitem>
</varlistentry>
<varlistentry id="config.prof">
@@ -826,68 +864,94 @@ for (i = 0; i < nbins; i++) {
build configuration.</para></listitem>
</varlistentry>
- <varlistentry id="config.tcache">
+
+ <varlistentry id="config.utrace">
<term>
- <mallctl>config.tcache</mallctl>
+ <mallctl>config.utrace</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--disable-tcache</option> was not specified
- during build configuration.</para></listitem>
+ <listitem><para><option>--enable-utrace</option> was specified during
+ build configuration.</para></listitem>
</varlistentry>
- <varlistentry id="config.tls">
+ <varlistentry id="config.xmalloc">
<term>
- <mallctl>config.tls</mallctl>
+ <mallctl>config.xmalloc</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--disable-tls</option> was not specified during
+ <listitem><para><option>--enable-xmalloc</option> was specified during
build configuration.</para></listitem>
</varlistentry>
- <varlistentry id="config.utrace">
+ <varlistentry id="opt.abort">
<term>
- <mallctl>config.utrace</mallctl>
+ <mallctl>opt.abort</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--enable-utrace</option> was specified during
- build configuration.</para></listitem>
+ <listitem><para>Abort-on-warning enabled/disabled. If true, most
+ warnings are fatal. Note that runtime option warnings are not included
+ (see <link
+ linkend="opt.abort_conf"><mallctl>opt.abort_conf</mallctl></link> for
+ that). The process will call
+ <citerefentry><refentrytitle>abort</refentrytitle>
+ <manvolnum>3</manvolnum></citerefentry> in these cases. This option is
+ disabled by default unless <option>--enable-debug</option> is
+ specified during configuration, in which case it is enabled by default.
+ </para></listitem>
</varlistentry>
- <varlistentry id="config.valgrind">
+ <varlistentry id="opt.abort_conf">
<term>
- <mallctl>config.valgrind</mallctl>
+ <mallctl>opt.abort_conf</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--enable-valgrind</option> was specified during
- build configuration.</para></listitem>
+ <listitem><para>Abort-on-invalid-configuration enabled/disabled. If
+ true, invalid runtime options are fatal. The process will call
+ <citerefentry><refentrytitle>abort</refentrytitle>
+ <manvolnum>3</manvolnum></citerefentry> in these cases. This option is
+ disabled by default unless <option>--enable-debug</option> is
+ specified during configuration, in which case it is enabled by default.
+ </para></listitem>
</varlistentry>
- <varlistentry id="config.xmalloc">
+ <varlistentry id="opt.metadata_thp">
<term>
- <mallctl>config.xmalloc</mallctl>
- (<type>bool</type>)
+ <mallctl>opt.metadata_thp</mallctl>
+ (<type>const char *</type>)
<literal>r-</literal>
</term>
- <listitem><para><option>--enable-xmalloc</option> was specified during
- build configuration.</para></listitem>
+ <listitem><para>Controls whether to allow jemalloc to use transparent
+ huge page (THP) for internal metadata (see <link
+ linkend="stats.metadata">stats.metadata</link>). <quote>always</quote>
+ allows such usage. <quote>auto</quote> uses no THP initially, but may
+ begin to do so when metadata usage reaches certain level. The default
+ is <quote>disabled</quote>.</para></listitem>
</varlistentry>
- <varlistentry id="opt.abort">
+ <varlistentry id="opt.retain">
<term>
- <mallctl>opt.abort</mallctl>
+ <mallctl>opt.retain</mallctl>
(<type>bool</type>)
<literal>r-</literal>
</term>
- <listitem><para>Abort-on-warning enabled/disabled. If true, most
- warnings are fatal. The process will call
- <citerefentry><refentrytitle>abort</refentrytitle>
- <manvolnum>3</manvolnum></citerefentry> in these cases. This option is
- disabled by default unless <option>--enable-debug</option> is
- specified during configuration, in which case it is enabled by default.
+ <listitem><para>If true, retain unused virtual memory for later reuse
+ rather than discarding it by calling
+ <citerefentry><refentrytitle>munmap</refentrytitle>
+ <manvolnum>2</manvolnum></citerefentry> or equivalent (see <link
+ linkend="stats.retained">stats.retained</link> for related details).
+ This option is disabled by default unless discarding virtual memory is
+ known to trigger
+ platform-specific performance problems, e.g. for [64-bit] Linux, which
+ has a quirk in its virtual memory allocation algorithm that causes
+ semi-permanent VM map holes under normal jemalloc operation. Although
+ <citerefentry><refentrytitle>munmap</refentrytitle>
+ <manvolnum>2</manvolnum></citerefentry> causes issues on 32-bit Linux as
+ well, retaining virtual memory for 32-bit Linux is disabled by default
+ due to the practical possibility of address space exhaustion.
</para></listitem>
</varlistentry>
@@ -904,61 +968,136 @@ for (i = 0; i < nbins; i++) {
settings are supported if
<citerefentry><refentrytitle>sbrk</refentrytitle>
<manvolnum>2</manvolnum></citerefentry> is supported by the operating
- system: &ldquo;disabled&rdquo;, &ldquo;primary&rdquo;, and
- &ldquo;secondary&rdquo;; otherwise only &ldquo;disabled&rdquo; is
- supported. The default is &ldquo;secondary&rdquo; if
+ system: <quote>disabled</quote>, <quote>primary</quote>, and
+ <quote>secondary</quote>; otherwise only <quote>disabled</quote> is
+ supported. The default is <quote>secondary</quote> if
<citerefentry><refentrytitle>sbrk</refentrytitle>
<manvolnum>2</manvolnum></citerefentry> is supported by the operating
- system; &ldquo;disabled&rdquo; otherwise.
+ system; <quote>disabled</quote> otherwise.
</para></listitem>
</varlistentry>
- <varlistentry id="opt.lg_chunk">
+ <varlistentry id="opt.narenas">
<term>
- <mallctl>opt.lg_chunk</mallctl>
- (<type>size_t</type>)
+ <mallctl>opt.narenas</mallctl>
+ (<type>unsigned</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Maximum number of arenas to use for automatic
+ multiplexing of threads and arenas. The default is four times the
+ number of CPUs, or one if there is a single CPU.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="opt.percpu_arena">
+ <term>
+ <mallctl>opt.percpu_arena</mallctl>
+ (<type>const char *</type>)
<literal>r-</literal>
</term>
- <listitem><para>Virtual memory chunk size (log base 2). If a chunk
- size outside the supported size range is specified, the size is
- silently clipped to the minimum/maximum supported size. The default
- chunk size is 2 MiB (2^21).
+ <listitem><para>Per CPU arena mode. Use the <quote>percpu</quote>
+ setting to enable this feature, which uses number of CPUs to determine
+ number of arenas, and bind threads to arenas dynamically based on the
+ CPU the thread runs on currently. <quote>phycpu</quote> setting uses
+ one arena per physical CPU, which means the two hyper threads on the
+ same CPU share one arena. Note that no runtime checking regarding the
+ availability of hyper threading is done at the moment. When set to
+ <quote>disabled</quote>, narenas and thread to arena association will
+ not be impacted by this option. The default is <quote>disabled</quote>.
</para></listitem>
</varlistentry>
- <varlistentry id="opt.narenas">
+ <varlistentry id="opt.background_thread">
<term>
- <mallctl>opt.narenas</mallctl>
- (<type>size_t</type>)
+ <mallctl>opt.background_thread</mallctl>
+ (<type>const bool</type>)
<literal>r-</literal>
</term>
- <listitem><para>Maximum number of arenas to use for automatic
- multiplexing of threads and arenas. The default is four times the
- number of CPUs, or one if there is a single CPU.</para></listitem>
+ <listitem><para>Internal background worker threads enabled/disabled.
+ Because of potential circular dependencies, enabling background thread
+ using this option may cause crash or deadlock during initialization. For
+ a reliable way to use this feature, see <link
+ linkend="background_thread">background_thread</link> for dynamic control
+ options and details. This option is disabled by
+ default.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="opt.max_background_threads">
+ <term>
+ <mallctl>opt.max_background_threads</mallctl>
+ (<type>const size_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Maximum number of background threads that will be created
+ if <link linkend="background_thread">background_thread</link> is set.
+ Defaults to number of cpus.</para></listitem>
</varlistentry>
- <varlistentry id="opt.lg_dirty_mult">
+ <varlistentry id="opt.dirty_decay_ms">
<term>
- <mallctl>opt.lg_dirty_mult</mallctl>
+ <mallctl>opt.dirty_decay_ms</mallctl>
(<type>ssize_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Per-arena minimum ratio (log base 2) of active to dirty
- pages. Some dirty unused pages may be allowed to accumulate, within
- the limit set by the ratio (or one chunk worth of dirty pages,
- whichever is greater), before informing the kernel about some of those
- pages via <citerefentry><refentrytitle>madvise</refentrytitle>
- <manvolnum>2</manvolnum></citerefentry> or a similar system call. This
- provides the kernel with sufficient information to recycle dirty pages
- if physical memory becomes scarce and the pages remain unused. The
- default minimum ratio is 8:1 (2^3:1); an option value of -1 will
- disable dirty page purging. See <link
- linkend="arenas.lg_dirty_mult"><mallctl>arenas.lg_dirty_mult</mallctl></link>
+ <listitem><para>Approximate time in milliseconds from the creation of a
+ set of unused dirty pages until an equivalent set of unused dirty pages
+ is purged (i.e. converted to muzzy via e.g.
+ <function>madvise(<parameter>...</parameter><parameter><constant>MADV_FREE</constant></parameter>)</function>
+ if supported by the operating system, or converted to clean otherwise)
+ and/or reused. Dirty pages are defined as previously having been
+ potentially written to by the application, and therefore consuming
+ physical memory, yet having no current use. The pages are incrementally
+ purged according to a sigmoidal decay curve that starts and ends with
+ zero purge rate. A decay time of 0 causes all unused dirty pages to be
+ purged immediately upon creation. A decay time of -1 disables purging.
+ The default decay time is 10 seconds. See <link
+ linkend="arenas.dirty_decay_ms"><mallctl>arenas.dirty_decay_ms</mallctl></link>
and <link
- linkend="arena.i.lg_dirty_mult"><mallctl>arena.&lt;i&gt;.lg_dirty_mult</mallctl></link>
+ linkend="arena.i.dirty_decay_ms"><mallctl>arena.&lt;i&gt;.dirty_decay_ms</mallctl></link>
+ for related dynamic control options. See <link
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzzy_decay_ms</mallctl></link>
+ for a description of muzzy pages.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="opt.muzzy_decay_ms">
+ <term>
+ <mallctl>opt.muzzy_decay_ms</mallctl>
+ (<type>ssize_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Approximate time in milliseconds from the creation of a
+ set of unused muzzy pages until an equivalent set of unused muzzy pages
+ is purged (i.e. converted to clean) and/or reused. Muzzy pages are
+ defined as previously having been unused dirty pages that were
+ subsequently purged in a manner that left them subject to the
+ reclamation whims of the operating system (e.g.
+ <function>madvise(<parameter>...</parameter><parameter><constant>MADV_FREE</constant></parameter>)</function>),
+ and therefore in an indeterminate state. The pages are incrementally
+ purged according to a sigmoidal decay curve that starts and ends with
+ zero purge rate. A decay time of 0 causes all unused muzzy pages to be
+ purged immediately upon creation. A decay time of -1 disables purging.
+ The default decay time is 10 seconds. See <link
+ linkend="arenas.muzzy_decay_ms"><mallctl>arenas.muzzy_decay_ms</mallctl></link>
+ and <link
+ linkend="arena.i.muzzy_decay_ms"><mallctl>arena.&lt;i&gt;.muzzy_decay_ms</mallctl></link>
for related dynamic control options.</para></listitem>
</varlistentry>
+ <varlistentry id="opt.lg_extent_max_active_fit">
+ <term>
+ <mallctl>opt.lg_extent_max_active_fit</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>When reusing dirty extents, this determines the (log
+ base 2 of the) maximum ratio between the size of the active extent
+ selected (to split off from) and the size of the requested allocation.
+ This prevents the splitting of large active extents for smaller
+ allocations, which can reduce fragmentation over the long run
+ (especially for non-active extents). Lower value may reduce
+ fragmentation, at the cost of extra active extents. The default value
+ is 6, which gives a maximum ratio of 64 (2^6).</para></listitem>
+ </varlistentry>
+
<varlistentry id="opt.stats_print">
<term>
<mallctl>opt.stats_print</mallctl>
@@ -966,82 +1105,61 @@ for (i = 0; i < nbins; i++) {
<literal>r-</literal>
</term>
<listitem><para>Enable/disable statistics printing at exit. If
- enabled, the <function>malloc_stats_print<parameter/></function>
+ enabled, the <function>malloc_stats_print()</function>
function is called at program exit via an
<citerefentry><refentrytitle>atexit</refentrytitle>
- <manvolnum>3</manvolnum></citerefentry> function. If
+ <manvolnum>3</manvolnum></citerefentry> function. <link
+ linkend="opt.stats_print_opts"><mallctl>opt.stats_print_opts</mallctl></link>
+ can be combined to specify output options. If
<option>--enable-stats</option> is specified during configuration, this
has the potential to cause deadlock for a multi-threaded process that
exits while one or more threads are executing in the memory allocation
- functions. Furthermore, <function>atexit<parameter/></function> may
+ functions. Furthermore, <function>atexit()</function> may
allocate memory during application initialization and then deadlock
internally when jemalloc in turn calls
- <function>atexit<parameter/></function>, so this option is not
- univerally usable (though the application can register its own
- <function>atexit<parameter/></function> function with equivalent
+ <function>atexit()</function>, so this option is not
+ universally usable (though the application can register its own
+ <function>atexit()</function> function with equivalent
functionality). Therefore, this option should only be used with care;
it is primarily intended as a performance tuning aid during application
development. This option is disabled by default.</para></listitem>
</varlistentry>
- <varlistentry id="opt.junk">
+ <varlistentry id="opt.stats_print_opts">
<term>
- <mallctl>opt.junk</mallctl>
+ <mallctl>opt.stats_print_opts</mallctl>
(<type>const char *</type>)
<literal>r-</literal>
- [<option>--enable-fill</option>]
</term>
- <listitem><para>Junk filling. If set to "alloc", each byte of
- uninitialized allocated memory will be initialized to
- <literal>0xa5</literal>. If set to "free", all deallocated memory will
- be initialized to <literal>0x5a</literal>. If set to "true", both
- allocated and deallocated memory will be initialized, and if set to
- "false", junk filling be disabled entirely. This is intended for
- debugging and will impact performance negatively. This option is
- "false" by default unless <option>--enable-debug</option> is specified
- during configuration, in which case it is "true" by default unless
- running inside <ulink
- url="http://valgrind.org/">Valgrind</ulink>.</para></listitem>
+ <listitem><para>Options (the <parameter>opts</parameter> string) to pass
+ to the <function>malloc_stats_print()</function> at exit (enabled
+ through <link
+ linkend="opt.stats_print"><mallctl>opt.stats_print</mallctl></link>). See
+ available options in <link
+ linkend="malloc_stats_print_opts"><function>malloc_stats_print()</function></link>.
+ Has no effect unless <link
+ linkend="opt.stats_print"><mallctl>opt.stats_print</mallctl></link> is
+ enabled. The default is <quote></quote>.</para></listitem>
</varlistentry>
- <varlistentry id="opt.quarantine">
- <term>
- <mallctl>opt.quarantine</mallctl>
- (<type>size_t</type>)
- <literal>r-</literal>
- [<option>--enable-fill</option>]
- </term>
- <listitem><para>Per thread quarantine size in bytes. If non-zero, each
- thread maintains a FIFO object quarantine that stores up to the
- specified number of bytes of memory. The quarantined memory is not
- freed until it is released from quarantine, though it is immediately
- junk-filled if the <link
- linkend="opt.junk"><mallctl>opt.junk</mallctl></link> option is
- enabled. This feature is of particular use in combination with <ulink
- url="http://valgrind.org/">Valgrind</ulink>, which can detect attempts
- to access quarantined objects. This is intended for debugging and will
- impact performance negatively. The default quarantine size is 0 unless
- running inside Valgrind, in which case the default is 16
- MiB.</para></listitem>
- </varlistentry>
-
- <varlistentry id="opt.redzone">
+ <varlistentry id="opt.junk">
<term>
- <mallctl>opt.redzone</mallctl>
- (<type>bool</type>)
+ <mallctl>opt.junk</mallctl>
+ (<type>const char *</type>)
<literal>r-</literal>
[<option>--enable-fill</option>]
</term>
- <listitem><para>Redzones enabled/disabled. If enabled, small
- allocations have redzones before and after them. Furthermore, if the
- <link linkend="opt.junk"><mallctl>opt.junk</mallctl></link> option is
- enabled, the redzones are checked for corruption during deallocation.
- However, the primary intended purpose of this feature is to be used in
- combination with <ulink url="http://valgrind.org/">Valgrind</ulink>,
- which needs redzones in order to do effective buffer overflow/underflow
- detection. This option is intended for debugging and will impact
- performance negatively. This option is disabled by
- default unless running inside Valgrind.</para></listitem>
+ <listitem><para>Junk filling. If set to <quote>alloc</quote>, each byte
+ of uninitialized allocated memory will be initialized to
+ <literal>0xa5</literal>. If set to <quote>free</quote>, all deallocated
+ memory will be initialized to <literal>0x5a</literal>. If set to
+ <quote>true</quote>, both allocated and deallocated memory will be
+ initialized, and if set to <quote>false</quote>, junk filling be
+ disabled entirely. This is intended for debugging and will impact
+ performance negatively. This option is <quote>false</quote> by default
+ unless <option>--enable-debug</option> is specified during
+ configuration, in which case it is <quote>true</quote> by
+ default.</para></listitem>
</varlistentry>
<varlistentry id="opt.zero">
@@ -1054,8 +1172,8 @@ for (i = 0; i < nbins; i++) {
<listitem><para>Zero filling enabled/disabled. If enabled, each byte
of uninitialized allocated memory will be initialized to 0. Note that
this initialization only happens once for each byte, so
- <function>realloc<parameter/></function> and
- <function>rallocx<parameter/></function> calls do not zero memory that
+ <function>realloc()</function> and
+ <function>rallocx()</function> calls do not zero memory that
was previously allocated. This is intended for debugging and will
impact performance negatively. This option is disabled by default.
</para></listitem>
@@ -1099,7 +1217,6 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>opt.tcache</mallctl>
(<type>bool</type>)
<literal>r-</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Thread-specific caching (tcache) enabled/disabled. When
there are multiple threads, each thread uses a tcache for objects up to
@@ -1108,9 +1225,7 @@ malloc_conf = "xmalloc:true";]]></programlisting>
increased memory use. See the <link
linkend="opt.lg_tcache_max"><mallctl>opt.lg_tcache_max</mallctl></link>
option for related tuning information. This option is enabled by
- default unless running inside <ulink
- url="http://valgrind.org/">Valgrind</ulink>, in which case it is
- forcefully disabled.</para></listitem>
+ default.</para></listitem>
</varlistentry>
<varlistentry id="opt.lg_tcache_max">
@@ -1118,7 +1233,6 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>opt.lg_tcache_max</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Maximum size class (log base 2) to cache in the
thread-specific cache (tcache). At a minimum, all small size classes
@@ -1126,6 +1240,28 @@ malloc_conf = "xmalloc:true";]]></programlisting>
default maximum is 32 KiB (2^15).</para></listitem>
</varlistentry>
+ <varlistentry id="opt.thp">
+ <term>
+ <mallctl>opt.thp</mallctl>
+ (<type>const char *</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Transparent hugepage (THP) mode. Settings "always",
+ "never" and "default" are available if THP is supported by the operating
+ system. The "always" setting enables transparent hugepage for all user
+ memory mappings with
+ <parameter><constant>MADV_HUGEPAGE</constant></parameter>; "never"
+ ensures no transparent hugepage with
+ <parameter><constant>MADV_NOHUGEPAGE</constant></parameter>; the default
+ setting "default" makes no changes. Note that: this option does not
+ affect THP for jemalloc internal metadata (see <link
+ linkend="opt.metadata_thp"><mallctl>opt.metadata_thp</mallctl></link>);
+ in addition, for arenas with customized <link
+ linkend="arena.i.extent_hooks"><mallctl>extent_hooks</mallctl></link>,
+ this option is bypassed as it is implemented as part of the default
+ extent hooks.</para></listitem>
+ </varlistentry>
+
<varlistentry id="opt.prof">
<term>
<mallctl>opt.prof</mallctl>
@@ -1150,7 +1286,8 @@ malloc_conf = "xmalloc:true";]]></programlisting>
the <command>jeprof</command> command, which is based on the
<command>pprof</command> that is developed as part of the <ulink
url="http://code.google.com/p/gperftools/">gperftools
- package</ulink>.</para></listitem>
+ package</ulink>. See <link linkend="heap_profile_format">HEAP PROFILE
+ FORMAT</link> for heap profile format documentation.</para></listitem>
</varlistentry>
<varlistentry id="opt.prof_prefix">
@@ -1277,11 +1414,11 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<filename>&lt;prefix&gt;.&lt;pid&gt;.&lt;seq&gt;.f.heap</filename>,
where <literal>&lt;prefix&gt;</literal> is controlled by the <link
linkend="opt.prof_prefix"><mallctl>opt.prof_prefix</mallctl></link>
- option. Note that <function>atexit<parameter/></function> may allocate
+ option. Note that <function>atexit()</function> may allocate
memory during application initialization and then deadlock internally
- when jemalloc in turn calls <function>atexit<parameter/></function>, so
- this option is not univerally usable (though the application can
- register its own <function>atexit<parameter/></function> function with
+ when jemalloc in turn calls <function>atexit()</function>, so
+ this option is not universally usable (though the application can
+ register its own <function>atexit()</function> function with
equivalent functionality). This option is disabled by
default.</para></listitem>
</varlistentry>
@@ -1311,7 +1448,7 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<listitem><para>Get or set the arena associated with the calling
thread. If the specified arena was not initialized beforehand (see the
<link
- linkend="arenas.initialized"><mallctl>arenas.initialized</mallctl></link>
+ linkend="arena.i.initialized"><mallctl>arena.i.initialized</mallctl></link>
mallctl), it will be automatically initialized as a side effect of
calling this interface.</para></listitem>
</varlistentry>
@@ -1340,7 +1477,7 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<link
linkend="thread.allocated"><mallctl>thread.allocated</mallctl></link>
mallctl. This is useful for avoiding the overhead of repeated
- <function>mallctl*<parameter/></function> calls.</para></listitem>
+ <function>mallctl*()</function> calls.</para></listitem>
</varlistentry>
<varlistentry id="thread.deallocated">
@@ -1367,7 +1504,7 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<link
linkend="thread.deallocated"><mallctl>thread.deallocated</mallctl></link>
mallctl. This is useful for avoiding the overhead of repeated
- <function>mallctl*<parameter/></function> calls.</para></listitem>
+ <function>mallctl*()</function> calls.</para></listitem>
</varlistentry>
<varlistentry id="thread.tcache.enabled">
@@ -1375,7 +1512,6 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>thread.tcache.enabled</mallctl>
(<type>bool</type>)
<literal>rw</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Enable/disable calling thread's tcache. The tcache is
implicitly flushed as a side effect of becoming
@@ -1389,7 +1525,6 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>thread.tcache.flush</mallctl>
(<type>void</type>)
<literal>--</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Flush calling thread's thread-specific cache (tcache).
This interface releases all cached objects and internal data structures
@@ -1418,8 +1553,8 @@ malloc_conf = "xmalloc:true";]]></programlisting>
can cause asynchronous string deallocation. Furthermore, each
invocation of this interface can only read or write; simultaneous
read/write is not supported due to string lifetime limitations. The
- name string must nil-terminated and comprised only of characters in the
- sets recognized
+ name string must be nil-terminated and comprised only of characters in
+ the sets recognized
by <citerefentry><refentrytitle>isgraph</refentrytitle>
<manvolnum>3</manvolnum></citerefentry> and
<citerefentry><refentrytitle>isblank</refentrytitle>
@@ -1445,7 +1580,6 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>tcache.create</mallctl>
(<type>unsigned</type>)
<literal>r-</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Create an explicit thread-specific cache (tcache) and
return an identifier that can be passed to the <link
@@ -1462,12 +1596,11 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>tcache.flush</mallctl>
(<type>unsigned</type>)
<literal>-w</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Flush the specified thread-specific cache (tcache). The
same considerations apply to this interface as to <link
linkend="thread.tcache.flush"><mallctl>thread.tcache.flush</mallctl></link>,
- except that the tcache will never be automatically be discarded.
+ except that the tcache will never be automatically discarded.
</para></listitem>
</varlistentry>
@@ -1476,25 +1609,86 @@ malloc_conf = "xmalloc:true";]]></programlisting>
<mallctl>tcache.destroy</mallctl>
(<type>unsigned</type>)
<literal>-w</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Flush the specified thread-specific cache (tcache) and
make the identifier available for use during a future tcache creation.
</para></listitem>
</varlistentry>
+ <varlistentry id="arena.i.initialized">
+ <term>
+ <mallctl>arena.&lt;i&gt;.initialized</mallctl>
+ (<type>bool</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Get whether the specified arena's statistics are
+ initialized (i.e. the arena was initialized prior to the current epoch).
+ This interface can also be nominally used to query whether the merged
+ statistics corresponding to <constant>MALLCTL_ARENAS_ALL</constant> are
+ initialized (always true).</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="arena.i.decay">
+ <term>
+ <mallctl>arena.&lt;i&gt;.decay</mallctl>
+ (<type>void</type>)
+ <literal>--</literal>
+ </term>
+ <listitem><para>Trigger decay-based purging of unused dirty/muzzy pages
+ for arena &lt;i&gt;, or for all arenas if &lt;i&gt; equals
+ <constant>MALLCTL_ARENAS_ALL</constant>. The proportion of unused
+ dirty/muzzy pages to be purged depends on the current time; see <link
+ linkend="opt.dirty_decay_ms"><mallctl>opt.dirty_decay_ms</mallctl></link>
+ and <link
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzy_decay_ms</mallctl></link>
+ for details.</para></listitem>
+ </varlistentry>
+
<varlistentry id="arena.i.purge">
<term>
<mallctl>arena.&lt;i&gt;.purge</mallctl>
(<type>void</type>)
<literal>--</literal>
</term>
- <listitem><para>Purge unused dirty pages for arena &lt;i&gt;, or for
- all arenas if &lt;i&gt; equals <link
- linkend="arenas.narenas"><mallctl>arenas.narenas</mallctl></link>.
+ <listitem><para>Purge all unused dirty pages for arena &lt;i&gt;, or for
+ all arenas if &lt;i&gt; equals <constant>MALLCTL_ARENAS_ALL</constant>.
</para></listitem>
</varlistentry>
+ <varlistentry id="arena.i.reset">
+ <term>
+ <mallctl>arena.&lt;i&gt;.reset</mallctl>
+ (<type>void</type>)
+ <literal>--</literal>
+ </term>
+ <listitem><para>Discard all of the arena's extant allocations. This
+ interface can only be used with arenas explicitly created via <link
+ linkend="arenas.create"><mallctl>arenas.create</mallctl></link>. None
+ of the arena's discarded/cached allocations may accessed afterward. As
+ part of this requirement, all thread caches which were used to
+ allocate/deallocate in conjunction with the arena must be flushed
+ beforehand.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="arena.i.destroy">
+ <term>
+ <mallctl>arena.&lt;i&gt;.destroy</mallctl>
+ (<type>void</type>)
+ <literal>--</literal>
+ </term>
+ <listitem><para>Destroy the arena. Discard all of the arena's extant
+ allocations using the same mechanism as for <link
+ linkend="arena.i.reset"><mallctl>arena.&lt;i&gt;.reset</mallctl></link>
+ (with all the same constraints and side effects), merge the arena stats
+ into those accessible at arena index
+ <constant>MALLCTL_ARENAS_DESTROYED</constant>, and then completely
+ discard all metadata associated with the arena. Future calls to <link
+ linkend="arenas.create"><mallctl>arenas.create</mallctl></link> may
+ recycle the arena index. Destruction will fail if any threads are
+ currently associated with the arena as a result of calls to <link
+ linkend="thread.arena"><mallctl>thread.arena</mallctl></link>.</para></listitem>
+ </varlistentry>
+
<varlistentry id="arena.i.dss">
<term>
<mallctl>arena.&lt;i&gt;.dss</mallctl>
@@ -1503,71 +1697,109 @@ malloc_conf = "xmalloc:true";]]></programlisting>
</term>
<listitem><para>Set the precedence of dss allocation as related to mmap
allocation for arena &lt;i&gt;, or for all arenas if &lt;i&gt; equals
- <link
- linkend="arenas.narenas"><mallctl>arenas.narenas</mallctl></link>. See
- <link linkend="opt.dss"><mallctl>opt.dss</mallctl></link> for supported
+ <constant>MALLCTL_ARENAS_ALL</constant>. See <link
+ linkend="opt.dss"><mallctl>opt.dss</mallctl></link> for supported
settings.</para></listitem>
</varlistentry>
- <varlistentry id="arena.i.lg_dirty_mult">
+ <varlistentry id="arena.i.dirty_decay_ms">
<term>
- <mallctl>arena.&lt;i&gt;.lg_dirty_mult</mallctl>
+ <mallctl>arena.&lt;i&gt;.dirty_decay_ms</mallctl>
(<type>ssize_t</type>)
<literal>rw</literal>
</term>
- <listitem><para>Current per-arena minimum ratio (log base 2) of active
- to dirty pages for arena &lt;i&gt;. Each time this interface is set and
- the ratio is increased, pages are synchronously purged as necessary to
- impose the new ratio. See <link
- linkend="opt.lg_dirty_mult"><mallctl>opt.lg_dirty_mult</mallctl></link>
+ <listitem><para>Current per-arena approximate time in milliseconds from
+ the creation of a set of unused dirty pages until an equivalent set of
+ unused dirty pages is purged and/or reused. Each time this interface is
+ set, all currently unused dirty pages are considered to have fully
+ decayed, which causes immediate purging of all unused dirty pages unless
+ the decay time is set to -1 (i.e. purging disabled). See <link
+ linkend="opt.dirty_decay_ms"><mallctl>opt.dirty_decay_ms</mallctl></link>
for additional information.</para></listitem>
</varlistentry>
- <varlistentry id="arena.i.chunk_hooks">
+ <varlistentry id="arena.i.muzzy_decay_ms">
<term>
- <mallctl>arena.&lt;i&gt;.chunk_hooks</mallctl>
- (<type>chunk_hooks_t</type>)
+ <mallctl>arena.&lt;i&gt;.muzzy_decay_ms</mallctl>
+ (<type>ssize_t</type>)
<literal>rw</literal>
</term>
- <listitem><para>Get or set the chunk management hook functions for arena
- &lt;i&gt;. The functions must be capable of operating on all extant
- chunks associated with arena &lt;i&gt;, usually by passing unknown
- chunks to the replaced functions. In practice, it is feasible to
- control allocation for arenas created via <link
- linkend="arenas.extend"><mallctl>arenas.extend</mallctl></link> such
- that all chunks originate from an application-supplied chunk allocator
- (by setting custom chunk hook functions just after arena creation), but
- the automatically created arenas may have already created chunks prior
- to the application having an opportunity to take over chunk
+ <listitem><para>Current per-arena approximate time in milliseconds from
+ the creation of a set of unused muzzy pages until an equivalent set of
+ unused muzzy pages is purged and/or reused. Each time this interface is
+ set, all currently unused muzzy pages are considered to have fully
+ decayed, which causes immediate purging of all unused muzzy pages unless
+ the decay time is set to -1 (i.e. purging disabled). See <link
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzzy_decay_ms</mallctl></link>
+ for additional information.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="arena.i.retain_grow_limit">
+ <term>
+ <mallctl>arena.&lt;i&gt;.retain_grow_limit</mallctl>
+ (<type>size_t</type>)
+ <literal>rw</literal>
+ </term>
+ <listitem><para>Maximum size to grow retained region (only relevant when
+ <link linkend="opt.retain"><mallctl>opt.retain</mallctl></link> is
+ enabled). This controls the maximum increment to expand virtual memory,
+ or allocation through <link
+ linkend="arena.i.extent_hooks"><mallctl>arena.&lt;i&gt;extent_hooks</mallctl></link>.
+ In particular, if customized extent hooks reserve physical memory
+ (e.g. 1G huge pages), this is useful to control the allocation hook's
+ input size. The default is no limit.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="arena.i.extent_hooks">
+ <term>
+ <mallctl>arena.&lt;i&gt;.extent_hooks</mallctl>
+ (<type>extent_hooks_t *</type>)
+ <literal>rw</literal>
+ </term>
+ <listitem><para>Get or set the extent management hook functions for
+ arena &lt;i&gt;. The functions must be capable of operating on all
+ extant extents associated with arena &lt;i&gt;, usually by passing
+ unknown extents to the replaced functions. In practice, it is feasible
+ to control allocation for arenas explicitly created via <link
+ linkend="arenas.create"><mallctl>arenas.create</mallctl></link> such
+ that all extents originate from an application-supplied extent allocator
+ (by specifying the custom extent hook functions during arena creation),
+ but the automatically created arenas will have already created extents
+ prior to the application having an opportunity to take over extent
allocation.</para>
<programlisting language="C"><![CDATA[
-typedef struct {
- chunk_alloc_t *alloc;
- chunk_dalloc_t *dalloc;
- chunk_commit_t *commit;
- chunk_decommit_t *decommit;
- chunk_purge_t *purge;
- chunk_split_t *split;
- chunk_merge_t *merge;
-} chunk_hooks_t;]]></programlisting>
- <para>The <type>chunk_hooks_t</type> structure comprises function
+typedef extent_hooks_s extent_hooks_t;
+struct extent_hooks_s {
+ extent_alloc_t *alloc;
+ extent_dalloc_t *dalloc;
+ extent_destroy_t *destroy;
+ extent_commit_t *commit;
+ extent_decommit_t *decommit;
+ extent_purge_t *purge_lazy;
+ extent_purge_t *purge_forced;
+ extent_split_t *split;
+ extent_merge_t *merge;
+};]]></programlisting>
+ <para>The <type>extent_hooks_t</type> structure comprises function
pointers which are described individually below. jemalloc uses these
- functions to manage chunk lifetime, which starts off with allocation of
+ functions to manage extent lifetime, which starts off with allocation of
mapped committed memory, in the simplest case followed by deallocation.
- However, there are performance and platform reasons to retain chunks for
- later reuse. Cleanup attempts cascade from deallocation to decommit to
- purging, which gives the chunk management functions opportunities to
- reject the most permanent cleanup operations in favor of less permanent
- (and often less costly) operations. The chunk splitting and merging
- operations can also be opted out of, but this is mainly intended to
- support platforms on which virtual memory mappings provided by the
- operating system kernel do not automatically coalesce and split, e.g.
- Windows.</para>
+ However, there are performance and platform reasons to retain extents
+ for later reuse. Cleanup attempts cascade from deallocation to decommit
+ to forced purging to lazy purging, which gives the extent management
+ functions opportunities to reject the most permanent cleanup operations
+ in favor of less permanent (and often less costly) operations. All
+ operations except allocation can be universally opted out of by setting
+ the hook pointers to <constant>NULL</constant>, or selectively opted out
+ of by returning failure. Note that once the extent hook is set, the
+ structure is accessed directly by the associated arenas, so it must
+ remain valid for the entire lifetime of the arenas.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef void *<function>(chunk_alloc_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
+ <funcdef>typedef void *<function>(extent_alloc_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>new_addr</parameter></paramdef>
<paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>size_t <parameter>alignment</parameter></paramdef>
<paramdef>bool *<parameter>zero</parameter></paramdef>
@@ -1575,62 +1807,83 @@ typedef struct {
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk allocation function conforms to the
- <type>chunk_alloc_t</type> type and upon success returns a pointer to
+ <para>An extent allocation function conforms to the
+ <type>extent_alloc_t</type> type and upon success returns a pointer to
<parameter>size</parameter> bytes of mapped memory on behalf of arena
- <parameter>arena_ind</parameter> such that the chunk's base address is a
- multiple of <parameter>alignment</parameter>, as well as setting
- <parameter>*zero</parameter> to indicate whether the chunk is zeroed and
- <parameter>*commit</parameter> to indicate whether the chunk is
+ <parameter>arena_ind</parameter> such that the extent's base address is
+ a multiple of <parameter>alignment</parameter>, as well as setting
+ <parameter>*zero</parameter> to indicate whether the extent is zeroed
+ and <parameter>*commit</parameter> to indicate whether the extent is
committed. Upon error the function returns <constant>NULL</constant>
and leaves <parameter>*zero</parameter> and
<parameter>*commit</parameter> unmodified. The
- <parameter>size</parameter> parameter is always a multiple of the chunk
+ <parameter>size</parameter> parameter is always a multiple of the page
size. The <parameter>alignment</parameter> parameter is always a power
- of two at least as large as the chunk size. Zeroing is mandatory if
+ of two at least as large as the page size. Zeroing is mandatory if
<parameter>*zero</parameter> is true upon function entry. Committing is
mandatory if <parameter>*commit</parameter> is true upon function entry.
- If <parameter>chunk</parameter> is not <constant>NULL</constant>, the
- returned pointer must be <parameter>chunk</parameter> on success or
+ If <parameter>new_addr</parameter> is not <constant>NULL</constant>, the
+ returned pointer must be <parameter>new_addr</parameter> on success or
<constant>NULL</constant> on error. Committed memory may be committed
in absolute terms as on a system that does not overcommit, or in
implicit terms as on a system that overcommits and satisfies physical
memory needs on demand via soft page faults. Note that replacing the
- default chunk allocation function makes the arena's <link
+ default extent allocation function makes the arena's <link
linkend="arena.i.dss"><mallctl>arena.&lt;i&gt;.dss</mallctl></link>
setting irrelevant.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_dalloc_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
+ <funcdef>typedef bool <function>(extent_dalloc_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
<paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>bool <parameter>committed</parameter></paramdef>
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
<para>
- A chunk deallocation function conforms to the
- <type>chunk_dalloc_t</type> type and deallocates a
- <parameter>chunk</parameter> of given <parameter>size</parameter> with
+ An extent deallocation function conforms to the
+ <type>extent_dalloc_t</type> type and deallocates an extent at given
+ <parameter>addr</parameter> and <parameter>size</parameter> with
<parameter>committed</parameter>/decommited memory as indicated, on
behalf of arena <parameter>arena_ind</parameter>, returning false upon
success. If the function returns true, this indicates opt-out from
- deallocation; the virtual memory mapping associated with the chunk
+ deallocation; the virtual memory mapping associated with the extent
remains mapped, in the same commit state, and available for future use,
in which case it will be automatically retained for later reuse.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_commit_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
+ <funcdef>typedef void <function>(extent_destroy_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
+ <paramdef>size_t <parameter>size</parameter></paramdef>
+ <paramdef>bool <parameter>committed</parameter></paramdef>
+ <paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
+ </funcprototype></funcsynopsis>
+ <literallayout></literallayout>
+ <para>
+ An extent destruction function conforms to the
+ <type>extent_destroy_t</type> type and unconditionally destroys an
+ extent at given <parameter>addr</parameter> and
+ <parameter>size</parameter> with
+ <parameter>committed</parameter>/decommited memory as indicated, on
+ behalf of arena <parameter>arena_ind</parameter>. This function may be
+ called to destroy retained extents during arena destruction (see <link
+ linkend="arena.i.destroy"><mallctl>arena.&lt;i&gt;.destroy</mallctl></link>).</para>
+
+ <funcsynopsis><funcprototype>
+ <funcdef>typedef bool <function>(extent_commit_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
<paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>size_t <parameter>offset</parameter></paramdef>
<paramdef>size_t <parameter>length</parameter></paramdef>
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk commit function conforms to the
- <type>chunk_commit_t</type> type and commits zeroed physical memory to
- back pages within a <parameter>chunk</parameter> of given
+ <para>An extent commit function conforms to the
+ <type>extent_commit_t</type> type and commits zeroed physical memory to
+ back pages within an extent at given <parameter>addr</parameter> and
<parameter>size</parameter> at <parameter>offset</parameter> bytes,
extending for <parameter>length</parameter> on behalf of arena
<parameter>arena_ind</parameter>, returning false upon success.
@@ -1641,46 +1894,56 @@ typedef struct {
physical memory to satisfy the request.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_decommit_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
+ <funcdef>typedef bool <function>(extent_decommit_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
<paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>size_t <parameter>offset</parameter></paramdef>
<paramdef>size_t <parameter>length</parameter></paramdef>
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk decommit function conforms to the
- <type>chunk_decommit_t</type> type and decommits any physical memory
- that is backing pages within a <parameter>chunk</parameter> of given
- <parameter>size</parameter> at <parameter>offset</parameter> bytes,
- extending for <parameter>length</parameter> on behalf of arena
+ <para>An extent decommit function conforms to the
+ <type>extent_decommit_t</type> type and decommits any physical memory
+ that is backing pages within an extent at given
+ <parameter>addr</parameter> and <parameter>size</parameter> at
+ <parameter>offset</parameter> bytes, extending for
+ <parameter>length</parameter> on behalf of arena
<parameter>arena_ind</parameter>, returning false upon success, in which
- case the pages will be committed via the chunk commit function before
+ case the pages will be committed via the extent commit function before
being reused. If the function returns true, this indicates opt-out from
decommit; the memory remains committed and available for future use, in
which case it will be automatically retained for later reuse.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_purge_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
- <paramdef>size_t<parameter>size</parameter></paramdef>
+ <funcdef>typedef bool <function>(extent_purge_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
+ <paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>size_t <parameter>offset</parameter></paramdef>
<paramdef>size_t <parameter>length</parameter></paramdef>
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk purge function conforms to the <type>chunk_purge_t</type>
- type and optionally discards physical pages within the virtual memory
- mapping associated with <parameter>chunk</parameter> of given
- <parameter>size</parameter> at <parameter>offset</parameter> bytes,
- extending for <parameter>length</parameter> on behalf of arena
- <parameter>arena_ind</parameter>, returning false if pages within the
- purged virtual memory range will be zero-filled the next time they are
- accessed.</para>
+ <para>An extent purge function conforms to the
+ <type>extent_purge_t</type> type and discards physical pages
+ within the virtual memory mapping associated with an extent at given
+ <parameter>addr</parameter> and <parameter>size</parameter> at
+ <parameter>offset</parameter> bytes, extending for
+ <parameter>length</parameter> on behalf of arena
+ <parameter>arena_ind</parameter>. A lazy extent purge function (e.g.
+ implemented via
+ <function>madvise(<parameter>...</parameter><parameter><constant>MADV_FREE</constant></parameter>)</function>)
+ can delay purging indefinitely and leave the pages within the purged
+ virtual memory range in an indeterminite state, whereas a forced extent
+ purge function immediately purges, and the pages within the virtual
+ memory range will be zero-filled the next time they are accessed. If
+ the function returns true, this indicates failure to purge.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_split_t)</function></funcdef>
- <paramdef>void *<parameter>chunk</parameter></paramdef>
+ <funcdef>typedef bool <function>(extent_split_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr</parameter></paramdef>
<paramdef>size_t <parameter>size</parameter></paramdef>
<paramdef>size_t <parameter>size_a</parameter></paramdef>
<paramdef>size_t <parameter>size_b</parameter></paramdef>
@@ -1688,35 +1951,36 @@ typedef struct {
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk split function conforms to the <type>chunk_split_t</type>
- type and optionally splits <parameter>chunk</parameter> of given
- <parameter>size</parameter> into two adjacent chunks, the first of
- <parameter>size_a</parameter> bytes, and the second of
- <parameter>size_b</parameter> bytes, operating on
+ <para>An extent split function conforms to the
+ <type>extent_split_t</type> type and optionally splits an extent at
+ given <parameter>addr</parameter> and <parameter>size</parameter> into
+ two adjacent extents, the first of <parameter>size_a</parameter> bytes,
+ and the second of <parameter>size_b</parameter> bytes, operating on
<parameter>committed</parameter>/decommitted memory as indicated, on
behalf of arena <parameter>arena_ind</parameter>, returning false upon
- success. If the function returns true, this indicates that the chunk
+ success. If the function returns true, this indicates that the extent
remains unsplit and therefore should continue to be operated on as a
whole.</para>
<funcsynopsis><funcprototype>
- <funcdef>typedef bool <function>(chunk_merge_t)</function></funcdef>
- <paramdef>void *<parameter>chunk_a</parameter></paramdef>
+ <funcdef>typedef bool <function>(extent_merge_t)</function></funcdef>
+ <paramdef>extent_hooks_t *<parameter>extent_hooks</parameter></paramdef>
+ <paramdef>void *<parameter>addr_a</parameter></paramdef>
<paramdef>size_t <parameter>size_a</parameter></paramdef>
- <paramdef>void *<parameter>chunk_b</parameter></paramdef>
+ <paramdef>void *<parameter>addr_b</parameter></paramdef>
<paramdef>size_t <parameter>size_b</parameter></paramdef>
<paramdef>bool <parameter>committed</parameter></paramdef>
<paramdef>unsigned <parameter>arena_ind</parameter></paramdef>
</funcprototype></funcsynopsis>
<literallayout></literallayout>
- <para>A chunk merge function conforms to the <type>chunk_merge_t</type>
- type and optionally merges adjacent chunks,
- <parameter>chunk_a</parameter> of given <parameter>size_a</parameter>
- and <parameter>chunk_b</parameter> of given
- <parameter>size_b</parameter> into one contiguous chunk, operating on
+ <para>An extent merge function conforms to the
+ <type>extent_merge_t</type> type and optionally merges adjacent extents,
+ at given <parameter>addr_a</parameter> and <parameter>size_a</parameter>
+ with given <parameter>addr_b</parameter> and
+ <parameter>size_b</parameter> into one contiguous extent, operating on
<parameter>committed</parameter>/decommitted memory as indicated, on
behalf of arena <parameter>arena_ind</parameter>, returning false upon
- success. If the function returns true, this indicates that the chunks
+ success. If the function returns true, this indicates that the extents
remain distinct mappings and therefore should continue to be operated on
independently.</para>
</listitem>
@@ -1731,29 +1995,35 @@ typedef struct {
<listitem><para>Current limit on number of arenas.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.initialized">
+ <varlistentry id="arenas.dirty_decay_ms">
<term>
- <mallctl>arenas.initialized</mallctl>
- (<type>bool *</type>)
- <literal>r-</literal>
+ <mallctl>arenas.dirty_decay_ms</mallctl>
+ (<type>ssize_t</type>)
+ <literal>rw</literal>
</term>
- <listitem><para>An array of <link
- linkend="arenas.narenas"><mallctl>arenas.narenas</mallctl></link>
- booleans. Each boolean indicates whether the corresponding arena is
- initialized.</para></listitem>
+ <listitem><para>Current default per-arena approximate time in
+ milliseconds from the creation of a set of unused dirty pages until an
+ equivalent set of unused dirty pages is purged and/or reused, used to
+ initialize <link
+ linkend="arena.i.dirty_decay_ms"><mallctl>arena.&lt;i&gt;.dirty_decay_ms</mallctl></link>
+ during arena creation. See <link
+ linkend="opt.dirty_decay_ms"><mallctl>opt.dirty_decay_ms</mallctl></link>
+ for additional information.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.lg_dirty_mult">
+ <varlistentry id="arenas.muzzy_decay_ms">
<term>
- <mallctl>arenas.lg_dirty_mult</mallctl>
+ <mallctl>arenas.muzzy_decay_ms</mallctl>
(<type>ssize_t</type>)
<literal>rw</literal>
</term>
- <listitem><para>Current default per-arena minimum ratio (log base 2) of
- active to dirty pages, used to initialize <link
- linkend="arena.i.lg_dirty_mult"><mallctl>arena.&lt;i&gt;.lg_dirty_mult</mallctl></link>
+ <listitem><para>Current default per-arena approximate time in
+ milliseconds from the creation of a set of unused muzzy pages until an
+ equivalent set of unused muzzy pages is purged and/or reused, used to
+ initialize <link
+ linkend="arena.i.muzzy_decay_ms"><mallctl>arena.&lt;i&gt;.muzzy_decay_ms</mallctl></link>
during arena creation. See <link
- linkend="opt.lg_dirty_mult"><mallctl>opt.lg_dirty_mult</mallctl></link>
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzzy_decay_ms</mallctl></link>
for additional information.</para></listitem>
</varlistentry>
@@ -1780,7 +2050,6 @@ typedef struct {
<mallctl>arenas.tcache_max</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Maximum thread-cached size class.</para></listitem>
</varlistentry>
@@ -1799,7 +2068,6 @@ typedef struct {
<mallctl>arenas.nhbins</mallctl>
(<type>unsigned</type>)
<literal>r-</literal>
- [<option>--enable-tcache</option>]
</term>
<listitem><para>Total number of thread cache bin size
classes.</para></listitem>
@@ -1820,30 +2088,30 @@ typedef struct {
(<type>uint32_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Number of regions per page run.</para></listitem>
+ <listitem><para>Number of regions per slab.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.bin.i.run_size">
+ <varlistentry id="arenas.bin.i.slab_size">
<term>
- <mallctl>arenas.bin.&lt;i&gt;.run_size</mallctl>
+ <mallctl>arenas.bin.&lt;i&gt;.slab_size</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Number of bytes per page run.</para></listitem>
+ <listitem><para>Number of bytes per slab.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.nlruns">
+ <varlistentry id="arenas.nlextents">
<term>
- <mallctl>arenas.nlruns</mallctl>
+ <mallctl>arenas.nlextents</mallctl>
(<type>unsigned</type>)
<literal>r-</literal>
</term>
<listitem><para>Total number of large size classes.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.lrun.i.size">
+ <varlistentry id="arenas.lextent.i.size">
<term>
- <mallctl>arenas.lrun.&lt;i&gt;.size</mallctl>
+ <mallctl>arenas.lextent.&lt;i&gt;.size</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
</term>
@@ -1851,33 +2119,24 @@ typedef struct {
class.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.nhchunks">
- <term>
- <mallctl>arenas.nhchunks</mallctl>
- (<type>unsigned</type>)
- <literal>r-</literal>
- </term>
- <listitem><para>Total number of huge size classes.</para></listitem>
- </varlistentry>
-
- <varlistentry id="arenas.hchunk.i.size">
+ <varlistentry id="arenas.create">
<term>
- <mallctl>arenas.hchunk.&lt;i&gt;.size</mallctl>
- (<type>size_t</type>)
- <literal>r-</literal>
+ <mallctl>arenas.create</mallctl>
+ (<type>unsigned</type>, <type>extent_hooks_t *</type>)
+ <literal>rw</literal>
</term>
- <listitem><para>Maximum size supported by this huge size
- class.</para></listitem>
+ <listitem><para>Explicitly create a new arena outside the range of
+ automatically managed arenas, with optionally specified extent hooks,
+ and return the new arena index.</para></listitem>
</varlistentry>
- <varlistentry id="arenas.extend">
+ <varlistentry id="arenas.lookup">
<term>
- <mallctl>arenas.extend</mallctl>
- (<type>unsigned</type>)
- <literal>r-</literal>
+ <mallctl>arenas.lookup</mallctl>
+ (<type>unsigned</type>, <type>void*</type>)
+ <literal>rw</literal>
</term>
- <listitem><para>Extend the array of arenas by appending a new arena,
- and returning the new arena index.</para></listitem>
+ <listitem><para>Index of the arena to which an allocation belongs to.</para></listitem>
</varlistentry>
<varlistentry id="prof.thread_active_init">
@@ -1976,30 +2235,12 @@ typedef struct {
[<option>--enable-prof</option>]
</term>
<listitem><para>Average number of bytes allocated between
- inverval-based profile dumps. See the
+ interval-based profile dumps. See the
<link
linkend="opt.lg_prof_interval"><mallctl>opt.lg_prof_interval</mallctl></link>
option for additional information.</para></listitem>
</varlistentry>
- <varlistentry id="stats.cactive">
- <term>
- <mallctl>stats.cactive</mallctl>
- (<type>size_t *</type>)
- <literal>r-</literal>
- [<option>--enable-stats</option>]
- </term>
- <listitem><para>Pointer to a counter that contains an approximate count
- of the current number of bytes in active pages. The estimate may be
- high, but never low, because each arena rounds up when computing its
- contribution to the counter. Note that the <link
- linkend="epoch"><mallctl>epoch</mallctl></link> mallctl has no bearing
- on this counter. Furthermore, counter consistency is maintained via
- atomic operations, so it is necessary to use an atomic operation in
- order to guarantee a consistent read when dereferencing the pointer.
- </para></listitem>
- </varlistentry>
-
<varlistentry id="stats.allocated">
<term>
<mallctl>stats.allocated</mallctl>
@@ -2023,7 +2264,9 @@ typedef struct {
equal to <link
linkend="stats.allocated"><mallctl>stats.allocated</mallctl></link>.
This does not include <link linkend="stats.arenas.i.pdirty">
- <mallctl>stats.arenas.&lt;i&gt;.pdirty</mallctl></link>, nor pages
+ <mallctl>stats.arenas.&lt;i&gt;.pdirty</mallctl></link>,
+ <link linkend="stats.arenas.i.pmuzzy">
+ <mallctl>stats.arenas.&lt;i&gt;.pmuzzy</mallctl></link>, nor pages
entirely devoted to allocator metadata.</para></listitem>
</varlistentry>
@@ -2035,11 +2278,28 @@ typedef struct {
[<option>--enable-stats</option>]
</term>
<listitem><para>Total number of bytes dedicated to metadata, which
- comprise base allocations used for bootstrap-sensitive internal
- allocator data structures, arena chunk headers (see <link
- linkend="stats.arenas.i.metadata.mapped"><mallctl>stats.arenas.&lt;i&gt;.metadata.mapped</mallctl></link>),
+ comprise base allocations used for bootstrap-sensitive allocator
+ metadata structures (see <link
+ linkend="stats.arenas.i.base"><mallctl>stats.arenas.&lt;i&gt;.base</mallctl></link>)
and internal allocations (see <link
- linkend="stats.arenas.i.metadata.allocated"><mallctl>stats.arenas.&lt;i&gt;.metadata.allocated</mallctl></link>).</para></listitem>
+ linkend="stats.arenas.i.internal"><mallctl>stats.arenas.&lt;i&gt;.internal</mallctl></link>).
+ Transparent huge page (enabled with <link
+ linkend="opt.metadata_thp">opt.metadata_thp</link>) usage is not
+ considered.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.metadata_thp">
+ <term>
+ <mallctl>stats.metadata_thp</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Number of transparent huge pages (THP) used for
+ metadata. See <link
+ linkend="stats.metadata"><mallctl>stats.metadata</mallctl></link> and
+ <link linkend="opt.metadata_thp">opt.metadata_thp</link>) for
+ details.</para></listitem>
</varlistentry>
<varlistentry id="stats.resident">
@@ -2066,15 +2326,155 @@ typedef struct {
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Total number of bytes in active chunks mapped by the
- allocator. This is a multiple of the chunk size, and is larger than
- <link linkend="stats.active"><mallctl>stats.active</mallctl></link>.
- This does not include inactive chunks, even those that contain unused
- dirty pages, which means that there is no strict ordering between this
- and <link
+ <listitem><para>Total number of bytes in active extents mapped by the
+ allocator. This is larger than <link
+ linkend="stats.active"><mallctl>stats.active</mallctl></link>. This
+ does not include inactive extents, even those that contain unused dirty
+ pages, which means that there is no strict ordering between this and
+ <link
linkend="stats.resident"><mallctl>stats.resident</mallctl></link>.</para></listitem>
</varlistentry>
+ <varlistentry id="stats.retained">
+ <term>
+ <mallctl>stats.retained</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Total number of bytes in virtual memory mappings that
+ were retained rather than being returned to the operating system via
+ e.g. <citerefentry><refentrytitle>munmap</refentrytitle>
+ <manvolnum>2</manvolnum></citerefentry> or similar. Retained virtual
+ memory is typically untouched, decommitted, or purged, so it has no
+ strongly associated physical memory (see <link
+ linkend="arena.i.extent_hooks">extent hooks</link> for details).
+ Retained memory is excluded from mapped memory statistics, e.g. <link
+ linkend="stats.mapped"><mallctl>stats.mapped</mallctl></link>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.background_thread.num_threads">
+ <term>
+ <mallctl>stats.background_thread.num_threads</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para> Number of <link linkend="background_thread">background
+ threads</link> running currently.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.background_thread.num_runs">
+ <term>
+ <mallctl>stats.background_thread.num_runs</mallctl>
+ (<type>uint64_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para> Total number of runs from all <link
+ linkend="background_thread">background threads</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.background_thread.run_interval">
+ <term>
+ <mallctl>stats.background_thread.run_interval</mallctl>
+ (<type>uint64_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para> Average run interval in nanoseconds of <link
+ linkend="background_thread">background threads</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.mutexes.ctl">
+ <term>
+ <mallctl>stats.mutexes.ctl.{counter};</mallctl>
+ (<type>counter specific type</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>ctl</varname> mutex (global
+ scope; mallctl related). <mallctl>{counter}</mallctl> is one of the
+ counters below:</para>
+ <varlistentry id="mutex_counters">
+ <listitem><para><varname>num_ops</varname> (<type>uint64_t</type>):
+ Total number of lock acquisition operations on this mutex.</para>
+
+ <para><varname>num_spin_acq</varname> (<type>uint64_t</type>): Number
+ of times the mutex was spin-acquired. When the mutex is currently
+ locked and cannot be acquired immediately, a short period of
+ spin-retry within jemalloc will be performed. Acquired through spin
+ generally means the contention was lightweight and not causing context
+ switches.</para>
+
+ <para><varname>num_wait</varname> (<type>uint64_t</type>): Number of
+ times the mutex was wait-acquired, which means the mutex contention
+ was not solved by spin-retry, and blocking operation was likely
+ involved in order to acquire the mutex. This event generally implies
+ higher cost / longer delay, and should be investigated if it happens
+ often.</para>
+
+ <para><varname>max_wait_time</varname> (<type>uint64_t</type>):
+ Maximum length of time in nanoseconds spent on a single wait-acquired
+ lock operation. Note that to avoid profiling overhead on the common
+ path, this does not consider spin-acquired cases.</para>
+
+ <para><varname>total_wait_time</varname> (<type>uint64_t</type>):
+ Cumulative time in nanoseconds spent on wait-acquired lock operations.
+ Similarly, spin-acquired cases are not considered.</para>
+
+ <para><varname>max_num_thds</varname> (<type>uint32_t</type>): Maximum
+ number of threads waiting on this mutex simultaneously. Similarly,
+ spin-acquired cases are not considered.</para>
+
+ <para><varname>num_owner_switch</varname> (<type>uint64_t</type>):
+ Number of times the current mutex owner is different from the previous
+ one. This event does not generally imply an issue; rather it is an
+ indicator of how often the protected data are accessed by different
+ threads.
+ </para>
+ </listitem>
+ </varlistentry>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.mutexes.background_thread">
+ <term>
+ <mallctl>stats.mutexes.background_thread.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>background_thread</varname> mutex
+ (global scope; <link
+ linkend="background_thread"><mallctl>background_thread</mallctl></link>
+ related). <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.mutexes.prof">
+ <term>
+ <mallctl>stats.mutexes.prof.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>prof</varname> mutex (global
+ scope; profiling related). <mallctl>{counter}</mallctl> is one of the
+ counters in <link linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.mutexes.reset">
+ <term>
+ <mallctl>stats.mutexes.reset</mallctl>
+ (<type>void</type>) <literal>--</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Reset all mutex profile statistics, including global
+ mutexes, arena mutexes and bin mutexes.</para></listitem>
+ </varlistentry>
+
<varlistentry id="stats.arenas.i.dss">
<term>
<mallctl>stats.arenas.&lt;i&gt;.dss</mallctl>
@@ -2089,15 +2489,29 @@ typedef struct {
</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.lg_dirty_mult">
+ <varlistentry id="stats.arenas.i.dirty_decay_ms">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.lg_dirty_mult</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.dirty_decay_ms</mallctl>
(<type>ssize_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Minimum ratio (log base 2) of active to dirty pages.
- See <link
- linkend="opt.lg_dirty_mult"><mallctl>opt.lg_dirty_mult</mallctl></link>
+ <listitem><para>Approximate time in milliseconds from the creation of a
+ set of unused dirty pages until an equivalent set of unused dirty pages
+ is purged and/or reused. See <link
+ linkend="opt.dirty_decay_ms"><mallctl>opt.dirty_decay_ms</mallctl></link>
+ for details.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.muzzy_decay_ms">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.muzzy_decay_ms</mallctl>
+ (<type>ssize_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Approximate time in milliseconds from the creation of a
+ set of unused muzzy pages until an equivalent set of unused muzzy pages
+ is purged and/or reused. See <link
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzzy_decay_ms</mallctl></link>
for details.</para></listitem>
</varlistentry>
@@ -2111,13 +2525,25 @@ typedef struct {
arena.</para></listitem>
</varlistentry>
+ <varlistentry id="stats.arenas.i.uptime">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.uptime</mallctl>
+ (<type>uint64_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Time elapsed (in nanoseconds) since the arena was
+ created. If &lt;i&gt; equals <constant>0</constant> or
+ <constant>MALLCTL_ARENAS_ALL</constant>, this is the uptime since malloc
+ initialization.</para></listitem>
+ </varlistentry>
+
<varlistentry id="stats.arenas.i.pactive">
<term>
<mallctl>stats.arenas.&lt;i&gt;.pactive</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Number of pages in active runs.</para></listitem>
+ <listitem><para>Number of pages in active extents.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.pdirty">
@@ -2126,10 +2552,23 @@ typedef struct {
(<type>size_t</type>)
<literal>r-</literal>
</term>
- <listitem><para>Number of pages within unused runs that are potentially
- dirty, and for which <function>madvise<parameter>...</parameter>
- <parameter><constant>MADV_DONTNEED</constant></parameter></function> or
- similar has not been called.</para></listitem>
+ <listitem><para>Number of pages within unused extents that are
+ potentially dirty, and for which <function>madvise()</function> or
+ similar has not been called. See <link
+ linkend="opt.dirty_decay_ms"><mallctl>opt.dirty_decay_ms</mallctl></link>
+ for a description of dirty pages.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.pmuzzy">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.pmuzzy</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ </term>
+ <listitem><para>Number of pages within unused extents that are muzzy.
+ See <link
+ linkend="opt.muzzy_decay_ms"><mallctl>opt.muzzy_decay_ms</mallctl></link>
+ for a description of muzzy pages.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.mapped">
@@ -2142,20 +2581,33 @@ typedef struct {
<listitem><para>Number of mapped bytes.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.metadata.mapped">
+ <varlistentry id="stats.arenas.i.retained">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.retained</mallctl>
+ (<type>size_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Number of retained bytes. See <link
+ linkend="stats.retained"><mallctl>stats.retained</mallctl></link> for
+ details.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.base">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.metadata.mapped</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.base</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of mapped bytes in arena chunk headers, which
- track the states of the non-metadata pages.</para></listitem>
+ <listitem><para>
+ Number of bytes dedicated to bootstrap-sensitive allocator metadata
+ structures.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.metadata.allocated">
+ <varlistentry id="stats.arenas.i.internal">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.metadata.allocated</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.internal</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
@@ -2163,180 +2615,199 @@ typedef struct {
<listitem><para>Number of bytes dedicated to internal allocations.
Internal allocations differ from application-originated allocations in
that they are for internal use, and that they are omitted from heap
- profiles. This statistic is reported separately from <link
- linkend="stats.metadata"><mallctl>stats.metadata</mallctl></link> and
- <link
- linkend="stats.arenas.i.metadata.mapped"><mallctl>stats.arenas.&lt;i&gt;.metadata.mapped</mallctl></link>
- because it overlaps with e.g. the <link
- linkend="stats.allocated"><mallctl>stats.allocated</mallctl></link> and
- <link linkend="stats.active"><mallctl>stats.active</mallctl></link>
- statistics, whereas the other metadata statistics do
- not.</para></listitem>
+ profiles.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.npurge">
+ <varlistentry id="stats.arenas.i.metadata_thp">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.npurge</mallctl>
- (<type>uint64_t</type>)
+ <mallctl>stats.arenas.&lt;i&gt;.metadata_thp</mallctl>
+ (<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of dirty page purge sweeps performed.
- </para></listitem>
+ <listitem><para>Number of transparent huge pages (THP) used for
+ metadata. See <link linkend="opt.metadata_thp">opt.metadata_thp</link>
+ for details.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.nmadvise">
+ <varlistentry id="stats.arenas.i.resident">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.nmadvise</mallctl>
- (<type>uint64_t</type>)
+ <mallctl>stats.arenas.&lt;i&gt;.resident</mallctl>
+ (<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of <function>madvise<parameter>...</parameter>
- <parameter><constant>MADV_DONTNEED</constant></parameter></function> or
- similar calls made to purge dirty pages.</para></listitem>
+ <listitem><para>Maximum number of bytes in physically resident data
+ pages mapped by the arena, comprising all pages dedicated to allocator
+ metadata, pages backing active allocations, and unused dirty pages.
+ This is a maximum rather than precise because pages may not actually be
+ physically resident if they correspond to demand-zeroed virtual memory
+ that has not yet been touched. This is a multiple of the page
+ size.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.purged">
+ <varlistentry id="stats.arenas.i.dirty_npurge">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.purged</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.dirty_npurge</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of pages purged.</para></listitem>
+ <listitem><para>Number of dirty page purge sweeps performed.
+ </para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.small.allocated">
+ <varlistentry id="stats.arenas.i.dirty_nmadvise">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.small.allocated</mallctl>
- (<type>size_t</type>)
+ <mallctl>stats.arenas.&lt;i&gt;.dirty_nmadvise</mallctl>
+ (<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of bytes currently allocated by small objects.
- </para></listitem>
+ <listitem><para>Number of <function>madvise()</function> or similar
+ calls made to purge dirty pages.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.small.nmalloc">
+ <varlistentry id="stats.arenas.i.dirty_purged">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.small.nmalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.dirty_purged</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation requests served by
- small bins.</para></listitem>
+ <listitem><para>Number of dirty pages purged.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.small.ndalloc">
+ <varlistentry id="stats.arenas.i.muzzy_npurge">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.small.ndalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.muzzy_npurge</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of small objects returned to bins.
+ <listitem><para>Number of muzzy page purge sweeps performed.
</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.small.nrequests">
+ <varlistentry id="stats.arenas.i.muzzy_nmadvise">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.small.nrequests</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.muzzy_nmadvise</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of small allocation requests.
- </para></listitem>
+ <listitem><para>Number of <function>madvise()</function> or similar
+ calls made to purge muzzy pages.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.large.allocated">
+ <varlistentry id="stats.arenas.i.muzzy_purged">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.large.allocated</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.muzzy_purged</mallctl>
+ (<type>uint64_t</type>)
+ <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Number of muzzy pages purged.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.small.allocated">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.small.allocated</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of bytes currently allocated by large objects.
+ <listitem><para>Number of bytes currently allocated by small objects.
</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.large.nmalloc">
+ <varlistentry id="stats.arenas.i.small.nmalloc">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.large.nmalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.small.nmalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of large allocation requests served
- directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a small allocation was
+ requested from the arena's bins, whether to fill the relevant tcache if
+ <link linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is
+ enabled, or to directly satisfy an allocation request
+ otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.large.ndalloc">
+ <varlistentry id="stats.arenas.i.small.ndalloc">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.large.ndalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.small.ndalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of large deallocation requests served
- directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a small allocation was
+ returned to the arena's bins, whether to flush the relevant tcache if
+ <link linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is
+ enabled, or to directly deallocate an allocation
+ otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.large.nrequests">
+ <varlistentry id="stats.arenas.i.small.nrequests">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.large.nrequests</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.small.nrequests</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of large allocation requests.
- </para></listitem>
+ <listitem><para>Cumulative number of allocation requests satisfied by
+ all bin size classes.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.huge.allocated">
+ <varlistentry id="stats.arenas.i.large.allocated">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.huge.allocated</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.large.allocated</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Number of bytes currently allocated by huge objects.
+ <listitem><para>Number of bytes currently allocated by large objects.
</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.huge.nmalloc">
+ <varlistentry id="stats.arenas.i.large.nmalloc">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.huge.nmalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.large.nmalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of huge allocation requests served
- directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a large extent was allocated
+ from the arena, whether to fill the relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled and
+ the size class is within the range being cached, or to directly satisfy
+ an allocation request otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.huge.ndalloc">
+ <varlistentry id="stats.arenas.i.large.ndalloc">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.huge.ndalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.large.ndalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of huge deallocation requests served
- directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a large extent was returned
+ to the arena, whether to flush the relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled and
+ the size class is within the range being cached, or to directly
+ deallocate an allocation otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.huge.nrequests">
+ <varlistentry id="stats.arenas.i.large.nrequests">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.huge.nrequests</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.large.nrequests</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of huge allocation requests.
- </para></listitem>
+ <listitem><para>Cumulative number of allocation requests satisfied by
+ all large size classes.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.bins.j.nmalloc">
@@ -2346,8 +2817,11 @@ typedef struct {
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocations served by bin.
- </para></listitem>
+ <listitem><para>Cumulative number of times a bin region of the
+ corresponding size class was allocated from the arena, whether to fill
+ the relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled, or
+ to directly satisfy an allocation request otherwise.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.bins.j.ndalloc">
@@ -2357,8 +2831,11 @@ typedef struct {
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocations returned to bin.
- </para></listitem>
+ <listitem><para>Cumulative number of times a bin region of the
+ corresponding size class was returned to the arena, whether to flush the
+ relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled, or
+ to directly deallocate an allocation otherwise.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.bins.j.nrequests">
@@ -2368,8 +2845,8 @@ typedef struct {
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation
- requests.</para></listitem>
+ <listitem><para>Cumulative number of allocation requests satisfied by
+ bin regions of the corresponding size class.</para></listitem>
</varlistentry>
<varlistentry id="stats.arenas.i.bins.j.curregs">
@@ -2388,7 +2865,6 @@ typedef struct {
<mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nfills</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
- [<option>--enable-stats</option> <option>--enable-tcache</option>]
</term>
<listitem><para>Cumulative number of tcache fills.</para></listitem>
</varlistentry>
@@ -2398,131 +2874,273 @@ typedef struct {
<mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nflushes</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
- [<option>--enable-stats</option> <option>--enable-tcache</option>]
</term>
<listitem><para>Cumulative number of tcache flushes.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.bins.j.nruns">
+ <varlistentry id="stats.arenas.i.bins.j.nslabs">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nruns</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nslabs</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of runs created.</para></listitem>
+ <listitem><para>Cumulative number of slabs created.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.bins.j.nreruns">
+ <varlistentry id="stats.arenas.i.bins.j.nreslabs">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nreruns</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.nreslabs</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of times the current run from which
+ <listitem><para>Cumulative number of times the current slab from which
to allocate changed.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.bins.j.curruns">
+ <varlistentry id="stats.arenas.i.bins.j.curslabs">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.curruns</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.curslabs</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Current number of runs.</para></listitem>
+ <listitem><para>Current number of slabs.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.lruns.j.nmalloc">
+ <varlistentry id="stats.arenas.i.bins.mutex">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.nmalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.bins.&lt;j&gt;.mutex.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on
+ <varname>arena.&lt;i&gt;.bins.&lt;j&gt;</varname> mutex (arena bin
+ scope; bin operation related). <mallctl>{counter}</mallctl> is one of
+ the counters in <link linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.lextents.j.nmalloc">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.lextents.&lt;j&gt;.nmalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation requests for this size
- class served directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a large extent of the
+ corresponding size class was allocated from the arena, whether to fill
+ the relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled and
+ the size class is within the range being cached, or to directly satisfy
+ an allocation request otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.lruns.j.ndalloc">
+ <varlistentry id="stats.arenas.i.lextents.j.ndalloc">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.ndalloc</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.lextents.&lt;j&gt;.ndalloc</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of deallocation requests for this
- size class served directly by the arena.</para></listitem>
+ <listitem><para>Cumulative number of times a large extent of the
+ corresponding size class was returned to the arena, whether to flush the
+ relevant tcache if <link
+ linkend="opt.tcache"><mallctl>opt.tcache</mallctl></link> is enabled and
+ the size class is within the range being cached, or to directly
+ deallocate an allocation otherwise.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.lruns.j.nrequests">
+ <varlistentry id="stats.arenas.i.lextents.j.nrequests">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.nrequests</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.lextents.&lt;j&gt;.nrequests</mallctl>
(<type>uint64_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation requests for this size
- class.</para></listitem>
+ <listitem><para>Cumulative number of allocation requests satisfied by
+ large extents of the corresponding size class.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.lruns.j.curruns">
+ <varlistentry id="stats.arenas.i.lextents.j.curlextents">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.lruns.&lt;j&gt;.curruns</mallctl>
+ <mallctl>stats.arenas.&lt;i&gt;.lextents.&lt;j&gt;.curlextents</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Current number of runs for this size class.
+ <listitem><para>Current number of large allocations for this size class.
</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.hchunks.j.nmalloc">
+ <varlistentry id="stats.arenas.i.mutexes.large">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.nmalloc</mallctl>
- (<type>uint64_t</type>)
- <literal>r-</literal>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.large.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation requests for this size
- class served directly by the arena.</para></listitem>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.large</varname>
+ mutex (arena scope; large allocation related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.hchunks.j.ndalloc">
+ <varlistentry id="stats.arenas.i.mutexes.extent_avail">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.ndalloc</mallctl>
- (<type>uint64_t</type>)
- <literal>r-</literal>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.extent_avail.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of deallocation requests for this
- size class served directly by the arena.</para></listitem>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.extent_avail
+ </varname> mutex (arena scope; extent avail related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.hchunks.j.nrequests">
+ <varlistentry id="stats.arenas.i.mutexes.extents_dirty">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.nrequests</mallctl>
- (<type>uint64_t</type>)
- <literal>r-</literal>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.extents_dirty.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Cumulative number of allocation requests for this size
- class.</para></listitem>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.extents_dirty
+ </varname> mutex (arena scope; dirty extents related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
</varlistentry>
- <varlistentry id="stats.arenas.i.hchunks.j.curhchunks">
+ <varlistentry id="stats.arenas.i.mutexes.extents_muzzy">
<term>
- <mallctl>stats.arenas.&lt;i&gt;.hchunks.&lt;j&gt;.curhchunks</mallctl>
- (<type>size_t</type>)
- <literal>r-</literal>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.extents_muzzy.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
[<option>--enable-stats</option>]
</term>
- <listitem><para>Current number of huge allocations for this size class.
- </para></listitem>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.extents_muzzy
+ </varname> mutex (arena scope; muzzy extents related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
</varlistentry>
+
+ <varlistentry id="stats.arenas.i.mutexes.extents_retained">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.extents_retained.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.extents_retained
+ </varname> mutex (arena scope; retained extents related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.mutexes.decay_dirty">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.decay_dirty.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.decay_dirty
+ </varname> mutex (arena scope; decay for dirty pages related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.mutexes.decay_muzzy">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.decay_muzzy.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.decay_muzzy
+ </varname> mutex (arena scope; decay for muzzy pages related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.mutexes.base">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.base.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on <varname>arena.&lt;i&gt;.base</varname>
+ mutex (arena scope; base allocator related).
+ <mallctl>{counter}</mallctl> is one of the counters in <link
+ linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry id="stats.arenas.i.mutexes.tcache_list">
+ <term>
+ <mallctl>stats.arenas.&lt;i&gt;.mutexes.tcache_list.{counter}</mallctl>
+ (<type>counter specific type</type>) <literal>r-</literal>
+ [<option>--enable-stats</option>]
+ </term>
+ <listitem><para>Statistics on
+ <varname>arena.&lt;i&gt;.tcache_list</varname> mutex (arena scope;
+ tcache to arena association related). This mutex is expected to be
+ accessed less often. <mallctl>{counter}</mallctl> is one of the
+ counters in <link linkend="mutex_counters">mutex profiling
+ counters</link>.</para></listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
+ <refsect1 id="heap_profile_format">
+ <title>HEAP PROFILE FORMAT</title>
+ <para>Although the heap profiling functionality was originally designed to
+ be compatible with the
+ <command>pprof</command> command that is developed as part of the <ulink
+ url="http://code.google.com/p/gperftools/">gperftools
+ package</ulink>, the addition of per thread heap profiling functionality
+ required a different heap profile format. The <command>jeprof</command>
+ command is derived from <command>pprof</command>, with enhancements to
+ support the heap profile format described here.</para>
+
+ <para>In the following hypothetical heap profile, <constant>[...]</constant>
+ indicates elision for the sake of compactness. <programlisting><![CDATA[
+heap_v2/524288
+ t*: 28106: 56637512 [0: 0]
+ [...]
+ t3: 352: 16777344 [0: 0]
+ [...]
+ t99: 17754: 29341640 [0: 0]
+ [...]
+@ 0x5f86da8 0x5f5a1dc [...] 0x29e4d4e 0xa200316 0xabb2988 [...]
+ t*: 13: 6688 [0: 0]
+ t3: 12: 6496 [0: ]
+ t99: 1: 192 [0: 0]
+[...]
+
+MAPPED_LIBRARIES:
+[...]]]></programlisting> The following matches the above heap profile, but most
+tokens are replaced with <constant>&lt;description&gt;</constant> to indicate
+descriptions of the corresponding fields. <programlisting><![CDATA[
+<heap_profile_format_version>/<mean_sample_interval>
+ <aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
+ [...]
+ <thread_3_aggregate>: <curobjs>: <curbytes>[<cumobjs>: <cumbytes>]
+ [...]
+ <thread_99_aggregate>: <curobjs>: <curbytes>[<cumobjs>: <cumbytes>]
+ [...]
+@ <top_frame> <frame> [...] <frame> <frame> <frame> [...]
+ <backtrace_aggregate>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
+ <backtrace_thread_3>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
+ <backtrace_thread_99>: <curobjs>: <curbytes> [<cumobjs>: <cumbytes>]
+[...]
+
+MAPPED_LIBRARIES:
+</proc/<pid>/maps>]]></programlisting></para>
+ </refsect1>
+
<refsect1 id="debugging_malloc_problems">
<title>DEBUGGING MALLOC PROBLEMS</title>
<para>When debugging, it is a good idea to configure/build jemalloc with
@@ -2532,7 +3150,7 @@ typedef struct {
of run-time assertions that catch application errors such as double-free,
write-after-free, etc.</para>
- <para>Programs often accidentally depend on &ldquo;uninitialized&rdquo;
+ <para>Programs often accidentally depend on <quote>uninitialized</quote>
memory actually being filled with zero bytes. Junk filling
(see the <link linkend="opt.junk"><mallctl>opt.junk</mallctl></link>
option) tends to expose such bugs in the form of obviously incorrect
@@ -2544,9 +3162,7 @@ typedef struct {
<para>This implementation does not provide much detail about the problems
it detects, because the performance impact for storing such information
- would be prohibitive. However, jemalloc does integrate with the most
- excellent <ulink url="http://valgrind.org/">Valgrind</ulink> tool if the
- <option>--enable-valgrind</option> configuration option is enabled.</para>
+ would be prohibitive.</para>
</refsect1>
<refsect1 id="diagnostic_messages">
<title>DIAGNOSTIC MESSAGES</title>
@@ -2561,29 +3177,29 @@ typedef struct {
to override the function which emits the text strings forming the errors
and warnings if for some reason the <constant>STDERR_FILENO</constant> file
descriptor is not suitable for this.
- <function>malloc_message<parameter/></function> takes the
+ <function>malloc_message()</function> takes the
<parameter>cbopaque</parameter> pointer argument that is
<constant>NULL</constant> unless overridden by the arguments in a call to
- <function>malloc_stats_print<parameter/></function>, followed by a string
+ <function>malloc_stats_print()</function>, followed by a string
pointer. Please note that doing anything which tries to allocate memory in
this function is likely to result in a crash or deadlock.</para>
<para>All messages are prefixed by
- &ldquo;<computeroutput>&lt;jemalloc&gt;: </computeroutput>&rdquo;.</para>
+ <quote><computeroutput>&lt;jemalloc&gt;: </computeroutput></quote>.</para>
</refsect1>
<refsect1 id="return_values">
<title>RETURN VALUES</title>
<refsect2>
<title>Standard API</title>
- <para>The <function>malloc<parameter/></function> and
- <function>calloc<parameter/></function> functions return a pointer to the
+ <para>The <function>malloc()</function> and
+ <function>calloc()</function> functions return a pointer to the
allocated memory if successful; otherwise a <constant>NULL</constant>
pointer is returned and <varname>errno</varname> is set to
<errorname>ENOMEM</errorname>.</para>
- <para>The <function>posix_memalign<parameter/></function> function
+ <para>The <function>posix_memalign()</function> function
returns the value 0 if successful; otherwise it returns an error value.
- The <function>posix_memalign<parameter/></function> function will fail
+ The <function>posix_memalign()</function> function will fail
if:
<variablelist>
<varlistentry>
@@ -2602,11 +3218,11 @@ typedef struct {
</variablelist>
</para>
- <para>The <function>aligned_alloc<parameter/></function> function returns
+ <para>The <function>aligned_alloc()</function> function returns
a pointer to the allocated memory if successful; otherwise a
<constant>NULL</constant> pointer is returned and
<varname>errno</varname> is set. The
- <function>aligned_alloc<parameter/></function> function will fail if:
+ <function>aligned_alloc()</function> function will fail if:
<variablelist>
<varlistentry>
<term><errorname>EINVAL</errorname></term>
@@ -2623,44 +3239,44 @@ typedef struct {
</variablelist>
</para>
- <para>The <function>realloc<parameter/></function> function returns a
+ <para>The <function>realloc()</function> function returns a
pointer, possibly identical to <parameter>ptr</parameter>, to the
allocated memory if successful; otherwise a <constant>NULL</constant>
pointer is returned, and <varname>errno</varname> is set to
<errorname>ENOMEM</errorname> if the error was the result of an
- allocation failure. The <function>realloc<parameter/></function>
+ allocation failure. The <function>realloc()</function>
function always leaves the original buffer intact when an error occurs.
</para>
- <para>The <function>free<parameter/></function> function returns no
+ <para>The <function>free()</function> function returns no
value.</para>
</refsect2>
<refsect2>
<title>Non-standard API</title>
- <para>The <function>mallocx<parameter/></function> and
- <function>rallocx<parameter/></function> functions return a pointer to
+ <para>The <function>mallocx()</function> and
+ <function>rallocx()</function> functions return a pointer to
the allocated memory if successful; otherwise a <constant>NULL</constant>
pointer is returned to indicate insufficient contiguous memory was
available to service the allocation request. </para>
- <para>The <function>xallocx<parameter/></function> function returns the
+ <para>The <function>xallocx()</function> function returns the
real size of the resulting resized allocation pointed to by
<parameter>ptr</parameter>, which is a value less than
<parameter>size</parameter> if the allocation could not be adequately
grown in place. </para>
- <para>The <function>sallocx<parameter/></function> function returns the
+ <para>The <function>sallocx()</function> function returns the
real size of the allocation pointed to by <parameter>ptr</parameter>.
</para>
- <para>The <function>nallocx<parameter/></function> returns the real size
+ <para>The <function>nallocx()</function> returns the real size
that would result from a successful equivalent
- <function>mallocx<parameter/></function> function call, or zero if
+ <function>mallocx()</function> function call, or zero if
insufficient memory is available to perform the size computation. </para>
- <para>The <function>mallctl<parameter/></function>,
- <function>mallctlnametomib<parameter/></function>, and
- <function>mallctlbymib<parameter/></function> functions return 0 on
+ <para>The <function>mallctl()</function>,
+ <function>mallctlnametomib()</function>, and
+ <function>mallctlbymib()</function> functions return 0 on
success; otherwise they return an error value. The functions will fail
if:
<variablelist>
@@ -2696,13 +3312,13 @@ typedef struct {
<term><errorname>EFAULT</errorname></term>
<listitem><para>An interface with side effects failed in some way
- not directly related to <function>mallctl*<parameter/></function>
+ not directly related to <function>mallctl*()</function>
read/write processing.</para></listitem>
</varlistentry>
</variablelist>
</para>
- <para>The <function>malloc_usable_size<parameter/></function> function
+ <para>The <function>malloc_usable_size()</function> function
returns the usable size of the allocation pointed to by
<parameter>ptr</parameter>. </para>
</refsect2>
@@ -2727,9 +3343,10 @@ typedef struct {
<para>To dump core whenever a problem occurs:
<screen>ln -s 'abort:true' /etc/malloc.conf</screen>
</para>
- <para>To specify in the source a chunk size that is 16 MiB:
+ <para>To specify in the source that only one arena should be automatically
+ created:
<programlisting language="C"><![CDATA[
-malloc_conf = "lg_chunk:24";]]></programlisting></para>
+malloc_conf = "narenas:1";]]></programlisting></para>
</refsect1>
<refsect1 id="see_also">
<title>SEE ALSO</title>
@@ -2750,13 +3367,13 @@ malloc_conf = "lg_chunk:24";]]></programlisting></para>
</refsect1>
<refsect1 id="standards">
<title>STANDARDS</title>
- <para>The <function>malloc<parameter/></function>,
- <function>calloc<parameter/></function>,
- <function>realloc<parameter/></function>, and
- <function>free<parameter/></function> functions conform to ISO/IEC
- 9899:1990 (&ldquo;ISO C90&rdquo;).</para>
-
- <para>The <function>posix_memalign<parameter/></function> function conforms
- to IEEE Std 1003.1-2001 (&ldquo;POSIX.1&rdquo;).</para>
+ <para>The <function>malloc()</function>,
+ <function>calloc()</function>,
+ <function>realloc()</function>, and
+ <function>free()</function> functions conform to ISO/IEC
+ 9899:1990 (<quote>ISO C90</quote>).</para>
+
+ <para>The <function>posix_memalign()</function> function conforms
+ to IEEE Std 1003.1-2001 (<quote>POSIX.1</quote>).</para>
</refsect1>
</refentry>
diff --git a/deps/jemalloc/doc/stylesheet.xsl b/deps/jemalloc/doc/stylesheet.xsl
index 4e334a86f..619365d82 100644
--- a/deps/jemalloc/doc/stylesheet.xsl
+++ b/deps/jemalloc/doc/stylesheet.xsl
@@ -1,7 +1,10 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="funcsynopsis.style">ansi</xsl:param>
- <xsl:param name="function.parens" select="1"/>
+ <xsl:param name="function.parens" select="0"/>
+ <xsl:template match="function">
+ <xsl:call-template name="inline.monoseq"/>
+ </xsl:template>
<xsl:template match="mallctl">
- "<xsl:call-template name="inline.monoseq"/>"
+ <quote><xsl:call-template name="inline.monoseq"/></quote>
</xsl:template>
</xsl:stylesheet>
diff --git a/deps/jemalloc/include/jemalloc/internal/arena.h b/deps/jemalloc/include/jemalloc/internal/arena.h
deleted file mode 100644
index 12c617979..000000000
--- a/deps/jemalloc/include/jemalloc/internal/arena.h
+++ /dev/null
@@ -1,1347 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#define LARGE_MINCLASS (ZU(1) << LG_LARGE_MINCLASS)
-
-/* Maximum number of regions in one run. */
-#define LG_RUN_MAXREGS (LG_PAGE - LG_TINY_MIN)
-#define RUN_MAXREGS (1U << LG_RUN_MAXREGS)
-
-/*
- * Minimum redzone size. Redzones may be larger than this if necessary to
- * preserve region alignment.
- */
-#define REDZONE_MINSIZE 16
-
-/*
- * The minimum ratio of active:dirty pages per arena is computed as:
- *
- * (nactive >> lg_dirty_mult) >= ndirty
- *
- * So, supposing that lg_dirty_mult is 3, there can be no less than 8 times as
- * many active pages as dirty pages.
- */
-#define LG_DIRTY_MULT_DEFAULT 3
-
-typedef struct arena_runs_dirty_link_s arena_runs_dirty_link_t;
-typedef struct arena_run_s arena_run_t;
-typedef struct arena_chunk_map_bits_s arena_chunk_map_bits_t;
-typedef struct arena_chunk_map_misc_s arena_chunk_map_misc_t;
-typedef struct arena_chunk_s arena_chunk_t;
-typedef struct arena_bin_info_s arena_bin_info_t;
-typedef struct arena_bin_s arena_bin_t;
-typedef struct arena_s arena_t;
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#ifdef JEMALLOC_ARENA_STRUCTS_A
-struct arena_run_s {
- /* Index of bin this run is associated with. */
- szind_t binind;
-
- /* Number of free regions in run. */
- unsigned nfree;
-
- /* Per region allocated/deallocated bitmap. */
- bitmap_t bitmap[BITMAP_GROUPS_MAX];
-};
-
-/* Each element of the chunk map corresponds to one page within the chunk. */
-struct arena_chunk_map_bits_s {
- /*
- * Run address (or size) and various flags are stored together. The bit
- * layout looks like (assuming 32-bit system):
- *
- * ???????? ???????? ???nnnnn nnndumla
- *
- * ? : Unallocated: Run address for first/last pages, unset for internal
- * pages.
- * Small: Run page offset.
- * Large: Run page count for first page, unset for trailing pages.
- * n : binind for small size class, BININD_INVALID for large size class.
- * d : dirty?
- * u : unzeroed?
- * m : decommitted?
- * l : large?
- * a : allocated?
- *
- * Following are example bit patterns for the three types of runs.
- *
- * p : run page offset
- * s : run size
- * n : binind for size class; large objects set these to BININD_INVALID
- * x : don't care
- * - : 0
- * + : 1
- * [DUMLA] : bit set
- * [dumla] : bit unset
- *
- * Unallocated (clean):
- * ssssssss ssssssss sss+++++ +++dum-a
- * xxxxxxxx xxxxxxxx xxxxxxxx xxx-Uxxx
- * ssssssss ssssssss sss+++++ +++dUm-a
- *
- * Unallocated (dirty):
- * ssssssss ssssssss sss+++++ +++D-m-a
- * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * ssssssss ssssssss sss+++++ +++D-m-a
- *
- * Small:
- * pppppppp pppppppp pppnnnnn nnnd---A
- * pppppppp pppppppp pppnnnnn nnn----A
- * pppppppp pppppppp pppnnnnn nnnd---A
- *
- * Large:
- * ssssssss ssssssss sss+++++ +++D--LA
- * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * -------- -------- ---+++++ +++D--LA
- *
- * Large (sampled, size <= LARGE_MINCLASS):
- * ssssssss ssssssss sssnnnnn nnnD--LA
- * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * -------- -------- ---+++++ +++D--LA
- *
- * Large (not sampled, size == LARGE_MINCLASS):
- * ssssssss ssssssss sss+++++ +++D--LA
- * xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
- * -------- -------- ---+++++ +++D--LA
- */
- size_t bits;
-#define CHUNK_MAP_ALLOCATED ((size_t)0x01U)
-#define CHUNK_MAP_LARGE ((size_t)0x02U)
-#define CHUNK_MAP_STATE_MASK ((size_t)0x3U)
-
-#define CHUNK_MAP_DECOMMITTED ((size_t)0x04U)
-#define CHUNK_MAP_UNZEROED ((size_t)0x08U)
-#define CHUNK_MAP_DIRTY ((size_t)0x10U)
-#define CHUNK_MAP_FLAGS_MASK ((size_t)0x1cU)
-
-#define CHUNK_MAP_BININD_SHIFT 5
-#define BININD_INVALID ((size_t)0xffU)
-#define CHUNK_MAP_BININD_MASK (BININD_INVALID << CHUNK_MAP_BININD_SHIFT)
-#define CHUNK_MAP_BININD_INVALID CHUNK_MAP_BININD_MASK
-
-#define CHUNK_MAP_RUNIND_SHIFT (CHUNK_MAP_BININD_SHIFT + 8)
-#define CHUNK_MAP_SIZE_SHIFT (CHUNK_MAP_RUNIND_SHIFT - LG_PAGE)
-#define CHUNK_MAP_SIZE_MASK \
- (~(CHUNK_MAP_BININD_MASK | CHUNK_MAP_FLAGS_MASK | CHUNK_MAP_STATE_MASK))
-};
-
-struct arena_runs_dirty_link_s {
- qr(arena_runs_dirty_link_t) rd_link;
-};
-
-/*
- * Each arena_chunk_map_misc_t corresponds to one page within the chunk, just
- * like arena_chunk_map_bits_t. Two separate arrays are stored within each
- * chunk header in order to improve cache locality.
- */
-struct arena_chunk_map_misc_s {
- /*
- * Linkage for run trees. There are two disjoint uses:
- *
- * 1) arena_t's runs_avail tree.
- * 2) arena_run_t conceptually uses this linkage for in-use non-full
- * runs, rather than directly embedding linkage.
- */
- rb_node(arena_chunk_map_misc_t) rb_link;
-
- union {
- /* Linkage for list of dirty runs. */
- arena_runs_dirty_link_t rd;
-
- /* Profile counters, used for large object runs. */
- union {
- void *prof_tctx_pun;
- prof_tctx_t *prof_tctx;
- };
-
- /* Small region run metadata. */
- arena_run_t run;
- };
-};
-typedef rb_tree(arena_chunk_map_misc_t) arena_avail_tree_t;
-typedef rb_tree(arena_chunk_map_misc_t) arena_run_tree_t;
-#endif /* JEMALLOC_ARENA_STRUCTS_A */
-
-#ifdef JEMALLOC_ARENA_STRUCTS_B
-/* Arena chunk header. */
-struct arena_chunk_s {
- /*
- * A pointer to the arena that owns the chunk is stored within the node.
- * This field as a whole is used by chunks_rtree to support both
- * ivsalloc() and core-based debugging.
- */
- extent_node_t node;
-
- /*
- * Map of pages within chunk that keeps track of free/large/small. The
- * first map_bias entries are omitted, since the chunk header does not
- * need to be tracked in the map. This omission saves a header page
- * for common chunk sizes (e.g. 4 MiB).
- */
- arena_chunk_map_bits_t map_bits[1]; /* Dynamically sized. */
-};
-
-/*
- * Read-only information associated with each element of arena_t's bins array
- * is stored separately, partly to reduce memory usage (only one copy, rather
- * than one per arena), but mainly to avoid false cacheline sharing.
- *
- * Each run has the following layout:
- *
- * /--------------------\
- * | pad? |
- * |--------------------|
- * | redzone |
- * reg0_offset | region 0 |
- * | redzone |
- * |--------------------| \
- * | redzone | |
- * | region 1 | > reg_interval
- * | redzone | /
- * |--------------------|
- * | ... |
- * | ... |
- * | ... |
- * |--------------------|
- * | redzone |
- * | region nregs-1 |
- * | redzone |
- * |--------------------|
- * | alignment pad? |
- * \--------------------/
- *
- * reg_interval has at least the same minimum alignment as reg_size; this
- * preserves the alignment constraint that sa2u() depends on. Alignment pad is
- * either 0 or redzone_size; it is present only if needed to align reg0_offset.
- */
-struct arena_bin_info_s {
- /* Size of regions in a run for this bin's size class. */
- size_t reg_size;
-
- /* Redzone size. */
- size_t redzone_size;
-
- /* Interval between regions (reg_size + (redzone_size << 1)). */
- size_t reg_interval;
-
- /* Total size of a run for this bin's size class. */
- size_t run_size;
-
- /* Total number of regions in a run for this bin's size class. */
- uint32_t nregs;
-
- /*
- * Metadata used to manipulate bitmaps for runs associated with this
- * bin.
- */
- bitmap_info_t bitmap_info;
-
- /* Offset of first region in a run for this bin's size class. */
- uint32_t reg0_offset;
-};
-
-struct arena_bin_s {
- /*
- * All operations on runcur, runs, and stats require that lock be
- * locked. Run allocation/deallocation are protected by the arena lock,
- * which may be acquired while holding one or more bin locks, but not
- * vise versa.
- */
- malloc_mutex_t lock;
-
- /*
- * Current run being used to service allocations of this bin's size
- * class.
- */
- arena_run_t *runcur;
-
- /*
- * Tree of non-full runs. This tree is used when looking for an
- * existing run when runcur is no longer usable. We choose the
- * non-full run that is lowest in memory; this policy tends to keep
- * objects packed well, and it can also help reduce the number of
- * almost-empty chunks.
- */
- arena_run_tree_t runs;
-
- /* Bin statistics. */
- malloc_bin_stats_t stats;
-};
-
-struct arena_s {
- /* This arena's index within the arenas array. */
- unsigned ind;
-
- /*
- * Number of threads currently assigned to this arena. This field is
- * protected by arenas_lock.
- */
- unsigned nthreads;
-
- /*
- * There are three classes of arena operations from a locking
- * perspective:
- * 1) Thread assignment (modifies nthreads) is protected by arenas_lock.
- * 2) Bin-related operations are protected by bin locks.
- * 3) Chunk- and run-related operations are protected by this mutex.
- */
- malloc_mutex_t lock;
-
- arena_stats_t stats;
- /*
- * List of tcaches for extant threads associated with this arena.
- * Stats from these are merged incrementally, and at exit if
- * opt_stats_print is enabled.
- */
- ql_head(tcache_t) tcache_ql;
-
- uint64_t prof_accumbytes;
-
- /*
- * PRNG state for cache index randomization of large allocation base
- * pointers.
- */
- uint64_t offset_state;
-
- dss_prec_t dss_prec;
-
- /*
- * In order to avoid rapid chunk allocation/deallocation when an arena
- * oscillates right on the cusp of needing a new chunk, cache the most
- * recently freed chunk. The spare is left in the arena's chunk trees
- * until it is deleted.
- *
- * There is one spare chunk per arena, rather than one spare total, in
- * order to avoid interactions between multiple threads that could make
- * a single spare inadequate.
- */
- arena_chunk_t *spare;
-
- /* Minimum ratio (log base 2) of nactive:ndirty. */
- ssize_t lg_dirty_mult;
-
- /* True if a thread is currently executing arena_purge(). */
- bool purging;
-
- /* Number of pages in active runs and huge regions. */
- size_t nactive;
-
- /*
- * Current count of pages within unused runs that are potentially
- * dirty, and for which madvise(... MADV_DONTNEED) has not been called.
- * By tracking this, we can institute a limit on how much dirty unused
- * memory is mapped for each arena.
- */
- size_t ndirty;
-
- /*
- * Size/address-ordered tree of this arena's available runs. The tree
- * is used for first-best-fit run allocation.
- */
- arena_avail_tree_t runs_avail;
-
- /*
- * Unused dirty memory this arena manages. Dirty memory is conceptually
- * tracked as an arbitrarily interleaved LRU of dirty runs and cached
- * chunks, but the list linkage is actually semi-duplicated in order to
- * avoid extra arena_chunk_map_misc_t space overhead.
- *
- * LRU-----------------------------------------------------------MRU
- *
- * /-- arena ---\
- * | |
- * | |
- * |------------| /- chunk -\
- * ...->|chunks_cache|<--------------------------->| /----\ |<--...
- * |------------| | |node| |
- * | | | | | |
- * | | /- run -\ /- run -\ | | | |
- * | | | | | | | | | |
- * | | | | | | | | | |
- * |------------| |-------| |-------| | |----| |
- * ...->|runs_dirty |<-->|rd |<-->|rd |<---->|rd |<----...
- * |------------| |-------| |-------| | |----| |
- * | | | | | | | | | |
- * | | | | | | | \----/ |
- * | | \-------/ \-------/ | |
- * | | | |
- * | | | |
- * \------------/ \---------/
- */
- arena_runs_dirty_link_t runs_dirty;
- extent_node_t chunks_cache;
-
- /* Extant huge allocations. */
- ql_head(extent_node_t) huge;
- /* Synchronizes all huge allocation/update/deallocation. */
- malloc_mutex_t huge_mtx;
-
- /*
- * Trees of chunks that were previously allocated (trees differ only in
- * node ordering). These are used when allocating chunks, in an attempt
- * to re-use address space. Depending on function, different tree
- * orderings are needed, which is why there are two trees with the same
- * contents.
- */
- extent_tree_t chunks_szad_cached;
- extent_tree_t chunks_ad_cached;
- extent_tree_t chunks_szad_retained;
- extent_tree_t chunks_ad_retained;
-
- malloc_mutex_t chunks_mtx;
- /* Cache of nodes that were allocated via base_alloc(). */
- ql_head(extent_node_t) node_cache;
- malloc_mutex_t node_cache_mtx;
-
- /* User-configurable chunk hook functions. */
- chunk_hooks_t chunk_hooks;
-
- /* bins is used to store trees of free regions. */
- arena_bin_t bins[NBINS];
-};
-#endif /* JEMALLOC_ARENA_STRUCTS_B */
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-static const size_t large_pad =
-#ifdef JEMALLOC_CACHE_OBLIVIOUS
- PAGE
-#else
- 0
-#endif
- ;
-
-extern ssize_t opt_lg_dirty_mult;
-
-extern arena_bin_info_t arena_bin_info[NBINS];
-
-extern size_t map_bias; /* Number of arena chunk header pages. */
-extern size_t map_misc_offset;
-extern size_t arena_maxrun; /* Max run size for arenas. */
-extern size_t large_maxclass; /* Max large size class. */
-extern unsigned nlclasses; /* Number of large size classes. */
-extern unsigned nhclasses; /* Number of huge size classes. */
-
-void arena_chunk_cache_maybe_insert(arena_t *arena, extent_node_t *node,
- bool cache);
-void arena_chunk_cache_maybe_remove(arena_t *arena, extent_node_t *node,
- bool cache);
-extent_node_t *arena_node_alloc(arena_t *arena);
-void arena_node_dalloc(arena_t *arena, extent_node_t *node);
-void *arena_chunk_alloc_huge(arena_t *arena, size_t usize, size_t alignment,
- bool *zero);
-void arena_chunk_dalloc_huge(arena_t *arena, void *chunk, size_t usize);
-void arena_chunk_ralloc_huge_similar(arena_t *arena, void *chunk,
- size_t oldsize, size_t usize);
-void arena_chunk_ralloc_huge_shrink(arena_t *arena, void *chunk,
- size_t oldsize, size_t usize);
-bool arena_chunk_ralloc_huge_expand(arena_t *arena, void *chunk,
- size_t oldsize, size_t usize, bool *zero);
-ssize_t arena_lg_dirty_mult_get(arena_t *arena);
-bool arena_lg_dirty_mult_set(arena_t *arena, ssize_t lg_dirty_mult);
-void arena_maybe_purge(arena_t *arena);
-void arena_purge_all(arena_t *arena);
-void arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin,
- szind_t binind, uint64_t prof_accumbytes);
-void arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info,
- bool zero);
-#ifdef JEMALLOC_JET
-typedef void (arena_redzone_corruption_t)(void *, size_t, bool, size_t,
- uint8_t);
-extern arena_redzone_corruption_t *arena_redzone_corruption;
-typedef void (arena_dalloc_junk_small_t)(void *, arena_bin_info_t *);
-extern arena_dalloc_junk_small_t *arena_dalloc_junk_small;
-#else
-void arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info);
-#endif
-void arena_quarantine_junk_small(void *ptr, size_t usize);
-void *arena_malloc_small(arena_t *arena, size_t size, bool zero);
-void *arena_malloc_large(arena_t *arena, size_t size, bool zero);
-void *arena_palloc(tsd_t *tsd, arena_t *arena, size_t usize,
- size_t alignment, bool zero, tcache_t *tcache);
-void arena_prof_promoted(const void *ptr, size_t size);
-void arena_dalloc_bin_junked_locked(arena_t *arena, arena_chunk_t *chunk,
- void *ptr, arena_chunk_map_bits_t *bitselm);
-void arena_dalloc_bin(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t pageind, arena_chunk_map_bits_t *bitselm);
-void arena_dalloc_small(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t pageind);
-#ifdef JEMALLOC_JET
-typedef void (arena_dalloc_junk_large_t)(void *, size_t);
-extern arena_dalloc_junk_large_t *arena_dalloc_junk_large;
-#else
-void arena_dalloc_junk_large(void *ptr, size_t usize);
-#endif
-void arena_dalloc_large_junked_locked(arena_t *arena, arena_chunk_t *chunk,
- void *ptr);
-void arena_dalloc_large(arena_t *arena, arena_chunk_t *chunk, void *ptr);
-#ifdef JEMALLOC_JET
-typedef void (arena_ralloc_junk_large_t)(void *, size_t, size_t);
-extern arena_ralloc_junk_large_t *arena_ralloc_junk_large;
-#endif
-bool arena_ralloc_no_move(void *ptr, size_t oldsize, size_t size,
- size_t extra, bool zero);
-void *arena_ralloc(tsd_t *tsd, arena_t *arena, void *ptr, size_t oldsize,
- size_t size, size_t alignment, bool zero, tcache_t *tcache);
-dss_prec_t arena_dss_prec_get(arena_t *arena);
-bool arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec);
-ssize_t arena_lg_dirty_mult_default_get(void);
-bool arena_lg_dirty_mult_default_set(ssize_t lg_dirty_mult);
-void arena_stats_merge(arena_t *arena, const char **dss,
- ssize_t *lg_dirty_mult, size_t *nactive, size_t *ndirty,
- arena_stats_t *astats, malloc_bin_stats_t *bstats,
- malloc_large_stats_t *lstats, malloc_huge_stats_t *hstats);
-arena_t *arena_new(unsigned ind);
-bool arena_boot(void);
-void arena_prefork(arena_t *arena);
-void arena_postfork_parent(arena_t *arena);
-void arena_postfork_child(arena_t *arena);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-arena_chunk_map_bits_t *arena_bitselm_get(arena_chunk_t *chunk,
- size_t pageind);
-arena_chunk_map_misc_t *arena_miscelm_get(arena_chunk_t *chunk,
- size_t pageind);
-size_t arena_miscelm_to_pageind(arena_chunk_map_misc_t *miscelm);
-void *arena_miscelm_to_rpages(arena_chunk_map_misc_t *miscelm);
-arena_chunk_map_misc_t *arena_rd_to_miscelm(arena_runs_dirty_link_t *rd);
-arena_chunk_map_misc_t *arena_run_to_miscelm(arena_run_t *run);
-size_t *arena_mapbitsp_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbitsp_read(size_t *mapbitsp);
-size_t arena_mapbits_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_size_decode(size_t mapbits);
-size_t arena_mapbits_unallocated_size_get(arena_chunk_t *chunk,
- size_t pageind);
-size_t arena_mapbits_large_size_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_small_runind_get(arena_chunk_t *chunk, size_t pageind);
-szind_t arena_mapbits_binind_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_dirty_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_unzeroed_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_decommitted_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_large_get(arena_chunk_t *chunk, size_t pageind);
-size_t arena_mapbits_allocated_get(arena_chunk_t *chunk, size_t pageind);
-void arena_mapbitsp_write(size_t *mapbitsp, size_t mapbits);
-size_t arena_mapbits_size_encode(size_t size);
-void arena_mapbits_unallocated_set(arena_chunk_t *chunk, size_t pageind,
- size_t size, size_t flags);
-void arena_mapbits_unallocated_size_set(arena_chunk_t *chunk, size_t pageind,
- size_t size);
-void arena_mapbits_internal_set(arena_chunk_t *chunk, size_t pageind,
- size_t flags);
-void arena_mapbits_large_set(arena_chunk_t *chunk, size_t pageind,
- size_t size, size_t flags);
-void arena_mapbits_large_binind_set(arena_chunk_t *chunk, size_t pageind,
- szind_t binind);
-void arena_mapbits_small_set(arena_chunk_t *chunk, size_t pageind,
- size_t runind, szind_t binind, size_t flags);
-void arena_metadata_allocated_add(arena_t *arena, size_t size);
-void arena_metadata_allocated_sub(arena_t *arena, size_t size);
-size_t arena_metadata_allocated_get(arena_t *arena);
-bool arena_prof_accum_impl(arena_t *arena, uint64_t accumbytes);
-bool arena_prof_accum_locked(arena_t *arena, uint64_t accumbytes);
-bool arena_prof_accum(arena_t *arena, uint64_t accumbytes);
-szind_t arena_ptr_small_binind_get(const void *ptr, size_t mapbits);
-szind_t arena_bin_index(arena_t *arena, arena_bin_t *bin);
-unsigned arena_run_regind(arena_run_t *run, arena_bin_info_t *bin_info,
- const void *ptr);
-prof_tctx_t *arena_prof_tctx_get(const void *ptr);
-void arena_prof_tctx_set(const void *ptr, size_t usize, prof_tctx_t *tctx);
-void arena_prof_tctx_reset(const void *ptr, size_t usize,
- const void *old_ptr, prof_tctx_t *old_tctx);
-void *arena_malloc(tsd_t *tsd, arena_t *arena, size_t size, bool zero,
- tcache_t *tcache);
-arena_t *arena_aalloc(const void *ptr);
-size_t arena_salloc(const void *ptr, bool demote);
-void arena_dalloc(tsd_t *tsd, void *ptr, tcache_t *tcache);
-void arena_sdalloc(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_ARENA_C_))
-# ifdef JEMALLOC_ARENA_INLINE_A
-JEMALLOC_ALWAYS_INLINE arena_chunk_map_bits_t *
-arena_bitselm_get(arena_chunk_t *chunk, size_t pageind)
-{
-
- assert(pageind >= map_bias);
- assert(pageind < chunk_npages);
-
- return (&chunk->map_bits[pageind-map_bias]);
-}
-
-JEMALLOC_ALWAYS_INLINE arena_chunk_map_misc_t *
-arena_miscelm_get(arena_chunk_t *chunk, size_t pageind)
-{
-
- assert(pageind >= map_bias);
- assert(pageind < chunk_npages);
-
- return ((arena_chunk_map_misc_t *)((uintptr_t)chunk +
- (uintptr_t)map_misc_offset) + pageind-map_bias);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_miscelm_to_pageind(arena_chunk_map_misc_t *miscelm)
-{
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm);
- size_t pageind = ((uintptr_t)miscelm - ((uintptr_t)chunk +
- map_misc_offset)) / sizeof(arena_chunk_map_misc_t) + map_bias;
-
- assert(pageind >= map_bias);
- assert(pageind < chunk_npages);
-
- return (pageind);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-arena_miscelm_to_rpages(arena_chunk_map_misc_t *miscelm)
-{
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
-
- return ((void *)((uintptr_t)chunk + (pageind << LG_PAGE)));
-}
-
-JEMALLOC_ALWAYS_INLINE arena_chunk_map_misc_t *
-arena_rd_to_miscelm(arena_runs_dirty_link_t *rd)
-{
- arena_chunk_map_misc_t *miscelm = (arena_chunk_map_misc_t
- *)((uintptr_t)rd - offsetof(arena_chunk_map_misc_t, rd));
-
- assert(arena_miscelm_to_pageind(miscelm) >= map_bias);
- assert(arena_miscelm_to_pageind(miscelm) < chunk_npages);
-
- return (miscelm);
-}
-
-JEMALLOC_ALWAYS_INLINE arena_chunk_map_misc_t *
-arena_run_to_miscelm(arena_run_t *run)
-{
- arena_chunk_map_misc_t *miscelm = (arena_chunk_map_misc_t
- *)((uintptr_t)run - offsetof(arena_chunk_map_misc_t, run));
-
- assert(arena_miscelm_to_pageind(miscelm) >= map_bias);
- assert(arena_miscelm_to_pageind(miscelm) < chunk_npages);
-
- return (miscelm);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t *
-arena_mapbitsp_get(arena_chunk_t *chunk, size_t pageind)
-{
-
- return (&arena_bitselm_get(chunk, pageind)->bits);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbitsp_read(size_t *mapbitsp)
-{
-
- return (*mapbitsp);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_get(arena_chunk_t *chunk, size_t pageind)
-{
-
- return (arena_mapbitsp_read(arena_mapbitsp_get(chunk, pageind)));
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_size_decode(size_t mapbits)
-{
- size_t size;
-
-#if CHUNK_MAP_SIZE_SHIFT > 0
- size = (mapbits & CHUNK_MAP_SIZE_MASK) >> CHUNK_MAP_SIZE_SHIFT;
-#elif CHUNK_MAP_SIZE_SHIFT == 0
- size = mapbits & CHUNK_MAP_SIZE_MASK;
-#else
- size = (mapbits & CHUNK_MAP_SIZE_MASK) << -CHUNK_MAP_SIZE_SHIFT;
-#endif
-
- return (size);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_unallocated_size_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & (CHUNK_MAP_LARGE|CHUNK_MAP_ALLOCATED)) == 0);
- return (arena_mapbits_size_decode(mapbits));
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_large_size_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & (CHUNK_MAP_LARGE|CHUNK_MAP_ALLOCATED)) ==
- (CHUNK_MAP_LARGE|CHUNK_MAP_ALLOCATED));
- return (arena_mapbits_size_decode(mapbits));
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_small_runind_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & (CHUNK_MAP_LARGE|CHUNK_MAP_ALLOCATED)) ==
- CHUNK_MAP_ALLOCATED);
- return (mapbits >> CHUNK_MAP_RUNIND_SHIFT);
-}
-
-JEMALLOC_ALWAYS_INLINE szind_t
-arena_mapbits_binind_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
- szind_t binind;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- binind = (mapbits & CHUNK_MAP_BININD_MASK) >> CHUNK_MAP_BININD_SHIFT;
- assert(binind < NBINS || binind == BININD_INVALID);
- return (binind);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_dirty_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & CHUNK_MAP_DECOMMITTED) == 0 || (mapbits &
- (CHUNK_MAP_DIRTY|CHUNK_MAP_UNZEROED)) == 0);
- return (mapbits & CHUNK_MAP_DIRTY);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_unzeroed_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & CHUNK_MAP_DECOMMITTED) == 0 || (mapbits &
- (CHUNK_MAP_DIRTY|CHUNK_MAP_UNZEROED)) == 0);
- return (mapbits & CHUNK_MAP_UNZEROED);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_decommitted_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & CHUNK_MAP_DECOMMITTED) == 0 || (mapbits &
- (CHUNK_MAP_DIRTY|CHUNK_MAP_UNZEROED)) == 0);
- return (mapbits & CHUNK_MAP_DECOMMITTED);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_large_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- return (mapbits & CHUNK_MAP_LARGE);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_allocated_get(arena_chunk_t *chunk, size_t pageind)
-{
- size_t mapbits;
-
- mapbits = arena_mapbits_get(chunk, pageind);
- return (mapbits & CHUNK_MAP_ALLOCATED);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbitsp_write(size_t *mapbitsp, size_t mapbits)
-{
-
- *mapbitsp = mapbits;
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-arena_mapbits_size_encode(size_t size)
-{
- size_t mapbits;
-
-#if CHUNK_MAP_SIZE_SHIFT > 0
- mapbits = size << CHUNK_MAP_SIZE_SHIFT;
-#elif CHUNK_MAP_SIZE_SHIFT == 0
- mapbits = size;
-#else
- mapbits = size >> -CHUNK_MAP_SIZE_SHIFT;
-#endif
-
- assert((mapbits & ~CHUNK_MAP_SIZE_MASK) == 0);
- return (mapbits);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_unallocated_set(arena_chunk_t *chunk, size_t pageind, size_t size,
- size_t flags)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
-
- assert((size & PAGE_MASK) == 0);
- assert((flags & CHUNK_MAP_FLAGS_MASK) == flags);
- assert((flags & CHUNK_MAP_DECOMMITTED) == 0 || (flags &
- (CHUNK_MAP_DIRTY|CHUNK_MAP_UNZEROED)) == 0);
- arena_mapbitsp_write(mapbitsp, arena_mapbits_size_encode(size) |
- CHUNK_MAP_BININD_INVALID | flags);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_unallocated_size_set(arena_chunk_t *chunk, size_t pageind,
- size_t size)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
- size_t mapbits = arena_mapbitsp_read(mapbitsp);
-
- assert((size & PAGE_MASK) == 0);
- assert((mapbits & (CHUNK_MAP_LARGE|CHUNK_MAP_ALLOCATED)) == 0);
- arena_mapbitsp_write(mapbitsp, arena_mapbits_size_encode(size) |
- (mapbits & ~CHUNK_MAP_SIZE_MASK));
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_internal_set(arena_chunk_t *chunk, size_t pageind, size_t flags)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
-
- assert((flags & CHUNK_MAP_UNZEROED) == flags);
- arena_mapbitsp_write(mapbitsp, flags);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_large_set(arena_chunk_t *chunk, size_t pageind, size_t size,
- size_t flags)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
-
- assert((size & PAGE_MASK) == 0);
- assert((flags & CHUNK_MAP_FLAGS_MASK) == flags);
- assert((flags & CHUNK_MAP_DECOMMITTED) == 0 || (flags &
- (CHUNK_MAP_DIRTY|CHUNK_MAP_UNZEROED)) == 0);
- arena_mapbitsp_write(mapbitsp, arena_mapbits_size_encode(size) |
- CHUNK_MAP_BININD_INVALID | flags | CHUNK_MAP_LARGE |
- CHUNK_MAP_ALLOCATED);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_large_binind_set(arena_chunk_t *chunk, size_t pageind,
- szind_t binind)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
- size_t mapbits = arena_mapbitsp_read(mapbitsp);
-
- assert(binind <= BININD_INVALID);
- assert(arena_mapbits_large_size_get(chunk, pageind) == LARGE_MINCLASS +
- large_pad);
- arena_mapbitsp_write(mapbitsp, (mapbits & ~CHUNK_MAP_BININD_MASK) |
- (binind << CHUNK_MAP_BININD_SHIFT));
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_mapbits_small_set(arena_chunk_t *chunk, size_t pageind, size_t runind,
- szind_t binind, size_t flags)
-{
- size_t *mapbitsp = arena_mapbitsp_get(chunk, pageind);
-
- assert(binind < BININD_INVALID);
- assert(pageind - runind >= map_bias);
- assert((flags & CHUNK_MAP_UNZEROED) == flags);
- arena_mapbitsp_write(mapbitsp, (runind << CHUNK_MAP_RUNIND_SHIFT) |
- (binind << CHUNK_MAP_BININD_SHIFT) | flags | CHUNK_MAP_ALLOCATED);
-}
-
-JEMALLOC_INLINE void
-arena_metadata_allocated_add(arena_t *arena, size_t size)
-{
-
- atomic_add_z(&arena->stats.metadata_allocated, size);
-}
-
-JEMALLOC_INLINE void
-arena_metadata_allocated_sub(arena_t *arena, size_t size)
-{
-
- atomic_sub_z(&arena->stats.metadata_allocated, size);
-}
-
-JEMALLOC_INLINE size_t
-arena_metadata_allocated_get(arena_t *arena)
-{
-
- return (atomic_read_z(&arena->stats.metadata_allocated));
-}
-
-JEMALLOC_INLINE bool
-arena_prof_accum_impl(arena_t *arena, uint64_t accumbytes)
-{
-
- cassert(config_prof);
- assert(prof_interval != 0);
-
- arena->prof_accumbytes += accumbytes;
- if (arena->prof_accumbytes >= prof_interval) {
- arena->prof_accumbytes -= prof_interval;
- return (true);
- }
- return (false);
-}
-
-JEMALLOC_INLINE bool
-arena_prof_accum_locked(arena_t *arena, uint64_t accumbytes)
-{
-
- cassert(config_prof);
-
- if (likely(prof_interval == 0))
- return (false);
- return (arena_prof_accum_impl(arena, accumbytes));
-}
-
-JEMALLOC_INLINE bool
-arena_prof_accum(arena_t *arena, uint64_t accumbytes)
-{
-
- cassert(config_prof);
-
- if (likely(prof_interval == 0))
- return (false);
-
- {
- bool ret;
-
- malloc_mutex_lock(&arena->lock);
- ret = arena_prof_accum_impl(arena, accumbytes);
- malloc_mutex_unlock(&arena->lock);
- return (ret);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE szind_t
-arena_ptr_small_binind_get(const void *ptr, size_t mapbits)
-{
- szind_t binind;
-
- binind = (mapbits & CHUNK_MAP_BININD_MASK) >> CHUNK_MAP_BININD_SHIFT;
-
- if (config_debug) {
- arena_chunk_t *chunk;
- arena_t *arena;
- size_t pageind;
- size_t actual_mapbits;
- size_t rpages_ind;
- arena_run_t *run;
- arena_bin_t *bin;
- szind_t run_binind, actual_binind;
- arena_bin_info_t *bin_info;
- arena_chunk_map_misc_t *miscelm;
- void *rpages;
-
- assert(binind != BININD_INVALID);
- assert(binind < NBINS);
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- arena = extent_node_arena_get(&chunk->node);
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- actual_mapbits = arena_mapbits_get(chunk, pageind);
- assert(mapbits == actual_mapbits);
- assert(arena_mapbits_large_get(chunk, pageind) == 0);
- assert(arena_mapbits_allocated_get(chunk, pageind) != 0);
- rpages_ind = pageind - arena_mapbits_small_runind_get(chunk,
- pageind);
- miscelm = arena_miscelm_get(chunk, rpages_ind);
- run = &miscelm->run;
- run_binind = run->binind;
- bin = &arena->bins[run_binind];
- actual_binind = bin - arena->bins;
- assert(run_binind == actual_binind);
- bin_info = &arena_bin_info[actual_binind];
- rpages = arena_miscelm_to_rpages(miscelm);
- assert(((uintptr_t)ptr - ((uintptr_t)rpages +
- (uintptr_t)bin_info->reg0_offset)) % bin_info->reg_interval
- == 0);
- }
-
- return (binind);
-}
-# endif /* JEMALLOC_ARENA_INLINE_A */
-
-# ifdef JEMALLOC_ARENA_INLINE_B
-JEMALLOC_INLINE szind_t
-arena_bin_index(arena_t *arena, arena_bin_t *bin)
-{
- szind_t binind = bin - arena->bins;
- assert(binind < NBINS);
- return (binind);
-}
-
-JEMALLOC_INLINE unsigned
-arena_run_regind(arena_run_t *run, arena_bin_info_t *bin_info, const void *ptr)
-{
- unsigned shift, diff, regind;
- size_t interval;
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
- void *rpages = arena_miscelm_to_rpages(miscelm);
-
- /*
- * Freeing a pointer lower than region zero can cause assertion
- * failure.
- */
- assert((uintptr_t)ptr >= (uintptr_t)rpages +
- (uintptr_t)bin_info->reg0_offset);
-
- /*
- * Avoid doing division with a variable divisor if possible. Using
- * actual division here can reduce allocator throughput by over 20%!
- */
- diff = (unsigned)((uintptr_t)ptr - (uintptr_t)rpages -
- bin_info->reg0_offset);
-
- /* Rescale (factor powers of 2 out of the numerator and denominator). */
- interval = bin_info->reg_interval;
- shift = jemalloc_ffs(interval) - 1;
- diff >>= shift;
- interval >>= shift;
-
- if (interval == 1) {
- /* The divisor was a power of 2. */
- regind = diff;
- } else {
- /*
- * To divide by a number D that is not a power of two we
- * multiply by (2^21 / D) and then right shift by 21 positions.
- *
- * X / D
- *
- * becomes
- *
- * (X * interval_invs[D - 3]) >> SIZE_INV_SHIFT
- *
- * We can omit the first three elements, because we never
- * divide by 0, and 1 and 2 are both powers of two, which are
- * handled above.
- */
-#define SIZE_INV_SHIFT ((sizeof(unsigned) << 3) - LG_RUN_MAXREGS)
-#define SIZE_INV(s) (((1U << SIZE_INV_SHIFT) / (s)) + 1)
- static const unsigned interval_invs[] = {
- SIZE_INV(3),
- SIZE_INV(4), SIZE_INV(5), SIZE_INV(6), SIZE_INV(7),
- SIZE_INV(8), SIZE_INV(9), SIZE_INV(10), SIZE_INV(11),
- SIZE_INV(12), SIZE_INV(13), SIZE_INV(14), SIZE_INV(15),
- SIZE_INV(16), SIZE_INV(17), SIZE_INV(18), SIZE_INV(19),
- SIZE_INV(20), SIZE_INV(21), SIZE_INV(22), SIZE_INV(23),
- SIZE_INV(24), SIZE_INV(25), SIZE_INV(26), SIZE_INV(27),
- SIZE_INV(28), SIZE_INV(29), SIZE_INV(30), SIZE_INV(31)
- };
-
- if (likely(interval <= ((sizeof(interval_invs) /
- sizeof(unsigned)) + 2))) {
- regind = (diff * interval_invs[interval - 3]) >>
- SIZE_INV_SHIFT;
- } else
- regind = diff / interval;
-#undef SIZE_INV
-#undef SIZE_INV_SHIFT
- }
- assert(diff == regind * interval);
- assert(regind < bin_info->nregs);
-
- return (regind);
-}
-
-JEMALLOC_INLINE prof_tctx_t *
-arena_prof_tctx_get(const void *ptr)
-{
- prof_tctx_t *ret;
- arena_chunk_t *chunk;
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- size_t mapbits = arena_mapbits_get(chunk, pageind);
- assert((mapbits & CHUNK_MAP_ALLOCATED) != 0);
- if (likely((mapbits & CHUNK_MAP_LARGE) == 0))
- ret = (prof_tctx_t *)(uintptr_t)1U;
- else {
- arena_chunk_map_misc_t *elm = arena_miscelm_get(chunk,
- pageind);
- ret = atomic_read_p(&elm->prof_tctx_pun);
- }
- } else
- ret = huge_prof_tctx_get(ptr);
-
- return (ret);
-}
-
-JEMALLOC_INLINE void
-arena_prof_tctx_set(const void *ptr, size_t usize, prof_tctx_t *tctx)
-{
- arena_chunk_t *chunk;
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
-
- assert(arena_mapbits_allocated_get(chunk, pageind) != 0);
-
- if (unlikely(usize > SMALL_MAXCLASS || (uintptr_t)tctx >
- (uintptr_t)1U)) {
- arena_chunk_map_misc_t *elm;
-
- assert(arena_mapbits_large_get(chunk, pageind) != 0);
-
- elm = arena_miscelm_get(chunk, pageind);
- atomic_write_p(&elm->prof_tctx_pun, tctx);
- } else {
- /*
- * tctx must always be initialized for large runs.
- * Assert that the surrounding conditional logic is
- * equivalent to checking whether ptr refers to a large
- * run.
- */
- assert(arena_mapbits_large_get(chunk, pageind) == 0);
- }
- } else
- huge_prof_tctx_set(ptr, tctx);
-}
-
-JEMALLOC_INLINE void
-arena_prof_tctx_reset(const void *ptr, size_t usize, const void *old_ptr,
- prof_tctx_t *old_tctx)
-{
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- if (unlikely(usize > SMALL_MAXCLASS || (ptr == old_ptr &&
- (uintptr_t)old_tctx > (uintptr_t)1U))) {
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- size_t pageind;
- arena_chunk_map_misc_t *elm;
-
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >>
- LG_PAGE;
- assert(arena_mapbits_allocated_get(chunk, pageind) !=
- 0);
- assert(arena_mapbits_large_get(chunk, pageind) != 0);
-
- elm = arena_miscelm_get(chunk, pageind);
- atomic_write_p(&elm->prof_tctx_pun,
- (prof_tctx_t *)(uintptr_t)1U);
- } else
- huge_prof_tctx_reset(ptr);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-arena_malloc(tsd_t *tsd, arena_t *arena, size_t size, bool zero,
- tcache_t *tcache)
-{
-
- assert(size != 0);
-
- arena = arena_choose(tsd, arena);
- if (unlikely(arena == NULL))
- return (NULL);
-
- if (likely(size <= SMALL_MAXCLASS)) {
- if (likely(tcache != NULL)) {
- return (tcache_alloc_small(tsd, arena, tcache, size,
- zero));
- } else
- return (arena_malloc_small(arena, size, zero));
- } else if (likely(size <= large_maxclass)) {
- /*
- * Initialize tcache after checking size in order to avoid
- * infinite recursion during tcache initialization.
- */
- if (likely(tcache != NULL) && size <= tcache_maxclass) {
- return (tcache_alloc_large(tsd, arena, tcache, size,
- zero));
- } else
- return (arena_malloc_large(arena, size, zero));
- } else
- return (huge_malloc(tsd, arena, size, zero, tcache));
-}
-
-JEMALLOC_ALWAYS_INLINE arena_t *
-arena_aalloc(const void *ptr)
-{
- arena_chunk_t *chunk;
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr))
- return (extent_node_arena_get(&chunk->node));
- else
- return (huge_aalloc(ptr));
-}
-
-/* Return the size of the allocation pointed to by ptr. */
-JEMALLOC_ALWAYS_INLINE size_t
-arena_salloc(const void *ptr, bool demote)
-{
- size_t ret;
- arena_chunk_t *chunk;
- size_t pageind;
- szind_t binind;
-
- assert(ptr != NULL);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- assert(arena_mapbits_allocated_get(chunk, pageind) != 0);
- binind = arena_mapbits_binind_get(chunk, pageind);
- if (unlikely(binind == BININD_INVALID || (config_prof && !demote
- && arena_mapbits_large_get(chunk, pageind) != 0))) {
- /*
- * Large allocation. In the common case (demote), and
- * as this is an inline function, most callers will only
- * end up looking at binind to determine that ptr is a
- * small allocation.
- */
- assert(config_cache_oblivious || ((uintptr_t)ptr &
- PAGE_MASK) == 0);
- ret = arena_mapbits_large_size_get(chunk, pageind) -
- large_pad;
- assert(ret != 0);
- assert(pageind + ((ret+large_pad)>>LG_PAGE) <=
- chunk_npages);
- assert(arena_mapbits_dirty_get(chunk, pageind) ==
- arena_mapbits_dirty_get(chunk,
- pageind+((ret+large_pad)>>LG_PAGE)-1));
- } else {
- /*
- * Small allocation (possibly promoted to a large
- * object).
- */
- assert(arena_mapbits_large_get(chunk, pageind) != 0 ||
- arena_ptr_small_binind_get(ptr,
- arena_mapbits_get(chunk, pageind)) == binind);
- ret = index2size(binind);
- }
- } else
- ret = huge_salloc(ptr);
-
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_dalloc(tsd_t *tsd, void *ptr, tcache_t *tcache)
-{
- arena_chunk_t *chunk;
- size_t pageind, mapbits;
-
- assert(ptr != NULL);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- mapbits = arena_mapbits_get(chunk, pageind);
- assert(arena_mapbits_allocated_get(chunk, pageind) != 0);
- if (likely((mapbits & CHUNK_MAP_LARGE) == 0)) {
- /* Small allocation. */
- if (likely(tcache != NULL)) {
- szind_t binind = arena_ptr_small_binind_get(ptr,
- mapbits);
- tcache_dalloc_small(tsd, tcache, ptr, binind);
- } else {
- arena_dalloc_small(extent_node_arena_get(
- &chunk->node), chunk, ptr, pageind);
- }
- } else {
- size_t size = arena_mapbits_large_size_get(chunk,
- pageind);
-
- assert(config_cache_oblivious || ((uintptr_t)ptr &
- PAGE_MASK) == 0);
-
- if (likely(tcache != NULL) && size - large_pad <=
- tcache_maxclass) {
- tcache_dalloc_large(tsd, tcache, ptr, size -
- large_pad);
- } else {
- arena_dalloc_large(extent_node_arena_get(
- &chunk->node), chunk, ptr);
- }
- }
- } else
- huge_dalloc(tsd, ptr, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-arena_sdalloc(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache)
-{
- arena_chunk_t *chunk;
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) {
- if (config_prof && opt_prof) {
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >>
- LG_PAGE;
- assert(arena_mapbits_allocated_get(chunk, pageind) != 0);
- if (arena_mapbits_large_get(chunk, pageind) != 0) {
- /*
- * Make sure to use promoted size, not request
- * size.
- */
- size = arena_mapbits_large_size_get(chunk,
- pageind) - large_pad;
- }
- }
- assert(s2u(size) == s2u(arena_salloc(ptr, false)));
-
- if (likely(size <= SMALL_MAXCLASS)) {
- /* Small allocation. */
- if (likely(tcache != NULL)) {
- szind_t binind = size2index(size);
- tcache_dalloc_small(tsd, tcache, ptr, binind);
- } else {
- size_t pageind = ((uintptr_t)ptr -
- (uintptr_t)chunk) >> LG_PAGE;
- arena_dalloc_small(extent_node_arena_get(
- &chunk->node), chunk, ptr, pageind);
- }
- } else {
- assert(config_cache_oblivious || ((uintptr_t)ptr &
- PAGE_MASK) == 0);
-
- if (likely(tcache != NULL) && size <= tcache_maxclass)
- tcache_dalloc_large(tsd, tcache, ptr, size);
- else {
- arena_dalloc_large(extent_node_arena_get(
- &chunk->node), chunk, ptr);
- }
- }
- } else
- huge_dalloc(tsd, ptr, tcache);
-}
-# endif /* JEMALLOC_ARENA_INLINE_B */
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_externs.h b/deps/jemalloc/include/jemalloc/internal/arena_externs.h
new file mode 100644
index 000000000..4b3732b41
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_externs.h
@@ -0,0 +1,94 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_EXTERNS_H
+#define JEMALLOC_INTERNAL_ARENA_EXTERNS_H
+
+#include "jemalloc/internal/bin.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/pages.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/stats.h"
+
+extern ssize_t opt_dirty_decay_ms;
+extern ssize_t opt_muzzy_decay_ms;
+
+extern percpu_arena_mode_t opt_percpu_arena;
+extern const char *percpu_arena_mode_names[];
+
+extern const uint64_t h_steps[SMOOTHSTEP_NSTEPS];
+extern malloc_mutex_t arenas_lock;
+
+void arena_basic_stats_merge(tsdn_t *tsdn, arena_t *arena,
+ unsigned *nthreads, const char **dss, ssize_t *dirty_decay_ms,
+ ssize_t *muzzy_decay_ms, size_t *nactive, size_t *ndirty, size_t *nmuzzy);
+void arena_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads,
+ const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms,
+ size_t *nactive, size_t *ndirty, size_t *nmuzzy, arena_stats_t *astats,
+ bin_stats_t *bstats, arena_stats_large_t *lstats);
+void arena_extents_dirty_dalloc(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent);
+#ifdef JEMALLOC_JET
+size_t arena_slab_regind(extent_t *slab, szind_t binind, const void *ptr);
+#endif
+extent_t *arena_extent_alloc_large(tsdn_t *tsdn, arena_t *arena,
+ size_t usize, size_t alignment, bool *zero);
+void arena_extent_dalloc_large_prep(tsdn_t *tsdn, arena_t *arena,
+ extent_t *extent);
+void arena_extent_ralloc_large_shrink(tsdn_t *tsdn, arena_t *arena,
+ extent_t *extent, size_t oldsize);
+void arena_extent_ralloc_large_expand(tsdn_t *tsdn, arena_t *arena,
+ extent_t *extent, size_t oldsize);
+ssize_t arena_dirty_decay_ms_get(arena_t *arena);
+bool arena_dirty_decay_ms_set(tsdn_t *tsdn, arena_t *arena, ssize_t decay_ms);
+ssize_t arena_muzzy_decay_ms_get(arena_t *arena);
+bool arena_muzzy_decay_ms_set(tsdn_t *tsdn, arena_t *arena, ssize_t decay_ms);
+void arena_decay(tsdn_t *tsdn, arena_t *arena, bool is_background_thread,
+ bool all);
+void arena_reset(tsd_t *tsd, arena_t *arena);
+void arena_destroy(tsd_t *tsd, arena_t *arena);
+void arena_tcache_fill_small(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache,
+ cache_bin_t *tbin, szind_t binind, uint64_t prof_accumbytes);
+void arena_alloc_junk_small(void *ptr, const bin_info_t *bin_info,
+ bool zero);
+
+typedef void (arena_dalloc_junk_small_t)(void *, const bin_info_t *);
+extern arena_dalloc_junk_small_t *JET_MUTABLE arena_dalloc_junk_small;
+
+void *arena_malloc_hard(tsdn_t *tsdn, arena_t *arena, size_t size,
+ szind_t ind, bool zero);
+void *arena_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize,
+ size_t alignment, bool zero, tcache_t *tcache);
+void arena_prof_promote(tsdn_t *tsdn, const void *ptr, size_t usize);
+void arena_dalloc_promoted(tsdn_t *tsdn, void *ptr, tcache_t *tcache,
+ bool slow_path);
+void arena_dalloc_bin_junked_locked(tsdn_t *tsdn, arena_t *arena,
+ extent_t *extent, void *ptr);
+void arena_dalloc_small(tsdn_t *tsdn, void *ptr);
+bool arena_ralloc_no_move(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size,
+ size_t extra, bool zero);
+void *arena_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t oldsize,
+ size_t size, size_t alignment, bool zero, tcache_t *tcache);
+dss_prec_t arena_dss_prec_get(arena_t *arena);
+bool arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec);
+ssize_t arena_dirty_decay_ms_default_get(void);
+bool arena_dirty_decay_ms_default_set(ssize_t decay_ms);
+ssize_t arena_muzzy_decay_ms_default_get(void);
+bool arena_muzzy_decay_ms_default_set(ssize_t decay_ms);
+bool arena_retain_grow_limit_get_set(tsd_t *tsd, arena_t *arena,
+ size_t *old_limit, size_t *new_limit);
+unsigned arena_nthreads_get(arena_t *arena, bool internal);
+void arena_nthreads_inc(arena_t *arena, bool internal);
+void arena_nthreads_dec(arena_t *arena, bool internal);
+size_t arena_extent_sn_next(arena_t *arena);
+arena_t *arena_new(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks);
+void arena_boot(void);
+void arena_prefork0(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork1(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork2(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork3(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork4(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork5(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork6(tsdn_t *tsdn, arena_t *arena);
+void arena_prefork7(tsdn_t *tsdn, arena_t *arena);
+void arena_postfork_parent(tsdn_t *tsdn, arena_t *arena);
+void arena_postfork_child(tsdn_t *tsdn, arena_t *arena);
+
+#endif /* JEMALLOC_INTERNAL_ARENA_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_inlines_a.h b/deps/jemalloc/include/jemalloc/internal/arena_inlines_a.h
new file mode 100644
index 000000000..9abf7f6ac
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_inlines_a.h
@@ -0,0 +1,57 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_INLINES_A_H
+#define JEMALLOC_INTERNAL_ARENA_INLINES_A_H
+
+static inline unsigned
+arena_ind_get(const arena_t *arena) {
+ return base_ind_get(arena->base);
+}
+
+static inline void
+arena_internal_add(arena_t *arena, size_t size) {
+ atomic_fetch_add_zu(&arena->stats.internal, size, ATOMIC_RELAXED);
+}
+
+static inline void
+arena_internal_sub(arena_t *arena, size_t size) {
+ atomic_fetch_sub_zu(&arena->stats.internal, size, ATOMIC_RELAXED);
+}
+
+static inline size_t
+arena_internal_get(arena_t *arena) {
+ return atomic_load_zu(&arena->stats.internal, ATOMIC_RELAXED);
+}
+
+static inline bool
+arena_prof_accum(tsdn_t *tsdn, arena_t *arena, uint64_t accumbytes) {
+ cassert(config_prof);
+
+ if (likely(prof_interval == 0 || !prof_active_get_unlocked())) {
+ return false;
+ }
+
+ return prof_accum_add(tsdn, &arena->prof_accum, accumbytes);
+}
+
+static inline void
+percpu_arena_update(tsd_t *tsd, unsigned cpu) {
+ assert(have_percpu_arena);
+ arena_t *oldarena = tsd_arena_get(tsd);
+ assert(oldarena != NULL);
+ unsigned oldind = arena_ind_get(oldarena);
+
+ if (oldind != cpu) {
+ unsigned newind = cpu;
+ arena_t *newarena = arena_get(tsd_tsdn(tsd), newind, true);
+ assert(newarena != NULL);
+
+ /* Set new arena/tcache associations. */
+ arena_migrate(tsd, oldind, newind);
+ tcache_t *tcache = tcache_get(tsd);
+ if (tcache != NULL) {
+ tcache_arena_reassociate(tsd_tsdn(tsd), tcache,
+ newarena);
+ }
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_ARENA_INLINES_A_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_inlines_b.h b/deps/jemalloc/include/jemalloc/internal/arena_inlines_b.h
new file mode 100644
index 000000000..2b7e77e72
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_inlines_b.h
@@ -0,0 +1,354 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_INLINES_B_H
+#define JEMALLOC_INTERNAL_ARENA_INLINES_B_H
+
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/sz.h"
+#include "jemalloc/internal/ticker.h"
+
+JEMALLOC_ALWAYS_INLINE prof_tctx_t *
+arena_prof_tctx_get(tsdn_t *tsdn, const void *ptr, alloc_ctx_t *alloc_ctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ /* Static check. */
+ if (alloc_ctx == NULL) {
+ const extent_t *extent = iealloc(tsdn, ptr);
+ if (unlikely(!extent_slab_get(extent))) {
+ return large_prof_tctx_get(tsdn, extent);
+ }
+ } else {
+ if (unlikely(!alloc_ctx->slab)) {
+ return large_prof_tctx_get(tsdn, iealloc(tsdn, ptr));
+ }
+ }
+ return (prof_tctx_t *)(uintptr_t)1U;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_prof_tctx_set(tsdn_t *tsdn, const void *ptr, UNUSED size_t usize,
+ alloc_ctx_t *alloc_ctx, prof_tctx_t *tctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ /* Static check. */
+ if (alloc_ctx == NULL) {
+ extent_t *extent = iealloc(tsdn, ptr);
+ if (unlikely(!extent_slab_get(extent))) {
+ large_prof_tctx_set(tsdn, extent, tctx);
+ }
+ } else {
+ if (unlikely(!alloc_ctx->slab)) {
+ large_prof_tctx_set(tsdn, iealloc(tsdn, ptr), tctx);
+ }
+ }
+}
+
+static inline void
+arena_prof_tctx_reset(tsdn_t *tsdn, const void *ptr, UNUSED prof_tctx_t *tctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ extent_t *extent = iealloc(tsdn, ptr);
+ assert(!extent_slab_get(extent));
+
+ large_prof_tctx_reset(tsdn, extent);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_decay_ticks(tsdn_t *tsdn, arena_t *arena, unsigned nticks) {
+ tsd_t *tsd;
+ ticker_t *decay_ticker;
+
+ if (unlikely(tsdn_null(tsdn))) {
+ return;
+ }
+ tsd = tsdn_tsd(tsdn);
+ decay_ticker = decay_ticker_get(tsd, arena_ind_get(arena));
+ if (unlikely(decay_ticker == NULL)) {
+ return;
+ }
+ if (unlikely(ticker_ticks(decay_ticker, nticks))) {
+ arena_decay(tsdn, arena, false, false);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_decay_tick(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_assert_not_owner(tsdn, &arena->decay_dirty.mtx);
+ malloc_mutex_assert_not_owner(tsdn, &arena->decay_muzzy.mtx);
+
+ arena_decay_ticks(tsdn, arena, 1);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+arena_malloc(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind, bool zero,
+ tcache_t *tcache, bool slow_path) {
+ assert(!tsdn_null(tsdn) || tcache == NULL);
+ assert(size != 0);
+
+ if (likely(tcache != NULL)) {
+ if (likely(size <= SMALL_MAXCLASS)) {
+ return tcache_alloc_small(tsdn_tsd(tsdn), arena,
+ tcache, size, ind, zero, slow_path);
+ }
+ if (likely(size <= tcache_maxclass)) {
+ return tcache_alloc_large(tsdn_tsd(tsdn), arena,
+ tcache, size, ind, zero, slow_path);
+ }
+ /* (size > tcache_maxclass) case falls through. */
+ assert(size > tcache_maxclass);
+ }
+
+ return arena_malloc_hard(tsdn, arena, size, ind, zero);
+}
+
+JEMALLOC_ALWAYS_INLINE arena_t *
+arena_aalloc(tsdn_t *tsdn, const void *ptr) {
+ return extent_arena_get(iealloc(tsdn, ptr));
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+arena_salloc(tsdn_t *tsdn, const void *ptr) {
+ assert(ptr != NULL);
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ szind_t szind = rtree_szind_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true);
+ assert(szind != NSIZES);
+
+ return sz_index2size(szind);
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+arena_vsalloc(tsdn_t *tsdn, const void *ptr) {
+ /*
+ * Return 0 if ptr is not within an extent managed by jemalloc. This
+ * function has two extra costs relative to isalloc():
+ * - The rtree calls cannot claim to be dependent lookups, which induces
+ * rtree lookup load dependencies.
+ * - The lookup may fail, so there is an extra branch to check for
+ * failure.
+ */
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ extent_t *extent;
+ szind_t szind;
+ if (rtree_extent_szind_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, false, &extent, &szind)) {
+ return 0;
+ }
+
+ if (extent == NULL) {
+ return 0;
+ }
+ assert(extent_state_get(extent) == extent_state_active);
+ /* Only slab members should be looked up via interior pointers. */
+ assert(extent_addr_get(extent) == ptr || extent_slab_get(extent));
+
+ assert(szind != NSIZES);
+
+ return sz_index2size(szind);
+}
+
+static inline void
+arena_dalloc_no_tcache(tsdn_t *tsdn, void *ptr) {
+ assert(ptr != NULL);
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ szind_t szind;
+ bool slab;
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr,
+ true, &szind, &slab);
+
+ if (config_debug) {
+ extent_t *extent = rtree_extent_read(tsdn, &extents_rtree,
+ rtree_ctx, (uintptr_t)ptr, true);
+ assert(szind == extent_szind_get(extent));
+ assert(szind < NSIZES);
+ assert(slab == extent_slab_get(extent));
+ }
+
+ if (likely(slab)) {
+ /* Small allocation. */
+ arena_dalloc_small(tsdn, ptr);
+ } else {
+ extent_t *extent = iealloc(tsdn, ptr);
+ large_dalloc(tsdn, extent);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_dalloc(tsdn_t *tsdn, void *ptr, tcache_t *tcache,
+ alloc_ctx_t *alloc_ctx, bool slow_path) {
+ assert(!tsdn_null(tsdn) || tcache == NULL);
+ assert(ptr != NULL);
+
+ if (unlikely(tcache == NULL)) {
+ arena_dalloc_no_tcache(tsdn, ptr);
+ return;
+ }
+
+ szind_t szind;
+ bool slab;
+ rtree_ctx_t *rtree_ctx;
+ if (alloc_ctx != NULL) {
+ szind = alloc_ctx->szind;
+ slab = alloc_ctx->slab;
+ assert(szind != NSIZES);
+ } else {
+ rtree_ctx = tsd_rtree_ctx(tsdn_tsd(tsdn));
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &szind, &slab);
+ }
+
+ if (config_debug) {
+ rtree_ctx = tsd_rtree_ctx(tsdn_tsd(tsdn));
+ extent_t *extent = rtree_extent_read(tsdn, &extents_rtree,
+ rtree_ctx, (uintptr_t)ptr, true);
+ assert(szind == extent_szind_get(extent));
+ assert(szind < NSIZES);
+ assert(slab == extent_slab_get(extent));
+ }
+
+ if (likely(slab)) {
+ /* Small allocation. */
+ tcache_dalloc_small(tsdn_tsd(tsdn), tcache, ptr, szind,
+ slow_path);
+ } else {
+ if (szind < nhbins) {
+ if (config_prof && unlikely(szind < NBINS)) {
+ arena_dalloc_promoted(tsdn, ptr, tcache,
+ slow_path);
+ } else {
+ tcache_dalloc_large(tsdn_tsd(tsdn), tcache, ptr,
+ szind, slow_path);
+ }
+ } else {
+ extent_t *extent = iealloc(tsdn, ptr);
+ large_dalloc(tsdn, extent);
+ }
+ }
+}
+
+static inline void
+arena_sdalloc_no_tcache(tsdn_t *tsdn, void *ptr, size_t size) {
+ assert(ptr != NULL);
+ assert(size <= LARGE_MAXCLASS);
+
+ szind_t szind;
+ bool slab;
+ if (!config_prof || !opt_prof) {
+ /*
+ * There is no risk of being confused by a promoted sampled
+ * object, so base szind and slab on the given size.
+ */
+ szind = sz_size2index(size);
+ slab = (szind < NBINS);
+ }
+
+ if ((config_prof && opt_prof) || config_debug) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn,
+ &rtree_ctx_fallback);
+
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &szind, &slab);
+
+ assert(szind == sz_size2index(size));
+ assert((config_prof && opt_prof) || slab == (szind < NBINS));
+
+ if (config_debug) {
+ extent_t *extent = rtree_extent_read(tsdn,
+ &extents_rtree, rtree_ctx, (uintptr_t)ptr, true);
+ assert(szind == extent_szind_get(extent));
+ assert(slab == extent_slab_get(extent));
+ }
+ }
+
+ if (likely(slab)) {
+ /* Small allocation. */
+ arena_dalloc_small(tsdn, ptr);
+ } else {
+ extent_t *extent = iealloc(tsdn, ptr);
+ large_dalloc(tsdn, extent);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_sdalloc(tsdn_t *tsdn, void *ptr, size_t size, tcache_t *tcache,
+ alloc_ctx_t *alloc_ctx, bool slow_path) {
+ assert(!tsdn_null(tsdn) || tcache == NULL);
+ assert(ptr != NULL);
+ assert(size <= LARGE_MAXCLASS);
+
+ if (unlikely(tcache == NULL)) {
+ arena_sdalloc_no_tcache(tsdn, ptr, size);
+ return;
+ }
+
+ szind_t szind;
+ bool slab;
+ UNUSED alloc_ctx_t local_ctx;
+ if (config_prof && opt_prof) {
+ if (alloc_ctx == NULL) {
+ /* Uncommon case and should be a static check. */
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn,
+ &rtree_ctx_fallback);
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &local_ctx.szind,
+ &local_ctx.slab);
+ assert(local_ctx.szind == sz_size2index(size));
+ alloc_ctx = &local_ctx;
+ }
+ slab = alloc_ctx->slab;
+ szind = alloc_ctx->szind;
+ } else {
+ /*
+ * There is no risk of being confused by a promoted sampled
+ * object, so base szind and slab on the given size.
+ */
+ szind = sz_size2index(size);
+ slab = (szind < NBINS);
+ }
+
+ if (config_debug) {
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsdn_tsd(tsdn));
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &szind, &slab);
+ extent_t *extent = rtree_extent_read(tsdn,
+ &extents_rtree, rtree_ctx, (uintptr_t)ptr, true);
+ assert(szind == extent_szind_get(extent));
+ assert(slab == extent_slab_get(extent));
+ }
+
+ if (likely(slab)) {
+ /* Small allocation. */
+ tcache_dalloc_small(tsdn_tsd(tsdn), tcache, ptr, szind,
+ slow_path);
+ } else {
+ if (szind < nhbins) {
+ if (config_prof && unlikely(szind < NBINS)) {
+ arena_dalloc_promoted(tsdn, ptr, tcache,
+ slow_path);
+ } else {
+ tcache_dalloc_large(tsdn_tsd(tsdn),
+ tcache, ptr, szind, slow_path);
+ }
+ } else {
+ extent_t *extent = iealloc(tsdn, ptr);
+ large_dalloc(tsdn, extent);
+ }
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_ARENA_INLINES_B_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_stats.h b/deps/jemalloc/include/jemalloc/internal/arena_stats.h
new file mode 100644
index 000000000..5f3dca8b1
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_stats.h
@@ -0,0 +1,237 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_STATS_H
+#define JEMALLOC_INTERNAL_ARENA_STATS_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_prof.h"
+#include "jemalloc/internal/size_classes.h"
+
+/*
+ * In those architectures that support 64-bit atomics, we use atomic updates for
+ * our 64-bit values. Otherwise, we use a plain uint64_t and synchronize
+ * externally.
+ */
+#ifdef JEMALLOC_ATOMIC_U64
+typedef atomic_u64_t arena_stats_u64_t;
+#else
+/* Must hold the arena stats mutex while reading atomically. */
+typedef uint64_t arena_stats_u64_t;
+#endif
+
+typedef struct arena_stats_large_s arena_stats_large_t;
+struct arena_stats_large_s {
+ /*
+ * Total number of allocation/deallocation requests served directly by
+ * the arena.
+ */
+ arena_stats_u64_t nmalloc;
+ arena_stats_u64_t ndalloc;
+
+ /*
+ * Number of allocation requests that correspond to this size class.
+ * This includes requests served by tcache, though tcache only
+ * periodically merges into this counter.
+ */
+ arena_stats_u64_t nrequests; /* Partially derived. */
+
+ /* Current number of allocations of this size class. */
+ size_t curlextents; /* Derived. */
+};
+
+typedef struct arena_stats_decay_s arena_stats_decay_t;
+struct arena_stats_decay_s {
+ /* Total number of purge sweeps. */
+ arena_stats_u64_t npurge;
+ /* Total number of madvise calls made. */
+ arena_stats_u64_t nmadvise;
+ /* Total number of pages purged. */
+ arena_stats_u64_t purged;
+};
+
+/*
+ * Arena stats. Note that fields marked "derived" are not directly maintained
+ * within the arena code; rather their values are derived during stats merge
+ * requests.
+ */
+typedef struct arena_stats_s arena_stats_t;
+struct arena_stats_s {
+#ifndef JEMALLOC_ATOMIC_U64
+ malloc_mutex_t mtx;
+#endif
+
+ /* Number of bytes currently mapped, excluding retained memory. */
+ atomic_zu_t mapped; /* Partially derived. */
+
+ /*
+ * Number of unused virtual memory bytes currently retained. Retained
+ * bytes are technically mapped (though always decommitted or purged),
+ * but they are excluded from the mapped statistic (above).
+ */
+ atomic_zu_t retained; /* Derived. */
+
+ arena_stats_decay_t decay_dirty;
+ arena_stats_decay_t decay_muzzy;
+
+ atomic_zu_t base; /* Derived. */
+ atomic_zu_t internal;
+ atomic_zu_t resident; /* Derived. */
+ atomic_zu_t metadata_thp;
+
+ atomic_zu_t allocated_large; /* Derived. */
+ arena_stats_u64_t nmalloc_large; /* Derived. */
+ arena_stats_u64_t ndalloc_large; /* Derived. */
+ arena_stats_u64_t nrequests_large; /* Derived. */
+
+ /* Number of bytes cached in tcache associated with this arena. */
+ atomic_zu_t tcache_bytes; /* Derived. */
+
+ mutex_prof_data_t mutex_prof_data[mutex_prof_num_arena_mutexes];
+
+ /* One element for each large size class. */
+ arena_stats_large_t lstats[NSIZES - NBINS];
+
+ /* Arena uptime. */
+ nstime_t uptime;
+};
+
+static inline bool
+arena_stats_init(UNUSED tsdn_t *tsdn, arena_stats_t *arena_stats) {
+ if (config_debug) {
+ for (size_t i = 0; i < sizeof(arena_stats_t); i++) {
+ assert(((char *)arena_stats)[i] == 0);
+ }
+ }
+#ifndef JEMALLOC_ATOMIC_U64
+ if (malloc_mutex_init(&arena_stats->mtx, "arena_stats",
+ WITNESS_RANK_ARENA_STATS, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+#endif
+ /* Memory is zeroed, so there is no need to clear stats. */
+ return false;
+}
+
+static inline void
+arena_stats_lock(tsdn_t *tsdn, arena_stats_t *arena_stats) {
+#ifndef JEMALLOC_ATOMIC_U64
+ malloc_mutex_lock(tsdn, &arena_stats->mtx);
+#endif
+}
+
+static inline void
+arena_stats_unlock(tsdn_t *tsdn, arena_stats_t *arena_stats) {
+#ifndef JEMALLOC_ATOMIC_U64
+ malloc_mutex_unlock(tsdn, &arena_stats->mtx);
+#endif
+}
+
+static inline uint64_t
+arena_stats_read_u64(tsdn_t *tsdn, arena_stats_t *arena_stats,
+ arena_stats_u64_t *p) {
+#ifdef JEMALLOC_ATOMIC_U64
+ return atomic_load_u64(p, ATOMIC_RELAXED);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ return *p;
+#endif
+}
+
+static inline void
+arena_stats_add_u64(tsdn_t *tsdn, arena_stats_t *arena_stats,
+ arena_stats_u64_t *p, uint64_t x) {
+#ifdef JEMALLOC_ATOMIC_U64
+ atomic_fetch_add_u64(p, x, ATOMIC_RELAXED);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ *p += x;
+#endif
+}
+
+UNUSED static inline void
+arena_stats_sub_u64(tsdn_t *tsdn, arena_stats_t *arena_stats,
+ arena_stats_u64_t *p, uint64_t x) {
+#ifdef JEMALLOC_ATOMIC_U64
+ UNUSED uint64_t r = atomic_fetch_sub_u64(p, x, ATOMIC_RELAXED);
+ assert(r - x <= r);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ *p -= x;
+ assert(*p + x >= *p);
+#endif
+}
+
+/*
+ * Non-atomically sets *dst += src. *dst needs external synchronization.
+ * This lets us avoid the cost of a fetch_add when its unnecessary (note that
+ * the types here are atomic).
+ */
+static inline void
+arena_stats_accum_u64(arena_stats_u64_t *dst, uint64_t src) {
+#ifdef JEMALLOC_ATOMIC_U64
+ uint64_t cur_dst = atomic_load_u64(dst, ATOMIC_RELAXED);
+ atomic_store_u64(dst, src + cur_dst, ATOMIC_RELAXED);
+#else
+ *dst += src;
+#endif
+}
+
+static inline size_t
+arena_stats_read_zu(tsdn_t *tsdn, arena_stats_t *arena_stats, atomic_zu_t *p) {
+#ifdef JEMALLOC_ATOMIC_U64
+ return atomic_load_zu(p, ATOMIC_RELAXED);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ return atomic_load_zu(p, ATOMIC_RELAXED);
+#endif
+}
+
+static inline void
+arena_stats_add_zu(tsdn_t *tsdn, arena_stats_t *arena_stats, atomic_zu_t *p,
+ size_t x) {
+#ifdef JEMALLOC_ATOMIC_U64
+ atomic_fetch_add_zu(p, x, ATOMIC_RELAXED);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ size_t cur = atomic_load_zu(p, ATOMIC_RELAXED);
+ atomic_store_zu(p, cur + x, ATOMIC_RELAXED);
+#endif
+}
+
+static inline void
+arena_stats_sub_zu(tsdn_t *tsdn, arena_stats_t *arena_stats, atomic_zu_t *p,
+ size_t x) {
+#ifdef JEMALLOC_ATOMIC_U64
+ UNUSED size_t r = atomic_fetch_sub_zu(p, x, ATOMIC_RELAXED);
+ assert(r - x <= r);
+#else
+ malloc_mutex_assert_owner(tsdn, &arena_stats->mtx);
+ size_t cur = atomic_load_zu(p, ATOMIC_RELAXED);
+ atomic_store_zu(p, cur - x, ATOMIC_RELAXED);
+#endif
+}
+
+/* Like the _u64 variant, needs an externally synchronized *dst. */
+static inline void
+arena_stats_accum_zu(atomic_zu_t *dst, size_t src) {
+ size_t cur_dst = atomic_load_zu(dst, ATOMIC_RELAXED);
+ atomic_store_zu(dst, src + cur_dst, ATOMIC_RELAXED);
+}
+
+static inline void
+arena_stats_large_nrequests_add(tsdn_t *tsdn, arena_stats_t *arena_stats,
+ szind_t szind, uint64_t nrequests) {
+ arena_stats_lock(tsdn, arena_stats);
+ arena_stats_add_u64(tsdn, arena_stats, &arena_stats->lstats[szind -
+ NBINS].nrequests, nrequests);
+ arena_stats_unlock(tsdn, arena_stats);
+}
+
+static inline void
+arena_stats_mapped_add(tsdn_t *tsdn, arena_stats_t *arena_stats, size_t size) {
+ arena_stats_lock(tsdn, arena_stats);
+ arena_stats_add_zu(tsdn, arena_stats, &arena_stats->mapped, size);
+ arena_stats_unlock(tsdn, arena_stats);
+}
+
+
+#endif /* JEMALLOC_INTERNAL_ARENA_STATS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_structs_a.h b/deps/jemalloc/include/jemalloc/internal/arena_structs_a.h
new file mode 100644
index 000000000..46aa77c88
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_structs_a.h
@@ -0,0 +1,11 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_STRUCTS_A_H
+#define JEMALLOC_INTERNAL_ARENA_STRUCTS_A_H
+
+#include "jemalloc/internal/bitmap.h"
+
+struct arena_slab_data_s {
+ /* Per region allocated/deallocated bitmap. */
+ bitmap_t bitmap[BITMAP_GROUPS_MAX];
+};
+
+#endif /* JEMALLOC_INTERNAL_ARENA_STRUCTS_A_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_structs_b.h b/deps/jemalloc/include/jemalloc/internal/arena_structs_b.h
new file mode 100644
index 000000000..38bc95962
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_structs_b.h
@@ -0,0 +1,229 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_STRUCTS_B_H
+#define JEMALLOC_INTERNAL_ARENA_STRUCTS_B_H
+
+#include "jemalloc/internal/arena_stats.h"
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/bin.h"
+#include "jemalloc/internal/bitmap.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/nstime.h"
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/smoothstep.h"
+#include "jemalloc/internal/ticker.h"
+
+struct arena_decay_s {
+ /* Synchronizes all non-atomic fields. */
+ malloc_mutex_t mtx;
+ /*
+ * True if a thread is currently purging the extents associated with
+ * this decay structure.
+ */
+ bool purging;
+ /*
+ * Approximate time in milliseconds from the creation of a set of unused
+ * dirty pages until an equivalent set of unused dirty pages is purged
+ * and/or reused.
+ */
+ atomic_zd_t time_ms;
+ /* time / SMOOTHSTEP_NSTEPS. */
+ nstime_t interval;
+ /*
+ * Time at which the current decay interval logically started. We do
+ * not actually advance to a new epoch until sometime after it starts
+ * because of scheduling and computation delays, and it is even possible
+ * to completely skip epochs. In all cases, during epoch advancement we
+ * merge all relevant activity into the most recently recorded epoch.
+ */
+ nstime_t epoch;
+ /* Deadline randomness generator. */
+ uint64_t jitter_state;
+ /*
+ * Deadline for current epoch. This is the sum of interval and per
+ * epoch jitter which is a uniform random variable in [0..interval).
+ * Epochs always advance by precise multiples of interval, but we
+ * randomize the deadline to reduce the likelihood of arenas purging in
+ * lockstep.
+ */
+ nstime_t deadline;
+ /*
+ * Number of unpurged pages at beginning of current epoch. During epoch
+ * advancement we use the delta between arena->decay_*.nunpurged and
+ * extents_npages_get(&arena->extents_*) to determine how many dirty
+ * pages, if any, were generated.
+ */
+ size_t nunpurged;
+ /*
+ * Trailing log of how many unused dirty pages were generated during
+ * each of the past SMOOTHSTEP_NSTEPS decay epochs, where the last
+ * element is the most recent epoch. Corresponding epoch times are
+ * relative to epoch.
+ */
+ size_t backlog[SMOOTHSTEP_NSTEPS];
+
+ /*
+ * Pointer to associated stats. These stats are embedded directly in
+ * the arena's stats due to how stats structures are shared between the
+ * arena and ctl code.
+ *
+ * Synchronization: Same as associated arena's stats field. */
+ arena_stats_decay_t *stats;
+ /* Peak number of pages in associated extents. Used for debug only. */
+ uint64_t ceil_npages;
+};
+
+struct arena_s {
+ /*
+ * Number of threads currently assigned to this arena. Each thread has
+ * two distinct assignments, one for application-serving allocation, and
+ * the other for internal metadata allocation. Internal metadata must
+ * not be allocated from arenas explicitly created via the arenas.create
+ * mallctl, because the arena.<i>.reset mallctl indiscriminately
+ * discards all allocations for the affected arena.
+ *
+ * 0: Application allocation.
+ * 1: Internal metadata allocation.
+ *
+ * Synchronization: atomic.
+ */
+ atomic_u_t nthreads[2];
+
+ /*
+ * When percpu_arena is enabled, to amortize the cost of reading /
+ * updating the current CPU id, track the most recent thread accessing
+ * this arena, and only read CPU if there is a mismatch.
+ */
+ tsdn_t *last_thd;
+
+ /* Synchronization: internal. */
+ arena_stats_t stats;
+
+ /*
+ * Lists of tcaches and cache_bin_array_descriptors for extant threads
+ * associated with this arena. Stats from these are merged
+ * incrementally, and at exit if opt_stats_print is enabled.
+ *
+ * Synchronization: tcache_ql_mtx.
+ */
+ ql_head(tcache_t) tcache_ql;
+ ql_head(cache_bin_array_descriptor_t) cache_bin_array_descriptor_ql;
+ malloc_mutex_t tcache_ql_mtx;
+
+ /* Synchronization: internal. */
+ prof_accum_t prof_accum;
+ uint64_t prof_accumbytes;
+
+ /*
+ * PRNG state for cache index randomization of large allocation base
+ * pointers.
+ *
+ * Synchronization: atomic.
+ */
+ atomic_zu_t offset_state;
+
+ /*
+ * Extent serial number generator state.
+ *
+ * Synchronization: atomic.
+ */
+ atomic_zu_t extent_sn_next;
+
+ /*
+ * Represents a dss_prec_t, but atomically.
+ *
+ * Synchronization: atomic.
+ */
+ atomic_u_t dss_prec;
+
+ /*
+ * Number of pages in active extents.
+ *
+ * Synchronization: atomic.
+ */
+ atomic_zu_t nactive;
+
+ /*
+ * Extant large allocations.
+ *
+ * Synchronization: large_mtx.
+ */
+ extent_list_t large;
+ /* Synchronizes all large allocation/update/deallocation. */
+ malloc_mutex_t large_mtx;
+
+ /*
+ * Collections of extents that were previously allocated. These are
+ * used when allocating extents, in an attempt to re-use address space.
+ *
+ * Synchronization: internal.
+ */
+ extents_t extents_dirty;
+ extents_t extents_muzzy;
+ extents_t extents_retained;
+
+ /*
+ * Decay-based purging state, responsible for scheduling extent state
+ * transitions.
+ *
+ * Synchronization: internal.
+ */
+ arena_decay_t decay_dirty; /* dirty --> muzzy */
+ arena_decay_t decay_muzzy; /* muzzy --> retained */
+
+ /*
+ * Next extent size class in a growing series to use when satisfying a
+ * request via the extent hooks (only if opt_retain). This limits the
+ * number of disjoint virtual memory ranges so that extent merging can
+ * be effective even if multiple arenas' extent allocation requests are
+ * highly interleaved.
+ *
+ * retain_grow_limit is the max allowed size ind to expand (unless the
+ * required size is greater). Default is no limit, and controlled
+ * through mallctl only.
+ *
+ * Synchronization: extent_grow_mtx
+ */
+ pszind_t extent_grow_next;
+ pszind_t retain_grow_limit;
+ malloc_mutex_t extent_grow_mtx;
+
+ /*
+ * Available extent structures that were allocated via
+ * base_alloc_extent().
+ *
+ * Synchronization: extent_avail_mtx.
+ */
+ extent_tree_t extent_avail;
+ malloc_mutex_t extent_avail_mtx;
+
+ /*
+ * bins is used to store heaps of free regions.
+ *
+ * Synchronization: internal.
+ */
+ bin_t bins[NBINS];
+
+ /*
+ * Base allocator, from which arena metadata are allocated.
+ *
+ * Synchronization: internal.
+ */
+ base_t *base;
+ /* Used to determine uptime. Read-only after initialization. */
+ nstime_t create_time;
+};
+
+/* Used in conjunction with tsd for fast arena-related context lookup. */
+struct arena_tdata_s {
+ ticker_t decay_ticker;
+};
+
+/* Used to pass rtree lookup context down the path. */
+struct alloc_ctx_s {
+ szind_t szind;
+ bool slab;
+};
+
+#endif /* JEMALLOC_INTERNAL_ARENA_STRUCTS_B_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/arena_types.h b/deps/jemalloc/include/jemalloc/internal/arena_types.h
new file mode 100644
index 000000000..70001b5f1
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/arena_types.h
@@ -0,0 +1,43 @@
+#ifndef JEMALLOC_INTERNAL_ARENA_TYPES_H
+#define JEMALLOC_INTERNAL_ARENA_TYPES_H
+
+/* Maximum number of regions in one slab. */
+#define LG_SLAB_MAXREGS (LG_PAGE - LG_TINY_MIN)
+#define SLAB_MAXREGS (1U << LG_SLAB_MAXREGS)
+
+/* Default decay times in milliseconds. */
+#define DIRTY_DECAY_MS_DEFAULT ZD(10 * 1000)
+#define MUZZY_DECAY_MS_DEFAULT ZD(10 * 1000)
+/* Number of event ticks between time checks. */
+#define DECAY_NTICKS_PER_UPDATE 1000
+
+typedef struct arena_slab_data_s arena_slab_data_t;
+typedef struct arena_decay_s arena_decay_t;
+typedef struct arena_s arena_t;
+typedef struct arena_tdata_s arena_tdata_t;
+typedef struct alloc_ctx_s alloc_ctx_t;
+
+typedef enum {
+ percpu_arena_mode_names_base = 0, /* Used for options processing. */
+
+ /*
+ * *_uninit are used only during bootstrapping, and must correspond
+ * to initialized variant plus percpu_arena_mode_enabled_base.
+ */
+ percpu_arena_uninit = 0,
+ per_phycpu_arena_uninit = 1,
+
+ /* All non-disabled modes must come after percpu_arena_disabled. */
+ percpu_arena_disabled = 2,
+
+ percpu_arena_mode_names_limit = 3, /* Used for options processing. */
+ percpu_arena_mode_enabled_base = 3,
+
+ percpu_arena = 3,
+ per_phycpu_arena = 4 /* Hyper threads share arena. */
+} percpu_arena_mode_t;
+
+#define PERCPU_ARENA_ENABLED(m) ((m) >= percpu_arena_mode_enabled_base)
+#define PERCPU_ARENA_DEFAULT percpu_arena_disabled
+
+#endif /* JEMALLOC_INTERNAL_ARENA_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/assert.h b/deps/jemalloc/include/jemalloc/internal/assert.h
new file mode 100644
index 000000000..be4d45b32
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/assert.h
@@ -0,0 +1,56 @@
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/util.h"
+
+/*
+ * Define a custom assert() in order to reduce the chances of deadlock during
+ * assertion failure.
+ */
+#ifndef assert
+#define assert(e) do { \
+ if (unlikely(config_debug && !(e))) { \
+ malloc_printf( \
+ "<jemalloc>: %s:%d: Failed assertion: \"%s\"\n", \
+ __FILE__, __LINE__, #e); \
+ abort(); \
+ } \
+} while (0)
+#endif
+
+#ifndef not_reached
+#define not_reached() do { \
+ if (config_debug) { \
+ malloc_printf( \
+ "<jemalloc>: %s:%d: Unreachable code reached\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+ } \
+ unreachable(); \
+} while (0)
+#endif
+
+#ifndef not_implemented
+#define not_implemented() do { \
+ if (config_debug) { \
+ malloc_printf("<jemalloc>: %s:%d: Not implemented\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+ } \
+} while (0)
+#endif
+
+#ifndef assert_not_implemented
+#define assert_not_implemented(e) do { \
+ if (unlikely(config_debug && !(e))) { \
+ not_implemented(); \
+ } \
+} while (0)
+#endif
+
+/* Use to assert a particular configuration, e.g., cassert(config_debug). */
+#ifndef cassert
+#define cassert(c) do { \
+ if (unlikely(!(c))) { \
+ not_reached(); \
+ } \
+} while (0)
+#endif
diff --git a/deps/jemalloc/include/jemalloc/internal/atomic.h b/deps/jemalloc/include/jemalloc/internal/atomic.h
index a9aad35d1..adadb1a3a 100644
--- a/deps/jemalloc/include/jemalloc/internal/atomic.h
+++ b/deps/jemalloc/include/jemalloc/internal/atomic.h
@@ -1,651 +1,77 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#define atomic_read_uint64(p) atomic_add_uint64(p, 0)
-#define atomic_read_uint32(p) atomic_add_uint32(p, 0)
-#define atomic_read_p(p) atomic_add_p(p, NULL)
-#define atomic_read_z(p) atomic_add_z(p, 0)
-#define atomic_read_u(p) atomic_add_u(p, 0)
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+#ifndef JEMALLOC_INTERNAL_ATOMIC_H
+#define JEMALLOC_INTERNAL_ATOMIC_H
+
+#define ATOMIC_INLINE static inline
+
+#if defined(JEMALLOC_GCC_ATOMIC_ATOMICS)
+# include "jemalloc/internal/atomic_gcc_atomic.h"
+#elif defined(JEMALLOC_GCC_SYNC_ATOMICS)
+# include "jemalloc/internal/atomic_gcc_sync.h"
+#elif defined(_MSC_VER)
+# include "jemalloc/internal/atomic_msvc.h"
+#elif defined(JEMALLOC_C11_ATOMICS)
+# include "jemalloc/internal/atomic_c11.h"
+#else
+# error "Don't have atomics implemented on this platform."
+#endif
/*
- * All arithmetic functions return the arithmetic result of the atomic
- * operation. Some atomic operation APIs return the value prior to mutation, in
- * which case the following functions must redundantly compute the result so
- * that it can be returned. These functions are normally inlined, so the extra
- * operations can be optimized away if the return values aren't used by the
- * callers.
+ * This header gives more or less a backport of C11 atomics. The user can write
+ * JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_sizeof_type); to generate
+ * counterparts of the C11 atomic functions for type, as so:
+ * JEMALLOC_GENERATE_ATOMICS(int *, pi, 3);
+ * and then write things like:
+ * int *some_ptr;
+ * atomic_pi_t atomic_ptr_to_int;
+ * atomic_store_pi(&atomic_ptr_to_int, some_ptr, ATOMIC_RELAXED);
+ * int *prev_value = atomic_exchange_pi(&ptr_to_int, NULL, ATOMIC_ACQ_REL);
+ * assert(some_ptr == prev_value);
+ * and expect things to work in the obvious way.
*
- * <t> atomic_read_<t>(<t> *p) { return (*p); }
- * <t> atomic_add_<t>(<t> *p, <t> x) { return (*p + x); }
- * <t> atomic_sub_<t>(<t> *p, <t> x) { return (*p - x); }
- * bool atomic_cas_<t>(<t> *p, <t> c, <t> s)
- * {
- * if (*p != c)
- * return (true);
- * *p = s;
- * return (false);
- * }
- * void atomic_write_<t>(<t> *p, <t> x) { *p = x; }
+ * Also included (with naming differences to avoid conflicts with the standard
+ * library):
+ * atomic_fence(atomic_memory_order_t) (mimics C11's atomic_thread_fence).
+ * ATOMIC_INIT (mimics C11's ATOMIC_VAR_INIT).
*/
-#ifndef JEMALLOC_ENABLE_INLINE
-uint64_t atomic_add_uint64(uint64_t *p, uint64_t x);
-uint64_t atomic_sub_uint64(uint64_t *p, uint64_t x);
-bool atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s);
-void atomic_write_uint64(uint64_t *p, uint64_t x);
-uint32_t atomic_add_uint32(uint32_t *p, uint32_t x);
-uint32_t atomic_sub_uint32(uint32_t *p, uint32_t x);
-bool atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s);
-void atomic_write_uint32(uint32_t *p, uint32_t x);
-void *atomic_add_p(void **p, void *x);
-void *atomic_sub_p(void **p, void *x);
-bool atomic_cas_p(void **p, void *c, void *s);
-void atomic_write_p(void **p, const void *x);
-size_t atomic_add_z(size_t *p, size_t x);
-size_t atomic_sub_z(size_t *p, size_t x);
-bool atomic_cas_z(size_t *p, size_t c, size_t s);
-void atomic_write_z(size_t *p, size_t x);
-unsigned atomic_add_u(unsigned *p, unsigned x);
-unsigned atomic_sub_u(unsigned *p, unsigned x);
-bool atomic_cas_u(unsigned *p, unsigned c, unsigned s);
-void atomic_write_u(unsigned *p, unsigned x);
-#endif
+/*
+ * Pure convenience, so that we don't have to type "atomic_memory_order_"
+ * quite so often.
+ */
+#define ATOMIC_RELAXED atomic_memory_order_relaxed
+#define ATOMIC_ACQUIRE atomic_memory_order_acquire
+#define ATOMIC_RELEASE atomic_memory_order_release
+#define ATOMIC_ACQ_REL atomic_memory_order_acq_rel
+#define ATOMIC_SEQ_CST atomic_memory_order_seq_cst
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_ATOMIC_C_))
-/******************************************************************************/
-/* 64-bit operations. */
+/*
+ * Not all platforms have 64-bit atomics. If we do, this #define exposes that
+ * fact.
+ */
#if (LG_SIZEOF_PTR == 3 || LG_SIZEOF_INT == 3)
-# if (defined(__amd64__) || defined(__x86_64__))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
- uint64_t t = x;
-
- asm volatile (
- "lock; xaddq %0, %1;"
- : "+r" (t), "=m" (*p) /* Outputs. */
- : "m" (*p) /* Inputs. */
- );
-
- return (t + x);
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
- uint64_t t;
-
- x = (uint64_t)(-(int64_t)x);
- t = x;
- asm volatile (
- "lock; xaddq %0, %1;"
- : "+r" (t), "=m" (*p) /* Outputs. */
- : "m" (*p) /* Inputs. */
- );
-
- return (t + x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
- uint8_t success;
-
- asm volatile (
- "lock; cmpxchgq %4, %0;"
- "sete %1;"
- : "=m" (*p), "=a" (success) /* Outputs. */
- : "m" (*p), "a" (c), "r" (s) /* Inputs. */
- : "memory" /* Clobbers. */
- );
-
- return (!(bool)success);
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
-
- asm volatile (
- "xchgq %1, %0;" /* Lock is implied by xchgq. */
- : "=m" (*p), "+r" (x) /* Outputs. */
- : "m" (*p) /* Inputs. */
- : "memory" /* Clobbers. */
- );
-}
-# elif (defined(JEMALLOC_C11ATOMICS))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
- volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
- return (atomic_fetch_add(a, x) + x);
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
- volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
- return (atomic_fetch_sub(a, x) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
- volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
- return (!atomic_compare_exchange_strong(a, &c, s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
- volatile atomic_uint_least64_t *a = (volatile atomic_uint_least64_t *)p;
- atomic_store(a, x);
-}
-# elif (defined(JEMALLOC_ATOMIC9))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
-
- /*
- * atomic_fetchadd_64() doesn't exist, but we only ever use this
- * function on LP64 systems, so atomic_fetchadd_long() will do.
- */
- assert(sizeof(uint64_t) == sizeof(unsigned long));
-
- return (atomic_fetchadd_long(p, (unsigned long)x) + x);
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
-
- assert(sizeof(uint64_t) == sizeof(unsigned long));
-
- return (atomic_fetchadd_long(p, (unsigned long)(-(long)x)) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
-
- assert(sizeof(uint64_t) == sizeof(unsigned long));
-
- return (!atomic_cmpset_long(p, (unsigned long)c, (unsigned long)s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
-
- assert(sizeof(uint64_t) == sizeof(unsigned long));
-
- atomic_store_rel_long(p, x);
-}
-# elif (defined(JEMALLOC_OSATOMIC))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
-
- return (OSAtomicAdd64((int64_t)x, (int64_t *)p));
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
-
- return (OSAtomicAdd64(-((int64_t)x), (int64_t *)p));
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
-
- return (!OSAtomicCompareAndSwap64(c, s, (int64_t *)p));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
- uint64_t o;
-
- /*The documented OSAtomic*() API does not expose an atomic exchange. */
- do {
- o = atomic_read_uint64(p);
- } while (atomic_cas_uint64(p, o, x));
-}
-# elif (defined(_MSC_VER))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
-
- return (InterlockedExchangeAdd64(p, x) + x);
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
-
- return (InterlockedExchangeAdd64(p, -((int64_t)x)) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
- uint64_t o;
-
- o = InterlockedCompareExchange64(p, s, c);
- return (o != c);
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
-
- InterlockedExchange64(p, x);
-}
-# elif (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) || \
- defined(JE_FORCE_SYNC_COMPARE_AND_SWAP_8))
-JEMALLOC_INLINE uint64_t
-atomic_add_uint64(uint64_t *p, uint64_t x)
-{
-
- return (__sync_add_and_fetch(p, x));
-}
-
-JEMALLOC_INLINE uint64_t
-atomic_sub_uint64(uint64_t *p, uint64_t x)
-{
-
- return (__sync_sub_and_fetch(p, x));
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint64(uint64_t *p, uint64_t c, uint64_t s)
-{
-
- return (!__sync_bool_compare_and_swap(p, c, s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint64(uint64_t *p, uint64_t x)
-{
-
- __sync_lock_test_and_set(p, x);
-}
-# else
-# error "Missing implementation for 64-bit atomic operations"
-# endif
+# define JEMALLOC_ATOMIC_U64
#endif
-/******************************************************************************/
-/* 32-bit operations. */
-#if (defined(__i386__) || defined(__amd64__) || defined(__x86_64__))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
- uint32_t t = x;
-
- asm volatile (
- "lock; xaddl %0, %1;"
- : "+r" (t), "=m" (*p) /* Outputs. */
- : "m" (*p) /* Inputs. */
- );
-
- return (t + x);
-}
-
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
- uint32_t t;
-
- x = (uint32_t)(-(int32_t)x);
- t = x;
- asm volatile (
- "lock; xaddl %0, %1;"
- : "+r" (t), "=m" (*p) /* Outputs. */
- : "m" (*p) /* Inputs. */
- );
-
- return (t + x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
- uint8_t success;
-
- asm volatile (
- "lock; cmpxchgl %4, %0;"
- "sete %1;"
- : "=m" (*p), "=a" (success) /* Outputs. */
- : "m" (*p), "a" (c), "r" (s) /* Inputs. */
- : "memory"
- );
-
- return (!(bool)success);
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
-
- asm volatile (
- "xchgl %1, %0;" /* Lock is implied by xchgl. */
- : "=m" (*p), "+r" (x) /* Outputs. */
- : "m" (*p) /* Inputs. */
- : "memory" /* Clobbers. */
- );
-}
-# elif (defined(JEMALLOC_C11ATOMICS))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
- volatile atomic_uint_least32_t *a = (volatile atomic_uint_least32_t *)p;
- return (atomic_fetch_add(a, x) + x);
-}
+JEMALLOC_GENERATE_ATOMICS(void *, p, LG_SIZEOF_PTR)
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
- volatile atomic_uint_least32_t *a = (volatile atomic_uint_least32_t *)p;
- return (atomic_fetch_sub(a, x) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
- volatile atomic_uint_least32_t *a = (volatile atomic_uint_least32_t *)p;
- return (!atomic_compare_exchange_strong(a, &c, s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
- volatile atomic_uint_least32_t *a = (volatile atomic_uint_least32_t *)p;
- atomic_store(a, x);
-}
-#elif (defined(JEMALLOC_ATOMIC9))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
-
- return (atomic_fetchadd_32(p, x) + x);
-}
-
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
-
- return (atomic_fetchadd_32(p, (uint32_t)(-(int32_t)x)) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
-
- return (!atomic_cmpset_32(p, c, s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
-
- atomic_store_rel_32(p, x);
-}
-#elif (defined(JEMALLOC_OSATOMIC))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
-
- return (OSAtomicAdd32((int32_t)x, (int32_t *)p));
-}
-
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
-
- return (OSAtomicAdd32(-((int32_t)x), (int32_t *)p));
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
-
- return (!OSAtomicCompareAndSwap32(c, s, (int32_t *)p));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
- uint32_t o;
-
- /*The documented OSAtomic*() API does not expose an atomic exchange. */
- do {
- o = atomic_read_uint32(p);
- } while (atomic_cas_uint32(p, o, x));
-}
-#elif (defined(_MSC_VER))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
-
- return (InterlockedExchangeAdd(p, x) + x);
-}
-
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
-
- return (InterlockedExchangeAdd(p, -((int32_t)x)) - x);
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
- uint32_t o;
-
- o = InterlockedCompareExchange(p, s, c);
- return (o != c);
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
-
- InterlockedExchange(p, x);
-}
-#elif (defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) || \
- defined(JE_FORCE_SYNC_COMPARE_AND_SWAP_4))
-JEMALLOC_INLINE uint32_t
-atomic_add_uint32(uint32_t *p, uint32_t x)
-{
-
- return (__sync_add_and_fetch(p, x));
-}
-
-JEMALLOC_INLINE uint32_t
-atomic_sub_uint32(uint32_t *p, uint32_t x)
-{
-
- return (__sync_sub_and_fetch(p, x));
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_uint32(uint32_t *p, uint32_t c, uint32_t s)
-{
-
- return (!__sync_bool_compare_and_swap(p, c, s));
-}
-
-JEMALLOC_INLINE void
-atomic_write_uint32(uint32_t *p, uint32_t x)
-{
-
- __sync_lock_test_and_set(p, x);
-}
-#else
-# error "Missing implementation for 32-bit atomic operations"
-#endif
-
-/******************************************************************************/
-/* Pointer operations. */
-JEMALLOC_INLINE void *
-atomic_add_p(void **p, void *x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return ((void *)atomic_add_uint64((uint64_t *)p, (uint64_t)x));
-#elif (LG_SIZEOF_PTR == 2)
- return ((void *)atomic_add_uint32((uint32_t *)p, (uint32_t)x));
-#endif
-}
-
-JEMALLOC_INLINE void *
-atomic_sub_p(void **p, void *x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return ((void *)atomic_add_uint64((uint64_t *)p,
- (uint64_t)-((int64_t)x)));
-#elif (LG_SIZEOF_PTR == 2)
- return ((void *)atomic_add_uint32((uint32_t *)p,
- (uint32_t)-((int32_t)x)));
-#endif
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_p(void **p, void *c, void *s)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return (atomic_cas_uint64((uint64_t *)p, (uint64_t)c, (uint64_t)s));
-#elif (LG_SIZEOF_PTR == 2)
- return (atomic_cas_uint32((uint32_t *)p, (uint32_t)c, (uint32_t)s));
-#endif
-}
-
-JEMALLOC_INLINE void
-atomic_write_p(void **p, const void *x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- atomic_write_uint64((uint64_t *)p, (uint64_t)x);
-#elif (LG_SIZEOF_PTR == 2)
- atomic_write_uint32((uint32_t *)p, (uint32_t)x);
-#endif
-}
-
-/******************************************************************************/
-/* size_t operations. */
-JEMALLOC_INLINE size_t
-atomic_add_z(size_t *p, size_t x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return ((size_t)atomic_add_uint64((uint64_t *)p, (uint64_t)x));
-#elif (LG_SIZEOF_PTR == 2)
- return ((size_t)atomic_add_uint32((uint32_t *)p, (uint32_t)x));
-#endif
-}
-
-JEMALLOC_INLINE size_t
-atomic_sub_z(size_t *p, size_t x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return ((size_t)atomic_add_uint64((uint64_t *)p,
- (uint64_t)-((int64_t)x)));
-#elif (LG_SIZEOF_PTR == 2)
- return ((size_t)atomic_add_uint32((uint32_t *)p,
- (uint32_t)-((int32_t)x)));
-#endif
-}
-
-JEMALLOC_INLINE bool
-atomic_cas_z(size_t *p, size_t c, size_t s)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- return (atomic_cas_uint64((uint64_t *)p, (uint64_t)c, (uint64_t)s));
-#elif (LG_SIZEOF_PTR == 2)
- return (atomic_cas_uint32((uint32_t *)p, (uint32_t)c, (uint32_t)s));
-#endif
-}
-
-JEMALLOC_INLINE void
-atomic_write_z(size_t *p, size_t x)
-{
-
-#if (LG_SIZEOF_PTR == 3)
- atomic_write_uint64((uint64_t *)p, (uint64_t)x);
-#elif (LG_SIZEOF_PTR == 2)
- atomic_write_uint32((uint32_t *)p, (uint32_t)x);
-#endif
-}
-
-/******************************************************************************/
-/* unsigned operations. */
-JEMALLOC_INLINE unsigned
-atomic_add_u(unsigned *p, unsigned x)
-{
-
-#if (LG_SIZEOF_INT == 3)
- return ((unsigned)atomic_add_uint64((uint64_t *)p, (uint64_t)x));
-#elif (LG_SIZEOF_INT == 2)
- return ((unsigned)atomic_add_uint32((uint32_t *)p, (uint32_t)x));
-#endif
-}
-
-JEMALLOC_INLINE unsigned
-atomic_sub_u(unsigned *p, unsigned x)
-{
+/*
+ * There's no actual guarantee that sizeof(bool) == 1, but it's true on the only
+ * platform that actually needs to know the size, MSVC.
+ */
+JEMALLOC_GENERATE_ATOMICS(bool, b, 0)
-#if (LG_SIZEOF_INT == 3)
- return ((unsigned)atomic_add_uint64((uint64_t *)p,
- (uint64_t)-((int64_t)x)));
-#elif (LG_SIZEOF_INT == 2)
- return ((unsigned)atomic_add_uint32((uint32_t *)p,
- (uint32_t)-((int32_t)x)));
-#endif
-}
+JEMALLOC_GENERATE_INT_ATOMICS(unsigned, u, LG_SIZEOF_INT)
-JEMALLOC_INLINE bool
-atomic_cas_u(unsigned *p, unsigned c, unsigned s)
-{
+JEMALLOC_GENERATE_INT_ATOMICS(size_t, zu, LG_SIZEOF_PTR)
-#if (LG_SIZEOF_INT == 3)
- return (atomic_cas_uint64((uint64_t *)p, (uint64_t)c, (uint64_t)s));
-#elif (LG_SIZEOF_INT == 2)
- return (atomic_cas_uint32((uint32_t *)p, (uint32_t)c, (uint32_t)s));
-#endif
-}
+JEMALLOC_GENERATE_INT_ATOMICS(ssize_t, zd, LG_SIZEOF_PTR)
-JEMALLOC_INLINE void
-atomic_write_u(unsigned *p, unsigned x)
-{
+JEMALLOC_GENERATE_INT_ATOMICS(uint32_t, u32, 2)
-#if (LG_SIZEOF_INT == 3)
- atomic_write_uint64((uint64_t *)p, (uint64_t)x);
-#elif (LG_SIZEOF_INT == 2)
- atomic_write_uint32((uint32_t *)p, (uint32_t)x);
+#ifdef JEMALLOC_ATOMIC_U64
+JEMALLOC_GENERATE_INT_ATOMICS(uint64_t, u64, 3)
#endif
-}
-/******************************************************************************/
-#endif
+#undef ATOMIC_INLINE
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_ATOMIC_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/atomic_c11.h b/deps/jemalloc/include/jemalloc/internal/atomic_c11.h
new file mode 100644
index 000000000..a5f9313a6
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/atomic_c11.h
@@ -0,0 +1,97 @@
+#ifndef JEMALLOC_INTERNAL_ATOMIC_C11_H
+#define JEMALLOC_INTERNAL_ATOMIC_C11_H
+
+#include <stdatomic.h>
+
+#define ATOMIC_INIT(...) ATOMIC_VAR_INIT(__VA_ARGS__)
+
+#define atomic_memory_order_t memory_order
+#define atomic_memory_order_relaxed memory_order_relaxed
+#define atomic_memory_order_acquire memory_order_acquire
+#define atomic_memory_order_release memory_order_release
+#define atomic_memory_order_acq_rel memory_order_acq_rel
+#define atomic_memory_order_seq_cst memory_order_seq_cst
+
+#define atomic_fence atomic_thread_fence
+
+#define JEMALLOC_GENERATE_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+typedef _Atomic(type) atomic_##short_type##_t; \
+ \
+ATOMIC_INLINE type \
+atomic_load_##short_type(const atomic_##short_type##_t *a, \
+ atomic_memory_order_t mo) { \
+ /* \
+ * A strict interpretation of the C standard prevents \
+ * atomic_load from taking a const argument, but it's \
+ * convenient for our purposes. This cast is a workaround. \
+ */ \
+ atomic_##short_type##_t* a_nonconst = \
+ (atomic_##short_type##_t*)a; \
+ return atomic_load_explicit(a_nonconst, mo); \
+} \
+ \
+ATOMIC_INLINE void \
+atomic_store_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ atomic_store_explicit(a, val, mo); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return atomic_exchange_explicit(a, val, mo); \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ return atomic_compare_exchange_weak_explicit(a, expected, \
+ desired, success_mo, failure_mo); \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ return atomic_compare_exchange_strong_explicit(a, expected, \
+ desired, success_mo, failure_mo); \
+}
+
+/*
+ * Integral types have some special operations available that non-integral ones
+ * lack.
+ */
+#define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_add_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return atomic_fetch_add_explicit(a, val, mo); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return atomic_fetch_sub_explicit(a, val, mo); \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_and_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return atomic_fetch_and_explicit(a, val, mo); \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_or_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return atomic_fetch_or_explicit(a, val, mo); \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return atomic_fetch_xor_explicit(a, val, mo); \
+}
+
+#endif /* JEMALLOC_INTERNAL_ATOMIC_C11_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/atomic_gcc_atomic.h b/deps/jemalloc/include/jemalloc/internal/atomic_gcc_atomic.h
new file mode 100644
index 000000000..6b73a14f8
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/atomic_gcc_atomic.h
@@ -0,0 +1,127 @@
+#ifndef JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H
+#define JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H
+
+#include "jemalloc/internal/assert.h"
+
+#define ATOMIC_INIT(...) {__VA_ARGS__}
+
+typedef enum {
+ atomic_memory_order_relaxed,
+ atomic_memory_order_acquire,
+ atomic_memory_order_release,
+ atomic_memory_order_acq_rel,
+ atomic_memory_order_seq_cst
+} atomic_memory_order_t;
+
+ATOMIC_INLINE int
+atomic_enum_to_builtin(atomic_memory_order_t mo) {
+ switch (mo) {
+ case atomic_memory_order_relaxed:
+ return __ATOMIC_RELAXED;
+ case atomic_memory_order_acquire:
+ return __ATOMIC_ACQUIRE;
+ case atomic_memory_order_release:
+ return __ATOMIC_RELEASE;
+ case atomic_memory_order_acq_rel:
+ return __ATOMIC_ACQ_REL;
+ case atomic_memory_order_seq_cst:
+ return __ATOMIC_SEQ_CST;
+ }
+ /* Can't happen; the switch is exhaustive. */
+ not_reached();
+}
+
+ATOMIC_INLINE void
+atomic_fence(atomic_memory_order_t mo) {
+ __atomic_thread_fence(atomic_enum_to_builtin(mo));
+}
+
+#define JEMALLOC_GENERATE_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+typedef struct { \
+ type repr; \
+} atomic_##short_type##_t; \
+ \
+ATOMIC_INLINE type \
+atomic_load_##short_type(const atomic_##short_type##_t *a, \
+ atomic_memory_order_t mo) { \
+ type result; \
+ __atomic_load(&a->repr, &result, atomic_enum_to_builtin(mo)); \
+ return result; \
+} \
+ \
+ATOMIC_INLINE void \
+atomic_store_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ __atomic_store(&a->repr, &val, atomic_enum_to_builtin(mo)); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ type result; \
+ __atomic_exchange(&a->repr, &val, &result, \
+ atomic_enum_to_builtin(mo)); \
+ return result; \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ return __atomic_compare_exchange(&a->repr, expected, &desired, \
+ true, atomic_enum_to_builtin(success_mo), \
+ atomic_enum_to_builtin(failure_mo)); \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ return __atomic_compare_exchange(&a->repr, expected, &desired, \
+ false, \
+ atomic_enum_to_builtin(success_mo), \
+ atomic_enum_to_builtin(failure_mo)); \
+}
+
+
+#define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_add_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __atomic_fetch_add(&a->repr, val, \
+ atomic_enum_to_builtin(mo)); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __atomic_fetch_sub(&a->repr, val, \
+ atomic_enum_to_builtin(mo)); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_and_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __atomic_fetch_and(&a->repr, val, \
+ atomic_enum_to_builtin(mo)); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_or_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __atomic_fetch_or(&a->repr, val, \
+ atomic_enum_to_builtin(mo)); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __atomic_fetch_xor(&a->repr, val, \
+ atomic_enum_to_builtin(mo)); \
+}
+
+#endif /* JEMALLOC_INTERNAL_ATOMIC_GCC_ATOMIC_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/atomic_gcc_sync.h b/deps/jemalloc/include/jemalloc/internal/atomic_gcc_sync.h
new file mode 100644
index 000000000..30846e4d2
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/atomic_gcc_sync.h
@@ -0,0 +1,191 @@
+#ifndef JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H
+#define JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H
+
+#define ATOMIC_INIT(...) {__VA_ARGS__}
+
+typedef enum {
+ atomic_memory_order_relaxed,
+ atomic_memory_order_acquire,
+ atomic_memory_order_release,
+ atomic_memory_order_acq_rel,
+ atomic_memory_order_seq_cst
+} atomic_memory_order_t;
+
+ATOMIC_INLINE void
+atomic_fence(atomic_memory_order_t mo) {
+ /* Easy cases first: no barrier, and full barrier. */
+ if (mo == atomic_memory_order_relaxed) {
+ asm volatile("" ::: "memory");
+ return;
+ }
+ if (mo == atomic_memory_order_seq_cst) {
+ asm volatile("" ::: "memory");
+ __sync_synchronize();
+ asm volatile("" ::: "memory");
+ return;
+ }
+ asm volatile("" ::: "memory");
+# if defined(__i386__) || defined(__x86_64__)
+ /* This is implicit on x86. */
+# elif defined(__ppc__)
+ asm volatile("lwsync");
+# elif defined(__sparc__) && defined(__arch64__)
+ if (mo == atomic_memory_order_acquire) {
+ asm volatile("membar #LoadLoad | #LoadStore");
+ } else if (mo == atomic_memory_order_release) {
+ asm volatile("membar #LoadStore | #StoreStore");
+ } else {
+ asm volatile("membar #LoadLoad | #LoadStore | #StoreStore");
+ }
+# else
+ __sync_synchronize();
+# endif
+ asm volatile("" ::: "memory");
+}
+
+/*
+ * A correct implementation of seq_cst loads and stores on weakly ordered
+ * architectures could do either of the following:
+ * 1. store() is weak-fence -> store -> strong fence, load() is load ->
+ * strong-fence.
+ * 2. store() is strong-fence -> store, load() is strong-fence -> load ->
+ * weak-fence.
+ * The tricky thing is, load() and store() above can be the load or store
+ * portions of a gcc __sync builtin, so we have to follow GCC's lead, which
+ * means going with strategy 2.
+ * On strongly ordered architectures, the natural strategy is to stick a strong
+ * fence after seq_cst stores, and have naked loads. So we want the strong
+ * fences in different places on different architectures.
+ * atomic_pre_sc_load_fence and atomic_post_sc_store_fence allow us to
+ * accomplish this.
+ */
+
+ATOMIC_INLINE void
+atomic_pre_sc_load_fence() {
+# if defined(__i386__) || defined(__x86_64__) || \
+ (defined(__sparc__) && defined(__arch64__))
+ atomic_fence(atomic_memory_order_relaxed);
+# else
+ atomic_fence(atomic_memory_order_seq_cst);
+# endif
+}
+
+ATOMIC_INLINE void
+atomic_post_sc_store_fence() {
+# if defined(__i386__) || defined(__x86_64__) || \
+ (defined(__sparc__) && defined(__arch64__))
+ atomic_fence(atomic_memory_order_seq_cst);
+# else
+ atomic_fence(atomic_memory_order_relaxed);
+# endif
+
+}
+
+#define JEMALLOC_GENERATE_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+typedef struct { \
+ type volatile repr; \
+} atomic_##short_type##_t; \
+ \
+ATOMIC_INLINE type \
+atomic_load_##short_type(const atomic_##short_type##_t *a, \
+ atomic_memory_order_t mo) { \
+ if (mo == atomic_memory_order_seq_cst) { \
+ atomic_pre_sc_load_fence(); \
+ } \
+ type result = a->repr; \
+ if (mo != atomic_memory_order_relaxed) { \
+ atomic_fence(atomic_memory_order_acquire); \
+ } \
+ return result; \
+} \
+ \
+ATOMIC_INLINE void \
+atomic_store_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ if (mo != atomic_memory_order_relaxed) { \
+ atomic_fence(atomic_memory_order_release); \
+ } \
+ a->repr = val; \
+ if (mo == atomic_memory_order_seq_cst) { \
+ atomic_post_sc_store_fence(); \
+ } \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ /* \
+ * Because of FreeBSD, we care about gcc 4.2, which doesn't have\
+ * an atomic exchange builtin. We fake it with a CAS loop. \
+ */ \
+ while (true) { \
+ type old = a->repr; \
+ if (__sync_bool_compare_and_swap(&a->repr, old, val)) { \
+ return old; \
+ } \
+ } \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ type prev = __sync_val_compare_and_swap(&a->repr, *expected, \
+ desired); \
+ if (prev == *expected) { \
+ return true; \
+ } else { \
+ *expected = prev; \
+ return false; \
+ } \
+} \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ type prev = __sync_val_compare_and_swap(&a->repr, *expected, \
+ desired); \
+ if (prev == *expected) { \
+ return true; \
+ } else { \
+ *expected = prev; \
+ return false; \
+ } \
+}
+
+#define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, \
+ /* unused */ lg_size) \
+JEMALLOC_GENERATE_ATOMICS(type, short_type, /* unused */ lg_size) \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_add_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __sync_fetch_and_add(&a->repr, val); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __sync_fetch_and_sub(&a->repr, val); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_and_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __sync_fetch_and_and(&a->repr, val); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_or_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __sync_fetch_and_or(&a->repr, val); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return __sync_fetch_and_xor(&a->repr, val); \
+}
+
+#endif /* JEMALLOC_INTERNAL_ATOMIC_GCC_SYNC_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/atomic_msvc.h b/deps/jemalloc/include/jemalloc/internal/atomic_msvc.h
new file mode 100644
index 000000000..67057ce50
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/atomic_msvc.h
@@ -0,0 +1,158 @@
+#ifndef JEMALLOC_INTERNAL_ATOMIC_MSVC_H
+#define JEMALLOC_INTERNAL_ATOMIC_MSVC_H
+
+#define ATOMIC_INIT(...) {__VA_ARGS__}
+
+typedef enum {
+ atomic_memory_order_relaxed,
+ atomic_memory_order_acquire,
+ atomic_memory_order_release,
+ atomic_memory_order_acq_rel,
+ atomic_memory_order_seq_cst
+} atomic_memory_order_t;
+
+typedef char atomic_repr_0_t;
+typedef short atomic_repr_1_t;
+typedef long atomic_repr_2_t;
+typedef __int64 atomic_repr_3_t;
+
+ATOMIC_INLINE void
+atomic_fence(atomic_memory_order_t mo) {
+ _ReadWriteBarrier();
+# if defined(_M_ARM) || defined(_M_ARM64)
+ /* ARM needs a barrier for everything but relaxed. */
+ if (mo != atomic_memory_order_relaxed) {
+ MemoryBarrier();
+ }
+# elif defined(_M_IX86) || defined (_M_X64)
+ /* x86 needs a barrier only for seq_cst. */
+ if (mo == atomic_memory_order_seq_cst) {
+ MemoryBarrier();
+ }
+# else
+# error "Don't know how to create atomics for this platform for MSVC."
+# endif
+ _ReadWriteBarrier();
+}
+
+#define ATOMIC_INTERLOCKED_REPR(lg_size) atomic_repr_ ## lg_size ## _t
+
+#define ATOMIC_CONCAT(a, b) ATOMIC_RAW_CONCAT(a, b)
+#define ATOMIC_RAW_CONCAT(a, b) a ## b
+
+#define ATOMIC_INTERLOCKED_NAME(base_name, lg_size) ATOMIC_CONCAT( \
+ base_name, ATOMIC_INTERLOCKED_SUFFIX(lg_size))
+
+#define ATOMIC_INTERLOCKED_SUFFIX(lg_size) \
+ ATOMIC_CONCAT(ATOMIC_INTERLOCKED_SUFFIX_, lg_size)
+
+#define ATOMIC_INTERLOCKED_SUFFIX_0 8
+#define ATOMIC_INTERLOCKED_SUFFIX_1 16
+#define ATOMIC_INTERLOCKED_SUFFIX_2
+#define ATOMIC_INTERLOCKED_SUFFIX_3 64
+
+#define JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_size) \
+typedef struct { \
+ ATOMIC_INTERLOCKED_REPR(lg_size) repr; \
+} atomic_##short_type##_t; \
+ \
+ATOMIC_INLINE type \
+atomic_load_##short_type(const atomic_##short_type##_t *a, \
+ atomic_memory_order_t mo) { \
+ ATOMIC_INTERLOCKED_REPR(lg_size) ret = a->repr; \
+ if (mo != atomic_memory_order_relaxed) { \
+ atomic_fence(atomic_memory_order_acquire); \
+ } \
+ return (type) ret; \
+} \
+ \
+ATOMIC_INLINE void \
+atomic_store_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ if (mo != atomic_memory_order_relaxed) { \
+ atomic_fence(atomic_memory_order_release); \
+ } \
+ a->repr = (ATOMIC_INTERLOCKED_REPR(lg_size)) val; \
+ if (mo == atomic_memory_order_seq_cst) { \
+ atomic_fence(atomic_memory_order_seq_cst); \
+ } \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_exchange_##short_type(atomic_##short_type##_t *a, type val, \
+ atomic_memory_order_t mo) { \
+ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedExchange, \
+ lg_size)(&a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_weak_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ ATOMIC_INTERLOCKED_REPR(lg_size) e = \
+ (ATOMIC_INTERLOCKED_REPR(lg_size))*expected; \
+ ATOMIC_INTERLOCKED_REPR(lg_size) d = \
+ (ATOMIC_INTERLOCKED_REPR(lg_size))desired; \
+ ATOMIC_INTERLOCKED_REPR(lg_size) old = \
+ ATOMIC_INTERLOCKED_NAME(_InterlockedCompareExchange, \
+ lg_size)(&a->repr, d, e); \
+ if (old == e) { \
+ return true; \
+ } else { \
+ *expected = (type)old; \
+ return false; \
+ } \
+} \
+ \
+ATOMIC_INLINE bool \
+atomic_compare_exchange_strong_##short_type(atomic_##short_type##_t *a, \
+ type *expected, type desired, atomic_memory_order_t success_mo, \
+ atomic_memory_order_t failure_mo) { \
+ /* We implement the weak version with strong semantics. */ \
+ return atomic_compare_exchange_weak_##short_type(a, expected, \
+ desired, success_mo, failure_mo); \
+}
+
+
+#define JEMALLOC_GENERATE_INT_ATOMICS(type, short_type, lg_size) \
+JEMALLOC_GENERATE_ATOMICS(type, short_type, lg_size) \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_add_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedExchangeAdd, \
+ lg_size)(&a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \
+} \
+ \
+ATOMIC_INLINE type \
+atomic_fetch_sub_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ /* \
+ * MSVC warns on negation of unsigned operands, but for us it \
+ * gives exactly the right semantics (MAX_TYPE + 1 - operand). \
+ */ \
+ __pragma(warning(push)) \
+ __pragma(warning(disable: 4146)) \
+ return atomic_fetch_add_##short_type(a, -val, mo); \
+ __pragma(warning(pop)) \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_and_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedAnd, lg_size)( \
+ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_or_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedOr, lg_size)( \
+ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \
+} \
+ATOMIC_INLINE type \
+atomic_fetch_xor_##short_type(atomic_##short_type##_t *a, \
+ type val, atomic_memory_order_t mo) { \
+ return (type)ATOMIC_INTERLOCKED_NAME(_InterlockedXor, lg_size)( \
+ &a->repr, (ATOMIC_INTERLOCKED_REPR(lg_size))val); \
+}
+
+#endif /* JEMALLOC_INTERNAL_ATOMIC_MSVC_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/background_thread_externs.h b/deps/jemalloc/include/jemalloc/internal/background_thread_externs.h
new file mode 100644
index 000000000..3209aa49f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/background_thread_externs.h
@@ -0,0 +1,33 @@
+#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H
+#define JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H
+
+extern bool opt_background_thread;
+extern size_t opt_max_background_threads;
+extern malloc_mutex_t background_thread_lock;
+extern atomic_b_t background_thread_enabled_state;
+extern size_t n_background_threads;
+extern size_t max_background_threads;
+extern background_thread_info_t *background_thread_info;
+extern bool can_enable_background_thread;
+
+bool background_thread_create(tsd_t *tsd, unsigned arena_ind);
+bool background_threads_enable(tsd_t *tsd);
+bool background_threads_disable(tsd_t *tsd);
+void background_thread_interval_check(tsdn_t *tsdn, arena_t *arena,
+ arena_decay_t *decay, size_t npages_new);
+void background_thread_prefork0(tsdn_t *tsdn);
+void background_thread_prefork1(tsdn_t *tsdn);
+void background_thread_postfork_parent(tsdn_t *tsdn);
+void background_thread_postfork_child(tsdn_t *tsdn);
+bool background_thread_stats_read(tsdn_t *tsdn,
+ background_thread_stats_t *stats);
+void background_thread_ctl_init(tsdn_t *tsdn);
+
+#ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER
+extern int pthread_create_wrapper(pthread_t *__restrict, const pthread_attr_t *,
+ void *(*)(void *), void *__restrict);
+#endif
+bool background_thread_boot0(void);
+bool background_thread_boot1(tsdn_t *tsdn);
+
+#endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/background_thread_inlines.h b/deps/jemalloc/include/jemalloc/internal/background_thread_inlines.h
new file mode 100644
index 000000000..ef50231e8
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/background_thread_inlines.h
@@ -0,0 +1,57 @@
+#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H
+#define JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H
+
+JEMALLOC_ALWAYS_INLINE bool
+background_thread_enabled(void) {
+ return atomic_load_b(&background_thread_enabled_state, ATOMIC_RELAXED);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+background_thread_enabled_set(tsdn_t *tsdn, bool state) {
+ malloc_mutex_assert_owner(tsdn, &background_thread_lock);
+ atomic_store_b(&background_thread_enabled_state, state, ATOMIC_RELAXED);
+}
+
+JEMALLOC_ALWAYS_INLINE background_thread_info_t *
+arena_background_thread_info_get(arena_t *arena) {
+ unsigned arena_ind = arena_ind_get(arena);
+ return &background_thread_info[arena_ind % ncpus];
+}
+
+JEMALLOC_ALWAYS_INLINE uint64_t
+background_thread_wakeup_time_get(background_thread_info_t *info) {
+ uint64_t next_wakeup = nstime_ns(&info->next_wakeup);
+ assert(atomic_load_b(&info->indefinite_sleep, ATOMIC_ACQUIRE) ==
+ (next_wakeup == BACKGROUND_THREAD_INDEFINITE_SLEEP));
+ return next_wakeup;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+background_thread_wakeup_time_set(tsdn_t *tsdn, background_thread_info_t *info,
+ uint64_t wakeup_time) {
+ malloc_mutex_assert_owner(tsdn, &info->mtx);
+ atomic_store_b(&info->indefinite_sleep,
+ wakeup_time == BACKGROUND_THREAD_INDEFINITE_SLEEP, ATOMIC_RELEASE);
+ nstime_init(&info->next_wakeup, wakeup_time);
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+background_thread_indefinite_sleep(background_thread_info_t *info) {
+ return atomic_load_b(&info->indefinite_sleep, ATOMIC_ACQUIRE);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+arena_background_thread_inactivity_check(tsdn_t *tsdn, arena_t *arena,
+ bool is_background_thread) {
+ if (!background_thread_enabled() || is_background_thread) {
+ return;
+ }
+ background_thread_info_t *info =
+ arena_background_thread_info_get(arena);
+ if (background_thread_indefinite_sleep(info)) {
+ background_thread_interval_check(tsdn, arena,
+ &arena->decay_dirty, 0);
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_INLINES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/background_thread_structs.h b/deps/jemalloc/include/jemalloc/internal/background_thread_structs.h
new file mode 100644
index 000000000..c1107dfe9
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/background_thread_structs.h
@@ -0,0 +1,53 @@
+#ifndef JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H
+#define JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H
+
+/* This file really combines "structs" and "types", but only transitionally. */
+
+#if defined(JEMALLOC_BACKGROUND_THREAD) || defined(JEMALLOC_LAZY_LOCK)
+# define JEMALLOC_PTHREAD_CREATE_WRAPPER
+#endif
+
+#define BACKGROUND_THREAD_INDEFINITE_SLEEP UINT64_MAX
+#define MAX_BACKGROUND_THREAD_LIMIT MALLOCX_ARENA_LIMIT
+
+typedef enum {
+ background_thread_stopped,
+ background_thread_started,
+ /* Thread waits on the global lock when paused (for arena_reset). */
+ background_thread_paused,
+} background_thread_state_t;
+
+struct background_thread_info_s {
+#ifdef JEMALLOC_BACKGROUND_THREAD
+ /* Background thread is pthread specific. */
+ pthread_t thread;
+ pthread_cond_t cond;
+#endif
+ malloc_mutex_t mtx;
+ background_thread_state_t state;
+ /* When true, it means no wakeup scheduled. */
+ atomic_b_t indefinite_sleep;
+ /* Next scheduled wakeup time (absolute time in ns). */
+ nstime_t next_wakeup;
+ /*
+ * Since the last background thread run, newly added number of pages
+ * that need to be purged by the next wakeup. This is adjusted on
+ * epoch advance, and is used to determine whether we should signal the
+ * background thread to wake up earlier.
+ */
+ size_t npages_to_purge_new;
+ /* Stats: total number of runs since started. */
+ uint64_t tot_n_runs;
+ /* Stats: total sleep time since started. */
+ nstime_t tot_sleep_time;
+};
+typedef struct background_thread_info_s background_thread_info_t;
+
+struct background_thread_stats_s {
+ size_t num_threads;
+ uint64_t num_runs;
+ nstime_t run_interval;
+};
+typedef struct background_thread_stats_s background_thread_stats_t;
+
+#endif /* JEMALLOC_INTERNAL_BACKGROUND_THREAD_STRUCTS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/base.h b/deps/jemalloc/include/jemalloc/internal/base.h
deleted file mode 100644
index 39e46ee44..000000000
--- a/deps/jemalloc/include/jemalloc/internal/base.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void *base_alloc(size_t size);
-void base_stats_get(size_t *allocated, size_t *resident, size_t *mapped);
-bool base_boot(void);
-void base_prefork(void);
-void base_postfork_parent(void);
-void base_postfork_child(void);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/base_externs.h b/deps/jemalloc/include/jemalloc/internal/base_externs.h
new file mode 100644
index 000000000..7b705c9b4
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/base_externs.h
@@ -0,0 +1,22 @@
+#ifndef JEMALLOC_INTERNAL_BASE_EXTERNS_H
+#define JEMALLOC_INTERNAL_BASE_EXTERNS_H
+
+extern metadata_thp_mode_t opt_metadata_thp;
+extern const char *metadata_thp_mode_names[];
+
+base_t *b0get(void);
+base_t *base_new(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks);
+void base_delete(tsdn_t *tsdn, base_t *base);
+extent_hooks_t *base_extent_hooks_get(base_t *base);
+extent_hooks_t *base_extent_hooks_set(base_t *base,
+ extent_hooks_t *extent_hooks);
+void *base_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment);
+extent_t *base_alloc_extent(tsdn_t *tsdn, base_t *base);
+void base_stats_get(tsdn_t *tsdn, base_t *base, size_t *allocated,
+ size_t *resident, size_t *mapped, size_t *n_thp);
+void base_prefork(tsdn_t *tsdn, base_t *base);
+void base_postfork_parent(tsdn_t *tsdn, base_t *base);
+void base_postfork_child(tsdn_t *tsdn, base_t *base);
+bool base_boot(tsdn_t *tsdn);
+
+#endif /* JEMALLOC_INTERNAL_BASE_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/base_inlines.h b/deps/jemalloc/include/jemalloc/internal/base_inlines.h
new file mode 100644
index 000000000..aec0e2e1e
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/base_inlines.h
@@ -0,0 +1,13 @@
+#ifndef JEMALLOC_INTERNAL_BASE_INLINES_H
+#define JEMALLOC_INTERNAL_BASE_INLINES_H
+
+static inline unsigned
+base_ind_get(const base_t *base) {
+ return base->ind;
+}
+
+static inline bool
+metadata_thp_enabled(void) {
+ return (opt_metadata_thp != metadata_thp_disabled);
+}
+#endif /* JEMALLOC_INTERNAL_BASE_INLINES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/base_structs.h b/deps/jemalloc/include/jemalloc/internal/base_structs.h
new file mode 100644
index 000000000..2102247ac
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/base_structs.h
@@ -0,0 +1,59 @@
+#ifndef JEMALLOC_INTERNAL_BASE_STRUCTS_H
+#define JEMALLOC_INTERNAL_BASE_STRUCTS_H
+
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/size_classes.h"
+
+/* Embedded at the beginning of every block of base-managed virtual memory. */
+struct base_block_s {
+ /* Total size of block's virtual memory mapping. */
+ size_t size;
+
+ /* Next block in list of base's blocks. */
+ base_block_t *next;
+
+ /* Tracks unused trailing space. */
+ extent_t extent;
+};
+
+struct base_s {
+ /* Associated arena's index within the arenas array. */
+ unsigned ind;
+
+ /*
+ * User-configurable extent hook functions. Points to an
+ * extent_hooks_t.
+ */
+ atomic_p_t extent_hooks;
+
+ /* Protects base_alloc() and base_stats_get() operations. */
+ malloc_mutex_t mtx;
+
+ /* Using THP when true (metadata_thp auto mode). */
+ bool auto_thp_switched;
+ /*
+ * Most recent size class in the series of increasingly large base
+ * extents. Logarithmic spacing between subsequent allocations ensures
+ * that the total number of distinct mappings remains small.
+ */
+ pszind_t pind_last;
+
+ /* Serial number generation state. */
+ size_t extent_sn_next;
+
+ /* Chain of all blocks associated with base. */
+ base_block_t *blocks;
+
+ /* Heap of extents that track unused trailing space within blocks. */
+ extent_heap_t avail[NSIZES];
+
+ /* Stats, only maintained if config_stats. */
+ size_t allocated;
+ size_t resident;
+ size_t mapped;
+ /* Number of THP regions touched. */
+ size_t n_thp;
+};
+
+#endif /* JEMALLOC_INTERNAL_BASE_STRUCTS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/base_types.h b/deps/jemalloc/include/jemalloc/internal/base_types.h
new file mode 100644
index 000000000..b6db77df7
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/base_types.h
@@ -0,0 +1,33 @@
+#ifndef JEMALLOC_INTERNAL_BASE_TYPES_H
+#define JEMALLOC_INTERNAL_BASE_TYPES_H
+
+typedef struct base_block_s base_block_t;
+typedef struct base_s base_t;
+
+#define METADATA_THP_DEFAULT metadata_thp_disabled
+
+/*
+ * In auto mode, arenas switch to huge pages for the base allocator on the
+ * second base block. a0 switches to thp on the 5th block (after 20 megabytes
+ * of metadata), since more metadata (e.g. rtree nodes) come from a0's base.
+ */
+
+#define BASE_AUTO_THP_THRESHOLD 2
+#define BASE_AUTO_THP_THRESHOLD_A0 5
+
+typedef enum {
+ metadata_thp_disabled = 0,
+ /*
+ * Lazily enable hugepage for metadata. To avoid high RSS caused by THP
+ * + low usage arena (i.e. THP becomes a significant percentage), the
+ * "auto" option only starts using THP after a base allocator used up
+ * the first THP region. Starting from the second hugepage (in a single
+ * arena), "auto" behaves the same as "always", i.e. madvise hugepage
+ * right away.
+ */
+ metadata_thp_auto = 1,
+ metadata_thp_always = 2,
+ metadata_thp_mode_limit = 3
+} metadata_thp_mode_t;
+
+#endif /* JEMALLOC_INTERNAL_BASE_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/bin.h b/deps/jemalloc/include/jemalloc/internal/bin.h
new file mode 100644
index 000000000..9b416ada7
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/bin.h
@@ -0,0 +1,106 @@
+#ifndef JEMALLOC_INTERNAL_BIN_H
+#define JEMALLOC_INTERNAL_BIN_H
+
+#include "jemalloc/internal/extent_types.h"
+#include "jemalloc/internal/extent_structs.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/bin_stats.h"
+
+/*
+ * A bin contains a set of extents that are currently being used for slab
+ * allocations.
+ */
+
+/*
+ * Read-only information associated with each element of arena_t's bins array
+ * is stored separately, partly to reduce memory usage (only one copy, rather
+ * than one per arena), but mainly to avoid false cacheline sharing.
+ *
+ * Each slab has the following layout:
+ *
+ * /--------------------\
+ * | region 0 |
+ * |--------------------|
+ * | region 1 |
+ * |--------------------|
+ * | ... |
+ * | ... |
+ * | ... |
+ * |--------------------|
+ * | region nregs-1 |
+ * \--------------------/
+ */
+typedef struct bin_info_s bin_info_t;
+struct bin_info_s {
+ /* Size of regions in a slab for this bin's size class. */
+ size_t reg_size;
+
+ /* Total size of a slab for this bin's size class. */
+ size_t slab_size;
+
+ /* Total number of regions in a slab for this bin's size class. */
+ uint32_t nregs;
+
+ /*
+ * Metadata used to manipulate bitmaps for slabs associated with this
+ * bin.
+ */
+ bitmap_info_t bitmap_info;
+};
+
+extern const bin_info_t bin_infos[NBINS];
+
+
+typedef struct bin_s bin_t;
+struct bin_s {
+ /* All operations on bin_t fields require lock ownership. */
+ malloc_mutex_t lock;
+
+ /*
+ * Current slab being used to service allocations of this bin's size
+ * class. slabcur is independent of slabs_{nonfull,full}; whenever
+ * slabcur is reassigned, the previous slab must be deallocated or
+ * inserted into slabs_{nonfull,full}.
+ */
+ extent_t *slabcur;
+
+ /*
+ * Heap of non-full slabs. This heap is used to assure that new
+ * allocations come from the non-full slab that is oldest/lowest in
+ * memory.
+ */
+ extent_heap_t slabs_nonfull;
+
+ /* List used to track full slabs. */
+ extent_list_t slabs_full;
+
+ /* Bin statistics. */
+ bin_stats_t stats;
+};
+
+/* Initializes a bin to empty. Returns true on error. */
+bool bin_init(bin_t *bin);
+
+/* Forking. */
+void bin_prefork(tsdn_t *tsdn, bin_t *bin);
+void bin_postfork_parent(tsdn_t *tsdn, bin_t *bin);
+void bin_postfork_child(tsdn_t *tsdn, bin_t *bin);
+
+/* Stats. */
+static inline void
+bin_stats_merge(tsdn_t *tsdn, bin_stats_t *dst_bin_stats, bin_t *bin) {
+ malloc_mutex_lock(tsdn, &bin->lock);
+ malloc_mutex_prof_read(tsdn, &dst_bin_stats->mutex_data, &bin->lock);
+ dst_bin_stats->nmalloc += bin->stats.nmalloc;
+ dst_bin_stats->ndalloc += bin->stats.ndalloc;
+ dst_bin_stats->nrequests += bin->stats.nrequests;
+ dst_bin_stats->curregs += bin->stats.curregs;
+ dst_bin_stats->nfills += bin->stats.nfills;
+ dst_bin_stats->nflushes += bin->stats.nflushes;
+ dst_bin_stats->nslabs += bin->stats.nslabs;
+ dst_bin_stats->reslabs += bin->stats.reslabs;
+ dst_bin_stats->curslabs += bin->stats.curslabs;
+ malloc_mutex_unlock(tsdn, &bin->lock);
+}
+
+#endif /* JEMALLOC_INTERNAL_BIN_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/bin_stats.h b/deps/jemalloc/include/jemalloc/internal/bin_stats.h
new file mode 100644
index 000000000..86e673ec4
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/bin_stats.h
@@ -0,0 +1,51 @@
+#ifndef JEMALLOC_INTERNAL_BIN_STATS_H
+#define JEMALLOC_INTERNAL_BIN_STATS_H
+
+#include "jemalloc/internal/mutex_prof.h"
+
+typedef struct bin_stats_s bin_stats_t;
+struct bin_stats_s {
+ /*
+ * Total number of allocation/deallocation requests served directly by
+ * the bin. Note that tcache may allocate an object, then recycle it
+ * many times, resulting many increments to nrequests, but only one
+ * each to nmalloc and ndalloc.
+ */
+ uint64_t nmalloc;
+ uint64_t ndalloc;
+
+ /*
+ * Number of allocation requests that correspond to the size of this
+ * bin. This includes requests served by tcache, though tcache only
+ * periodically merges into this counter.
+ */
+ uint64_t nrequests;
+
+ /*
+ * Current number of regions of this size class, including regions
+ * currently cached by tcache.
+ */
+ size_t curregs;
+
+ /* Number of tcache fills from this bin. */
+ uint64_t nfills;
+
+ /* Number of tcache flushes to this bin. */
+ uint64_t nflushes;
+
+ /* Total number of slabs created for this bin's size class. */
+ uint64_t nslabs;
+
+ /*
+ * Total number of slabs reused by extracting them from the slabs heap
+ * for this bin's size class.
+ */
+ uint64_t reslabs;
+
+ /* Current number of slabs in this bin. */
+ size_t curslabs;
+
+ mutex_prof_data_t mutex_data;
+};
+
+#endif /* JEMALLOC_INTERNAL_BIN_STATS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/bit_util.h b/deps/jemalloc/include/jemalloc/internal/bit_util.h
new file mode 100644
index 000000000..8d078a8a3
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/bit_util.h
@@ -0,0 +1,165 @@
+#ifndef JEMALLOC_INTERNAL_BIT_UTIL_H
+#define JEMALLOC_INTERNAL_BIT_UTIL_H
+
+#include "jemalloc/internal/assert.h"
+
+#define BIT_UTIL_INLINE static inline
+
+/* Sanity check. */
+#if !defined(JEMALLOC_INTERNAL_FFSLL) || !defined(JEMALLOC_INTERNAL_FFSL) \
+ || !defined(JEMALLOC_INTERNAL_FFS)
+# error JEMALLOC_INTERNAL_FFS{,L,LL} should have been defined by configure
+#endif
+
+
+BIT_UTIL_INLINE unsigned
+ffs_llu(unsigned long long bitmap) {
+ return JEMALLOC_INTERNAL_FFSLL(bitmap);
+}
+
+BIT_UTIL_INLINE unsigned
+ffs_lu(unsigned long bitmap) {
+ return JEMALLOC_INTERNAL_FFSL(bitmap);
+}
+
+BIT_UTIL_INLINE unsigned
+ffs_u(unsigned bitmap) {
+ return JEMALLOC_INTERNAL_FFS(bitmap);
+}
+
+BIT_UTIL_INLINE unsigned
+ffs_zu(size_t bitmap) {
+#if LG_SIZEOF_PTR == LG_SIZEOF_INT
+ return ffs_u(bitmap);
+#elif LG_SIZEOF_PTR == LG_SIZEOF_LONG
+ return ffs_lu(bitmap);
+#elif LG_SIZEOF_PTR == LG_SIZEOF_LONG_LONG
+ return ffs_llu(bitmap);
+#else
+#error No implementation for size_t ffs()
+#endif
+}
+
+BIT_UTIL_INLINE unsigned
+ffs_u64(uint64_t bitmap) {
+#if LG_SIZEOF_LONG == 3
+ return ffs_lu(bitmap);
+#elif LG_SIZEOF_LONG_LONG == 3
+ return ffs_llu(bitmap);
+#else
+#error No implementation for 64-bit ffs()
+#endif
+}
+
+BIT_UTIL_INLINE unsigned
+ffs_u32(uint32_t bitmap) {
+#if LG_SIZEOF_INT == 2
+ return ffs_u(bitmap);
+#else
+#error No implementation for 32-bit ffs()
+#endif
+ return ffs_u(bitmap);
+}
+
+BIT_UTIL_INLINE uint64_t
+pow2_ceil_u64(uint64_t x) {
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x |= x >> 32;
+ x++;
+ return x;
+}
+
+BIT_UTIL_INLINE uint32_t
+pow2_ceil_u32(uint32_t x) {
+ x--;
+ x |= x >> 1;
+ x |= x >> 2;
+ x |= x >> 4;
+ x |= x >> 8;
+ x |= x >> 16;
+ x++;
+ return x;
+}
+
+/* Compute the smallest power of 2 that is >= x. */
+BIT_UTIL_INLINE size_t
+pow2_ceil_zu(size_t x) {
+#if (LG_SIZEOF_PTR == 3)
+ return pow2_ceil_u64(x);
+#else
+ return pow2_ceil_u32(x);
+#endif
+}
+
+#if (defined(__i386__) || defined(__amd64__) || defined(__x86_64__))
+BIT_UTIL_INLINE unsigned
+lg_floor(size_t x) {
+ size_t ret;
+ assert(x != 0);
+
+ asm ("bsr %1, %0"
+ : "=r"(ret) // Outputs.
+ : "r"(x) // Inputs.
+ );
+ assert(ret < UINT_MAX);
+ return (unsigned)ret;
+}
+#elif (defined(_MSC_VER))
+BIT_UTIL_INLINE unsigned
+lg_floor(size_t x) {
+ unsigned long ret;
+
+ assert(x != 0);
+
+#if (LG_SIZEOF_PTR == 3)
+ _BitScanReverse64(&ret, x);
+#elif (LG_SIZEOF_PTR == 2)
+ _BitScanReverse(&ret, x);
+#else
+# error "Unsupported type size for lg_floor()"
+#endif
+ assert(ret < UINT_MAX);
+ return (unsigned)ret;
+}
+#elif (defined(JEMALLOC_HAVE_BUILTIN_CLZ))
+BIT_UTIL_INLINE unsigned
+lg_floor(size_t x) {
+ assert(x != 0);
+
+#if (LG_SIZEOF_PTR == LG_SIZEOF_INT)
+ return ((8 << LG_SIZEOF_PTR) - 1) - __builtin_clz(x);
+#elif (LG_SIZEOF_PTR == LG_SIZEOF_LONG)
+ return ((8 << LG_SIZEOF_PTR) - 1) - __builtin_clzl(x);
+#else
+# error "Unsupported type size for lg_floor()"
+#endif
+}
+#else
+BIT_UTIL_INLINE unsigned
+lg_floor(size_t x) {
+ assert(x != 0);
+
+ x |= (x >> 1);
+ x |= (x >> 2);
+ x |= (x >> 4);
+ x |= (x >> 8);
+ x |= (x >> 16);
+#if (LG_SIZEOF_PTR == 3)
+ x |= (x >> 32);
+#endif
+ if (x == SIZE_T_MAX) {
+ return (8 << LG_SIZEOF_PTR) - 1;
+ }
+ x++;
+ return ffs_zu(x) - 2;
+}
+#endif
+
+#undef BIT_UTIL_INLINE
+
+#endif /* JEMALLOC_INTERNAL_BIT_UTIL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/bitmap.h b/deps/jemalloc/include/jemalloc/internal/bitmap.h
index fcc6005c7..ac990290a 100644
--- a/deps/jemalloc/include/jemalloc/internal/bitmap.h
+++ b/deps/jemalloc/include/jemalloc/internal/bitmap.h
@@ -1,83 +1,159 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_BITMAP_H
+#define JEMALLOC_INTERNAL_BITMAP_H
-/* Maximum bitmap bit count is 2^LG_BITMAP_MAXBITS. */
-#define LG_BITMAP_MAXBITS LG_RUN_MAXREGS
-#define BITMAP_MAXBITS (ZU(1) << LG_BITMAP_MAXBITS)
+#include "jemalloc/internal/arena_types.h"
+#include "jemalloc/internal/bit_util.h"
+#include "jemalloc/internal/size_classes.h"
-typedef struct bitmap_level_s bitmap_level_t;
-typedef struct bitmap_info_s bitmap_info_t;
typedef unsigned long bitmap_t;
-#define LG_SIZEOF_BITMAP LG_SIZEOF_LONG
+#define LG_SIZEOF_BITMAP LG_SIZEOF_LONG
+
+/* Maximum bitmap bit count is 2^LG_BITMAP_MAXBITS. */
+#if LG_SLAB_MAXREGS > LG_CEIL_NSIZES
+/* Maximum bitmap bit count is determined by maximum regions per slab. */
+# define LG_BITMAP_MAXBITS LG_SLAB_MAXREGS
+#else
+/* Maximum bitmap bit count is determined by number of extent size classes. */
+# define LG_BITMAP_MAXBITS LG_CEIL_NSIZES
+#endif
+#define BITMAP_MAXBITS (ZU(1) << LG_BITMAP_MAXBITS)
/* Number of bits per group. */
-#define LG_BITMAP_GROUP_NBITS (LG_SIZEOF_BITMAP + 3)
-#define BITMAP_GROUP_NBITS (ZU(1) << LG_BITMAP_GROUP_NBITS)
-#define BITMAP_GROUP_NBITS_MASK (BITMAP_GROUP_NBITS-1)
+#define LG_BITMAP_GROUP_NBITS (LG_SIZEOF_BITMAP + 3)
+#define BITMAP_GROUP_NBITS (1U << LG_BITMAP_GROUP_NBITS)
+#define BITMAP_GROUP_NBITS_MASK (BITMAP_GROUP_NBITS-1)
+
+/*
+ * Do some analysis on how big the bitmap is before we use a tree. For a brute
+ * force linear search, if we would have to call ffs_lu() more than 2^3 times,
+ * use a tree instead.
+ */
+#if LG_BITMAP_MAXBITS - LG_BITMAP_GROUP_NBITS > 3
+# define BITMAP_USE_TREE
+#endif
/* Number of groups required to store a given number of bits. */
-#define BITMAP_BITS2GROUPS(nbits) \
- ((nbits + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS)
+#define BITMAP_BITS2GROUPS(nbits) \
+ (((nbits) + BITMAP_GROUP_NBITS_MASK) >> LG_BITMAP_GROUP_NBITS)
/*
* Number of groups required at a particular level for a given number of bits.
*/
-#define BITMAP_GROUPS_L0(nbits) \
+#define BITMAP_GROUPS_L0(nbits) \
BITMAP_BITS2GROUPS(nbits)
-#define BITMAP_GROUPS_L1(nbits) \
+#define BITMAP_GROUPS_L1(nbits) \
BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(nbits))
-#define BITMAP_GROUPS_L2(nbits) \
+#define BITMAP_GROUPS_L2(nbits) \
BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS((nbits))))
-#define BITMAP_GROUPS_L3(nbits) \
+#define BITMAP_GROUPS_L3(nbits) \
BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS( \
BITMAP_BITS2GROUPS((nbits)))))
+#define BITMAP_GROUPS_L4(nbits) \
+ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS( \
+ BITMAP_BITS2GROUPS(BITMAP_BITS2GROUPS((nbits))))))
/*
* Assuming the number of levels, number of groups required for a given number
* of bits.
*/
-#define BITMAP_GROUPS_1_LEVEL(nbits) \
+#define BITMAP_GROUPS_1_LEVEL(nbits) \
BITMAP_GROUPS_L0(nbits)
-#define BITMAP_GROUPS_2_LEVEL(nbits) \
+#define BITMAP_GROUPS_2_LEVEL(nbits) \
(BITMAP_GROUPS_1_LEVEL(nbits) + BITMAP_GROUPS_L1(nbits))
-#define BITMAP_GROUPS_3_LEVEL(nbits) \
+#define BITMAP_GROUPS_3_LEVEL(nbits) \
(BITMAP_GROUPS_2_LEVEL(nbits) + BITMAP_GROUPS_L2(nbits))
-#define BITMAP_GROUPS_4_LEVEL(nbits) \
+#define BITMAP_GROUPS_4_LEVEL(nbits) \
(BITMAP_GROUPS_3_LEVEL(nbits) + BITMAP_GROUPS_L3(nbits))
+#define BITMAP_GROUPS_5_LEVEL(nbits) \
+ (BITMAP_GROUPS_4_LEVEL(nbits) + BITMAP_GROUPS_L4(nbits))
/*
* Maximum number of groups required to support LG_BITMAP_MAXBITS.
*/
+#ifdef BITMAP_USE_TREE
+
#if LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS
+# define BITMAP_GROUPS(nbits) BITMAP_GROUPS_1_LEVEL(nbits)
# define BITMAP_GROUPS_MAX BITMAP_GROUPS_1_LEVEL(BITMAP_MAXBITS)
#elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 2
+# define BITMAP_GROUPS(nbits) BITMAP_GROUPS_2_LEVEL(nbits)
# define BITMAP_GROUPS_MAX BITMAP_GROUPS_2_LEVEL(BITMAP_MAXBITS)
#elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 3
+# define BITMAP_GROUPS(nbits) BITMAP_GROUPS_3_LEVEL(nbits)
# define BITMAP_GROUPS_MAX BITMAP_GROUPS_3_LEVEL(BITMAP_MAXBITS)
#elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 4
+# define BITMAP_GROUPS(nbits) BITMAP_GROUPS_4_LEVEL(nbits)
# define BITMAP_GROUPS_MAX BITMAP_GROUPS_4_LEVEL(BITMAP_MAXBITS)
+#elif LG_BITMAP_MAXBITS <= LG_BITMAP_GROUP_NBITS * 5
+# define BITMAP_GROUPS(nbits) BITMAP_GROUPS_5_LEVEL(nbits)
+# define BITMAP_GROUPS_MAX BITMAP_GROUPS_5_LEVEL(BITMAP_MAXBITS)
#else
# error "Unsupported bitmap size"
#endif
-/* Maximum number of levels possible. */
-#define BITMAP_MAX_LEVELS \
- (LG_BITMAP_MAXBITS / LG_SIZEOF_BITMAP) \
- + !!(LG_BITMAP_MAXBITS % LG_SIZEOF_BITMAP)
+/*
+ * Maximum number of levels possible. This could be statically computed based
+ * on LG_BITMAP_MAXBITS:
+ *
+ * #define BITMAP_MAX_LEVELS \
+ * (LG_BITMAP_MAXBITS / LG_SIZEOF_BITMAP) \
+ * + !!(LG_BITMAP_MAXBITS % LG_SIZEOF_BITMAP)
+ *
+ * However, that would not allow the generic BITMAP_INFO_INITIALIZER() macro, so
+ * instead hardcode BITMAP_MAX_LEVELS to the largest number supported by the
+ * various cascading macros. The only additional cost this incurs is some
+ * unused trailing entries in bitmap_info_t structures; the bitmaps themselves
+ * are not impacted.
+ */
+#define BITMAP_MAX_LEVELS 5
+
+#define BITMAP_INFO_INITIALIZER(nbits) { \
+ /* nbits. */ \
+ nbits, \
+ /* nlevels. */ \
+ (BITMAP_GROUPS_L0(nbits) > BITMAP_GROUPS_L1(nbits)) + \
+ (BITMAP_GROUPS_L1(nbits) > BITMAP_GROUPS_L2(nbits)) + \
+ (BITMAP_GROUPS_L2(nbits) > BITMAP_GROUPS_L3(nbits)) + \
+ (BITMAP_GROUPS_L3(nbits) > BITMAP_GROUPS_L4(nbits)) + 1, \
+ /* levels. */ \
+ { \
+ {0}, \
+ {BITMAP_GROUPS_L0(nbits)}, \
+ {BITMAP_GROUPS_L1(nbits) + BITMAP_GROUPS_L0(nbits)}, \
+ {BITMAP_GROUPS_L2(nbits) + BITMAP_GROUPS_L1(nbits) + \
+ BITMAP_GROUPS_L0(nbits)}, \
+ {BITMAP_GROUPS_L3(nbits) + BITMAP_GROUPS_L2(nbits) + \
+ BITMAP_GROUPS_L1(nbits) + BITMAP_GROUPS_L0(nbits)}, \
+ {BITMAP_GROUPS_L4(nbits) + BITMAP_GROUPS_L3(nbits) + \
+ BITMAP_GROUPS_L2(nbits) + BITMAP_GROUPS_L1(nbits) \
+ + BITMAP_GROUPS_L0(nbits)} \
+ } \
+}
+
+#else /* BITMAP_USE_TREE */
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
+#define BITMAP_GROUPS(nbits) BITMAP_BITS2GROUPS(nbits)
+#define BITMAP_GROUPS_MAX BITMAP_BITS2GROUPS(BITMAP_MAXBITS)
-struct bitmap_level_s {
+#define BITMAP_INFO_INITIALIZER(nbits) { \
+ /* nbits. */ \
+ nbits, \
+ /* ngroups. */ \
+ BITMAP_BITS2GROUPS(nbits) \
+}
+
+#endif /* BITMAP_USE_TREE */
+
+typedef struct bitmap_level_s {
/* Offset of this level's groups within the array of groups. */
size_t group_offset;
-};
+} bitmap_level_t;
-struct bitmap_info_s {
+typedef struct bitmap_info_s {
/* Logical number of bits in bitmap (stored at bottom level). */
size_t nbits;
+#ifdef BITMAP_USE_TREE
/* Number of levels necessary for nbits. */
unsigned nlevels;
@@ -86,54 +162,48 @@ struct bitmap_info_s {
* bottom to top (e.g. the bottom level is stored in levels[0]).
*/
bitmap_level_t levels[BITMAP_MAX_LEVELS+1];
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void bitmap_info_init(bitmap_info_t *binfo, size_t nbits);
-size_t bitmap_info_ngroups(const bitmap_info_t *binfo);
-size_t bitmap_size(size_t nbits);
-void bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-bool bitmap_full(bitmap_t *bitmap, const bitmap_info_t *binfo);
-bool bitmap_get(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit);
-void bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit);
-size_t bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo);
-void bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit);
-#endif
+#else /* BITMAP_USE_TREE */
+ /* Number of groups necessary for nbits. */
+ size_t ngroups;
+#endif /* BITMAP_USE_TREE */
+} bitmap_info_t;
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_BITMAP_C_))
-JEMALLOC_INLINE bool
-bitmap_full(bitmap_t *bitmap, const bitmap_info_t *binfo)
-{
- unsigned rgoff = binfo->levels[binfo->nlevels].group_offset - 1;
+void bitmap_info_init(bitmap_info_t *binfo, size_t nbits);
+void bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill);
+size_t bitmap_size(const bitmap_info_t *binfo);
+
+static inline bool
+bitmap_full(bitmap_t *bitmap, const bitmap_info_t *binfo) {
+#ifdef BITMAP_USE_TREE
+ size_t rgoff = binfo->levels[binfo->nlevels].group_offset - 1;
bitmap_t rg = bitmap[rgoff];
/* The bitmap is full iff the root group is 0. */
return (rg == 0);
+#else
+ size_t i;
+
+ for (i = 0; i < binfo->ngroups; i++) {
+ if (bitmap[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+#endif
}
-JEMALLOC_INLINE bool
-bitmap_get(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
-{
+static inline bool
+bitmap_get(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) {
size_t goff;
bitmap_t g;
assert(bit < binfo->nbits);
goff = bit >> LG_BITMAP_GROUP_NBITS;
g = bitmap[goff];
- return (!(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK))));
+ return !(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK)));
}
-JEMALLOC_INLINE void
-bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
-{
+static inline void
+bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) {
size_t goff;
bitmap_t *gp;
bitmap_t g;
@@ -143,10 +213,11 @@ bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
goff = bit >> LG_BITMAP_GROUP_NBITS;
gp = &bitmap[goff];
g = *gp;
- assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
- g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
+ assert(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK)));
+ g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK);
*gp = g;
assert(bitmap_get(bitmap, binfo, bit));
+#ifdef BITMAP_USE_TREE
/* Propagate group state transitions up the tree. */
if (g == 0) {
unsigned i;
@@ -155,45 +226,113 @@ bitmap_set(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
goff = bit >> LG_BITMAP_GROUP_NBITS;
gp = &bitmap[binfo->levels[i].group_offset + goff];
g = *gp;
- assert(g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)));
- g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
+ assert(g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK)));
+ g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK);
*gp = g;
- if (g != 0)
+ if (g != 0) {
break;
+ }
}
}
+#endif
+}
+
+/* ffu: find first unset >= bit. */
+static inline size_t
+bitmap_ffu(const bitmap_t *bitmap, const bitmap_info_t *binfo, size_t min_bit) {
+ assert(min_bit < binfo->nbits);
+
+#ifdef BITMAP_USE_TREE
+ size_t bit = 0;
+ for (unsigned level = binfo->nlevels; level--;) {
+ size_t lg_bits_per_group = (LG_BITMAP_GROUP_NBITS * (level +
+ 1));
+ bitmap_t group = bitmap[binfo->levels[level].group_offset + (bit
+ >> lg_bits_per_group)];
+ unsigned group_nmask = (unsigned)(((min_bit > bit) ? (min_bit -
+ bit) : 0) >> (lg_bits_per_group - LG_BITMAP_GROUP_NBITS));
+ assert(group_nmask <= BITMAP_GROUP_NBITS);
+ bitmap_t group_mask = ~((1LU << group_nmask) - 1);
+ bitmap_t group_masked = group & group_mask;
+ if (group_masked == 0LU) {
+ if (group == 0LU) {
+ return binfo->nbits;
+ }
+ /*
+ * min_bit was preceded by one or more unset bits in
+ * this group, but there are no other unset bits in this
+ * group. Try again starting at the first bit of the
+ * next sibling. This will recurse at most once per
+ * non-root level.
+ */
+ size_t sib_base = bit + (ZU(1) << lg_bits_per_group);
+ assert(sib_base > min_bit);
+ assert(sib_base > bit);
+ if (sib_base >= binfo->nbits) {
+ return binfo->nbits;
+ }
+ return bitmap_ffu(bitmap, binfo, sib_base);
+ }
+ bit += ((size_t)(ffs_lu(group_masked) - 1)) <<
+ (lg_bits_per_group - LG_BITMAP_GROUP_NBITS);
+ }
+ assert(bit >= min_bit);
+ assert(bit < binfo->nbits);
+ return bit;
+#else
+ size_t i = min_bit >> LG_BITMAP_GROUP_NBITS;
+ bitmap_t g = bitmap[i] & ~((1LU << (min_bit & BITMAP_GROUP_NBITS_MASK))
+ - 1);
+ size_t bit;
+ do {
+ bit = ffs_lu(g);
+ if (bit != 0) {
+ return (i << LG_BITMAP_GROUP_NBITS) + (bit - 1);
+ }
+ i++;
+ g = bitmap[i];
+ } while (i < binfo->ngroups);
+ return binfo->nbits;
+#endif
}
/* sfu: set first unset. */
-JEMALLOC_INLINE size_t
-bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo)
-{
+static inline size_t
+bitmap_sfu(bitmap_t *bitmap, const bitmap_info_t *binfo) {
size_t bit;
bitmap_t g;
unsigned i;
assert(!bitmap_full(bitmap, binfo));
+#ifdef BITMAP_USE_TREE
i = binfo->nlevels - 1;
g = bitmap[binfo->levels[i].group_offset];
- bit = jemalloc_ffsl(g) - 1;
+ bit = ffs_lu(g) - 1;
while (i > 0) {
i--;
g = bitmap[binfo->levels[i].group_offset + bit];
- bit = (bit << LG_BITMAP_GROUP_NBITS) + (jemalloc_ffsl(g) - 1);
+ bit = (bit << LG_BITMAP_GROUP_NBITS) + (ffs_lu(g) - 1);
}
-
+#else
+ i = 0;
+ g = bitmap[0];
+ while ((bit = ffs_lu(g)) == 0) {
+ i++;
+ g = bitmap[i];
+ }
+ bit = (i << LG_BITMAP_GROUP_NBITS) + (bit - 1);
+#endif
bitmap_set(bitmap, binfo, bit);
- return (bit);
+ return bit;
}
-JEMALLOC_INLINE void
-bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
-{
+static inline void
+bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit) {
size_t goff;
bitmap_t *gp;
bitmap_t g;
- bool propagate;
+ UNUSED bool propagate;
assert(bit < binfo->nbits);
assert(bitmap_get(bitmap, binfo, bit));
@@ -201,10 +340,11 @@ bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
gp = &bitmap[goff];
g = *gp;
propagate = (g == 0);
- assert((g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK))) == 0);
- g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
+ assert((g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK))) == 0);
+ g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK);
*gp = g;
assert(!bitmap_get(bitmap, binfo, bit));
+#ifdef BITMAP_USE_TREE
/* Propagate group state transitions up the tree. */
if (propagate) {
unsigned i;
@@ -214,17 +354,16 @@ bitmap_unset(bitmap_t *bitmap, const bitmap_info_t *binfo, size_t bit)
gp = &bitmap[binfo->levels[i].group_offset + goff];
g = *gp;
propagate = (g == 0);
- assert((g & (1LU << (bit & BITMAP_GROUP_NBITS_MASK)))
+ assert((g & (ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK)))
== 0);
- g ^= 1LU << (bit & BITMAP_GROUP_NBITS_MASK);
+ g ^= ZU(1) << (bit & BITMAP_GROUP_NBITS_MASK);
*gp = g;
- if (!propagate)
+ if (!propagate) {
break;
+ }
}
}
+#endif /* BITMAP_USE_TREE */
}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_BITMAP_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/cache_bin.h b/deps/jemalloc/include/jemalloc/internal/cache_bin.h
new file mode 100644
index 000000000..12f3ef2dd
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/cache_bin.h
@@ -0,0 +1,114 @@
+#ifndef JEMALLOC_INTERNAL_CACHE_BIN_H
+#define JEMALLOC_INTERNAL_CACHE_BIN_H
+
+#include "jemalloc/internal/ql.h"
+
+/*
+ * The cache_bins are the mechanism that the tcache and the arena use to
+ * communicate. The tcache fills from and flushes to the arena by passing a
+ * cache_bin_t to fill/flush. When the arena needs to pull stats from the
+ * tcaches associated with it, it does so by iterating over its
+ * cache_bin_array_descriptor_t objects and reading out per-bin stats it
+ * contains. This makes it so that the arena need not know about the existence
+ * of the tcache at all.
+ */
+
+
+/*
+ * The count of the number of cached allocations in a bin. We make this signed
+ * so that negative numbers can encode "invalid" states (e.g. a low water mark
+ * of -1 for a cache that has been depleted).
+ */
+typedef int32_t cache_bin_sz_t;
+
+typedef struct cache_bin_stats_s cache_bin_stats_t;
+struct cache_bin_stats_s {
+ /*
+ * Number of allocation requests that corresponded to the size of this
+ * bin.
+ */
+ uint64_t nrequests;
+};
+
+/*
+ * Read-only information associated with each element of tcache_t's tbins array
+ * is stored separately, mainly to reduce memory usage.
+ */
+typedef struct cache_bin_info_s cache_bin_info_t;
+struct cache_bin_info_s {
+ /* Upper limit on ncached. */
+ cache_bin_sz_t ncached_max;
+};
+
+typedef struct cache_bin_s cache_bin_t;
+struct cache_bin_s {
+ /* Min # cached since last GC. */
+ cache_bin_sz_t low_water;
+ /* # of cached objects. */
+ cache_bin_sz_t ncached;
+ /*
+ * ncached and stats are both modified frequently. Let's keep them
+ * close so that they have a higher chance of being on the same
+ * cacheline, thus less write-backs.
+ */
+ cache_bin_stats_t tstats;
+ /*
+ * Stack of available objects.
+ *
+ * To make use of adjacent cacheline prefetch, the items in the avail
+ * stack goes to higher address for newer allocations. avail points
+ * just above the available space, which means that
+ * avail[-ncached, ... -1] are available items and the lowest item will
+ * be allocated first.
+ */
+ void **avail;
+};
+
+typedef struct cache_bin_array_descriptor_s cache_bin_array_descriptor_t;
+struct cache_bin_array_descriptor_s {
+ /*
+ * The arena keeps a list of the cache bins associated with it, for
+ * stats collection.
+ */
+ ql_elm(cache_bin_array_descriptor_t) link;
+ /* Pointers to the tcache bins. */
+ cache_bin_t *bins_small;
+ cache_bin_t *bins_large;
+};
+
+static inline void
+cache_bin_array_descriptor_init(cache_bin_array_descriptor_t *descriptor,
+ cache_bin_t *bins_small, cache_bin_t *bins_large) {
+ ql_elm_new(descriptor, link);
+ descriptor->bins_small = bins_small;
+ descriptor->bins_large = bins_large;
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+cache_bin_alloc_easy(cache_bin_t *bin, bool *success) {
+ void *ret;
+
+ if (unlikely(bin->ncached == 0)) {
+ bin->low_water = -1;
+ *success = false;
+ return NULL;
+ }
+ /*
+ * success (instead of ret) should be checked upon the return of this
+ * function. We avoid checking (ret == NULL) because there is never a
+ * null stored on the avail stack (which is unknown to the compiler),
+ * and eagerly checking ret would cause pipeline stall (waiting for the
+ * cacheline).
+ */
+ *success = true;
+ ret = *(bin->avail - bin->ncached);
+ bin->ncached--;
+
+ if (unlikely(bin->ncached < bin->low_water)) {
+ bin->low_water = bin->ncached;
+ }
+
+ return ret;
+}
+
+#endif /* JEMALLOC_INTERNAL_CACHE_BIN_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/chunk.h b/deps/jemalloc/include/jemalloc/internal/chunk.h
deleted file mode 100644
index 5d1938353..000000000
--- a/deps/jemalloc/include/jemalloc/internal/chunk.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-/*
- * Size and alignment of memory chunks that are allocated by the OS's virtual
- * memory system.
- */
-#define LG_CHUNK_DEFAULT 21
-
-/* Return the chunk address for allocation address a. */
-#define CHUNK_ADDR2BASE(a) \
- ((void *)((uintptr_t)(a) & ~chunksize_mask))
-
-/* Return the chunk offset of address a. */
-#define CHUNK_ADDR2OFFSET(a) \
- ((size_t)((uintptr_t)(a) & chunksize_mask))
-
-/* Return the smallest chunk multiple that is >= s. */
-#define CHUNK_CEILING(s) \
- (((s) + chunksize_mask) & ~chunksize_mask)
-
-#define CHUNK_HOOKS_INITIALIZER { \
- NULL, \
- NULL, \
- NULL, \
- NULL, \
- NULL, \
- NULL, \
- NULL \
-}
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-extern size_t opt_lg_chunk;
-extern const char *opt_dss;
-
-extern rtree_t chunks_rtree;
-
-extern size_t chunksize;
-extern size_t chunksize_mask; /* (chunksize - 1). */
-extern size_t chunk_npages;
-
-extern const chunk_hooks_t chunk_hooks_default;
-
-chunk_hooks_t chunk_hooks_get(arena_t *arena);
-chunk_hooks_t chunk_hooks_set(arena_t *arena,
- const chunk_hooks_t *chunk_hooks);
-
-bool chunk_register(const void *chunk, const extent_node_t *node);
-void chunk_deregister(const void *chunk, const extent_node_t *node);
-void *chunk_alloc_base(size_t size);
-void *chunk_alloc_cache(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *new_addr, size_t size, size_t alignment, bool *zero,
- bool dalloc_node);
-void *chunk_alloc_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit);
-void chunk_dalloc_cache(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *chunk, size_t size, bool committed);
-void chunk_dalloc_arena(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *chunk, size_t size, bool zeroed, bool committed);
-void chunk_dalloc_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *chunk, size_t size, bool committed);
-bool chunk_purge_arena(arena_t *arena, void *chunk, size_t offset,
- size_t length);
-bool chunk_purge_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *chunk, size_t size, size_t offset, size_t length);
-bool chunk_boot(void);
-void chunk_prefork(void);
-void chunk_postfork_parent(void);
-void chunk_postfork_child(void);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-extent_node_t *chunk_lookup(const void *chunk, bool dependent);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_CHUNK_C_))
-JEMALLOC_INLINE extent_node_t *
-chunk_lookup(const void *ptr, bool dependent)
-{
-
- return (rtree_get(&chunks_rtree, (uintptr_t)ptr, dependent));
-}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
-
-#include "jemalloc/internal/chunk_dss.h"
-#include "jemalloc/internal/chunk_mmap.h"
diff --git a/deps/jemalloc/include/jemalloc/internal/chunk_dss.h b/deps/jemalloc/include/jemalloc/internal/chunk_dss.h
deleted file mode 100644
index 388f46be0..000000000
--- a/deps/jemalloc/include/jemalloc/internal/chunk_dss.h
+++ /dev/null
@@ -1,39 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef enum {
- dss_prec_disabled = 0,
- dss_prec_primary = 1,
- dss_prec_secondary = 2,
-
- dss_prec_limit = 3
-} dss_prec_t;
-#define DSS_PREC_DEFAULT dss_prec_secondary
-#define DSS_DEFAULT "secondary"
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-extern const char *dss_prec_names[];
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-dss_prec_t chunk_dss_prec_get(void);
-bool chunk_dss_prec_set(dss_prec_t dss_prec);
-void *chunk_alloc_dss(arena_t *arena, void *new_addr, size_t size,
- size_t alignment, bool *zero, bool *commit);
-bool chunk_in_dss(void *chunk);
-bool chunk_dss_boot(void);
-void chunk_dss_prefork(void);
-void chunk_dss_postfork_parent(void);
-void chunk_dss_postfork_child(void);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/chunk_mmap.h b/deps/jemalloc/include/jemalloc/internal/chunk_mmap.h
deleted file mode 100644
index 7d8014c58..000000000
--- a/deps/jemalloc/include/jemalloc/internal/chunk_mmap.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void *chunk_alloc_mmap(size_t size, size_t alignment, bool *zero,
- bool *commit);
-bool chunk_dalloc_mmap(void *chunk, size_t size);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/ckh.h b/deps/jemalloc/include/jemalloc/internal/ckh.h
index 75c1c979f..7b3850bc1 100644
--- a/deps/jemalloc/include/jemalloc/internal/ckh.h
+++ b/deps/jemalloc/include/jemalloc/internal/ckh.h
@@ -1,88 +1,101 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_CKH_H
+#define JEMALLOC_INTERNAL_CKH_H
-typedef struct ckh_s ckh_t;
-typedef struct ckhc_s ckhc_t;
+#include "jemalloc/internal/tsd.h"
-/* Typedefs to allow easy function pointer passing. */
-typedef void ckh_hash_t (const void *, size_t[2]);
-typedef bool ckh_keycomp_t (const void *, const void *);
+/* Cuckoo hashing implementation. Skip to the end for the interface. */
+
+/******************************************************************************/
+/* INTERNAL DEFINITIONS -- IGNORE */
+/******************************************************************************/
/* Maintain counters used to get an idea of performance. */
-/* #define CKH_COUNT */
+/* #define CKH_COUNT */
/* Print counter values in ckh_delete() (requires CKH_COUNT). */
-/* #define CKH_VERBOSE */
+/* #define CKH_VERBOSE */
/*
* There are 2^LG_CKH_BUCKET_CELLS cells in each hash table bucket. Try to fit
* one bucket per L1 cache line.
*/
-#define LG_CKH_BUCKET_CELLS (LG_CACHELINE - LG_SIZEOF_PTR - 1)
+#define LG_CKH_BUCKET_CELLS (LG_CACHELINE - LG_SIZEOF_PTR - 1)
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
+/* Typedefs to allow easy function pointer passing. */
+typedef void ckh_hash_t (const void *, size_t[2]);
+typedef bool ckh_keycomp_t (const void *, const void *);
/* Hash table cell. */
-struct ckhc_s {
- const void *key;
- const void *data;
-};
+typedef struct {
+ const void *key;
+ const void *data;
+} ckhc_t;
-struct ckh_s {
+/* The hash table itself. */
+typedef struct {
#ifdef CKH_COUNT
/* Counters used to get an idea of performance. */
- uint64_t ngrows;
- uint64_t nshrinks;
- uint64_t nshrinkfails;
- uint64_t ninserts;
- uint64_t nrelocs;
+ uint64_t ngrows;
+ uint64_t nshrinks;
+ uint64_t nshrinkfails;
+ uint64_t ninserts;
+ uint64_t nrelocs;
#endif
/* Used for pseudo-random number generation. */
-#define CKH_A 1103515241
-#define CKH_C 12347
- uint32_t prng_state;
+ uint64_t prng_state;
/* Total number of items. */
- size_t count;
+ size_t count;
/*
* Minimum and current number of hash table buckets. There are
* 2^LG_CKH_BUCKET_CELLS cells per bucket.
*/
- unsigned lg_minbuckets;
- unsigned lg_curbuckets;
+ unsigned lg_minbuckets;
+ unsigned lg_curbuckets;
/* Hash and comparison functions. */
- ckh_hash_t *hash;
- ckh_keycomp_t *keycomp;
+ ckh_hash_t *hash;
+ ckh_keycomp_t *keycomp;
/* Hash table with 2^lg_curbuckets buckets. */
- ckhc_t *tab;
-};
+ ckhc_t *tab;
+} ckh_t;
-#endif /* JEMALLOC_H_STRUCTS */
/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
+/* BEGIN PUBLIC API */
+/******************************************************************************/
-bool ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash,
+/* Lifetime management. Minitems is the initial capacity. */
+bool ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash,
ckh_keycomp_t *keycomp);
-void ckh_delete(tsd_t *tsd, ckh_t *ckh);
-size_t ckh_count(ckh_t *ckh);
-bool ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data);
-bool ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data);
-bool ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key,
+void ckh_delete(tsd_t *tsd, ckh_t *ckh);
+
+/* Get the number of elements in the set. */
+size_t ckh_count(ckh_t *ckh);
+
+/*
+ * To iterate over the elements in the table, initialize *tabind to 0 and call
+ * this function until it returns true. Each call that returns false will
+ * update *key and *data to the next element in the table, assuming the pointers
+ * are non-NULL.
+ */
+bool ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data);
+
+/*
+ * Basic hash table operations -- insert, removal, lookup. For ckh_remove and
+ * ckh_search, key or data can be NULL. The hash-table only stores pointers to
+ * the key and value, and doesn't do any lifetime management.
+ */
+bool ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data);
+bool ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key,
void **data);
-bool ckh_search(ckh_t *ckh, const void *seachkey, void **key, void **data);
-void ckh_string_hash(const void *key, size_t r_hash[2]);
-bool ckh_string_keycomp(const void *k1, const void *k2);
-void ckh_pointer_hash(const void *key, size_t r_hash[2]);
-bool ckh_pointer_keycomp(const void *k1, const void *k2);
+bool ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data);
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+/* Some useful hash and comparison functions for strings and pointers. */
+void ckh_string_hash(const void *key, size_t r_hash[2]);
+bool ckh_string_keycomp(const void *k1, const void *k2);
+void ckh_pointer_hash(const void *key, size_t r_hash[2]);
+bool ckh_pointer_keycomp(const void *k1, const void *k2);
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_CKH_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/ctl.h b/deps/jemalloc/include/jemalloc/internal/ctl.h
index 751c14b5b..d927d9480 100644
--- a/deps/jemalloc/include/jemalloc/internal/ctl.h
+++ b/deps/jemalloc/include/jemalloc/internal/ctl.h
@@ -1,81 +1,107 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct ctl_node_s ctl_node_t;
-typedef struct ctl_named_node_s ctl_named_node_t;
-typedef struct ctl_indexed_node_s ctl_indexed_node_t;
-typedef struct ctl_arena_stats_s ctl_arena_stats_t;
-typedef struct ctl_stats_s ctl_stats_t;
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-struct ctl_node_s {
- bool named;
-};
-
-struct ctl_named_node_s {
- struct ctl_node_s node;
- const char *name;
+#ifndef JEMALLOC_INTERNAL_CTL_H
+#define JEMALLOC_INTERNAL_CTL_H
+
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/mutex_prof.h"
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/stats.h"
+
+/* Maximum ctl tree depth. */
+#define CTL_MAX_DEPTH 7
+
+typedef struct ctl_node_s {
+ bool named;
+} ctl_node_t;
+
+typedef struct ctl_named_node_s {
+ ctl_node_t node;
+ const char *name;
/* If (nchildren == 0), this is a terminal node. */
- unsigned nchildren;
- const ctl_node_t *children;
- int (*ctl)(const size_t *, size_t, void *, size_t *,
- void *, size_t);
-};
+ size_t nchildren;
+ const ctl_node_t *children;
+ int (*ctl)(tsd_t *, const size_t *, size_t, void *, size_t *, void *,
+ size_t);
+} ctl_named_node_t;
-struct ctl_indexed_node_s {
- struct ctl_node_s node;
- const ctl_named_node_t *(*index)(const size_t *, size_t, size_t);
-};
+typedef struct ctl_indexed_node_s {
+ struct ctl_node_s node;
+ const ctl_named_node_t *(*index)(tsdn_t *, const size_t *, size_t,
+ size_t);
+} ctl_indexed_node_t;
-struct ctl_arena_stats_s {
- bool initialized;
- unsigned nthreads;
- const char *dss;
- ssize_t lg_dirty_mult;
- size_t pactive;
- size_t pdirty;
- arena_stats_t astats;
+typedef struct ctl_arena_stats_s {
+ arena_stats_t astats;
/* Aggregate stats for small size classes, based on bin stats. */
- size_t allocated_small;
- uint64_t nmalloc_small;
- uint64_t ndalloc_small;
- uint64_t nrequests_small;
-
- malloc_bin_stats_t bstats[NBINS];
- malloc_large_stats_t *lstats; /* nlclasses elements. */
- malloc_huge_stats_t *hstats; /* nhclasses elements. */
+ size_t allocated_small;
+ uint64_t nmalloc_small;
+ uint64_t ndalloc_small;
+ uint64_t nrequests_small;
+
+ bin_stats_t bstats[NBINS];
+ arena_stats_large_t lstats[NSIZES - NBINS];
+} ctl_arena_stats_t;
+
+typedef struct ctl_stats_s {
+ size_t allocated;
+ size_t active;
+ size_t metadata;
+ size_t metadata_thp;
+ size_t resident;
+ size_t mapped;
+ size_t retained;
+
+ background_thread_stats_t background_thread;
+ mutex_prof_data_t mutex_prof_data[mutex_prof_num_global_mutexes];
+} ctl_stats_t;
+
+typedef struct ctl_arena_s ctl_arena_t;
+struct ctl_arena_s {
+ unsigned arena_ind;
+ bool initialized;
+ ql_elm(ctl_arena_t) destroyed_link;
+
+ /* Basic stats, supported even if !config_stats. */
+ unsigned nthreads;
+ const char *dss;
+ ssize_t dirty_decay_ms;
+ ssize_t muzzy_decay_ms;
+ size_t pactive;
+ size_t pdirty;
+ size_t pmuzzy;
+
+ /* NULL if !config_stats. */
+ ctl_arena_stats_t *astats;
};
-struct ctl_stats_s {
- size_t allocated;
- size_t active;
- size_t metadata;
- size_t resident;
- size_t mapped;
- unsigned narenas;
- ctl_arena_stats_t *arenas; /* (narenas + 1) elements. */
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-int ctl_byname(const char *name, void *oldp, size_t *oldlenp, void *newp,
- size_t newlen);
-int ctl_nametomib(const char *name, size_t *mibp, size_t *miblenp);
-
-int ctl_bymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
+typedef struct ctl_arenas_s {
+ uint64_t epoch;
+ unsigned narenas;
+ ql_head(ctl_arena_t) destroyed;
+
+ /*
+ * Element 0 corresponds to merged stats for extant arenas (accessed via
+ * MALLCTL_ARENAS_ALL), element 1 corresponds to merged stats for
+ * destroyed arenas (accessed via MALLCTL_ARENAS_DESTROYED), and the
+ * remaining MALLOCX_ARENA_LIMIT elements correspond to arenas.
+ */
+ ctl_arena_t *arenas[2 + MALLOCX_ARENA_LIMIT];
+} ctl_arenas_t;
+
+int ctl_byname(tsd_t *tsd, const char *name, void *oldp, size_t *oldlenp,
void *newp, size_t newlen);
-bool ctl_boot(void);
-void ctl_prefork(void);
-void ctl_postfork_parent(void);
-void ctl_postfork_child(void);
+int ctl_nametomib(tsd_t *tsd, const char *name, size_t *mibp, size_t *miblenp);
-#define xmallctl(name, oldp, oldlenp, newp, newlen) do { \
+int ctl_bymib(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen);
+bool ctl_boot(void);
+void ctl_prefork(tsdn_t *tsdn);
+void ctl_postfork_parent(tsdn_t *tsdn);
+void ctl_postfork_child(tsdn_t *tsdn);
+
+#define xmallctl(name, oldp, oldlenp, newp, newlen) do { \
if (je_mallctl(name, oldp, oldlenp, newp, newlen) \
!= 0) { \
malloc_printf( \
@@ -85,7 +111,7 @@ void ctl_postfork_child(void);
} \
} while (0)
-#define xmallctlnametomib(name, mibp, miblenp) do { \
+#define xmallctlnametomib(name, mibp, miblenp) do { \
if (je_mallctlnametomib(name, mibp, miblenp) != 0) { \
malloc_printf("<jemalloc>: Failure in " \
"xmallctlnametomib(\"%s\", ...)\n", name); \
@@ -93,7 +119,7 @@ void ctl_postfork_child(void);
} \
} while (0)
-#define xmallctlbymib(mib, miblen, oldp, oldlenp, newp, newlen) do { \
+#define xmallctlbymib(mib, miblen, oldp, oldlenp, newp, newlen) do { \
if (je_mallctlbymib(mib, miblen, oldp, oldlenp, newp, \
newlen) != 0) { \
malloc_write( \
@@ -102,10 +128,4 @@ void ctl_postfork_child(void);
} \
} while (0)
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
-
+#endif /* JEMALLOC_INTERNAL_CTL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/div.h b/deps/jemalloc/include/jemalloc/internal/div.h
new file mode 100644
index 000000000..aebae9398
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/div.h
@@ -0,0 +1,41 @@
+#ifndef JEMALLOC_INTERNAL_DIV_H
+#define JEMALLOC_INTERNAL_DIV_H
+
+#include "jemalloc/internal/assert.h"
+
+/*
+ * This module does the division that computes the index of a region in a slab,
+ * given its offset relative to the base.
+ * That is, given a divisor d, an n = i * d (all integers), we'll return i.
+ * We do some pre-computation to do this more quickly than a CPU division
+ * instruction.
+ * We bound n < 2^32, and don't support dividing by one.
+ */
+
+typedef struct div_info_s div_info_t;
+struct div_info_s {
+ uint32_t magic;
+#ifdef JEMALLOC_DEBUG
+ size_t d;
+#endif
+};
+
+void div_init(div_info_t *div_info, size_t divisor);
+
+static inline size_t
+div_compute(div_info_t *div_info, size_t n) {
+ assert(n <= (uint32_t)-1);
+ /*
+ * This generates, e.g. mov; imul; shr on x86-64. On a 32-bit machine,
+ * the compilers I tried were all smart enough to turn this into the
+ * appropriate "get the high 32 bits of the result of a multiply" (e.g.
+ * mul; mov edx eax; on x86, umull on arm, etc.).
+ */
+ size_t i = ((uint64_t)n * (uint64_t)div_info->magic) >> 32;
+#ifdef JEMALLOC_DEBUG
+ assert(i * div_info->d == n);
+#endif
+ return i;
+}
+
+#endif /* JEMALLOC_INTERNAL_DIV_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/emitter.h b/deps/jemalloc/include/jemalloc/internal/emitter.h
new file mode 100644
index 000000000..3a2b2f7f2
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/emitter.h
@@ -0,0 +1,435 @@
+#ifndef JEMALLOC_INTERNAL_EMITTER_H
+#define JEMALLOC_INTERNAL_EMITTER_H
+
+#include "jemalloc/internal/ql.h"
+
+typedef enum emitter_output_e emitter_output_t;
+enum emitter_output_e {
+ emitter_output_json,
+ emitter_output_table
+};
+
+typedef enum emitter_justify_e emitter_justify_t;
+enum emitter_justify_e {
+ emitter_justify_left,
+ emitter_justify_right,
+ /* Not for users; just to pass to internal functions. */
+ emitter_justify_none
+};
+
+typedef enum emitter_type_e emitter_type_t;
+enum emitter_type_e {
+ emitter_type_bool,
+ emitter_type_int,
+ emitter_type_unsigned,
+ emitter_type_uint32,
+ emitter_type_uint64,
+ emitter_type_size,
+ emitter_type_ssize,
+ emitter_type_string,
+ /*
+ * A title is a column title in a table; it's just a string, but it's
+ * not quoted.
+ */
+ emitter_type_title,
+};
+
+typedef struct emitter_col_s emitter_col_t;
+struct emitter_col_s {
+ /* Filled in by the user. */
+ emitter_justify_t justify;
+ int width;
+ emitter_type_t type;
+ union {
+ bool bool_val;
+ int int_val;
+ unsigned unsigned_val;
+ uint32_t uint32_val;
+ uint64_t uint64_val;
+ size_t size_val;
+ ssize_t ssize_val;
+ const char *str_val;
+ };
+
+ /* Filled in by initialization. */
+ ql_elm(emitter_col_t) link;
+};
+
+typedef struct emitter_row_s emitter_row_t;
+struct emitter_row_s {
+ ql_head(emitter_col_t) cols;
+};
+
+static inline void
+emitter_row_init(emitter_row_t *row) {
+ ql_new(&row->cols);
+}
+
+static inline void
+emitter_col_init(emitter_col_t *col, emitter_row_t *row) {
+ ql_elm_new(col, link);
+ ql_tail_insert(&row->cols, col, link);
+}
+
+typedef struct emitter_s emitter_t;
+struct emitter_s {
+ emitter_output_t output;
+ /* The output information. */
+ void (*write_cb)(void *, const char *);
+ void *cbopaque;
+ int nesting_depth;
+ /* True if we've already emitted a value at the given depth. */
+ bool item_at_depth;
+};
+
+static inline void
+emitter_init(emitter_t *emitter, emitter_output_t emitter_output,
+ void (*write_cb)(void *, const char *), void *cbopaque) {
+ emitter->output = emitter_output;
+ emitter->write_cb = write_cb;
+ emitter->cbopaque = cbopaque;
+ emitter->item_at_depth = false;
+ emitter->nesting_depth = 0;
+}
+
+/* Internal convenience function. Write to the emitter the given string. */
+JEMALLOC_FORMAT_PRINTF(2, 3)
+static inline void
+emitter_printf(emitter_t *emitter, const char *format, ...) {
+ va_list ap;
+
+ va_start(ap, format);
+ malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap);
+ va_end(ap);
+}
+
+/* Write to the emitter the given string, but only in table mode. */
+JEMALLOC_FORMAT_PRINTF(2, 3)
+static inline void
+emitter_table_printf(emitter_t *emitter, const char *format, ...) {
+ if (emitter->output == emitter_output_table) {
+ va_list ap;
+ va_start(ap, format);
+ malloc_vcprintf(emitter->write_cb, emitter->cbopaque, format, ap);
+ va_end(ap);
+ }
+}
+
+static inline void
+emitter_gen_fmt(char *out_fmt, size_t out_size, const char *fmt_specifier,
+ emitter_justify_t justify, int width) {
+ size_t written;
+ if (justify == emitter_justify_none) {
+ written = malloc_snprintf(out_fmt, out_size,
+ "%%%s", fmt_specifier);
+ } else if (justify == emitter_justify_left) {
+ written = malloc_snprintf(out_fmt, out_size,
+ "%%-%d%s", width, fmt_specifier);
+ } else {
+ written = malloc_snprintf(out_fmt, out_size,
+ "%%%d%s", width, fmt_specifier);
+ }
+ /* Only happens in case of bad format string, which *we* choose. */
+ assert(written < out_size);
+}
+
+/*
+ * Internal. Emit the given value type in the relevant encoding (so that the
+ * bool true gets mapped to json "true", but the string "true" gets mapped to
+ * json "\"true\"", for instance.
+ *
+ * Width is ignored if justify is emitter_justify_none.
+ */
+static inline void
+emitter_print_value(emitter_t *emitter, emitter_justify_t justify, int width,
+ emitter_type_t value_type, const void *value) {
+ size_t str_written;
+#define BUF_SIZE 256
+#define FMT_SIZE 10
+ /*
+ * We dynamically generate a format string to emit, to let us use the
+ * snprintf machinery. This is kinda hacky, but gets the job done
+ * quickly without having to think about the various snprintf edge
+ * cases.
+ */
+ char fmt[FMT_SIZE];
+ char buf[BUF_SIZE];
+
+#define EMIT_SIMPLE(type, format) \
+ emitter_gen_fmt(fmt, FMT_SIZE, format, justify, width); \
+ emitter_printf(emitter, fmt, *(const type *)value); \
+
+ switch (value_type) {
+ case emitter_type_bool:
+ emitter_gen_fmt(fmt, FMT_SIZE, "s", justify, width);
+ emitter_printf(emitter, fmt, *(const bool *)value ?
+ "true" : "false");
+ break;
+ case emitter_type_int:
+ EMIT_SIMPLE(int, "d")
+ break;
+ case emitter_type_unsigned:
+ EMIT_SIMPLE(unsigned, "u")
+ break;
+ case emitter_type_ssize:
+ EMIT_SIMPLE(ssize_t, "zd")
+ break;
+ case emitter_type_size:
+ EMIT_SIMPLE(size_t, "zu")
+ break;
+ case emitter_type_string:
+ str_written = malloc_snprintf(buf, BUF_SIZE, "\"%s\"",
+ *(const char *const *)value);
+ /*
+ * We control the strings we output; we shouldn't get anything
+ * anywhere near the fmt size.
+ */
+ assert(str_written < BUF_SIZE);
+ emitter_gen_fmt(fmt, FMT_SIZE, "s", justify, width);
+ emitter_printf(emitter, fmt, buf);
+ break;
+ case emitter_type_uint32:
+ EMIT_SIMPLE(uint32_t, FMTu32)
+ break;
+ case emitter_type_uint64:
+ EMIT_SIMPLE(uint64_t, FMTu64)
+ break;
+ case emitter_type_title:
+ EMIT_SIMPLE(char *const, "s");
+ break;
+ default:
+ unreachable();
+ }
+#undef BUF_SIZE
+#undef FMT_SIZE
+}
+
+
+/* Internal functions. In json mode, tracks nesting state. */
+static inline void
+emitter_nest_inc(emitter_t *emitter) {
+ emitter->nesting_depth++;
+ emitter->item_at_depth = false;
+}
+
+static inline void
+emitter_nest_dec(emitter_t *emitter) {
+ emitter->nesting_depth--;
+ emitter->item_at_depth = true;
+}
+
+static inline void
+emitter_indent(emitter_t *emitter) {
+ int amount = emitter->nesting_depth;
+ const char *indent_str;
+ if (emitter->output == emitter_output_json) {
+ indent_str = "\t";
+ } else {
+ amount *= 2;
+ indent_str = " ";
+ }
+ for (int i = 0; i < amount; i++) {
+ emitter_printf(emitter, "%s", indent_str);
+ }
+}
+
+static inline void
+emitter_json_key_prefix(emitter_t *emitter) {
+ emitter_printf(emitter, "%s\n", emitter->item_at_depth ? "," : "");
+ emitter_indent(emitter);
+}
+
+static inline void
+emitter_begin(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth == 0);
+ emitter_printf(emitter, "{");
+ emitter_nest_inc(emitter);
+ } else {
+ // tabular init
+ emitter_printf(emitter, "%s", "");
+ }
+}
+
+static inline void
+emitter_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth == 1);
+ emitter_nest_dec(emitter);
+ emitter_printf(emitter, "\n}\n");
+ }
+}
+
+/*
+ * Note emits a different kv pair as well, but only in table mode. Omits the
+ * note if table_note_key is NULL.
+ */
+static inline void
+emitter_kv_note(emitter_t *emitter, const char *json_key, const char *table_key,
+ emitter_type_t value_type, const void *value,
+ const char *table_note_key, emitter_type_t table_note_value_type,
+ const void *table_note_value) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth > 0);
+ emitter_json_key_prefix(emitter);
+ emitter_printf(emitter, "\"%s\": ", json_key);
+ emitter_print_value(emitter, emitter_justify_none, -1,
+ value_type, value);
+ } else {
+ emitter_indent(emitter);
+ emitter_printf(emitter, "%s: ", table_key);
+ emitter_print_value(emitter, emitter_justify_none, -1,
+ value_type, value);
+ if (table_note_key != NULL) {
+ emitter_printf(emitter, " (%s: ", table_note_key);
+ emitter_print_value(emitter, emitter_justify_none, -1,
+ table_note_value_type, table_note_value);
+ emitter_printf(emitter, ")");
+ }
+ emitter_printf(emitter, "\n");
+ }
+ emitter->item_at_depth = true;
+}
+
+static inline void
+emitter_kv(emitter_t *emitter, const char *json_key, const char *table_key,
+ emitter_type_t value_type, const void *value) {
+ emitter_kv_note(emitter, json_key, table_key, value_type, value, NULL,
+ emitter_type_bool, NULL);
+}
+
+static inline void
+emitter_json_kv(emitter_t *emitter, const char *json_key,
+ emitter_type_t value_type, const void *value) {
+ if (emitter->output == emitter_output_json) {
+ emitter_kv(emitter, json_key, NULL, value_type, value);
+ }
+}
+
+static inline void
+emitter_table_kv(emitter_t *emitter, const char *table_key,
+ emitter_type_t value_type, const void *value) {
+ if (emitter->output == emitter_output_table) {
+ emitter_kv(emitter, NULL, table_key, value_type, value);
+ }
+}
+
+static inline void
+emitter_dict_begin(emitter_t *emitter, const char *json_key,
+ const char *table_header) {
+ if (emitter->output == emitter_output_json) {
+ emitter_json_key_prefix(emitter);
+ emitter_printf(emitter, "\"%s\": {", json_key);
+ emitter_nest_inc(emitter);
+ } else {
+ emitter_indent(emitter);
+ emitter_printf(emitter, "%s\n", table_header);
+ emitter_nest_inc(emitter);
+ }
+}
+
+static inline void
+emitter_dict_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth > 0);
+ emitter_nest_dec(emitter);
+ emitter_printf(emitter, "\n");
+ emitter_indent(emitter);
+ emitter_printf(emitter, "}");
+ } else {
+ emitter_nest_dec(emitter);
+ }
+}
+
+static inline void
+emitter_json_dict_begin(emitter_t *emitter, const char *json_key) {
+ if (emitter->output == emitter_output_json) {
+ emitter_dict_begin(emitter, json_key, NULL);
+ }
+}
+
+static inline void
+emitter_json_dict_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ emitter_dict_end(emitter);
+ }
+}
+
+static inline void
+emitter_table_dict_begin(emitter_t *emitter, const char *table_key) {
+ if (emitter->output == emitter_output_table) {
+ emitter_dict_begin(emitter, NULL, table_key);
+ }
+}
+
+static inline void
+emitter_table_dict_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_table) {
+ emitter_dict_end(emitter);
+ }
+}
+
+static inline void
+emitter_json_arr_begin(emitter_t *emitter, const char *json_key) {
+ if (emitter->output == emitter_output_json) {
+ emitter_json_key_prefix(emitter);
+ emitter_printf(emitter, "\"%s\": [", json_key);
+ emitter_nest_inc(emitter);
+ }
+}
+
+static inline void
+emitter_json_arr_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth > 0);
+ emitter_nest_dec(emitter);
+ emitter_printf(emitter, "\n");
+ emitter_indent(emitter);
+ emitter_printf(emitter, "]");
+ }
+}
+
+static inline void
+emitter_json_arr_obj_begin(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ emitter_json_key_prefix(emitter);
+ emitter_printf(emitter, "{");
+ emitter_nest_inc(emitter);
+ }
+}
+
+static inline void
+emitter_json_arr_obj_end(emitter_t *emitter) {
+ if (emitter->output == emitter_output_json) {
+ assert(emitter->nesting_depth > 0);
+ emitter_nest_dec(emitter);
+ emitter_printf(emitter, "\n");
+ emitter_indent(emitter);
+ emitter_printf(emitter, "}");
+ }
+}
+
+static inline void
+emitter_json_arr_value(emitter_t *emitter, emitter_type_t value_type,
+ const void *value) {
+ if (emitter->output == emitter_output_json) {
+ emitter_json_key_prefix(emitter);
+ emitter_print_value(emitter, emitter_justify_none, -1,
+ value_type, value);
+ }
+}
+
+static inline void
+emitter_table_row(emitter_t *emitter, emitter_row_t *row) {
+ if (emitter->output != emitter_output_table) {
+ return;
+ }
+ emitter_col_t *col;
+ ql_foreach(col, &row->cols, link) {
+ emitter_print_value(emitter, col->justify, col->width,
+ col->type, (const void *)&col->bool_val);
+ }
+ emitter_table_printf(emitter, "\n");
+}
+
+#endif /* JEMALLOC_INTERNAL_EMITTER_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent.h b/deps/jemalloc/include/jemalloc/internal/extent.h
deleted file mode 100644
index 386d50ef4..000000000
--- a/deps/jemalloc/include/jemalloc/internal/extent.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct extent_node_s extent_node_t;
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-/* Tree of extents. Use accessor functions for en_* fields. */
-struct extent_node_s {
- /* Arena from which this extent came, if any. */
- arena_t *en_arena;
-
- /* Pointer to the extent that this tree node is responsible for. */
- void *en_addr;
-
- /* Total region size. */
- size_t en_size;
-
- /*
- * The zeroed flag is used by chunk recycling code to track whether
- * memory is zero-filled.
- */
- bool en_zeroed;
-
- /*
- * True if physical memory is committed to the extent, whether
- * explicitly or implicitly as on a system that overcommits and
- * satisfies physical memory needs on demand via soft page faults.
- */
- bool en_committed;
-
- /*
- * The achunk flag is used to validate that huge allocation lookups
- * don't return arena chunks.
- */
- bool en_achunk;
-
- /* Profile counters, used for huge objects. */
- prof_tctx_t *en_prof_tctx;
-
- /* Linkage for arena's runs_dirty and chunks_cache rings. */
- arena_runs_dirty_link_t rd;
- qr(extent_node_t) cc_link;
-
- union {
- /* Linkage for the size/address-ordered tree. */
- rb_node(extent_node_t) szad_link;
-
- /* Linkage for arena's huge and node_cache lists. */
- ql_elm(extent_node_t) ql_link;
- };
-
- /* Linkage for the address-ordered tree. */
- rb_node(extent_node_t) ad_link;
-};
-typedef rb_tree(extent_node_t) extent_tree_t;
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-rb_proto(, extent_tree_szad_, extent_tree_t, extent_node_t)
-
-rb_proto(, extent_tree_ad_, extent_tree_t, extent_node_t)
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-arena_t *extent_node_arena_get(const extent_node_t *node);
-void *extent_node_addr_get(const extent_node_t *node);
-size_t extent_node_size_get(const extent_node_t *node);
-bool extent_node_zeroed_get(const extent_node_t *node);
-bool extent_node_committed_get(const extent_node_t *node);
-bool extent_node_achunk_get(const extent_node_t *node);
-prof_tctx_t *extent_node_prof_tctx_get(const extent_node_t *node);
-void extent_node_arena_set(extent_node_t *node, arena_t *arena);
-void extent_node_addr_set(extent_node_t *node, void *addr);
-void extent_node_size_set(extent_node_t *node, size_t size);
-void extent_node_zeroed_set(extent_node_t *node, bool zeroed);
-void extent_node_committed_set(extent_node_t *node, bool committed);
-void extent_node_achunk_set(extent_node_t *node, bool achunk);
-void extent_node_prof_tctx_set(extent_node_t *node, prof_tctx_t *tctx);
-void extent_node_init(extent_node_t *node, arena_t *arena, void *addr,
- size_t size, bool zeroed, bool committed);
-void extent_node_dirty_linkage_init(extent_node_t *node);
-void extent_node_dirty_insert(extent_node_t *node,
- arena_runs_dirty_link_t *runs_dirty, extent_node_t *chunks_dirty);
-void extent_node_dirty_remove(extent_node_t *node);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_EXTENT_C_))
-JEMALLOC_INLINE arena_t *
-extent_node_arena_get(const extent_node_t *node)
-{
-
- return (node->en_arena);
-}
-
-JEMALLOC_INLINE void *
-extent_node_addr_get(const extent_node_t *node)
-{
-
- return (node->en_addr);
-}
-
-JEMALLOC_INLINE size_t
-extent_node_size_get(const extent_node_t *node)
-{
-
- return (node->en_size);
-}
-
-JEMALLOC_INLINE bool
-extent_node_zeroed_get(const extent_node_t *node)
-{
-
- return (node->en_zeroed);
-}
-
-JEMALLOC_INLINE bool
-extent_node_committed_get(const extent_node_t *node)
-{
-
- assert(!node->en_achunk);
- return (node->en_committed);
-}
-
-JEMALLOC_INLINE bool
-extent_node_achunk_get(const extent_node_t *node)
-{
-
- return (node->en_achunk);
-}
-
-JEMALLOC_INLINE prof_tctx_t *
-extent_node_prof_tctx_get(const extent_node_t *node)
-{
-
- return (node->en_prof_tctx);
-}
-
-JEMALLOC_INLINE void
-extent_node_arena_set(extent_node_t *node, arena_t *arena)
-{
-
- node->en_arena = arena;
-}
-
-JEMALLOC_INLINE void
-extent_node_addr_set(extent_node_t *node, void *addr)
-{
-
- node->en_addr = addr;
-}
-
-JEMALLOC_INLINE void
-extent_node_size_set(extent_node_t *node, size_t size)
-{
-
- node->en_size = size;
-}
-
-JEMALLOC_INLINE void
-extent_node_zeroed_set(extent_node_t *node, bool zeroed)
-{
-
- node->en_zeroed = zeroed;
-}
-
-JEMALLOC_INLINE void
-extent_node_committed_set(extent_node_t *node, bool committed)
-{
-
- node->en_committed = committed;
-}
-
-JEMALLOC_INLINE void
-extent_node_achunk_set(extent_node_t *node, bool achunk)
-{
-
- node->en_achunk = achunk;
-}
-
-JEMALLOC_INLINE void
-extent_node_prof_tctx_set(extent_node_t *node, prof_tctx_t *tctx)
-{
-
- node->en_prof_tctx = tctx;
-}
-
-JEMALLOC_INLINE void
-extent_node_init(extent_node_t *node, arena_t *arena, void *addr, size_t size,
- bool zeroed, bool committed)
-{
-
- extent_node_arena_set(node, arena);
- extent_node_addr_set(node, addr);
- extent_node_size_set(node, size);
- extent_node_zeroed_set(node, zeroed);
- extent_node_committed_set(node, committed);
- extent_node_achunk_set(node, false);
- if (config_prof)
- extent_node_prof_tctx_set(node, NULL);
-}
-
-JEMALLOC_INLINE void
-extent_node_dirty_linkage_init(extent_node_t *node)
-{
-
- qr_new(&node->rd, rd_link);
- qr_new(node, cc_link);
-}
-
-JEMALLOC_INLINE void
-extent_node_dirty_insert(extent_node_t *node,
- arena_runs_dirty_link_t *runs_dirty, extent_node_t *chunks_dirty)
-{
-
- qr_meld(runs_dirty, &node->rd, rd_link);
- qr_meld(chunks_dirty, node, cc_link);
-}
-
-JEMALLOC_INLINE void
-extent_node_dirty_remove(extent_node_t *node)
-{
-
- qr_remove(&node->rd, rd_link);
- qr_remove(node, cc_link);
-}
-
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
-
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_dss.h b/deps/jemalloc/include/jemalloc/internal/extent_dss.h
new file mode 100644
index 000000000..e8f02ce2a
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_dss.h
@@ -0,0 +1,26 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_DSS_H
+#define JEMALLOC_INTERNAL_EXTENT_DSS_H
+
+typedef enum {
+ dss_prec_disabled = 0,
+ dss_prec_primary = 1,
+ dss_prec_secondary = 2,
+
+ dss_prec_limit = 3
+} dss_prec_t;
+#define DSS_PREC_DEFAULT dss_prec_secondary
+#define DSS_DEFAULT "secondary"
+
+extern const char *dss_prec_names[];
+
+extern const char *opt_dss;
+
+dss_prec_t extent_dss_prec_get(void);
+bool extent_dss_prec_set(dss_prec_t dss_prec);
+void *extent_alloc_dss(tsdn_t *tsdn, arena_t *arena, void *new_addr,
+ size_t size, size_t alignment, bool *zero, bool *commit);
+bool extent_in_dss(void *addr);
+bool extent_dss_mergeable(void *addr_a, void *addr_b);
+void extent_dss_boot(void);
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_DSS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_externs.h b/deps/jemalloc/include/jemalloc/internal/extent_externs.h
new file mode 100644
index 000000000..b8a4d026c
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_externs.h
@@ -0,0 +1,73 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_EXTERNS_H
+#define JEMALLOC_INTERNAL_EXTENT_EXTERNS_H
+
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_pool.h"
+#include "jemalloc/internal/ph.h"
+#include "jemalloc/internal/rtree.h"
+
+extern size_t opt_lg_extent_max_active_fit;
+
+extern rtree_t extents_rtree;
+extern const extent_hooks_t extent_hooks_default;
+extern mutex_pool_t extent_mutex_pool;
+
+extent_t *extent_alloc(tsdn_t *tsdn, arena_t *arena);
+void extent_dalloc(tsdn_t *tsdn, arena_t *arena, extent_t *extent);
+
+extent_hooks_t *extent_hooks_get(arena_t *arena);
+extent_hooks_t *extent_hooks_set(tsd_t *tsd, arena_t *arena,
+ extent_hooks_t *extent_hooks);
+
+#ifdef JEMALLOC_JET
+size_t extent_size_quantize_floor(size_t size);
+size_t extent_size_quantize_ceil(size_t size);
+#endif
+
+rb_proto(, extent_avail_, extent_tree_t, extent_t)
+ph_proto(, extent_heap_, extent_heap_t, extent_t)
+
+bool extents_init(tsdn_t *tsdn, extents_t *extents, extent_state_t state,
+ bool delay_coalesce);
+extent_state_t extents_state_get(const extents_t *extents);
+size_t extents_npages_get(extents_t *extents);
+extent_t *extents_alloc(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, void *new_addr,
+ size_t size, size_t pad, size_t alignment, bool slab, szind_t szind,
+ bool *zero, bool *commit);
+void extents_dalloc(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, extent_t *extent);
+extent_t *extents_evict(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, size_t npages_min);
+void extents_prefork(tsdn_t *tsdn, extents_t *extents);
+void extents_postfork_parent(tsdn_t *tsdn, extents_t *extents);
+void extents_postfork_child(tsdn_t *tsdn, extents_t *extents);
+extent_t *extent_alloc_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit);
+void extent_dalloc_gap(tsdn_t *tsdn, arena_t *arena, extent_t *extent);
+void extent_dalloc_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent);
+void extent_destroy_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent);
+bool extent_commit_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length);
+bool extent_decommit_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length);
+bool extent_purge_lazy_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length);
+bool extent_purge_forced_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length);
+extent_t *extent_split_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
+ szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b);
+bool extent_merge_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b);
+
+bool extent_boot(void);
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_inlines.h b/deps/jemalloc/include/jemalloc/internal/extent_inlines.h
new file mode 100644
index 000000000..77181df8d
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_inlines.h
@@ -0,0 +1,433 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_INLINES_H
+#define JEMALLOC_INTERNAL_EXTENT_INLINES_H
+
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_pool.h"
+#include "jemalloc/internal/pages.h"
+#include "jemalloc/internal/prng.h"
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/sz.h"
+
+static inline void
+extent_lock(tsdn_t *tsdn, extent_t *extent) {
+ assert(extent != NULL);
+ mutex_pool_lock(tsdn, &extent_mutex_pool, (uintptr_t)extent);
+}
+
+static inline void
+extent_unlock(tsdn_t *tsdn, extent_t *extent) {
+ assert(extent != NULL);
+ mutex_pool_unlock(tsdn, &extent_mutex_pool, (uintptr_t)extent);
+}
+
+static inline void
+extent_lock2(tsdn_t *tsdn, extent_t *extent1, extent_t *extent2) {
+ assert(extent1 != NULL && extent2 != NULL);
+ mutex_pool_lock2(tsdn, &extent_mutex_pool, (uintptr_t)extent1,
+ (uintptr_t)extent2);
+}
+
+static inline void
+extent_unlock2(tsdn_t *tsdn, extent_t *extent1, extent_t *extent2) {
+ assert(extent1 != NULL && extent2 != NULL);
+ mutex_pool_unlock2(tsdn, &extent_mutex_pool, (uintptr_t)extent1,
+ (uintptr_t)extent2);
+}
+
+static inline arena_t *
+extent_arena_get(const extent_t *extent) {
+ unsigned arena_ind = (unsigned)((extent->e_bits &
+ EXTENT_BITS_ARENA_MASK) >> EXTENT_BITS_ARENA_SHIFT);
+ /*
+ * The following check is omitted because we should never actually read
+ * a NULL arena pointer.
+ */
+ if (false && arena_ind >= MALLOCX_ARENA_LIMIT) {
+ return NULL;
+ }
+ assert(arena_ind < MALLOCX_ARENA_LIMIT);
+ return (arena_t *)atomic_load_p(&arenas[arena_ind], ATOMIC_ACQUIRE);
+}
+
+static inline szind_t
+extent_szind_get_maybe_invalid(const extent_t *extent) {
+ szind_t szind = (szind_t)((extent->e_bits & EXTENT_BITS_SZIND_MASK) >>
+ EXTENT_BITS_SZIND_SHIFT);
+ assert(szind <= NSIZES);
+ return szind;
+}
+
+static inline szind_t
+extent_szind_get(const extent_t *extent) {
+ szind_t szind = extent_szind_get_maybe_invalid(extent);
+ assert(szind < NSIZES); /* Never call when "invalid". */
+ return szind;
+}
+
+static inline size_t
+extent_usize_get(const extent_t *extent) {
+ return sz_index2size(extent_szind_get(extent));
+}
+
+static inline size_t
+extent_sn_get(const extent_t *extent) {
+ return (size_t)((extent->e_bits & EXTENT_BITS_SN_MASK) >>
+ EXTENT_BITS_SN_SHIFT);
+}
+
+static inline extent_state_t
+extent_state_get(const extent_t *extent) {
+ return (extent_state_t)((extent->e_bits & EXTENT_BITS_STATE_MASK) >>
+ EXTENT_BITS_STATE_SHIFT);
+}
+
+static inline bool
+extent_zeroed_get(const extent_t *extent) {
+ return (bool)((extent->e_bits & EXTENT_BITS_ZEROED_MASK) >>
+ EXTENT_BITS_ZEROED_SHIFT);
+}
+
+static inline bool
+extent_committed_get(const extent_t *extent) {
+ return (bool)((extent->e_bits & EXTENT_BITS_COMMITTED_MASK) >>
+ EXTENT_BITS_COMMITTED_SHIFT);
+}
+
+static inline bool
+extent_dumpable_get(const extent_t *extent) {
+ return (bool)((extent->e_bits & EXTENT_BITS_DUMPABLE_MASK) >>
+ EXTENT_BITS_DUMPABLE_SHIFT);
+}
+
+static inline bool
+extent_slab_get(const extent_t *extent) {
+ return (bool)((extent->e_bits & EXTENT_BITS_SLAB_MASK) >>
+ EXTENT_BITS_SLAB_SHIFT);
+}
+
+static inline unsigned
+extent_nfree_get(const extent_t *extent) {
+ assert(extent_slab_get(extent));
+ return (unsigned)((extent->e_bits & EXTENT_BITS_NFREE_MASK) >>
+ EXTENT_BITS_NFREE_SHIFT);
+}
+
+static inline void *
+extent_base_get(const extent_t *extent) {
+ assert(extent->e_addr == PAGE_ADDR2BASE(extent->e_addr) ||
+ !extent_slab_get(extent));
+ return PAGE_ADDR2BASE(extent->e_addr);
+}
+
+static inline void *
+extent_addr_get(const extent_t *extent) {
+ assert(extent->e_addr == PAGE_ADDR2BASE(extent->e_addr) ||
+ !extent_slab_get(extent));
+ return extent->e_addr;
+}
+
+static inline size_t
+extent_size_get(const extent_t *extent) {
+ return (extent->e_size_esn & EXTENT_SIZE_MASK);
+}
+
+static inline size_t
+extent_esn_get(const extent_t *extent) {
+ return (extent->e_size_esn & EXTENT_ESN_MASK);
+}
+
+static inline size_t
+extent_bsize_get(const extent_t *extent) {
+ return extent->e_bsize;
+}
+
+static inline void *
+extent_before_get(const extent_t *extent) {
+ return (void *)((uintptr_t)extent_base_get(extent) - PAGE);
+}
+
+static inline void *
+extent_last_get(const extent_t *extent) {
+ return (void *)((uintptr_t)extent_base_get(extent) +
+ extent_size_get(extent) - PAGE);
+}
+
+static inline void *
+extent_past_get(const extent_t *extent) {
+ return (void *)((uintptr_t)extent_base_get(extent) +
+ extent_size_get(extent));
+}
+
+static inline arena_slab_data_t *
+extent_slab_data_get(extent_t *extent) {
+ assert(extent_slab_get(extent));
+ return &extent->e_slab_data;
+}
+
+static inline const arena_slab_data_t *
+extent_slab_data_get_const(const extent_t *extent) {
+ assert(extent_slab_get(extent));
+ return &extent->e_slab_data;
+}
+
+static inline prof_tctx_t *
+extent_prof_tctx_get(const extent_t *extent) {
+ return (prof_tctx_t *)atomic_load_p(&extent->e_prof_tctx,
+ ATOMIC_ACQUIRE);
+}
+
+static inline void
+extent_arena_set(extent_t *extent, arena_t *arena) {
+ unsigned arena_ind = (arena != NULL) ? arena_ind_get(arena) : ((1U <<
+ MALLOCX_ARENA_BITS) - 1);
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_ARENA_MASK) |
+ ((uint64_t)arena_ind << EXTENT_BITS_ARENA_SHIFT);
+}
+
+static inline void
+extent_addr_set(extent_t *extent, void *addr) {
+ extent->e_addr = addr;
+}
+
+static inline void
+extent_addr_randomize(UNUSED tsdn_t *tsdn, extent_t *extent, size_t alignment) {
+ assert(extent_base_get(extent) == extent_addr_get(extent));
+
+ if (alignment < PAGE) {
+ unsigned lg_range = LG_PAGE -
+ lg_floor(CACHELINE_CEILING(alignment));
+ size_t r;
+ if (!tsdn_null(tsdn)) {
+ tsd_t *tsd = tsdn_tsd(tsdn);
+ r = (size_t)prng_lg_range_u64(
+ tsd_offset_statep_get(tsd), lg_range);
+ } else {
+ r = prng_lg_range_zu(
+ &extent_arena_get(extent)->offset_state,
+ lg_range, true);
+ }
+ uintptr_t random_offset = ((uintptr_t)r) << (LG_PAGE -
+ lg_range);
+ extent->e_addr = (void *)((uintptr_t)extent->e_addr +
+ random_offset);
+ assert(ALIGNMENT_ADDR2BASE(extent->e_addr, alignment) ==
+ extent->e_addr);
+ }
+}
+
+static inline void
+extent_size_set(extent_t *extent, size_t size) {
+ assert((size & ~EXTENT_SIZE_MASK) == 0);
+ extent->e_size_esn = size | (extent->e_size_esn & ~EXTENT_SIZE_MASK);
+}
+
+static inline void
+extent_esn_set(extent_t *extent, size_t esn) {
+ extent->e_size_esn = (extent->e_size_esn & ~EXTENT_ESN_MASK) | (esn &
+ EXTENT_ESN_MASK);
+}
+
+static inline void
+extent_bsize_set(extent_t *extent, size_t bsize) {
+ extent->e_bsize = bsize;
+}
+
+static inline void
+extent_szind_set(extent_t *extent, szind_t szind) {
+ assert(szind <= NSIZES); /* NSIZES means "invalid". */
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_SZIND_MASK) |
+ ((uint64_t)szind << EXTENT_BITS_SZIND_SHIFT);
+}
+
+static inline void
+extent_nfree_set(extent_t *extent, unsigned nfree) {
+ assert(extent_slab_get(extent));
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_NFREE_MASK) |
+ ((uint64_t)nfree << EXTENT_BITS_NFREE_SHIFT);
+}
+
+static inline void
+extent_nfree_inc(extent_t *extent) {
+ assert(extent_slab_get(extent));
+ extent->e_bits += ((uint64_t)1U << EXTENT_BITS_NFREE_SHIFT);
+}
+
+static inline void
+extent_nfree_dec(extent_t *extent) {
+ assert(extent_slab_get(extent));
+ extent->e_bits -= ((uint64_t)1U << EXTENT_BITS_NFREE_SHIFT);
+}
+
+static inline void
+extent_sn_set(extent_t *extent, size_t sn) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_SN_MASK) |
+ ((uint64_t)sn << EXTENT_BITS_SN_SHIFT);
+}
+
+static inline void
+extent_state_set(extent_t *extent, extent_state_t state) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_STATE_MASK) |
+ ((uint64_t)state << EXTENT_BITS_STATE_SHIFT);
+}
+
+static inline void
+extent_zeroed_set(extent_t *extent, bool zeroed) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_ZEROED_MASK) |
+ ((uint64_t)zeroed << EXTENT_BITS_ZEROED_SHIFT);
+}
+
+static inline void
+extent_committed_set(extent_t *extent, bool committed) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_COMMITTED_MASK) |
+ ((uint64_t)committed << EXTENT_BITS_COMMITTED_SHIFT);
+}
+
+static inline void
+extent_dumpable_set(extent_t *extent, bool dumpable) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_DUMPABLE_MASK) |
+ ((uint64_t)dumpable << EXTENT_BITS_DUMPABLE_SHIFT);
+}
+
+static inline void
+extent_slab_set(extent_t *extent, bool slab) {
+ extent->e_bits = (extent->e_bits & ~EXTENT_BITS_SLAB_MASK) |
+ ((uint64_t)slab << EXTENT_BITS_SLAB_SHIFT);
+}
+
+static inline void
+extent_prof_tctx_set(extent_t *extent, prof_tctx_t *tctx) {
+ atomic_store_p(&extent->e_prof_tctx, tctx, ATOMIC_RELEASE);
+}
+
+static inline void
+extent_init(extent_t *extent, arena_t *arena, void *addr, size_t size,
+ bool slab, szind_t szind, size_t sn, extent_state_t state, bool zeroed,
+ bool committed, bool dumpable) {
+ assert(addr == PAGE_ADDR2BASE(addr) || !slab);
+
+ extent_arena_set(extent, arena);
+ extent_addr_set(extent, addr);
+ extent_size_set(extent, size);
+ extent_slab_set(extent, slab);
+ extent_szind_set(extent, szind);
+ extent_sn_set(extent, sn);
+ extent_state_set(extent, state);
+ extent_zeroed_set(extent, zeroed);
+ extent_committed_set(extent, committed);
+ extent_dumpable_set(extent, dumpable);
+ ql_elm_new(extent, ql_link);
+ if (config_prof) {
+ extent_prof_tctx_set(extent, NULL);
+ }
+}
+
+static inline void
+extent_binit(extent_t *extent, void *addr, size_t bsize, size_t sn) {
+ extent_arena_set(extent, NULL);
+ extent_addr_set(extent, addr);
+ extent_bsize_set(extent, bsize);
+ extent_slab_set(extent, false);
+ extent_szind_set(extent, NSIZES);
+ extent_sn_set(extent, sn);
+ extent_state_set(extent, extent_state_active);
+ extent_zeroed_set(extent, true);
+ extent_committed_set(extent, true);
+ extent_dumpable_set(extent, true);
+}
+
+static inline void
+extent_list_init(extent_list_t *list) {
+ ql_new(list);
+}
+
+static inline extent_t *
+extent_list_first(const extent_list_t *list) {
+ return ql_first(list);
+}
+
+static inline extent_t *
+extent_list_last(const extent_list_t *list) {
+ return ql_last(list, ql_link);
+}
+
+static inline void
+extent_list_append(extent_list_t *list, extent_t *extent) {
+ ql_tail_insert(list, extent, ql_link);
+}
+
+static inline void
+extent_list_prepend(extent_list_t *list, extent_t *extent) {
+ ql_head_insert(list, extent, ql_link);
+}
+
+static inline void
+extent_list_replace(extent_list_t *list, extent_t *to_remove,
+ extent_t *to_insert) {
+ ql_after_insert(to_remove, to_insert, ql_link);
+ ql_remove(list, to_remove, ql_link);
+}
+
+static inline void
+extent_list_remove(extent_list_t *list, extent_t *extent) {
+ ql_remove(list, extent, ql_link);
+}
+
+static inline int
+extent_sn_comp(const extent_t *a, const extent_t *b) {
+ size_t a_sn = extent_sn_get(a);
+ size_t b_sn = extent_sn_get(b);
+
+ return (a_sn > b_sn) - (a_sn < b_sn);
+}
+
+static inline int
+extent_esn_comp(const extent_t *a, const extent_t *b) {
+ size_t a_esn = extent_esn_get(a);
+ size_t b_esn = extent_esn_get(b);
+
+ return (a_esn > b_esn) - (a_esn < b_esn);
+}
+
+static inline int
+extent_ad_comp(const extent_t *a, const extent_t *b) {
+ uintptr_t a_addr = (uintptr_t)extent_addr_get(a);
+ uintptr_t b_addr = (uintptr_t)extent_addr_get(b);
+
+ return (a_addr > b_addr) - (a_addr < b_addr);
+}
+
+static inline int
+extent_ead_comp(const extent_t *a, const extent_t *b) {
+ uintptr_t a_eaddr = (uintptr_t)a;
+ uintptr_t b_eaddr = (uintptr_t)b;
+
+ return (a_eaddr > b_eaddr) - (a_eaddr < b_eaddr);
+}
+
+static inline int
+extent_snad_comp(const extent_t *a, const extent_t *b) {
+ int ret;
+
+ ret = extent_sn_comp(a, b);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = extent_ad_comp(a, b);
+ return ret;
+}
+
+static inline int
+extent_esnead_comp(const extent_t *a, const extent_t *b) {
+ int ret;
+
+ ret = extent_esn_comp(a, b);
+ if (ret != 0) {
+ return ret;
+ }
+
+ ret = extent_ead_comp(a, b);
+ return ret;
+}
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_INLINES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_mmap.h b/deps/jemalloc/include/jemalloc/internal/extent_mmap.h
new file mode 100644
index 000000000..55f17ee48
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_mmap.h
@@ -0,0 +1,10 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H
+#define JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H
+
+extern bool opt_retain;
+
+void *extent_alloc_mmap(void *new_addr, size_t size, size_t alignment,
+ bool *zero, bool *commit);
+bool extent_dalloc_mmap(void *addr, size_t size);
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_MMAP_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_structs.h b/deps/jemalloc/include/jemalloc/internal/extent_structs.h
new file mode 100644
index 000000000..4873b9e9e
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_structs.h
@@ -0,0 +1,219 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_STRUCTS_H
+#define JEMALLOC_INTERNAL_EXTENT_STRUCTS_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/bitmap.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/ph.h"
+#include "jemalloc/internal/size_classes.h"
+
+typedef enum {
+ extent_state_active = 0,
+ extent_state_dirty = 1,
+ extent_state_muzzy = 2,
+ extent_state_retained = 3
+} extent_state_t;
+
+/* Extent (span of pages). Use accessor functions for e_* fields. */
+struct extent_s {
+ /*
+ * Bitfield containing several fields:
+ *
+ * a: arena_ind
+ * b: slab
+ * c: committed
+ * d: dumpable
+ * z: zeroed
+ * t: state
+ * i: szind
+ * f: nfree
+ * n: sn
+ *
+ * nnnnnnnn ... nnnnffff ffffffii iiiiiitt zdcbaaaa aaaaaaaa
+ *
+ * arena_ind: Arena from which this extent came, or all 1 bits if
+ * unassociated.
+ *
+ * slab: The slab flag indicates whether the extent is used for a slab
+ * of small regions. This helps differentiate small size classes,
+ * and it indicates whether interior pointers can be looked up via
+ * iealloc().
+ *
+ * committed: The committed flag indicates whether physical memory is
+ * committed to the extent, whether explicitly or implicitly
+ * as on a system that overcommits and satisfies physical
+ * memory needs on demand via soft page faults.
+ *
+ * dumpable: The dumpable flag indicates whether or not we've set the
+ * memory in question to be dumpable. Note that this
+ * interacts somewhat subtly with user-specified extent hooks,
+ * since we don't know if *they* are fiddling with
+ * dumpability (in which case, we don't want to undo whatever
+ * they're doing). To deal with this scenario, we:
+ * - Make dumpable false only for memory allocated with the
+ * default hooks.
+ * - Only allow memory to go from non-dumpable to dumpable,
+ * and only once.
+ * - Never make the OS call to allow dumping when the
+ * dumpable bit is already set.
+ * These three constraints mean that we will never
+ * accidentally dump user memory that the user meant to set
+ * nondumpable with their extent hooks.
+ *
+ *
+ * zeroed: The zeroed flag is used by extent recycling code to track
+ * whether memory is zero-filled.
+ *
+ * state: The state flag is an extent_state_t.
+ *
+ * szind: The szind flag indicates usable size class index for
+ * allocations residing in this extent, regardless of whether the
+ * extent is a slab. Extent size and usable size often differ
+ * even for non-slabs, either due to sz_large_pad or promotion of
+ * sampled small regions.
+ *
+ * nfree: Number of free regions in slab.
+ *
+ * sn: Serial number (potentially non-unique).
+ *
+ * Serial numbers may wrap around if !opt_retain, but as long as
+ * comparison functions fall back on address comparison for equal
+ * serial numbers, stable (if imperfect) ordering is maintained.
+ *
+ * Serial numbers may not be unique even in the absence of
+ * wrap-around, e.g. when splitting an extent and assigning the same
+ * serial number to both resulting adjacent extents.
+ */
+ uint64_t e_bits;
+#define MASK(CURRENT_FIELD_WIDTH, CURRENT_FIELD_SHIFT) ((((((uint64_t)0x1U) << (CURRENT_FIELD_WIDTH)) - 1)) << (CURRENT_FIELD_SHIFT))
+
+#define EXTENT_BITS_ARENA_WIDTH MALLOCX_ARENA_BITS
+#define EXTENT_BITS_ARENA_SHIFT 0
+#define EXTENT_BITS_ARENA_MASK MASK(EXTENT_BITS_ARENA_WIDTH, EXTENT_BITS_ARENA_SHIFT)
+
+#define EXTENT_BITS_SLAB_WIDTH 1
+#define EXTENT_BITS_SLAB_SHIFT (EXTENT_BITS_ARENA_WIDTH + EXTENT_BITS_ARENA_SHIFT)
+#define EXTENT_BITS_SLAB_MASK MASK(EXTENT_BITS_SLAB_WIDTH, EXTENT_BITS_SLAB_SHIFT)
+
+#define EXTENT_BITS_COMMITTED_WIDTH 1
+#define EXTENT_BITS_COMMITTED_SHIFT (EXTENT_BITS_SLAB_WIDTH + EXTENT_BITS_SLAB_SHIFT)
+#define EXTENT_BITS_COMMITTED_MASK MASK(EXTENT_BITS_COMMITTED_WIDTH, EXTENT_BITS_COMMITTED_SHIFT)
+
+#define EXTENT_BITS_DUMPABLE_WIDTH 1
+#define EXTENT_BITS_DUMPABLE_SHIFT (EXTENT_BITS_COMMITTED_WIDTH + EXTENT_BITS_COMMITTED_SHIFT)
+#define EXTENT_BITS_DUMPABLE_MASK MASK(EXTENT_BITS_DUMPABLE_WIDTH, EXTENT_BITS_DUMPABLE_SHIFT)
+
+#define EXTENT_BITS_ZEROED_WIDTH 1
+#define EXTENT_BITS_ZEROED_SHIFT (EXTENT_BITS_DUMPABLE_WIDTH + EXTENT_BITS_DUMPABLE_SHIFT)
+#define EXTENT_BITS_ZEROED_MASK MASK(EXTENT_BITS_ZEROED_WIDTH, EXTENT_BITS_ZEROED_SHIFT)
+
+#define EXTENT_BITS_STATE_WIDTH 2
+#define EXTENT_BITS_STATE_SHIFT (EXTENT_BITS_ZEROED_WIDTH + EXTENT_BITS_ZEROED_SHIFT)
+#define EXTENT_BITS_STATE_MASK MASK(EXTENT_BITS_STATE_WIDTH, EXTENT_BITS_STATE_SHIFT)
+
+#define EXTENT_BITS_SZIND_WIDTH LG_CEIL_NSIZES
+#define EXTENT_BITS_SZIND_SHIFT (EXTENT_BITS_STATE_WIDTH + EXTENT_BITS_STATE_SHIFT)
+#define EXTENT_BITS_SZIND_MASK MASK(EXTENT_BITS_SZIND_WIDTH, EXTENT_BITS_SZIND_SHIFT)
+
+#define EXTENT_BITS_NFREE_WIDTH (LG_SLAB_MAXREGS + 1)
+#define EXTENT_BITS_NFREE_SHIFT (EXTENT_BITS_SZIND_WIDTH + EXTENT_BITS_SZIND_SHIFT)
+#define EXTENT_BITS_NFREE_MASK MASK(EXTENT_BITS_NFREE_WIDTH, EXTENT_BITS_NFREE_SHIFT)
+
+#define EXTENT_BITS_SN_SHIFT (EXTENT_BITS_NFREE_WIDTH + EXTENT_BITS_NFREE_SHIFT)
+#define EXTENT_BITS_SN_MASK (UINT64_MAX << EXTENT_BITS_SN_SHIFT)
+
+ /* Pointer to the extent that this structure is responsible for. */
+ void *e_addr;
+
+ union {
+ /*
+ * Extent size and serial number associated with the extent
+ * structure (different than the serial number for the extent at
+ * e_addr).
+ *
+ * ssssssss [...] ssssssss ssssnnnn nnnnnnnn
+ */
+ size_t e_size_esn;
+ #define EXTENT_SIZE_MASK ((size_t)~(PAGE-1))
+ #define EXTENT_ESN_MASK ((size_t)PAGE-1)
+ /* Base extent size, which may not be a multiple of PAGE. */
+ size_t e_bsize;
+ };
+
+ /*
+ * List linkage, used by a variety of lists:
+ * - bin_t's slabs_full
+ * - extents_t's LRU
+ * - stashed dirty extents
+ * - arena's large allocations
+ */
+ ql_elm(extent_t) ql_link;
+
+ /*
+ * Linkage for per size class sn/address-ordered heaps, and
+ * for extent_avail
+ */
+ phn(extent_t) ph_link;
+
+ union {
+ /* Small region slab metadata. */
+ arena_slab_data_t e_slab_data;
+
+ /*
+ * Profile counters, used for large objects. Points to a
+ * prof_tctx_t.
+ */
+ atomic_p_t e_prof_tctx;
+ };
+};
+typedef ql_head(extent_t) extent_list_t;
+typedef ph(extent_t) extent_tree_t;
+typedef ph(extent_t) extent_heap_t;
+
+/* Quantized collection of extents, with built-in LRU queue. */
+struct extents_s {
+ malloc_mutex_t mtx;
+
+ /*
+ * Quantized per size class heaps of extents.
+ *
+ * Synchronization: mtx.
+ */
+ extent_heap_t heaps[NPSIZES+1];
+
+ /*
+ * Bitmap for which set bits correspond to non-empty heaps.
+ *
+ * Synchronization: mtx.
+ */
+ bitmap_t bitmap[BITMAP_GROUPS(NPSIZES+1)];
+
+ /*
+ * LRU of all extents in heaps.
+ *
+ * Synchronization: mtx.
+ */
+ extent_list_t lru;
+
+ /*
+ * Page sum for all extents in heaps.
+ *
+ * The synchronization here is a little tricky. Modifications to npages
+ * must hold mtx, but reads need not (though, a reader who sees npages
+ * without holding the mutex can't assume anything about the rest of the
+ * state of the extents_t).
+ */
+ atomic_zu_t npages;
+
+ /* All stored extents must be in the same state. */
+ extent_state_t state;
+
+ /*
+ * If true, delay coalescing until eviction; otherwise coalesce during
+ * deallocation.
+ */
+ bool delay_coalesce;
+};
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_STRUCTS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/extent_types.h b/deps/jemalloc/include/jemalloc/internal/extent_types.h
new file mode 100644
index 000000000..c0561d99f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/extent_types.h
@@ -0,0 +1,17 @@
+#ifndef JEMALLOC_INTERNAL_EXTENT_TYPES_H
+#define JEMALLOC_INTERNAL_EXTENT_TYPES_H
+
+typedef struct extent_s extent_t;
+typedef struct extents_s extents_t;
+
+#define EXTENT_HOOKS_INITIALIZER NULL
+
+#define EXTENT_GROW_MAX_PIND (NPSIZES - 1)
+
+/*
+ * When reuse (and split) an active extent, (1U << opt_lg_extent_max_active_fit)
+ * is the max ratio between the size of the active extent and the new extent.
+ */
+#define LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT 6
+
+#endif /* JEMALLOC_INTERNAL_EXTENT_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/hash.h b/deps/jemalloc/include/jemalloc/internal/hash.h
index bcead337a..dcfc992df 100644
--- a/deps/jemalloc/include/jemalloc/internal/hash.h
+++ b/deps/jemalloc/include/jemalloc/internal/hash.h
@@ -1,93 +1,76 @@
+#ifndef JEMALLOC_INTERNAL_HASH_H
+#define JEMALLOC_INTERNAL_HASH_H
+
+#include "jemalloc/internal/assert.h"
+
/*
* The following hash function is based on MurmurHash3, placed into the public
- * domain by Austin Appleby. See http://code.google.com/p/smhasher/ for
+ * domain by Austin Appleby. See https://github.com/aappleby/smhasher for
* details.
*/
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-uint32_t hash_x86_32(const void *key, int len, uint32_t seed);
-void hash_x86_128(const void *key, const int len, uint32_t seed,
- uint64_t r_out[2]);
-void hash_x64_128(const void *key, const int len, const uint32_t seed,
- uint64_t r_out[2]);
-void hash(const void *key, size_t len, const uint32_t seed,
- size_t r_hash[2]);
-#endif
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_HASH_C_))
/******************************************************************************/
/* Internal implementation. */
-JEMALLOC_INLINE uint32_t
-hash_rotl_32(uint32_t x, int8_t r)
-{
-
+static inline uint32_t
+hash_rotl_32(uint32_t x, int8_t r) {
return ((x << r) | (x >> (32 - r)));
}
-JEMALLOC_INLINE uint64_t
-hash_rotl_64(uint64_t x, int8_t r)
-{
-
+static inline uint64_t
+hash_rotl_64(uint64_t x, int8_t r) {
return ((x << r) | (x >> (64 - r)));
}
-JEMALLOC_INLINE uint32_t
-hash_get_block_32(const uint32_t *p, int i)
-{
+static inline uint32_t
+hash_get_block_32(const uint32_t *p, int i) {
+ /* Handle unaligned read. */
+ if (unlikely((uintptr_t)p & (sizeof(uint32_t)-1)) != 0) {
+ uint32_t ret;
- return (p[i]);
+ memcpy(&ret, (uint8_t *)(p + i), sizeof(uint32_t));
+ return ret;
+ }
+
+ return p[i];
}
-JEMALLOC_INLINE uint64_t
-hash_get_block_64(const uint64_t *p, int i)
-{
+static inline uint64_t
+hash_get_block_64(const uint64_t *p, int i) {
+ /* Handle unaligned read. */
+ if (unlikely((uintptr_t)p & (sizeof(uint64_t)-1)) != 0) {
+ uint64_t ret;
- return (p[i]);
-}
+ memcpy(&ret, (uint8_t *)(p + i), sizeof(uint64_t));
+ return ret;
+ }
-JEMALLOC_INLINE uint32_t
-hash_fmix_32(uint32_t h)
-{
+ return p[i];
+}
+static inline uint32_t
+hash_fmix_32(uint32_t h) {
h ^= h >> 16;
h *= 0x85ebca6b;
h ^= h >> 13;
h *= 0xc2b2ae35;
h ^= h >> 16;
- return (h);
+ return h;
}
-JEMALLOC_INLINE uint64_t
-hash_fmix_64(uint64_t k)
-{
-
+static inline uint64_t
+hash_fmix_64(uint64_t k) {
k ^= k >> 33;
k *= KQU(0xff51afd7ed558ccd);
k ^= k >> 33;
k *= KQU(0xc4ceb9fe1a85ec53);
k ^= k >> 33;
- return (k);
+ return k;
}
-JEMALLOC_INLINE uint32_t
-hash_x86_32(const void *key, int len, uint32_t seed)
-{
+static inline uint32_t
+hash_x86_32(const void *key, int len, uint32_t seed) {
const uint8_t *data = (const uint8_t *) key;
const int nblocks = len / 4;
@@ -133,13 +116,12 @@ hash_x86_32(const void *key, int len, uint32_t seed)
h1 = hash_fmix_32(h1);
- return (h1);
+ return h1;
}
-UNUSED JEMALLOC_INLINE void
+UNUSED static inline void
hash_x86_128(const void *key, const int len, uint32_t seed,
- uint64_t r_out[2])
-{
+ uint64_t r_out[2]) {
const uint8_t * data = (const uint8_t *) key;
const int nblocks = len / 16;
@@ -238,10 +220,9 @@ hash_x86_128(const void *key, const int len, uint32_t seed,
r_out[1] = (((uint64_t) h4) << 32) | h3;
}
-UNUSED JEMALLOC_INLINE void
+UNUSED static inline void
hash_x64_128(const void *key, const int len, const uint32_t seed,
- uint64_t r_out[2])
-{
+ uint64_t r_out[2]) {
const uint8_t *data = (const uint8_t *) key;
const int nblocks = len / 16;
@@ -279,22 +260,22 @@ hash_x64_128(const void *key, const int len, const uint32_t seed,
uint64_t k2 = 0;
switch (len & 15) {
- case 15: k2 ^= ((uint64_t)(tail[14])) << 48;
- case 14: k2 ^= ((uint64_t)(tail[13])) << 40;
- case 13: k2 ^= ((uint64_t)(tail[12])) << 32;
- case 12: k2 ^= ((uint64_t)(tail[11])) << 24;
- case 11: k2 ^= ((uint64_t)(tail[10])) << 16;
- case 10: k2 ^= ((uint64_t)(tail[ 9])) << 8;
+ case 15: k2 ^= ((uint64_t)(tail[14])) << 48; /* falls through */
+ case 14: k2 ^= ((uint64_t)(tail[13])) << 40; /* falls through */
+ case 13: k2 ^= ((uint64_t)(tail[12])) << 32; /* falls through */
+ case 12: k2 ^= ((uint64_t)(tail[11])) << 24; /* falls through */
+ case 11: k2 ^= ((uint64_t)(tail[10])) << 16; /* falls through */
+ case 10: k2 ^= ((uint64_t)(tail[ 9])) << 8; /* falls through */
case 9: k2 ^= ((uint64_t)(tail[ 8])) << 0;
k2 *= c2; k2 = hash_rotl_64(k2, 33); k2 *= c1; h2 ^= k2;
-
- case 8: k1 ^= ((uint64_t)(tail[ 7])) << 56;
- case 7: k1 ^= ((uint64_t)(tail[ 6])) << 48;
- case 6: k1 ^= ((uint64_t)(tail[ 5])) << 40;
- case 5: k1 ^= ((uint64_t)(tail[ 4])) << 32;
- case 4: k1 ^= ((uint64_t)(tail[ 3])) << 24;
- case 3: k1 ^= ((uint64_t)(tail[ 2])) << 16;
- case 2: k1 ^= ((uint64_t)(tail[ 1])) << 8;
+ /* falls through */
+ case 8: k1 ^= ((uint64_t)(tail[ 7])) << 56; /* falls through */
+ case 7: k1 ^= ((uint64_t)(tail[ 6])) << 48; /* falls through */
+ case 6: k1 ^= ((uint64_t)(tail[ 5])) << 40; /* falls through */
+ case 5: k1 ^= ((uint64_t)(tail[ 4])) << 32; /* falls through */
+ case 4: k1 ^= ((uint64_t)(tail[ 3])) << 24; /* falls through */
+ case 3: k1 ^= ((uint64_t)(tail[ 2])) << 16; /* falls through */
+ case 2: k1 ^= ((uint64_t)(tail[ 1])) << 8; /* falls through */
case 1: k1 ^= ((uint64_t)(tail[ 0])) << 0;
k1 *= c1; k1 = hash_rotl_64(k1, 31); k1 *= c2; h1 ^= k1;
}
@@ -318,19 +299,20 @@ hash_x64_128(const void *key, const int len, const uint32_t seed,
/******************************************************************************/
/* API. */
-JEMALLOC_INLINE void
-hash(const void *key, size_t len, const uint32_t seed, size_t r_hash[2])
-{
+static inline void
+hash(const void *key, size_t len, const uint32_t seed, size_t r_hash[2]) {
+ assert(len <= INT_MAX); /* Unfortunate implementation limitation. */
+
#if (LG_SIZEOF_PTR == 3 && !defined(JEMALLOC_BIG_ENDIAN))
- hash_x64_128(key, len, seed, (uint64_t *)r_hash);
+ hash_x64_128(key, (int)len, seed, (uint64_t *)r_hash);
#else
- uint64_t hashes[2];
- hash_x86_128(key, len, seed, hashes);
- r_hash[0] = (size_t)hashes[0];
- r_hash[1] = (size_t)hashes[1];
+ {
+ uint64_t hashes[2];
+ hash_x86_128(key, (int)len, seed, hashes);
+ r_hash[0] = (size_t)hashes[0];
+ r_hash[1] = (size_t)hashes[1];
+ }
#endif
}
-#endif
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_HASH_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/hooks.h b/deps/jemalloc/include/jemalloc/internal/hooks.h
new file mode 100644
index 000000000..cd49afcb0
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/hooks.h
@@ -0,0 +1,19 @@
+#ifndef JEMALLOC_INTERNAL_HOOKS_H
+#define JEMALLOC_INTERNAL_HOOKS_H
+
+extern JEMALLOC_EXPORT void (*hooks_arena_new_hook)();
+extern JEMALLOC_EXPORT void (*hooks_libc_hook)();
+
+#define JEMALLOC_HOOK(fn, hook) ((void)(hook != NULL && (hook(), 0)), fn)
+
+#define open JEMALLOC_HOOK(open, hooks_libc_hook)
+#define read JEMALLOC_HOOK(read, hooks_libc_hook)
+#define write JEMALLOC_HOOK(write, hooks_libc_hook)
+#define readlink JEMALLOC_HOOK(readlink, hooks_libc_hook)
+#define close JEMALLOC_HOOK(close, hooks_libc_hook)
+#define creat JEMALLOC_HOOK(creat, hooks_libc_hook)
+#define secure_getenv JEMALLOC_HOOK(secure_getenv, hooks_libc_hook)
+/* Note that this is undef'd and re-define'd in src/prof.c. */
+#define _Unwind_Backtrace JEMALLOC_HOOK(_Unwind_Backtrace, hooks_libc_hook)
+
+#endif /* JEMALLOC_INTERNAL_HOOKS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/huge.h b/deps/jemalloc/include/jemalloc/internal/huge.h
deleted file mode 100644
index ece7af980..000000000
--- a/deps/jemalloc/include/jemalloc/internal/huge.h
+++ /dev/null
@@ -1,36 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void *huge_malloc(tsd_t *tsd, arena_t *arena, size_t size, bool zero,
- tcache_t *tcache);
-void *huge_palloc(tsd_t *tsd, arena_t *arena, size_t size, size_t alignment,
- bool zero, tcache_t *tcache);
-bool huge_ralloc_no_move(void *ptr, size_t oldsize, size_t usize_min,
- size_t usize_max, bool zero);
-void *huge_ralloc(tsd_t *tsd, arena_t *arena, void *ptr, size_t oldsize,
- size_t usize, size_t alignment, bool zero, tcache_t *tcache);
-#ifdef JEMALLOC_JET
-typedef void (huge_dalloc_junk_t)(void *, size_t);
-extern huge_dalloc_junk_t *huge_dalloc_junk;
-#endif
-void huge_dalloc(tsd_t *tsd, void *ptr, tcache_t *tcache);
-arena_t *huge_aalloc(const void *ptr);
-size_t huge_salloc(const void *ptr);
-prof_tctx_t *huge_prof_tctx_get(const void *ptr);
-void huge_prof_tctx_set(const void *ptr, prof_tctx_t *tctx);
-void huge_prof_tctx_reset(const void *ptr);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal.h.in b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal.h.in
deleted file mode 100644
index 8536a3eda..000000000
--- a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal.h.in
+++ /dev/null
@@ -1,1134 +0,0 @@
-#ifndef JEMALLOC_INTERNAL_H
-#define JEMALLOC_INTERNAL_H
-
-#include "jemalloc_internal_defs.h"
-#include "jemalloc/internal/jemalloc_internal_decls.h"
-
-#ifdef JEMALLOC_UTRACE
-#include <sys/ktrace.h>
-#endif
-
-#define JEMALLOC_NO_DEMANGLE
-#ifdef JEMALLOC_JET
-# define JEMALLOC_N(n) jet_##n
-# include "jemalloc/internal/public_namespace.h"
-# define JEMALLOC_NO_RENAME
-# include "../jemalloc@install_suffix@.h"
-# undef JEMALLOC_NO_RENAME
-#else
-# define JEMALLOC_N(n) @private_namespace@##n
-# include "../jemalloc@install_suffix@.h"
-#endif
-#include "jemalloc/internal/private_namespace.h"
-
-static const bool config_debug =
-#ifdef JEMALLOC_DEBUG
- true
-#else
- false
-#endif
- ;
-static const bool have_dss =
-#ifdef JEMALLOC_DSS
- true
-#else
- false
-#endif
- ;
-static const bool config_fill =
-#ifdef JEMALLOC_FILL
- true
-#else
- false
-#endif
- ;
-static const bool config_lazy_lock =
-#ifdef JEMALLOC_LAZY_LOCK
- true
-#else
- false
-#endif
- ;
-static const bool config_prof =
-#ifdef JEMALLOC_PROF
- true
-#else
- false
-#endif
- ;
-static const bool config_prof_libgcc =
-#ifdef JEMALLOC_PROF_LIBGCC
- true
-#else
- false
-#endif
- ;
-static const bool config_prof_libunwind =
-#ifdef JEMALLOC_PROF_LIBUNWIND
- true
-#else
- false
-#endif
- ;
-static const bool maps_coalesce =
-#ifdef JEMALLOC_MAPS_COALESCE
- true
-#else
- false
-#endif
- ;
-static const bool config_munmap =
-#ifdef JEMALLOC_MUNMAP
- true
-#else
- false
-#endif
- ;
-static const bool config_stats =
-#ifdef JEMALLOC_STATS
- true
-#else
- false
-#endif
- ;
-static const bool config_tcache =
-#ifdef JEMALLOC_TCACHE
- true
-#else
- false
-#endif
- ;
-static const bool config_tls =
-#ifdef JEMALLOC_TLS
- true
-#else
- false
-#endif
- ;
-static const bool config_utrace =
-#ifdef JEMALLOC_UTRACE
- true
-#else
- false
-#endif
- ;
-static const bool config_valgrind =
-#ifdef JEMALLOC_VALGRIND
- true
-#else
- false
-#endif
- ;
-static const bool config_xmalloc =
-#ifdef JEMALLOC_XMALLOC
- true
-#else
- false
-#endif
- ;
-static const bool config_ivsalloc =
-#ifdef JEMALLOC_IVSALLOC
- true
-#else
- false
-#endif
- ;
-static const bool config_cache_oblivious =
-#ifdef JEMALLOC_CACHE_OBLIVIOUS
- true
-#else
- false
-#endif
- ;
-
-#ifdef JEMALLOC_C11ATOMICS
-#include <stdatomic.h>
-#endif
-
-#ifdef JEMALLOC_ATOMIC9
-#include <machine/atomic.h>
-#endif
-
-#if (defined(JEMALLOC_OSATOMIC) || defined(JEMALLOC_OSSPIN))
-#include <libkern/OSAtomic.h>
-#endif
-
-#ifdef JEMALLOC_ZONE
-#include <mach/mach_error.h>
-#include <mach/mach_init.h>
-#include <mach/vm_map.h>
-#include <malloc/malloc.h>
-#endif
-
-#define RB_COMPACT
-#include "jemalloc/internal/rb.h"
-#include "jemalloc/internal/qr.h"
-#include "jemalloc/internal/ql.h"
-
-/*
- * jemalloc can conceptually be broken into components (arena, tcache, etc.),
- * but there are circular dependencies that cannot be broken without
- * substantial performance degradation. In order to reduce the effect on
- * visual code flow, read the header files in multiple passes, with one of the
- * following cpp variables defined during each pass:
- *
- * JEMALLOC_H_TYPES : Preprocessor-defined constants and psuedo-opaque data
- * types.
- * JEMALLOC_H_STRUCTS : Data structures.
- * JEMALLOC_H_EXTERNS : Extern data declarations and function prototypes.
- * JEMALLOC_H_INLINES : Inline functions.
- */
-/******************************************************************************/
-#define JEMALLOC_H_TYPES
-
-#include "jemalloc/internal/jemalloc_internal_macros.h"
-
-/* Size class index type. */
-typedef unsigned szind_t;
-
-/*
- * Flags bits:
- *
- * a: arena
- * t: tcache
- * 0: unused
- * z: zero
- * n: alignment
- *
- * aaaaaaaa aaaatttt tttttttt 0znnnnnn
- */
-#define MALLOCX_ARENA_MASK ((int)~0xfffff)
-#define MALLOCX_ARENA_MAX 0xffe
-#define MALLOCX_TCACHE_MASK ((int)~0xfff000ffU)
-#define MALLOCX_TCACHE_MAX 0xffd
-#define MALLOCX_LG_ALIGN_MASK ((int)0x3f)
-/* Use MALLOCX_ALIGN_GET() if alignment may not be specified in flags. */
-#define MALLOCX_ALIGN_GET_SPECIFIED(flags) \
- (ZU(1) << (flags & MALLOCX_LG_ALIGN_MASK))
-#define MALLOCX_ALIGN_GET(flags) \
- (MALLOCX_ALIGN_GET_SPECIFIED(flags) & (SIZE_T_MAX-1))
-#define MALLOCX_ZERO_GET(flags) \
- ((bool)(flags & MALLOCX_ZERO))
-
-#define MALLOCX_TCACHE_GET(flags) \
- (((unsigned)((flags & MALLOCX_TCACHE_MASK) >> 8)) - 2)
-#define MALLOCX_ARENA_GET(flags) \
- (((unsigned)(((unsigned)flags) >> 20)) - 1)
-
-/* Smallest size class to support. */
-#define TINY_MIN (1U << LG_TINY_MIN)
-
-/*
- * Minimum allocation alignment is 2^LG_QUANTUM bytes (ignoring tiny size
- * classes).
- */
-#ifndef LG_QUANTUM
-# if (defined(__i386__) || defined(_M_IX86))
-# define LG_QUANTUM 4
-# endif
-# ifdef __ia64__
-# define LG_QUANTUM 4
-# endif
-# ifdef __alpha__
-# define LG_QUANTUM 4
-# endif
-# if (defined(__sparc64__) || defined(__sparcv9))
-# define LG_QUANTUM 4
-# endif
-# if (defined(__amd64__) || defined(__x86_64__) || defined(_M_X64))
-# define LG_QUANTUM 4
-# endif
-# ifdef __arm__
-# define LG_QUANTUM 3
-# endif
-# ifdef __aarch64__
-# define LG_QUANTUM 4
-# endif
-# ifdef __hppa__
-# define LG_QUANTUM 4
-# endif
-# ifdef __mips__
-# define LG_QUANTUM 3
-# endif
-# ifdef __or1k__
-# define LG_QUANTUM 3
-# endif
-# ifdef __powerpc__
-# define LG_QUANTUM 4
-# endif
-# ifdef __s390__
-# define LG_QUANTUM 4
-# endif
-# ifdef __SH4__
-# define LG_QUANTUM 4
-# endif
-# ifdef __tile__
-# define LG_QUANTUM 4
-# endif
-# ifdef __le32__
-# define LG_QUANTUM 4
-# endif
-# ifndef LG_QUANTUM
-# error "Unknown minimum alignment for architecture; specify via "
- "--with-lg-quantum"
-# endif
-#endif
-
-#define QUANTUM ((size_t)(1U << LG_QUANTUM))
-#define QUANTUM_MASK (QUANTUM - 1)
-
-/* Return the smallest quantum multiple that is >= a. */
-#define QUANTUM_CEILING(a) \
- (((a) + QUANTUM_MASK) & ~QUANTUM_MASK)
-
-#define LONG ((size_t)(1U << LG_SIZEOF_LONG))
-#define LONG_MASK (LONG - 1)
-
-/* Return the smallest long multiple that is >= a. */
-#define LONG_CEILING(a) \
- (((a) + LONG_MASK) & ~LONG_MASK)
-
-#define SIZEOF_PTR (1U << LG_SIZEOF_PTR)
-#define PTR_MASK (SIZEOF_PTR - 1)
-
-/* Return the smallest (void *) multiple that is >= a. */
-#define PTR_CEILING(a) \
- (((a) + PTR_MASK) & ~PTR_MASK)
-
-/*
- * Maximum size of L1 cache line. This is used to avoid cache line aliasing.
- * In addition, this controls the spacing of cacheline-spaced size classes.
- *
- * CACHELINE cannot be based on LG_CACHELINE because __declspec(align()) can
- * only handle raw constants.
- */
-#define LG_CACHELINE 6
-#define CACHELINE 64
-#define CACHELINE_MASK (CACHELINE - 1)
-
-/* Return the smallest cacheline multiple that is >= s. */
-#define CACHELINE_CEILING(s) \
- (((s) + CACHELINE_MASK) & ~CACHELINE_MASK)
-
-/* Page size. LG_PAGE is determined by the configure script. */
-#ifdef PAGE_MASK
-# undef PAGE_MASK
-#endif
-#define PAGE ((size_t)(1U << LG_PAGE))
-#define PAGE_MASK ((size_t)(PAGE - 1))
-
-/* Return the smallest pagesize multiple that is >= s. */
-#define PAGE_CEILING(s) \
- (((s) + PAGE_MASK) & ~PAGE_MASK)
-
-/* Return the nearest aligned address at or below a. */
-#define ALIGNMENT_ADDR2BASE(a, alignment) \
- ((void *)((uintptr_t)(a) & (-(alignment))))
-
-/* Return the offset between a and the nearest aligned address at or below a. */
-#define ALIGNMENT_ADDR2OFFSET(a, alignment) \
- ((size_t)((uintptr_t)(a) & (alignment - 1)))
-
-/* Return the smallest alignment multiple that is >= s. */
-#define ALIGNMENT_CEILING(s, alignment) \
- (((s) + (alignment - 1)) & (-(alignment)))
-
-/* Declare a variable-length array. */
-#if __STDC_VERSION__ < 199901L
-# ifdef _MSC_VER
-# include <malloc.h>
-# define alloca _alloca
-# else
-# ifdef JEMALLOC_HAS_ALLOCA_H
-# include <alloca.h>
-# else
-# include <stdlib.h>
-# endif
-# endif
-# define VARIABLE_ARRAY(type, name, count) \
- type *name = alloca(sizeof(type) * (count))
-#else
-# define VARIABLE_ARRAY(type, name, count) type name[(count)]
-#endif
-
-#include "jemalloc/internal/valgrind.h"
-#include "jemalloc/internal/util.h"
-#include "jemalloc/internal/atomic.h"
-#include "jemalloc/internal/prng.h"
-#include "jemalloc/internal/ckh.h"
-#include "jemalloc/internal/size_classes.h"
-#include "jemalloc/internal/stats.h"
-#include "jemalloc/internal/ctl.h"
-#include "jemalloc/internal/mutex.h"
-#include "jemalloc/internal/tsd.h"
-#include "jemalloc/internal/mb.h"
-#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/arena.h"
-#include "jemalloc/internal/bitmap.h"
-#include "jemalloc/internal/base.h"
-#include "jemalloc/internal/rtree.h"
-#include "jemalloc/internal/pages.h"
-#include "jemalloc/internal/chunk.h"
-#include "jemalloc/internal/huge.h"
-#include "jemalloc/internal/tcache.h"
-#include "jemalloc/internal/hash.h"
-#include "jemalloc/internal/quarantine.h"
-#include "jemalloc/internal/prof.h"
-
-#undef JEMALLOC_H_TYPES
-/******************************************************************************/
-#define JEMALLOC_H_STRUCTS
-
-#include "jemalloc/internal/valgrind.h"
-#include "jemalloc/internal/util.h"
-#include "jemalloc/internal/atomic.h"
-#include "jemalloc/internal/prng.h"
-#include "jemalloc/internal/ckh.h"
-#include "jemalloc/internal/size_classes.h"
-#include "jemalloc/internal/stats.h"
-#include "jemalloc/internal/ctl.h"
-#include "jemalloc/internal/mutex.h"
-#include "jemalloc/internal/mb.h"
-#include "jemalloc/internal/bitmap.h"
-#define JEMALLOC_ARENA_STRUCTS_A
-#include "jemalloc/internal/arena.h"
-#undef JEMALLOC_ARENA_STRUCTS_A
-#include "jemalloc/internal/extent.h"
-#define JEMALLOC_ARENA_STRUCTS_B
-#include "jemalloc/internal/arena.h"
-#undef JEMALLOC_ARENA_STRUCTS_B
-#include "jemalloc/internal/base.h"
-#include "jemalloc/internal/rtree.h"
-#include "jemalloc/internal/pages.h"
-#include "jemalloc/internal/chunk.h"
-#include "jemalloc/internal/huge.h"
-#include "jemalloc/internal/tcache.h"
-#include "jemalloc/internal/hash.h"
-#include "jemalloc/internal/quarantine.h"
-#include "jemalloc/internal/prof.h"
-
-#include "jemalloc/internal/tsd.h"
-
-#undef JEMALLOC_H_STRUCTS
-/******************************************************************************/
-#define JEMALLOC_H_EXTERNS
-
-extern bool opt_abort;
-extern const char *opt_junk;
-extern bool opt_junk_alloc;
-extern bool opt_junk_free;
-extern size_t opt_quarantine;
-extern bool opt_redzone;
-extern bool opt_utrace;
-extern bool opt_xmalloc;
-extern bool opt_zero;
-extern size_t opt_narenas;
-
-extern bool in_valgrind;
-
-/* Number of CPUs. */
-extern unsigned ncpus;
-
-/*
- * index2size_tab encodes the same information as could be computed (at
- * unacceptable cost in some code paths) by index2size_compute().
- */
-extern size_t const index2size_tab[NSIZES];
-/*
- * size2index_tab is a compact lookup table that rounds request sizes up to
- * size classes. In order to reduce cache footprint, the table is compressed,
- * and all accesses are via size2index().
- */
-extern uint8_t const size2index_tab[];
-
-arena_t *a0get(void);
-void *a0malloc(size_t size);
-void a0dalloc(void *ptr);
-void *bootstrap_malloc(size_t size);
-void *bootstrap_calloc(size_t num, size_t size);
-void bootstrap_free(void *ptr);
-arena_t *arenas_extend(unsigned ind);
-arena_t *arena_init(unsigned ind);
-unsigned narenas_total_get(void);
-arena_t *arena_get_hard(tsd_t *tsd, unsigned ind, bool init_if_missing);
-arena_t *arena_choose_hard(tsd_t *tsd);
-void arena_migrate(tsd_t *tsd, unsigned oldind, unsigned newind);
-unsigned arena_nbound(unsigned ind);
-void thread_allocated_cleanup(tsd_t *tsd);
-void thread_deallocated_cleanup(tsd_t *tsd);
-void arena_cleanup(tsd_t *tsd);
-void arenas_cache_cleanup(tsd_t *tsd);
-void narenas_cache_cleanup(tsd_t *tsd);
-void arenas_cache_bypass_cleanup(tsd_t *tsd);
-void jemalloc_prefork(void);
-void jemalloc_postfork_parent(void);
-void jemalloc_postfork_child(void);
-
-#include "jemalloc/internal/valgrind.h"
-#include "jemalloc/internal/util.h"
-#include "jemalloc/internal/atomic.h"
-#include "jemalloc/internal/prng.h"
-#include "jemalloc/internal/ckh.h"
-#include "jemalloc/internal/size_classes.h"
-#include "jemalloc/internal/stats.h"
-#include "jemalloc/internal/ctl.h"
-#include "jemalloc/internal/mutex.h"
-#include "jemalloc/internal/mb.h"
-#include "jemalloc/internal/bitmap.h"
-#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/arena.h"
-#include "jemalloc/internal/base.h"
-#include "jemalloc/internal/rtree.h"
-#include "jemalloc/internal/pages.h"
-#include "jemalloc/internal/chunk.h"
-#include "jemalloc/internal/huge.h"
-#include "jemalloc/internal/tcache.h"
-#include "jemalloc/internal/hash.h"
-#include "jemalloc/internal/quarantine.h"
-#include "jemalloc/internal/prof.h"
-#include "jemalloc/internal/tsd.h"
-
-#undef JEMALLOC_H_EXTERNS
-/******************************************************************************/
-#define JEMALLOC_H_INLINES
-
-#include "jemalloc/internal/valgrind.h"
-#include "jemalloc/internal/util.h"
-#include "jemalloc/internal/atomic.h"
-#include "jemalloc/internal/prng.h"
-#include "jemalloc/internal/ckh.h"
-#include "jemalloc/internal/size_classes.h"
-#include "jemalloc/internal/stats.h"
-#include "jemalloc/internal/ctl.h"
-#include "jemalloc/internal/mutex.h"
-#include "jemalloc/internal/tsd.h"
-#include "jemalloc/internal/mb.h"
-#include "jemalloc/internal/extent.h"
-#include "jemalloc/internal/base.h"
-#include "jemalloc/internal/rtree.h"
-#include "jemalloc/internal/pages.h"
-#include "jemalloc/internal/chunk.h"
-#include "jemalloc/internal/huge.h"
-
-#ifndef JEMALLOC_ENABLE_INLINE
-szind_t size2index_compute(size_t size);
-szind_t size2index_lookup(size_t size);
-szind_t size2index(size_t size);
-size_t index2size_compute(szind_t index);
-size_t index2size_lookup(szind_t index);
-size_t index2size(szind_t index);
-size_t s2u_compute(size_t size);
-size_t s2u_lookup(size_t size);
-size_t s2u(size_t size);
-size_t sa2u(size_t size, size_t alignment);
-arena_t *arena_choose(tsd_t *tsd, arena_t *arena);
-arena_t *arena_get(tsd_t *tsd, unsigned ind, bool init_if_missing,
- bool refresh_if_missing);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_C_))
-JEMALLOC_INLINE szind_t
-size2index_compute(size_t size)
-{
-
-#if (NTBINS != 0)
- if (size <= (ZU(1) << LG_TINY_MAXCLASS)) {
- size_t lg_tmin = LG_TINY_MAXCLASS - NTBINS + 1;
- size_t lg_ceil = lg_floor(pow2_ceil(size));
- return (lg_ceil < lg_tmin ? 0 : lg_ceil - lg_tmin);
- }
-#endif
- {
- size_t x = unlikely(ZI(size) < 0) ? ((size<<1) ?
- (ZU(1)<<(LG_SIZEOF_PTR+3)) : ((ZU(1)<<(LG_SIZEOF_PTR+3))-1))
- : lg_floor((size<<1)-1);
- size_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
- x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);
- size_t grp = shift << LG_SIZE_CLASS_GROUP;
-
- size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
- ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
-
- size_t delta_inverse_mask = ZI(-1) << lg_delta;
- size_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
- ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);
-
- size_t index = NTBINS + grp + mod;
- return (index);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE szind_t
-size2index_lookup(size_t size)
-{
-
- assert(size <= LOOKUP_MAXCLASS);
- {
- size_t ret = ((size_t)(size2index_tab[(size-1) >>
- LG_TINY_MIN]));
- assert(ret == size2index_compute(size));
- return (ret);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE szind_t
-size2index(size_t size)
-{
-
- assert(size > 0);
- if (likely(size <= LOOKUP_MAXCLASS))
- return (size2index_lookup(size));
- return (size2index_compute(size));
-}
-
-JEMALLOC_INLINE size_t
-index2size_compute(szind_t index)
-{
-
-#if (NTBINS > 0)
- if (index < NTBINS)
- return (ZU(1) << (LG_TINY_MAXCLASS - NTBINS + 1 + index));
-#endif
- {
- size_t reduced_index = index - NTBINS;
- size_t grp = reduced_index >> LG_SIZE_CLASS_GROUP;
- size_t mod = reduced_index & ((ZU(1) << LG_SIZE_CLASS_GROUP) -
- 1);
-
- size_t grp_size_mask = ~((!!grp)-1);
- size_t grp_size = ((ZU(1) << (LG_QUANTUM +
- (LG_SIZE_CLASS_GROUP-1))) << grp) & grp_size_mask;
-
- size_t shift = (grp == 0) ? 1 : grp;
- size_t lg_delta = shift + (LG_QUANTUM-1);
- size_t mod_size = (mod+1) << lg_delta;
-
- size_t usize = grp_size + mod_size;
- return (usize);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-index2size_lookup(szind_t index)
-{
- size_t ret = (size_t)index2size_tab[index];
- assert(ret == index2size_compute(index));
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-index2size(szind_t index)
-{
-
- assert(index < NSIZES);
- return (index2size_lookup(index));
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-s2u_compute(size_t size)
-{
-
-#if (NTBINS > 0)
- if (size <= (ZU(1) << LG_TINY_MAXCLASS)) {
- size_t lg_tmin = LG_TINY_MAXCLASS - NTBINS + 1;
- size_t lg_ceil = lg_floor(pow2_ceil(size));
- return (lg_ceil < lg_tmin ? (ZU(1) << lg_tmin) :
- (ZU(1) << lg_ceil));
- }
-#endif
- {
- size_t x = unlikely(ZI(size) < 0) ? ((size<<1) ?
- (ZU(1)<<(LG_SIZEOF_PTR+3)) : ((ZU(1)<<(LG_SIZEOF_PTR+3))-1))
- : lg_floor((size<<1)-1);
- size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
- ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
- size_t delta = ZU(1) << lg_delta;
- size_t delta_mask = delta - 1;
- size_t usize = (size + delta_mask) & ~delta_mask;
- return (usize);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-s2u_lookup(size_t size)
-{
- size_t ret = index2size_lookup(size2index_lookup(size));
-
- assert(ret == s2u_compute(size));
- return (ret);
-}
-
-/*
- * Compute usable size that would result from allocating an object with the
- * specified size.
- */
-JEMALLOC_ALWAYS_INLINE size_t
-s2u(size_t size)
-{
-
- assert(size > 0);
- if (likely(size <= LOOKUP_MAXCLASS))
- return (s2u_lookup(size));
- return (s2u_compute(size));
-}
-
-/*
- * Compute usable size that would result from allocating an object with the
- * specified size and alignment.
- */
-JEMALLOC_ALWAYS_INLINE size_t
-sa2u(size_t size, size_t alignment)
-{
- size_t usize;
-
- assert(alignment != 0 && ((alignment - 1) & alignment) == 0);
-
- /* Try for a small size class. */
- if (size <= SMALL_MAXCLASS && alignment < PAGE) {
- /*
- * Round size up to the nearest multiple of alignment.
- *
- * This done, we can take advantage of the fact that for each
- * small size class, every object is aligned at the smallest
- * power of two that is non-zero in the base two representation
- * of the size. For example:
- *
- * Size | Base 2 | Minimum alignment
- * -----+----------+------------------
- * 96 | 1100000 | 32
- * 144 | 10100000 | 32
- * 192 | 11000000 | 64
- */
- usize = s2u(ALIGNMENT_CEILING(size, alignment));
- if (usize < LARGE_MINCLASS)
- return (usize);
- }
-
- /* Try for a large size class. */
- if (likely(size <= large_maxclass) && likely(alignment < chunksize)) {
- /*
- * We can't achieve subpage alignment, so round up alignment
- * to the minimum that can actually be supported.
- */
- alignment = PAGE_CEILING(alignment);
-
- /* Make sure result is a large size class. */
- usize = (size <= LARGE_MINCLASS) ? LARGE_MINCLASS : s2u(size);
-
- /*
- * Calculate the size of the over-size run that arena_palloc()
- * would need to allocate in order to guarantee the alignment.
- */
- if (usize + large_pad + alignment - PAGE <= arena_maxrun)
- return (usize);
- }
-
- /* Huge size class. Beware of size_t overflow. */
-
- /*
- * We can't achieve subchunk alignment, so round up alignment to the
- * minimum that can actually be supported.
- */
- alignment = CHUNK_CEILING(alignment);
- if (alignment == 0) {
- /* size_t overflow. */
- return (0);
- }
-
- /* Make sure result is a huge size class. */
- if (size <= chunksize)
- usize = chunksize;
- else {
- usize = s2u(size);
- if (usize < size) {
- /* size_t overflow. */
- return (0);
- }
- }
-
- /*
- * Calculate the multi-chunk mapping that huge_palloc() would need in
- * order to guarantee the alignment.
- */
- if (usize + alignment - PAGE < usize) {
- /* size_t overflow. */
- return (0);
- }
- return (usize);
-}
-
-/* Choose an arena based on a per-thread value. */
-JEMALLOC_INLINE arena_t *
-arena_choose(tsd_t *tsd, arena_t *arena)
-{
- arena_t *ret;
-
- if (arena != NULL)
- return (arena);
-
- if (unlikely((ret = tsd_arena_get(tsd)) == NULL))
- ret = arena_choose_hard(tsd);
-
- return (ret);
-}
-
-JEMALLOC_INLINE arena_t *
-arena_get(tsd_t *tsd, unsigned ind, bool init_if_missing,
- bool refresh_if_missing)
-{
- arena_t *arena;
- arena_t **arenas_cache = tsd_arenas_cache_get(tsd);
-
- /* init_if_missing requires refresh_if_missing. */
- assert(!init_if_missing || refresh_if_missing);
-
- if (unlikely(arenas_cache == NULL)) {
- /* arenas_cache hasn't been initialized yet. */
- return (arena_get_hard(tsd, ind, init_if_missing));
- }
- if (unlikely(ind >= tsd_narenas_cache_get(tsd))) {
- /*
- * ind is invalid, cache is old (too small), or arena to be
- * initialized.
- */
- return (refresh_if_missing ? arena_get_hard(tsd, ind,
- init_if_missing) : NULL);
- }
- arena = arenas_cache[ind];
- if (likely(arena != NULL) || !refresh_if_missing)
- return (arena);
- return (arena_get_hard(tsd, ind, init_if_missing));
-}
-#endif
-
-#include "jemalloc/internal/bitmap.h"
-/*
- * Include portions of arena.h interleaved with tcache.h in order to resolve
- * circular dependencies.
- */
-#define JEMALLOC_ARENA_INLINE_A
-#include "jemalloc/internal/arena.h"
-#undef JEMALLOC_ARENA_INLINE_A
-#include "jemalloc/internal/tcache.h"
-#define JEMALLOC_ARENA_INLINE_B
-#include "jemalloc/internal/arena.h"
-#undef JEMALLOC_ARENA_INLINE_B
-#include "jemalloc/internal/hash.h"
-#include "jemalloc/internal/quarantine.h"
-
-#ifndef JEMALLOC_ENABLE_INLINE
-arena_t *iaalloc(const void *ptr);
-size_t isalloc(const void *ptr, bool demote);
-void *iallocztm(tsd_t *tsd, size_t size, bool zero, tcache_t *tcache,
- bool is_metadata, arena_t *arena);
-void *imalloct(tsd_t *tsd, size_t size, tcache_t *tcache, arena_t *arena);
-void *imalloc(tsd_t *tsd, size_t size);
-void *icalloct(tsd_t *tsd, size_t size, tcache_t *tcache, arena_t *arena);
-void *icalloc(tsd_t *tsd, size_t size);
-void *ipallocztm(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, bool is_metadata, arena_t *arena);
-void *ipalloct(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, arena_t *arena);
-void *ipalloc(tsd_t *tsd, size_t usize, size_t alignment, bool zero);
-size_t ivsalloc(const void *ptr, bool demote);
-size_t u2rz(size_t usize);
-size_t p2rz(const void *ptr);
-void idalloctm(tsd_t *tsd, void *ptr, tcache_t *tcache, bool is_metadata);
-void idalloct(tsd_t *tsd, void *ptr, tcache_t *tcache);
-void idalloc(tsd_t *tsd, void *ptr);
-void iqalloc(tsd_t *tsd, void *ptr, tcache_t *tcache);
-void isdalloct(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache);
-void isqalloc(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache);
-void *iralloct_realign(tsd_t *tsd, void *ptr, size_t oldsize, size_t size,
- size_t extra, size_t alignment, bool zero, tcache_t *tcache,
- arena_t *arena);
-void *iralloct(tsd_t *tsd, void *ptr, size_t oldsize, size_t size,
- size_t alignment, bool zero, tcache_t *tcache, arena_t *arena);
-void *iralloc(tsd_t *tsd, void *ptr, size_t oldsize, size_t size,
- size_t alignment, bool zero);
-bool ixalloc(void *ptr, size_t oldsize, size_t size, size_t extra,
- size_t alignment, bool zero);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_C_))
-JEMALLOC_ALWAYS_INLINE arena_t *
-iaalloc(const void *ptr)
-{
-
- assert(ptr != NULL);
-
- return (arena_aalloc(ptr));
-}
-
-/*
- * Typical usage:
- * void *ptr = [...]
- * size_t sz = isalloc(ptr, config_prof);
- */
-JEMALLOC_ALWAYS_INLINE size_t
-isalloc(const void *ptr, bool demote)
-{
-
- assert(ptr != NULL);
- /* Demotion only makes sense if config_prof is true. */
- assert(config_prof || !demote);
-
- return (arena_salloc(ptr, demote));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-iallocztm(tsd_t *tsd, size_t size, bool zero, tcache_t *tcache, bool is_metadata,
- arena_t *arena)
-{
- void *ret;
-
- assert(size != 0);
-
- ret = arena_malloc(tsd, arena, size, zero, tcache);
- if (config_stats && is_metadata && likely(ret != NULL)) {
- arena_metadata_allocated_add(iaalloc(ret), isalloc(ret,
- config_prof));
- }
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-imalloct(tsd_t *tsd, size_t size, tcache_t *tcache, arena_t *arena)
-{
-
- return (iallocztm(tsd, size, false, tcache, false, arena));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-imalloc(tsd_t *tsd, size_t size)
-{
-
- return (iallocztm(tsd, size, false, tcache_get(tsd, true), false, NULL));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-icalloct(tsd_t *tsd, size_t size, tcache_t *tcache, arena_t *arena)
-{
-
- return (iallocztm(tsd, size, true, tcache, false, arena));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-icalloc(tsd_t *tsd, size_t size)
-{
-
- return (iallocztm(tsd, size, true, tcache_get(tsd, true), false, NULL));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-ipallocztm(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, bool is_metadata, arena_t *arena)
-{
- void *ret;
-
- assert(usize != 0);
- assert(usize == sa2u(usize, alignment));
-
- ret = arena_palloc(tsd, arena, usize, alignment, zero, tcache);
- assert(ALIGNMENT_ADDR2BASE(ret, alignment) == ret);
- if (config_stats && is_metadata && likely(ret != NULL)) {
- arena_metadata_allocated_add(iaalloc(ret), isalloc(ret,
- config_prof));
- }
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-ipalloct(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, arena_t *arena)
-{
-
- return (ipallocztm(tsd, usize, alignment, zero, tcache, false, arena));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-ipalloc(tsd_t *tsd, size_t usize, size_t alignment, bool zero)
-{
-
- return (ipallocztm(tsd, usize, alignment, zero, tcache_get(tsd,
- NULL), false, NULL));
-}
-
-JEMALLOC_ALWAYS_INLINE size_t
-ivsalloc(const void *ptr, bool demote)
-{
- extent_node_t *node;
-
- /* Return 0 if ptr is not within a chunk managed by jemalloc. */
- node = chunk_lookup(ptr, false);
- if (node == NULL)
- return (0);
- /* Only arena chunks should be looked up via interior pointers. */
- assert(extent_node_addr_get(node) == ptr ||
- extent_node_achunk_get(node));
-
- return (isalloc(ptr, demote));
-}
-
-JEMALLOC_INLINE size_t
-u2rz(size_t usize)
-{
- size_t ret;
-
- if (usize <= SMALL_MAXCLASS) {
- szind_t binind = size2index(usize);
- ret = arena_bin_info[binind].redzone_size;
- } else
- ret = 0;
-
- return (ret);
-}
-
-JEMALLOC_INLINE size_t
-p2rz(const void *ptr)
-{
- size_t usize = isalloc(ptr, false);
-
- return (u2rz(usize));
-}
-
-JEMALLOC_ALWAYS_INLINE void
-idalloctm(tsd_t *tsd, void *ptr, tcache_t *tcache, bool is_metadata)
-{
-
- assert(ptr != NULL);
- if (config_stats && is_metadata) {
- arena_metadata_allocated_sub(iaalloc(ptr), isalloc(ptr,
- config_prof));
- }
-
- arena_dalloc(tsd, ptr, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-idalloct(tsd_t *tsd, void *ptr, tcache_t *tcache)
-{
-
- idalloctm(tsd, ptr, tcache, false);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-idalloc(tsd_t *tsd, void *ptr)
-{
-
- idalloctm(tsd, ptr, tcache_get(tsd, false), false);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-iqalloc(tsd_t *tsd, void *ptr, tcache_t *tcache)
-{
-
- if (config_fill && unlikely(opt_quarantine))
- quarantine(tsd, ptr);
- else
- idalloctm(tsd, ptr, tcache, false);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-isdalloct(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache)
-{
-
- arena_sdalloc(tsd, ptr, size, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-isqalloc(tsd_t *tsd, void *ptr, size_t size, tcache_t *tcache)
-{
-
- if (config_fill && unlikely(opt_quarantine))
- quarantine(tsd, ptr);
- else
- isdalloct(tsd, ptr, size, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-iralloct_realign(tsd_t *tsd, void *ptr, size_t oldsize, size_t size,
- size_t extra, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena)
-{
- void *p;
- size_t usize, copysize;
-
- usize = sa2u(size + extra, alignment);
- if (usize == 0)
- return (NULL);
- p = ipalloct(tsd, usize, alignment, zero, tcache, arena);
- if (p == NULL) {
- if (extra == 0)
- return (NULL);
- /* Try again, without extra this time. */
- usize = sa2u(size, alignment);
- if (usize == 0)
- return (NULL);
- p = ipalloct(tsd, usize, alignment, zero, tcache, arena);
- if (p == NULL)
- return (NULL);
- }
- /*
- * Copy at most size bytes (not size+extra), since the caller has no
- * expectation that the extra bytes will be reliably preserved.
- */
- copysize = (size < oldsize) ? size : oldsize;
- memcpy(p, ptr, copysize);
- isqalloc(tsd, ptr, oldsize, tcache);
- return (p);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-iralloct(tsd_t *tsd, void *ptr, size_t oldsize, size_t size, size_t alignment,
- bool zero, tcache_t *tcache, arena_t *arena)
-{
-
- assert(ptr != NULL);
- assert(size != 0);
-
- if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1))
- != 0) {
- /*
- * Existing object alignment is inadequate; allocate new space
- * and copy.
- */
- return (iralloct_realign(tsd, ptr, oldsize, size, 0, alignment,
- zero, tcache, arena));
- }
-
- return (arena_ralloc(tsd, arena, ptr, oldsize, size, alignment, zero,
- tcache));
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-iralloc(tsd_t *tsd, void *ptr, size_t oldsize, size_t size, size_t alignment,
- bool zero)
-{
-
- return (iralloct(tsd, ptr, oldsize, size, alignment, zero,
- tcache_get(tsd, true), NULL));
-}
-
-JEMALLOC_ALWAYS_INLINE bool
-ixalloc(void *ptr, size_t oldsize, size_t size, size_t extra, size_t alignment,
- bool zero)
-{
-
- assert(ptr != NULL);
- assert(size != 0);
-
- if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1))
- != 0) {
- /* Existing object alignment is inadequate. */
- return (true);
- }
-
- return (arena_ralloc_no_move(ptr, oldsize, size, extra, zero));
-}
-#endif
-
-#include "jemalloc/internal/prof.h"
-
-#undef JEMALLOC_H_INLINES
-/******************************************************************************/
-#endif /* JEMALLOC_INTERNAL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h
index a601d6ebb..be70df510 100644
--- a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_decls.h
@@ -1,11 +1,20 @@
#ifndef JEMALLOC_INTERNAL_DECLS_H
-#define JEMALLOC_INTERNAL_DECLS_H
+#define JEMALLOC_INTERNAL_DECLS_H
#include <math.h>
#ifdef _WIN32
# include <windows.h>
# include "msvc_compat/windows_extra.h"
-
+# ifdef _WIN64
+# if LG_VADDR <= 32
+# error Generate the headers using x64 vcargs
+# endif
+# else
+# if LG_VADDR > 32
+# undef LG_VADDR
+# define LG_VADDR 32
+# endif
+# endif
#else
# include <sys/param.h>
# include <sys/mman.h>
@@ -14,10 +23,27 @@
# if !defined(SYS_write) && defined(__NR_write)
# define SYS_write __NR_write
# endif
+# if defined(SYS_open) && defined(__aarch64__)
+ /* Android headers may define SYS_open to __NR_open even though
+ * __NR_open may not exist on AArch64 (superseded by __NR_openat). */
+# undef SYS_open
+# endif
# include <sys/uio.h>
# endif
# include <pthread.h>
+# include <signal.h>
+# ifdef JEMALLOC_OS_UNFAIR_LOCK
+# include <os/lock.h>
+# endif
+# ifdef JEMALLOC_GLIBC_MALLOC_HOOK
+# include <sched.h>
+# endif
# include <errno.h>
+# include <sys/time.h>
+# include <time.h>
+# ifdef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME
+# include <mach/mach_time.h>
+# endif
#endif
#include <sys/types.h>
@@ -25,6 +51,9 @@
#ifndef SIZE_T_MAX
# define SIZE_T_MAX SIZE_MAX
#endif
+#ifndef SSIZE_MAX
+# define SSIZE_MAX ((ssize_t)(SIZE_T_MAX >> 1))
+#endif
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
@@ -50,9 +79,7 @@ typedef intptr_t ssize_t;
# pragma warning(disable: 4996)
#if _MSC_VER < 1800
static int
-isblank(int c)
-{
-
+isblank(int c) {
return (c == '\t' || c == ' ');
}
#endif
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in
index b0f8caaf8..8dad9a1db 100644
--- a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_defs.h.in
@@ -1,5 +1,5 @@
#ifndef JEMALLOC_INTERNAL_DEFS_H_
-#define JEMALLOC_INTERNAL_DEFS_H_
+#define JEMALLOC_INTERNAL_DEFS_H_
/*
* If JEMALLOC_PREFIX is defined via --with-jemalloc-prefix, it will cause all
* public APIs to be prefixed. This makes it possible, with some care, to use
@@ -9,6 +9,18 @@
#undef JEMALLOC_CPREFIX
/*
+ * Define overrides for non-standard allocator-related functions if they are
+ * present on the system.
+ */
+#undef JEMALLOC_OVERRIDE___LIBC_CALLOC
+#undef JEMALLOC_OVERRIDE___LIBC_FREE
+#undef JEMALLOC_OVERRIDE___LIBC_MALLOC
+#undef JEMALLOC_OVERRIDE___LIBC_MEMALIGN
+#undef JEMALLOC_OVERRIDE___LIBC_REALLOC
+#undef JEMALLOC_OVERRIDE___LIBC_VALLOC
+#undef JEMALLOC_OVERRIDE___POSIX_MEMALIGN
+
+/*
* JEMALLOC_PRIVATE_NAMESPACE is used as a prefix for all library-private APIs.
* For shared libraries, symbol visibility mechanisms prevent these symbols
* from being exported, but for static libraries, naming collisions are a real
@@ -21,18 +33,24 @@
* order to yield to another virtual CPU.
*/
#undef CPU_SPINWAIT
+/* 1 if CPU_SPINWAIT is defined, 0 otherwise. */
+#undef HAVE_CPU_SPINWAIT
+
+/*
+ * Number of significant bits in virtual addresses. This may be less than the
+ * total number of bits in a pointer, e.g. on x64, for which the uppermost 16
+ * bits are the same as bit 47.
+ */
+#undef LG_VADDR
/* Defined if C11 atomics are available. */
-#undef JEMALLOC_C11ATOMICS
+#undef JEMALLOC_C11_ATOMICS
-/* Defined if the equivalent of FreeBSD's atomic(9) functions are available. */
-#undef JEMALLOC_ATOMIC9
+/* Defined if GCC __atomic atomics are available. */
+#undef JEMALLOC_GCC_ATOMIC_ATOMICS
-/*
- * Defined if OSAtomic*() functions are available, as provided by Darwin, and
- * documented in the atomic(3) manual page.
- */
-#undef JEMALLOC_OSATOMIC
+/* Defined if GCC __sync atomics are available. */
+#undef JEMALLOC_GCC_SYNC_ATOMICS
/*
* Defined if __sync_add_and_fetch(uint32_t *, uint32_t) and
@@ -56,9 +74,9 @@
#undef JEMALLOC_HAVE_BUILTIN_CLZ
/*
- * Defined if madvise(2) is available.
+ * Defined if os_unfair_lock_*() functions are available, as provided by Darwin.
*/
-#undef JEMALLOC_HAVE_MADVISE
+#undef JEMALLOC_OS_UNFAIR_LOCK
/*
* Defined if OSSpin*() functions are available, as provided by Darwin, and
@@ -66,6 +84,9 @@
*/
#undef JEMALLOC_OSSPIN
+/* Defined if syscall(2) is usable. */
+#undef JEMALLOC_USE_SYSCALL
+
/*
* Defined if secure_getenv(3) is available.
*/
@@ -76,6 +97,27 @@
*/
#undef JEMALLOC_HAVE_ISSETUGID
+/* Defined if pthread_atfork(3) is available. */
+#undef JEMALLOC_HAVE_PTHREAD_ATFORK
+
+/* Defined if pthread_setname_np(3) is available. */
+#undef JEMALLOC_HAVE_PTHREAD_SETNAME_NP
+
+/*
+ * Defined if clock_gettime(CLOCK_MONOTONIC_COARSE, ...) is available.
+ */
+#undef JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE
+
+/*
+ * Defined if clock_gettime(CLOCK_MONOTONIC, ...) is available.
+ */
+#undef JEMALLOC_HAVE_CLOCK_MONOTONIC
+
+/*
+ * Defined if mach_absolute_time() is available.
+ */
+#undef JEMALLOC_HAVE_MACH_ABSOLUTE_TIME
+
/*
* Defined if _malloc_thread_cleanup() exists. At least in the case of
* FreeBSD, pthread_key_create() allocates, which if used during malloc
@@ -102,12 +144,6 @@
/* Non-empty if the tls_model attribute is supported. */
#undef JEMALLOC_TLS_MODEL
-/* JEMALLOC_CC_SILENCE enables code that silences unuseful compiler warnings. */
-#undef JEMALLOC_CC_SILENCE
-
-/* JEMALLOC_CODE_COVERAGE enables test code coverage analysis. */
-#undef JEMALLOC_CODE_COVERAGE
-
/*
* JEMALLOC_DEBUG enables assertions and other sanity checks, and disables
* inline functions.
@@ -130,36 +166,23 @@
#undef JEMALLOC_PROF_GCC
/*
- * JEMALLOC_TCACHE enables a thread-specific caching layer for small objects.
- * This makes it possible to allocate/deallocate objects without any locking
- * when the cache is in the steady state.
- */
-#undef JEMALLOC_TCACHE
-
-/*
- * JEMALLOC_DSS enables use of sbrk(2) to allocate chunks from the data storage
+ * JEMALLOC_DSS enables use of sbrk(2) to allocate extents from the data storage
* segment (DSS).
*/
#undef JEMALLOC_DSS
-/* Support memory filling (junk/zero/quarantine/redzone). */
+/* Support memory filling (junk/zero). */
#undef JEMALLOC_FILL
/* Support utrace(2)-based tracing. */
#undef JEMALLOC_UTRACE
-/* Support Valgrind. */
-#undef JEMALLOC_VALGRIND
-
/* Support optional abort() on OOM. */
#undef JEMALLOC_XMALLOC
/* Support lazy locking (avoid locking unless a second thread is launched). */
#undef JEMALLOC_LAZY_LOCK
-/* Minimum size class to support is 2^LG_TINY_MIN bytes. */
-#undef LG_TINY_MIN
-
/*
* Minimum allocation alignment is 2^LG_QUANTUM bytes (ignoring tiny size
* classes).
@@ -170,6 +193,13 @@
#undef LG_PAGE
/*
+ * One huge page is 2^LG_HUGEPAGE bytes. Note that this is defined even if the
+ * system does not explicitly support huge pages; system calls that require
+ * explicit huge page support are separately configured.
+ */
+#undef LG_HUGEPAGE
+
+/*
* If defined, adjacent virtual memory mappings with identical attributes
* automatically coalesce, and they fragment when changes are made to subranges.
* This is the normal order of things for mmap()/munmap(), but on Windows
@@ -179,27 +209,29 @@
#undef JEMALLOC_MAPS_COALESCE
/*
- * If defined, use munmap() to unmap freed chunks, rather than storing them for
- * later reuse. This is disabled by default on Linux because common sequences
- * of mmap()/munmap() calls will cause virtual memory map holes.
+ * If defined, retain memory for later reuse by default rather than using e.g.
+ * munmap() to unmap freed extents. This is enabled on 64-bit Linux because
+ * common sequences of mmap()/munmap() calls will cause virtual memory map
+ * holes.
*/
-#undef JEMALLOC_MUNMAP
+#undef JEMALLOC_RETAIN
/* TLS is used to map arenas and magazine caches to threads. */
#undef JEMALLOC_TLS
/*
- * ffs()/ffsl() functions to use for bitmapping. Don't use these directly;
- * instead, use jemalloc_ffs() or jemalloc_ffsl() from util.h.
+ * Used to mark unreachable code to quiet "end of non-void" compiler warnings.
+ * Don't use this directly; instead use unreachable() from util.h
*/
-#undef JEMALLOC_INTERNAL_FFSL
-#undef JEMALLOC_INTERNAL_FFS
+#undef JEMALLOC_INTERNAL_UNREACHABLE
/*
- * JEMALLOC_IVSALLOC enables ivsalloc(), which verifies that pointers reside
- * within jemalloc-owned chunks before dereferencing them.
+ * ffs*() functions to use for bitmapping. Don't use these directly; instead,
+ * use ffs_*() from util.h.
*/
-#undef JEMALLOC_IVSALLOC
+#undef JEMALLOC_INTERNAL_FFSLL
+#undef JEMALLOC_INTERNAL_FFSL
+#undef JEMALLOC_INTERNAL_FFS
/*
* If defined, explicitly attempt to more uniformly distribute large allocation
@@ -208,23 +240,64 @@
#undef JEMALLOC_CACHE_OBLIVIOUS
/*
+ * If defined, enable logging facilities. We make this a configure option to
+ * avoid taking extra branches everywhere.
+ */
+#undef JEMALLOC_LOG
+
+/*
* Darwin (OS X) uses zones to work around Mach-O symbol override shortcomings.
*/
#undef JEMALLOC_ZONE
-#undef JEMALLOC_ZONE_VERSION
+
+/*
+ * Methods for determining whether the OS overcommits.
+ * JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY: Linux's
+ * /proc/sys/vm.overcommit_memory file.
+ * JEMALLOC_SYSCTL_VM_OVERCOMMIT: FreeBSD's vm.overcommit sysctl.
+ */
+#undef JEMALLOC_SYSCTL_VM_OVERCOMMIT
+#undef JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY
+
+/* Defined if madvise(2) is available. */
+#undef JEMALLOC_HAVE_MADVISE
+
+/*
+ * Defined if transparent huge pages are supported via the MADV_[NO]HUGEPAGE
+ * arguments to madvise(2).
+ */
+#undef JEMALLOC_HAVE_MADVISE_HUGE
/*
* Methods for purging unused pages differ between operating systems.
*
- * madvise(..., MADV_DONTNEED) : On Linux, this immediately discards pages,
+ * madvise(..., MADV_FREE) : This marks pages as being unused, such that they
+ * will be discarded rather than swapped out.
+ * madvise(..., MADV_DONTNEED) : If JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS is
+ * defined, this immediately discards pages,
* such that new pages will be demand-zeroed if
- * the address region is later touched.
- * madvise(..., MADV_FREE) : On FreeBSD and Darwin, this marks pages as being
- * unused, such that they will be discarded rather
- * than swapped out.
+ * the address region is later touched;
+ * otherwise this behaves similarly to
+ * MADV_FREE, though typically with higher
+ * system overhead.
*/
-#undef JEMALLOC_PURGE_MADVISE_DONTNEED
#undef JEMALLOC_PURGE_MADVISE_FREE
+#undef JEMALLOC_PURGE_MADVISE_DONTNEED
+#undef JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS
+
+/* Defined if madvise(2) is available but MADV_FREE is not (x86 Linux only). */
+#undef JEMALLOC_DEFINE_MADVISE_FREE
+
+/*
+ * Defined if MADV_DO[NT]DUMP is supported as an argument to madvise.
+ */
+#undef JEMALLOC_MADVISE_DONTDUMP
+
+/*
+ * Defined if transparent huge pages (THPs) are supported via the
+ * MADV_[NO]HUGEPAGE arguments to madvise(2), and THP support is enabled.
+ */
+#undef JEMALLOC_THP
/* Define if operating system has alloca.h header. */
#undef JEMALLOC_HAS_ALLOCA_H
@@ -241,6 +314,9 @@
/* sizeof(long) == 2^LG_SIZEOF_LONG. */
#undef LG_SIZEOF_LONG
+/* sizeof(long long) == 2^LG_SIZEOF_LONG_LONG. */
+#undef LG_SIZEOF_LONG_LONG
+
/* sizeof(intmax_t) == 2^LG_SIZEOF_INTMAX_T. */
#undef LG_SIZEOF_INTMAX_T
@@ -250,13 +326,41 @@
/* glibc memalign hook. */
#undef JEMALLOC_GLIBC_MEMALIGN_HOOK
+/* pthread support */
+#undef JEMALLOC_HAVE_PTHREAD
+
+/* dlsym() support */
+#undef JEMALLOC_HAVE_DLSYM
+
/* Adaptive mutex support in pthreads. */
#undef JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP
+/* GNU specific sched_getcpu support */
+#undef JEMALLOC_HAVE_SCHED_GETCPU
+
+/* GNU specific sched_setaffinity support */
+#undef JEMALLOC_HAVE_SCHED_SETAFFINITY
+
+/*
+ * If defined, all the features necessary for background threads are present.
+ */
+#undef JEMALLOC_BACKGROUND_THREAD
+
/*
* If defined, jemalloc symbols are not exported (doesn't work when
* JEMALLOC_PREFIX is not defined).
*/
#undef JEMALLOC_EXPORT
+/* config.malloc_conf options string. */
+#undef JEMALLOC_CONFIG_MALLOC_CONF
+
+/* If defined, jemalloc takes the malloc/free/etc. symbol names. */
+#undef JEMALLOC_IS_MALLOC
+
+/*
+ * Defined if strerror_r returns char * if _GNU_SOURCE is defined.
+ */
+#undef JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE
+
#endif /* JEMALLOC_INTERNAL_DEFS_H_ */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_externs.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_externs.h
new file mode 100644
index 000000000..e10fb275d
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_externs.h
@@ -0,0 +1,53 @@
+#ifndef JEMALLOC_INTERNAL_EXTERNS_H
+#define JEMALLOC_INTERNAL_EXTERNS_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/tsd_types.h"
+
+/* TSD checks this to set thread local slow state accordingly. */
+extern bool malloc_slow;
+
+/* Run-time options. */
+extern bool opt_abort;
+extern bool opt_abort_conf;
+extern const char *opt_junk;
+extern bool opt_junk_alloc;
+extern bool opt_junk_free;
+extern bool opt_utrace;
+extern bool opt_xmalloc;
+extern bool opt_zero;
+extern unsigned opt_narenas;
+
+/* Number of CPUs. */
+extern unsigned ncpus;
+
+/* Number of arenas used for automatic multiplexing of threads and arenas. */
+extern unsigned narenas_auto;
+
+/*
+ * Arenas that are used to service external requests. Not all elements of the
+ * arenas array are necessarily used; arenas are created lazily as needed.
+ */
+extern atomic_p_t arenas[];
+
+void *a0malloc(size_t size);
+void a0dalloc(void *ptr);
+void *bootstrap_malloc(size_t size);
+void *bootstrap_calloc(size_t num, size_t size);
+void bootstrap_free(void *ptr);
+void arena_set(unsigned ind, arena_t *arena);
+unsigned narenas_total_get(void);
+arena_t *arena_init(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks);
+arena_tdata_t *arena_tdata_get_hard(tsd_t *tsd, unsigned ind);
+arena_t *arena_choose_hard(tsd_t *tsd, bool internal);
+void arena_migrate(tsd_t *tsd, unsigned oldind, unsigned newind);
+void iarena_cleanup(tsd_t *tsd);
+void arena_cleanup(tsd_t *tsd);
+void arenas_tdata_cleanup(tsd_t *tsd);
+void jemalloc_prefork(void);
+void jemalloc_postfork_parent(void);
+void jemalloc_postfork_child(void);
+bool malloc_initialized(void);
+
+#endif /* JEMALLOC_INTERNAL_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_includes.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_includes.h
new file mode 100644
index 000000000..437eaa407
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_includes.h
@@ -0,0 +1,94 @@
+#ifndef JEMALLOC_INTERNAL_INCLUDES_H
+#define JEMALLOC_INTERNAL_INCLUDES_H
+
+/*
+ * jemalloc can conceptually be broken into components (arena, tcache, etc.),
+ * but there are circular dependencies that cannot be broken without
+ * substantial performance degradation.
+ *
+ * Historically, we dealt with this by each header into four sections (types,
+ * structs, externs, and inlines), and included each header file multiple times
+ * in this file, picking out the portion we want on each pass using the
+ * following #defines:
+ * JEMALLOC_H_TYPES : Preprocessor-defined constants and psuedo-opaque data
+ * types.
+ * JEMALLOC_H_STRUCTS : Data structures.
+ * JEMALLOC_H_EXTERNS : Extern data declarations and function prototypes.
+ * JEMALLOC_H_INLINES : Inline functions.
+ *
+ * We're moving toward a world in which the dependencies are explicit; each file
+ * will #include the headers it depends on (rather than relying on them being
+ * implicitly available via this file including every header file in the
+ * project).
+ *
+ * We're now in an intermediate state: we've broken up the header files to avoid
+ * having to include each one multiple times, but have not yet moved the
+ * dependency information into the header files (i.e. we still rely on the
+ * ordering in this file to ensure all a header's dependencies are available in
+ * its translation unit). Each component is now broken up into multiple header
+ * files, corresponding to the sections above (e.g. instead of "foo.h", we now
+ * have "foo_types.h", "foo_structs.h", "foo_externs.h", "foo_inlines.h").
+ *
+ * Those files which have been converted to explicitly include their
+ * inter-component dependencies are now in the initial HERMETIC HEADERS
+ * section. All headers may still rely on jemalloc_preamble.h (which, by fiat,
+ * must be included first in every translation unit) for system headers and
+ * global jemalloc definitions, however.
+ */
+
+/******************************************************************************/
+/* TYPES */
+/******************************************************************************/
+
+#include "jemalloc/internal/extent_types.h"
+#include "jemalloc/internal/base_types.h"
+#include "jemalloc/internal/arena_types.h"
+#include "jemalloc/internal/tcache_types.h"
+#include "jemalloc/internal/prof_types.h"
+
+/******************************************************************************/
+/* STRUCTS */
+/******************************************************************************/
+
+#include "jemalloc/internal/arena_structs_a.h"
+#include "jemalloc/internal/extent_structs.h"
+#include "jemalloc/internal/base_structs.h"
+#include "jemalloc/internal/prof_structs.h"
+#include "jemalloc/internal/arena_structs_b.h"
+#include "jemalloc/internal/tcache_structs.h"
+#include "jemalloc/internal/background_thread_structs.h"
+
+/******************************************************************************/
+/* EXTERNS */
+/******************************************************************************/
+
+#include "jemalloc/internal/jemalloc_internal_externs.h"
+#include "jemalloc/internal/extent_externs.h"
+#include "jemalloc/internal/base_externs.h"
+#include "jemalloc/internal/arena_externs.h"
+#include "jemalloc/internal/large_externs.h"
+#include "jemalloc/internal/tcache_externs.h"
+#include "jemalloc/internal/prof_externs.h"
+#include "jemalloc/internal/background_thread_externs.h"
+
+/******************************************************************************/
+/* INLINES */
+/******************************************************************************/
+
+#include "jemalloc/internal/jemalloc_internal_inlines_a.h"
+#include "jemalloc/internal/base_inlines.h"
+/*
+ * Include portions of arena code interleaved with tcache code in order to
+ * resolve circular dependencies.
+ */
+#include "jemalloc/internal/prof_inlines_a.h"
+#include "jemalloc/internal/arena_inlines_a.h"
+#include "jemalloc/internal/extent_inlines.h"
+#include "jemalloc/internal/jemalloc_internal_inlines_b.h"
+#include "jemalloc/internal/tcache_inlines.h"
+#include "jemalloc/internal/arena_inlines_b.h"
+#include "jemalloc/internal/jemalloc_internal_inlines_c.h"
+#include "jemalloc/internal/prof_inlines_b.h"
+#include "jemalloc/internal/background_thread_inlines.h"
+
+#endif /* JEMALLOC_INTERNAL_INCLUDES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_a.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_a.h
new file mode 100644
index 000000000..c6a1f7eb2
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_a.h
@@ -0,0 +1,172 @@
+#ifndef JEMALLOC_INTERNAL_INLINES_A_H
+#define JEMALLOC_INTERNAL_INLINES_A_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/bit_util.h"
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/ticker.h"
+
+JEMALLOC_ALWAYS_INLINE malloc_cpuid_t
+malloc_getcpu(void) {
+ assert(have_percpu_arena);
+#if defined(JEMALLOC_HAVE_SCHED_GETCPU)
+ return (malloc_cpuid_t)sched_getcpu();
+#else
+ not_reached();
+ return -1;
+#endif
+}
+
+/* Return the chosen arena index based on current cpu. */
+JEMALLOC_ALWAYS_INLINE unsigned
+percpu_arena_choose(void) {
+ assert(have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena));
+
+ malloc_cpuid_t cpuid = malloc_getcpu();
+ assert(cpuid >= 0);
+
+ unsigned arena_ind;
+ if ((opt_percpu_arena == percpu_arena) || ((unsigned)cpuid < ncpus /
+ 2)) {
+ arena_ind = cpuid;
+ } else {
+ assert(opt_percpu_arena == per_phycpu_arena);
+ /* Hyper threads on the same physical CPU share arena. */
+ arena_ind = cpuid - ncpus / 2;
+ }
+
+ return arena_ind;
+}
+
+/* Return the limit of percpu auto arena range, i.e. arenas[0...ind_limit). */
+JEMALLOC_ALWAYS_INLINE unsigned
+percpu_arena_ind_limit(percpu_arena_mode_t mode) {
+ assert(have_percpu_arena && PERCPU_ARENA_ENABLED(mode));
+ if (mode == per_phycpu_arena && ncpus > 1) {
+ if (ncpus % 2) {
+ /* This likely means a misconfig. */
+ return ncpus / 2 + 1;
+ }
+ return ncpus / 2;
+ } else {
+ return ncpus;
+ }
+}
+
+static inline arena_tdata_t *
+arena_tdata_get(tsd_t *tsd, unsigned ind, bool refresh_if_missing) {
+ arena_tdata_t *tdata;
+ arena_tdata_t *arenas_tdata = tsd_arenas_tdata_get(tsd);
+
+ if (unlikely(arenas_tdata == NULL)) {
+ /* arenas_tdata hasn't been initialized yet. */
+ return arena_tdata_get_hard(tsd, ind);
+ }
+ if (unlikely(ind >= tsd_narenas_tdata_get(tsd))) {
+ /*
+ * ind is invalid, cache is old (too small), or tdata to be
+ * initialized.
+ */
+ return (refresh_if_missing ? arena_tdata_get_hard(tsd, ind) :
+ NULL);
+ }
+
+ tdata = &arenas_tdata[ind];
+ if (likely(tdata != NULL) || !refresh_if_missing) {
+ return tdata;
+ }
+ return arena_tdata_get_hard(tsd, ind);
+}
+
+static inline arena_t *
+arena_get(tsdn_t *tsdn, unsigned ind, bool init_if_missing) {
+ arena_t *ret;
+
+ assert(ind < MALLOCX_ARENA_LIMIT);
+
+ ret = (arena_t *)atomic_load_p(&arenas[ind], ATOMIC_ACQUIRE);
+ if (unlikely(ret == NULL)) {
+ if (init_if_missing) {
+ ret = arena_init(tsdn, ind,
+ (extent_hooks_t *)&extent_hooks_default);
+ }
+ }
+ return ret;
+}
+
+static inline ticker_t *
+decay_ticker_get(tsd_t *tsd, unsigned ind) {
+ arena_tdata_t *tdata;
+
+ tdata = arena_tdata_get(tsd, ind, true);
+ if (unlikely(tdata == NULL)) {
+ return NULL;
+ }
+ return &tdata->decay_ticker;
+}
+
+JEMALLOC_ALWAYS_INLINE cache_bin_t *
+tcache_small_bin_get(tcache_t *tcache, szind_t binind) {
+ assert(binind < NBINS);
+ return &tcache->bins_small[binind];
+}
+
+JEMALLOC_ALWAYS_INLINE cache_bin_t *
+tcache_large_bin_get(tcache_t *tcache, szind_t binind) {
+ assert(binind >= NBINS &&binind < nhbins);
+ return &tcache->bins_large[binind - NBINS];
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tcache_available(tsd_t *tsd) {
+ /*
+ * Thread specific auto tcache might be unavailable if: 1) during tcache
+ * initialization, or 2) disabled through thread.tcache.enabled mallctl
+ * or config options. This check covers all cases.
+ */
+ if (likely(tsd_tcache_enabled_get(tsd))) {
+ /* Associated arena == NULL implies tcache init in progress. */
+ assert(tsd_tcachep_get(tsd)->arena == NULL ||
+ tcache_small_bin_get(tsd_tcachep_get(tsd), 0)->avail !=
+ NULL);
+ return true;
+ }
+
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE tcache_t *
+tcache_get(tsd_t *tsd) {
+ if (!tcache_available(tsd)) {
+ return NULL;
+ }
+
+ return tsd_tcachep_get(tsd);
+}
+
+static inline void
+pre_reentrancy(tsd_t *tsd, arena_t *arena) {
+ /* arena is the current context. Reentry from a0 is not allowed. */
+ assert(arena != arena_get(tsd_tsdn(tsd), 0, false));
+
+ bool fast = tsd_fast(tsd);
+ assert(tsd_reentrancy_level_get(tsd) < INT8_MAX);
+ ++*tsd_reentrancy_levelp_get(tsd);
+ if (fast) {
+ /* Prepare slow path for reentrancy. */
+ tsd_slow_update(tsd);
+ assert(tsd->state == tsd_state_nominal_slow);
+ }
+}
+
+static inline void
+post_reentrancy(tsd_t *tsd) {
+ int8_t *reentrancy_level = tsd_reentrancy_levelp_get(tsd);
+ assert(*reentrancy_level > 0);
+ if (--*reentrancy_level == 0) {
+ tsd_slow_update(tsd);
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_INLINES_A_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_b.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_b.h
new file mode 100644
index 000000000..2e76e5d8f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_b.h
@@ -0,0 +1,86 @@
+#ifndef JEMALLOC_INTERNAL_INLINES_B_H
+#define JEMALLOC_INTERNAL_INLINES_B_H
+
+#include "jemalloc/internal/rtree.h"
+
+/* Choose an arena based on a per-thread value. */
+static inline arena_t *
+arena_choose_impl(tsd_t *tsd, arena_t *arena, bool internal) {
+ arena_t *ret;
+
+ if (arena != NULL) {
+ return arena;
+ }
+
+ /* During reentrancy, arena 0 is the safest bet. */
+ if (unlikely(tsd_reentrancy_level_get(tsd) > 0)) {
+ return arena_get(tsd_tsdn(tsd), 0, true);
+ }
+
+ ret = internal ? tsd_iarena_get(tsd) : tsd_arena_get(tsd);
+ if (unlikely(ret == NULL)) {
+ ret = arena_choose_hard(tsd, internal);
+ assert(ret);
+ if (tcache_available(tsd)) {
+ tcache_t *tcache = tcache_get(tsd);
+ if (tcache->arena != NULL) {
+ /* See comments in tcache_data_init().*/
+ assert(tcache->arena ==
+ arena_get(tsd_tsdn(tsd), 0, false));
+ if (tcache->arena != ret) {
+ tcache_arena_reassociate(tsd_tsdn(tsd),
+ tcache, ret);
+ }
+ } else {
+ tcache_arena_associate(tsd_tsdn(tsd), tcache,
+ ret);
+ }
+ }
+ }
+
+ /*
+ * Note that for percpu arena, if the current arena is outside of the
+ * auto percpu arena range, (i.e. thread is assigned to a manually
+ * managed arena), then percpu arena is skipped.
+ */
+ if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena) &&
+ !internal && (arena_ind_get(ret) <
+ percpu_arena_ind_limit(opt_percpu_arena)) && (ret->last_thd !=
+ tsd_tsdn(tsd))) {
+ unsigned ind = percpu_arena_choose();
+ if (arena_ind_get(ret) != ind) {
+ percpu_arena_update(tsd, ind);
+ ret = tsd_arena_get(tsd);
+ }
+ ret->last_thd = tsd_tsdn(tsd);
+ }
+
+ return ret;
+}
+
+static inline arena_t *
+arena_choose(tsd_t *tsd, arena_t *arena) {
+ return arena_choose_impl(tsd, arena, false);
+}
+
+static inline arena_t *
+arena_ichoose(tsd_t *tsd, arena_t *arena) {
+ return arena_choose_impl(tsd, arena, true);
+}
+
+static inline bool
+arena_is_auto(arena_t *arena) {
+ assert(narenas_auto > 0);
+ return (arena_ind_get(arena) < narenas_auto);
+}
+
+JEMALLOC_ALWAYS_INLINE extent_t *
+iealloc(tsdn_t *tsdn, const void *ptr) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ return rtree_extent_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true);
+}
+
+#endif /* JEMALLOC_INTERNAL_INLINES_B_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h
new file mode 100644
index 000000000..290e5cf99
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_inlines_c.h
@@ -0,0 +1,246 @@
+#ifndef JEMALLOC_INTERNAL_INLINES_C_H
+#define JEMALLOC_INTERNAL_INLINES_C_H
+
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/sz.h"
+#include "jemalloc/internal/witness.h"
+
+/*
+ * Translating the names of the 'i' functions:
+ * Abbreviations used in the first part of the function name (before
+ * alloc/dalloc) describe what that function accomplishes:
+ * a: arena (query)
+ * s: size (query, or sized deallocation)
+ * e: extent (query)
+ * p: aligned (allocates)
+ * vs: size (query, without knowing that the pointer is into the heap)
+ * r: rallocx implementation
+ * x: xallocx implementation
+ * Abbreviations used in the second part of the function name (after
+ * alloc/dalloc) describe the arguments it takes
+ * z: whether to return zeroed memory
+ * t: accepts a tcache_t * parameter
+ * m: accepts an arena_t * parameter
+ */
+
+JEMALLOC_ALWAYS_INLINE arena_t *
+iaalloc(tsdn_t *tsdn, const void *ptr) {
+ assert(ptr != NULL);
+
+ return arena_aalloc(tsdn, ptr);
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+isalloc(tsdn_t *tsdn, const void *ptr) {
+ assert(ptr != NULL);
+
+ return arena_salloc(tsdn, ptr);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+iallocztm(tsdn_t *tsdn, size_t size, szind_t ind, bool zero, tcache_t *tcache,
+ bool is_internal, arena_t *arena, bool slow_path) {
+ void *ret;
+
+ assert(size != 0);
+ assert(!is_internal || tcache == NULL);
+ assert(!is_internal || arena == NULL || arena_is_auto(arena));
+ if (!tsdn_null(tsdn) && tsd_reentrancy_level_get(tsdn_tsd(tsdn)) == 0) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+ }
+
+ ret = arena_malloc(tsdn, arena, size, ind, zero, tcache, slow_path);
+ if (config_stats && is_internal && likely(ret != NULL)) {
+ arena_internal_add(iaalloc(tsdn, ret), isalloc(tsdn, ret));
+ }
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+ialloc(tsd_t *tsd, size_t size, szind_t ind, bool zero, bool slow_path) {
+ return iallocztm(tsd_tsdn(tsd), size, ind, zero, tcache_get(tsd), false,
+ NULL, slow_path);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+ipallocztm(tsdn_t *tsdn, size_t usize, size_t alignment, bool zero,
+ tcache_t *tcache, bool is_internal, arena_t *arena) {
+ void *ret;
+
+ assert(usize != 0);
+ assert(usize == sz_sa2u(usize, alignment));
+ assert(!is_internal || tcache == NULL);
+ assert(!is_internal || arena == NULL || arena_is_auto(arena));
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ ret = arena_palloc(tsdn, arena, usize, alignment, zero, tcache);
+ assert(ALIGNMENT_ADDR2BASE(ret, alignment) == ret);
+ if (config_stats && is_internal && likely(ret != NULL)) {
+ arena_internal_add(iaalloc(tsdn, ret), isalloc(tsdn, ret));
+ }
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+ipalloct(tsdn_t *tsdn, size_t usize, size_t alignment, bool zero,
+ tcache_t *tcache, arena_t *arena) {
+ return ipallocztm(tsdn, usize, alignment, zero, tcache, false, arena);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+ipalloc(tsd_t *tsd, size_t usize, size_t alignment, bool zero) {
+ return ipallocztm(tsd_tsdn(tsd), usize, alignment, zero,
+ tcache_get(tsd), false, NULL);
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+ivsalloc(tsdn_t *tsdn, const void *ptr) {
+ return arena_vsalloc(tsdn, ptr);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+idalloctm(tsdn_t *tsdn, void *ptr, tcache_t *tcache, alloc_ctx_t *alloc_ctx,
+ bool is_internal, bool slow_path) {
+ assert(ptr != NULL);
+ assert(!is_internal || tcache == NULL);
+ assert(!is_internal || arena_is_auto(iaalloc(tsdn, ptr)));
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+ if (config_stats && is_internal) {
+ arena_internal_sub(iaalloc(tsdn, ptr), isalloc(tsdn, ptr));
+ }
+ if (!is_internal && !tsdn_null(tsdn) &&
+ tsd_reentrancy_level_get(tsdn_tsd(tsdn)) != 0) {
+ assert(tcache == NULL);
+ }
+ arena_dalloc(tsdn, ptr, tcache, alloc_ctx, slow_path);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+idalloc(tsd_t *tsd, void *ptr) {
+ idalloctm(tsd_tsdn(tsd), ptr, tcache_get(tsd), NULL, false, true);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+isdalloct(tsdn_t *tsdn, void *ptr, size_t size, tcache_t *tcache,
+ alloc_ctx_t *alloc_ctx, bool slow_path) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+ arena_sdalloc(tsdn, ptr, size, tcache, alloc_ctx, slow_path);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+iralloct_realign(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size,
+ size_t extra, size_t alignment, bool zero, tcache_t *tcache,
+ arena_t *arena) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+ void *p;
+ size_t usize, copysize;
+
+ usize = sz_sa2u(size + extra, alignment);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
+ return NULL;
+ }
+ p = ipalloct(tsdn, usize, alignment, zero, tcache, arena);
+ if (p == NULL) {
+ if (extra == 0) {
+ return NULL;
+ }
+ /* Try again, without extra this time. */
+ usize = sz_sa2u(size, alignment);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
+ return NULL;
+ }
+ p = ipalloct(tsdn, usize, alignment, zero, tcache, arena);
+ if (p == NULL) {
+ return NULL;
+ }
+ }
+ /*
+ * Copy at most size bytes (not size+extra), since the caller has no
+ * expectation that the extra bytes will be reliably preserved.
+ */
+ copysize = (size < oldsize) ? size : oldsize;
+ memcpy(p, ptr, copysize);
+ isdalloct(tsdn, ptr, oldsize, tcache, NULL, true);
+ return p;
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+iralloct(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t alignment,
+ bool zero, tcache_t *tcache, arena_t *arena) {
+ assert(ptr != NULL);
+ assert(size != 0);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1))
+ != 0) {
+ /*
+ * Existing object alignment is inadequate; allocate new space
+ * and copy.
+ */
+ return iralloct_realign(tsdn, ptr, oldsize, size, 0, alignment,
+ zero, tcache, arena);
+ }
+
+ return arena_ralloc(tsdn, arena, ptr, oldsize, size, alignment, zero,
+ tcache);
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+iralloc(tsd_t *tsd, void *ptr, size_t oldsize, size_t size, size_t alignment,
+ bool zero) {
+ return iralloct(tsd_tsdn(tsd), ptr, oldsize, size, alignment, zero,
+ tcache_get(tsd), NULL);
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+ixalloc(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size, size_t extra,
+ size_t alignment, bool zero) {
+ assert(ptr != NULL);
+ assert(size != 0);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ if (alignment != 0 && ((uintptr_t)ptr & ((uintptr_t)alignment-1))
+ != 0) {
+ /* Existing object alignment is inadequate. */
+ return true;
+ }
+
+ return arena_ralloc_no_move(tsdn, ptr, oldsize, size, extra, zero);
+}
+
+JEMALLOC_ALWAYS_INLINE int
+iget_defrag_hint(tsdn_t *tsdn, void* ptr, int *bin_util, int *run_util) {
+ int defrag = 0;
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ szind_t szind;
+ bool is_slab;
+ rtree_szind_slab_read(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr, true, &szind, &is_slab);
+ if (likely(is_slab)) {
+ /* Small allocation. */
+ extent_t *slab = iealloc(tsdn, ptr);
+ arena_t *arena = extent_arena_get(slab);
+ szind_t binind = extent_szind_get(slab);
+ bin_t *bin = &arena->bins[binind];
+ malloc_mutex_lock(tsdn, &bin->lock);
+ /* don't bother moving allocations from the slab currently used for new allocations */
+ if (slab != bin->slabcur) {
+ const bin_info_t *bin_info = &bin_infos[binind];
+ size_t availregs = bin_info->nregs * bin->stats.curslabs;
+ *bin_util = ((long long)bin->stats.curregs<<16) / availregs;
+ *run_util = ((long long)(bin_info->nregs - extent_nfree_get(slab))<<16) / bin_info->nregs;
+ defrag = 1;
+ }
+ malloc_mutex_unlock(tsdn, &bin->lock);
+ }
+ return defrag;
+}
+
+#endif /* JEMALLOC_INTERNAL_INLINES_C_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h
index a08ba772e..ed75d3768 100644
--- a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_macros.h
@@ -1,57 +1,43 @@
-/*
- * JEMALLOC_ALWAYS_INLINE and JEMALLOC_INLINE are used within header files for
- * functions that are static inline functions if inlining is enabled, and
- * single-definition library-private functions if inlining is disabled.
- *
- * JEMALLOC_ALWAYS_INLINE_C and JEMALLOC_INLINE_C are for use in .c files, in
- * which case the denoted functions are always static, regardless of whether
- * inlining is enabled.
- */
-#if defined(JEMALLOC_DEBUG) || defined(JEMALLOC_CODE_COVERAGE)
- /* Disable inlining to make debugging/profiling easier. */
-# define JEMALLOC_ALWAYS_INLINE
-# define JEMALLOC_ALWAYS_INLINE_C static
-# define JEMALLOC_INLINE
-# define JEMALLOC_INLINE_C static
-# define inline
-#else
-# define JEMALLOC_ENABLE_INLINE
-# ifdef JEMALLOC_HAVE_ATTR
-# define JEMALLOC_ALWAYS_INLINE \
- static inline JEMALLOC_ATTR(unused) JEMALLOC_ATTR(always_inline)
-# define JEMALLOC_ALWAYS_INLINE_C \
- static inline JEMALLOC_ATTR(always_inline)
-# else
-# define JEMALLOC_ALWAYS_INLINE static inline
-# define JEMALLOC_ALWAYS_INLINE_C static inline
-# endif
-# define JEMALLOC_INLINE static inline
-# define JEMALLOC_INLINE_C static inline
-# ifdef _MSC_VER
-# define inline _inline
-# endif
-#endif
+#ifndef JEMALLOC_INTERNAL_MACROS_H
+#define JEMALLOC_INTERNAL_MACROS_H
-#ifdef JEMALLOC_CC_SILENCE
-# define UNUSED JEMALLOC_ATTR(unused)
+#ifdef JEMALLOC_DEBUG
+# define JEMALLOC_ALWAYS_INLINE static inline
#else
-# define UNUSED
+# define JEMALLOC_ALWAYS_INLINE JEMALLOC_ATTR(always_inline) static inline
+#endif
+#ifdef _MSC_VER
+# define inline _inline
#endif
-#define ZU(z) ((size_t)z)
-#define ZI(z) ((ssize_t)z)
-#define QU(q) ((uint64_t)q)
-#define QI(q) ((int64_t)q)
+#define UNUSED JEMALLOC_ATTR(unused)
-#define KZU(z) ZU(z##ULL)
-#define KZI(z) ZI(z##LL)
-#define KQU(q) QU(q##ULL)
-#define KQI(q) QI(q##LL)
+#define ZU(z) ((size_t)z)
+#define ZD(z) ((ssize_t)z)
+#define QU(q) ((uint64_t)q)
+#define QD(q) ((int64_t)q)
+
+#define KZU(z) ZU(z##ULL)
+#define KZD(z) ZD(z##LL)
+#define KQU(q) QU(q##ULL)
+#define KQD(q) QI(q##LL)
#ifndef __DECONST
# define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var))
#endif
-#ifndef JEMALLOC_HAS_RESTRICT
+#if !defined(JEMALLOC_HAS_RESTRICT) || defined(__cplusplus)
# define restrict
#endif
+
+/* Various function pointers are statick and immutable except during testing. */
+#ifdef JEMALLOC_JET
+# define JET_MUTABLE
+#else
+# define JET_MUTABLE const
+#endif
+
+#define JEMALLOC_VA_ARGS_HEAD(head, ...) head
+#define JEMALLOC_VA_ARGS_TAIL(head, ...) __VA_ARGS__
+
+#endif /* JEMALLOC_INTERNAL_MACROS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_types.h b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_types.h
new file mode 100644
index 000000000..1b750b122
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_internal_types.h
@@ -0,0 +1,185 @@
+#ifndef JEMALLOC_INTERNAL_TYPES_H
+#define JEMALLOC_INTERNAL_TYPES_H
+
+/* Page size index type. */
+typedef unsigned pszind_t;
+
+/* Size class index type. */
+typedef unsigned szind_t;
+
+/* Processor / core id type. */
+typedef int malloc_cpuid_t;
+
+/*
+ * Flags bits:
+ *
+ * a: arena
+ * t: tcache
+ * 0: unused
+ * z: zero
+ * n: alignment
+ *
+ * aaaaaaaa aaaatttt tttttttt 0znnnnnn
+ */
+#define MALLOCX_ARENA_BITS 12
+#define MALLOCX_TCACHE_BITS 12
+#define MALLOCX_LG_ALIGN_BITS 6
+#define MALLOCX_ARENA_SHIFT 20
+#define MALLOCX_TCACHE_SHIFT 8
+#define MALLOCX_ARENA_MASK \
+ (((1 << MALLOCX_ARENA_BITS) - 1) << MALLOCX_ARENA_SHIFT)
+/* NB: Arena index bias decreases the maximum number of arenas by 1. */
+#define MALLOCX_ARENA_LIMIT ((1 << MALLOCX_ARENA_BITS) - 1)
+#define MALLOCX_TCACHE_MASK \
+ (((1 << MALLOCX_TCACHE_BITS) - 1) << MALLOCX_TCACHE_SHIFT)
+#define MALLOCX_TCACHE_MAX ((1 << MALLOCX_TCACHE_BITS) - 3)
+#define MALLOCX_LG_ALIGN_MASK ((1 << MALLOCX_LG_ALIGN_BITS) - 1)
+/* Use MALLOCX_ALIGN_GET() if alignment may not be specified in flags. */
+#define MALLOCX_ALIGN_GET_SPECIFIED(flags) \
+ (ZU(1) << (flags & MALLOCX_LG_ALIGN_MASK))
+#define MALLOCX_ALIGN_GET(flags) \
+ (MALLOCX_ALIGN_GET_SPECIFIED(flags) & (SIZE_T_MAX-1))
+#define MALLOCX_ZERO_GET(flags) \
+ ((bool)(flags & MALLOCX_ZERO))
+
+#define MALLOCX_TCACHE_GET(flags) \
+ (((unsigned)((flags & MALLOCX_TCACHE_MASK) >> MALLOCX_TCACHE_SHIFT)) - 2)
+#define MALLOCX_ARENA_GET(flags) \
+ (((unsigned)(((unsigned)flags) >> MALLOCX_ARENA_SHIFT)) - 1)
+
+/* Smallest size class to support. */
+#define TINY_MIN (1U << LG_TINY_MIN)
+
+/*
+ * Minimum allocation alignment is 2^LG_QUANTUM bytes (ignoring tiny size
+ * classes).
+ */
+#ifndef LG_QUANTUM
+# if (defined(__i386__) || defined(_M_IX86))
+# define LG_QUANTUM 4
+# endif
+# ifdef __ia64__
+# define LG_QUANTUM 4
+# endif
+# ifdef __alpha__
+# define LG_QUANTUM 4
+# endif
+# if (defined(__sparc64__) || defined(__sparcv9) || defined(__sparc_v9__))
+# define LG_QUANTUM 4
+# endif
+# if (defined(__amd64__) || defined(__x86_64__) || defined(_M_X64))
+# define LG_QUANTUM 4
+# endif
+# ifdef __arm__
+# define LG_QUANTUM 3
+# endif
+# ifdef __aarch64__
+# define LG_QUANTUM 4
+# endif
+# ifdef __hppa__
+# define LG_QUANTUM 4
+# endif
+# ifdef __m68k__
+# define LG_QUANTUM 3
+# endif
+# ifdef __mips__
+# define LG_QUANTUM 3
+# endif
+# ifdef __nios2__
+# define LG_QUANTUM 3
+# endif
+# ifdef __or1k__
+# define LG_QUANTUM 3
+# endif
+# ifdef __powerpc__
+# define LG_QUANTUM 4
+# endif
+# if defined(__riscv) || defined(__riscv__)
+# define LG_QUANTUM 4
+# endif
+# ifdef __s390__
+# define LG_QUANTUM 4
+# endif
+# if (defined (__SH3E__) || defined(__SH4_SINGLE__) || defined(__SH4__) || \
+ defined(__SH4_SINGLE_ONLY__))
+# define LG_QUANTUM 4
+# endif
+# ifdef __tile__
+# define LG_QUANTUM 4
+# endif
+# ifdef __le32__
+# define LG_QUANTUM 4
+# endif
+# ifndef LG_QUANTUM
+# error "Unknown minimum alignment for architecture; specify via "
+ "--with-lg-quantum"
+# endif
+#endif
+
+#define QUANTUM ((size_t)(1U << LG_QUANTUM))
+#define QUANTUM_MASK (QUANTUM - 1)
+
+/* Return the smallest quantum multiple that is >= a. */
+#define QUANTUM_CEILING(a) \
+ (((a) + QUANTUM_MASK) & ~QUANTUM_MASK)
+
+#define LONG ((size_t)(1U << LG_SIZEOF_LONG))
+#define LONG_MASK (LONG - 1)
+
+/* Return the smallest long multiple that is >= a. */
+#define LONG_CEILING(a) \
+ (((a) + LONG_MASK) & ~LONG_MASK)
+
+#define SIZEOF_PTR (1U << LG_SIZEOF_PTR)
+#define PTR_MASK (SIZEOF_PTR - 1)
+
+/* Return the smallest (void *) multiple that is >= a. */
+#define PTR_CEILING(a) \
+ (((a) + PTR_MASK) & ~PTR_MASK)
+
+/*
+ * Maximum size of L1 cache line. This is used to avoid cache line aliasing.
+ * In addition, this controls the spacing of cacheline-spaced size classes.
+ *
+ * CACHELINE cannot be based on LG_CACHELINE because __declspec(align()) can
+ * only handle raw constants.
+ */
+#define LG_CACHELINE 6
+#define CACHELINE 64
+#define CACHELINE_MASK (CACHELINE - 1)
+
+/* Return the smallest cacheline multiple that is >= s. */
+#define CACHELINE_CEILING(s) \
+ (((s) + CACHELINE_MASK) & ~CACHELINE_MASK)
+
+/* Return the nearest aligned address at or below a. */
+#define ALIGNMENT_ADDR2BASE(a, alignment) \
+ ((void *)((uintptr_t)(a) & ((~(alignment)) + 1)))
+
+/* Return the offset between a and the nearest aligned address at or below a. */
+#define ALIGNMENT_ADDR2OFFSET(a, alignment) \
+ ((size_t)((uintptr_t)(a) & (alignment - 1)))
+
+/* Return the smallest alignment multiple that is >= s. */
+#define ALIGNMENT_CEILING(s, alignment) \
+ (((s) + (alignment - 1)) & ((~(alignment)) + 1))
+
+/* Declare a variable-length array. */
+#if __STDC_VERSION__ < 199901L
+# ifdef _MSC_VER
+# include <malloc.h>
+# define alloca _alloca
+# else
+# ifdef JEMALLOC_HAS_ALLOCA_H
+# include <alloca.h>
+# else
+# include <stdlib.h>
+# endif
+# endif
+# define VARIABLE_ARRAY(type, name, count) \
+ type *name = alloca(sizeof(type) * (count))
+#else
+# define VARIABLE_ARRAY(type, name, count) type name[(count)]
+#endif
+
+#endif /* JEMALLOC_INTERNAL_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/jemalloc_preamble.h.in b/deps/jemalloc/include/jemalloc/internal/jemalloc_preamble.h.in
new file mode 100644
index 000000000..e621fbc85
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/jemalloc_preamble.h.in
@@ -0,0 +1,194 @@
+#ifndef JEMALLOC_PREAMBLE_H
+#define JEMALLOC_PREAMBLE_H
+
+#include "jemalloc_internal_defs.h"
+#include "jemalloc/internal/jemalloc_internal_decls.h"
+
+#ifdef JEMALLOC_UTRACE
+#include <sys/ktrace.h>
+#endif
+
+#define JEMALLOC_NO_DEMANGLE
+#ifdef JEMALLOC_JET
+# undef JEMALLOC_IS_MALLOC
+# define JEMALLOC_N(n) jet_##n
+# include "jemalloc/internal/public_namespace.h"
+# define JEMALLOC_NO_RENAME
+# include "../jemalloc@install_suffix@.h"
+# undef JEMALLOC_NO_RENAME
+#else
+# define JEMALLOC_N(n) @private_namespace@##n
+# include "../jemalloc@install_suffix@.h"
+#endif
+
+#if (defined(JEMALLOC_OSATOMIC) || defined(JEMALLOC_OSSPIN))
+#include <libkern/OSAtomic.h>
+#endif
+
+#ifdef JEMALLOC_ZONE
+#include <mach/mach_error.h>
+#include <mach/mach_init.h>
+#include <mach/vm_map.h>
+#endif
+
+#include "jemalloc/internal/jemalloc_internal_macros.h"
+
+/*
+ * Note that the ordering matters here; the hook itself is name-mangled. We
+ * want the inclusion of hooks to happen early, so that we hook as much as
+ * possible.
+ */
+#ifndef JEMALLOC_NO_PRIVATE_NAMESPACE
+# ifndef JEMALLOC_JET
+# include "jemalloc/internal/private_namespace.h"
+# else
+# include "jemalloc/internal/private_namespace_jet.h"
+# endif
+#endif
+#include "jemalloc/internal/hooks.h"
+
+#ifdef JEMALLOC_DEFINE_MADVISE_FREE
+# define JEMALLOC_MADV_FREE 8
+#endif
+
+static const bool config_debug =
+#ifdef JEMALLOC_DEBUG
+ true
+#else
+ false
+#endif
+ ;
+static const bool have_dss =
+#ifdef JEMALLOC_DSS
+ true
+#else
+ false
+#endif
+ ;
+static const bool have_madvise_huge =
+#ifdef JEMALLOC_HAVE_MADVISE_HUGE
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_fill =
+#ifdef JEMALLOC_FILL
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_lazy_lock =
+#ifdef JEMALLOC_LAZY_LOCK
+ true
+#else
+ false
+#endif
+ ;
+static const char * const config_malloc_conf = JEMALLOC_CONFIG_MALLOC_CONF;
+static const bool config_prof =
+#ifdef JEMALLOC_PROF
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_prof_libgcc =
+#ifdef JEMALLOC_PROF_LIBGCC
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_prof_libunwind =
+#ifdef JEMALLOC_PROF_LIBUNWIND
+ true
+#else
+ false
+#endif
+ ;
+static const bool maps_coalesce =
+#ifdef JEMALLOC_MAPS_COALESCE
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_stats =
+#ifdef JEMALLOC_STATS
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_tls =
+#ifdef JEMALLOC_TLS
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_utrace =
+#ifdef JEMALLOC_UTRACE
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_xmalloc =
+#ifdef JEMALLOC_XMALLOC
+ true
+#else
+ false
+#endif
+ ;
+static const bool config_cache_oblivious =
+#ifdef JEMALLOC_CACHE_OBLIVIOUS
+ true
+#else
+ false
+#endif
+ ;
+/*
+ * Undocumented, for jemalloc development use only at the moment. See the note
+ * in jemalloc/internal/log.h.
+ */
+static const bool config_log =
+#ifdef JEMALLOC_LOG
+ true
+#else
+ false
+#endif
+ ;
+#ifdef JEMALLOC_HAVE_SCHED_GETCPU
+/* Currently percpu_arena depends on sched_getcpu. */
+#define JEMALLOC_PERCPU_ARENA
+#endif
+static const bool have_percpu_arena =
+#ifdef JEMALLOC_PERCPU_ARENA
+ true
+#else
+ false
+#endif
+ ;
+/*
+ * Undocumented, and not recommended; the application should take full
+ * responsibility for tracking provenance.
+ */
+static const bool force_ivsalloc =
+#ifdef JEMALLOC_FORCE_IVSALLOC
+ true
+#else
+ false
+#endif
+ ;
+static const bool have_background_thread =
+#ifdef JEMALLOC_BACKGROUND_THREAD
+ true
+#else
+ false
+#endif
+ ;
+
+#endif /* JEMALLOC_PREAMBLE_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/large_externs.h b/deps/jemalloc/include/jemalloc/internal/large_externs.h
new file mode 100644
index 000000000..3f36282cd
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/large_externs.h
@@ -0,0 +1,26 @@
+#ifndef JEMALLOC_INTERNAL_LARGE_EXTERNS_H
+#define JEMALLOC_INTERNAL_LARGE_EXTERNS_H
+
+void *large_malloc(tsdn_t *tsdn, arena_t *arena, size_t usize, bool zero);
+void *large_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment,
+ bool zero);
+bool large_ralloc_no_move(tsdn_t *tsdn, extent_t *extent, size_t usize_min,
+ size_t usize_max, bool zero);
+void *large_ralloc(tsdn_t *tsdn, arena_t *arena, extent_t *extent, size_t usize,
+ size_t alignment, bool zero, tcache_t *tcache);
+
+typedef void (large_dalloc_junk_t)(void *, size_t);
+extern large_dalloc_junk_t *JET_MUTABLE large_dalloc_junk;
+
+typedef void (large_dalloc_maybe_junk_t)(void *, size_t);
+extern large_dalloc_maybe_junk_t *JET_MUTABLE large_dalloc_maybe_junk;
+
+void large_dalloc_prep_junked_locked(tsdn_t *tsdn, extent_t *extent);
+void large_dalloc_finish(tsdn_t *tsdn, extent_t *extent);
+void large_dalloc(tsdn_t *tsdn, extent_t *extent);
+size_t large_salloc(tsdn_t *tsdn, const extent_t *extent);
+prof_tctx_t *large_prof_tctx_get(tsdn_t *tsdn, const extent_t *extent);
+void large_prof_tctx_set(tsdn_t *tsdn, extent_t *extent, prof_tctx_t *tctx);
+void large_prof_tctx_reset(tsdn_t *tsdn, extent_t *extent);
+
+#endif /* JEMALLOC_INTERNAL_LARGE_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/log.h b/deps/jemalloc/include/jemalloc/internal/log.h
new file mode 100644
index 000000000..642085863
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/log.h
@@ -0,0 +1,115 @@
+#ifndef JEMALLOC_INTERNAL_LOG_H
+#define JEMALLOC_INTERNAL_LOG_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/mutex.h"
+
+#ifdef JEMALLOC_LOG
+# define JEMALLOC_LOG_VAR_BUFSIZE 1000
+#else
+# define JEMALLOC_LOG_VAR_BUFSIZE 1
+#endif
+
+#define JEMALLOC_LOG_BUFSIZE 4096
+
+/*
+ * The log malloc_conf option is a '|'-delimited list of log_var name segments
+ * which should be logged. The names are themselves hierarchical, with '.' as
+ * the delimiter (a "segment" is just a prefix in the log namespace). So, if
+ * you have:
+ *
+ * log("arena", "log msg for arena"); // 1
+ * log("arena.a", "log msg for arena.a"); // 2
+ * log("arena.b", "log msg for arena.b"); // 3
+ * log("arena.a.a", "log msg for arena.a.a"); // 4
+ * log("extent.a", "log msg for extent.a"); // 5
+ * log("extent.b", "log msg for extent.b"); // 6
+ *
+ * And your malloc_conf option is "log=arena.a|extent", then lines 2, 4, 5, and
+ * 6 will print at runtime. You can enable logging from all log vars by
+ * writing "log=.".
+ *
+ * None of this should be regarded as a stable API for right now. It's intended
+ * as a debugging interface, to let us keep around some of our printf-debugging
+ * statements.
+ */
+
+extern char log_var_names[JEMALLOC_LOG_VAR_BUFSIZE];
+extern atomic_b_t log_init_done;
+
+typedef struct log_var_s log_var_t;
+struct log_var_s {
+ /*
+ * Lowest bit is "inited", second lowest is "enabled". Putting them in
+ * a single word lets us avoid any fences on weak architectures.
+ */
+ atomic_u_t state;
+ const char *name;
+};
+
+#define LOG_NOT_INITIALIZED 0U
+#define LOG_INITIALIZED_NOT_ENABLED 1U
+#define LOG_ENABLED 2U
+
+#define LOG_VAR_INIT(name_str) {ATOMIC_INIT(LOG_NOT_INITIALIZED), name_str}
+
+/*
+ * Returns the value we should assume for state (which is not necessarily
+ * accurate; if logging is done before logging has finished initializing, then
+ * we default to doing the safe thing by logging everything).
+ */
+unsigned log_var_update_state(log_var_t *log_var);
+
+/* We factor out the metadata management to allow us to test more easily. */
+#define log_do_begin(log_var) \
+if (config_log) { \
+ unsigned log_state = atomic_load_u(&(log_var).state, \
+ ATOMIC_RELAXED); \
+ if (unlikely(log_state == LOG_NOT_INITIALIZED)) { \
+ log_state = log_var_update_state(&(log_var)); \
+ assert(log_state != LOG_NOT_INITIALIZED); \
+ } \
+ if (log_state == LOG_ENABLED) { \
+ {
+ /* User code executes here. */
+#define log_do_end(log_var) \
+ } \
+ } \
+}
+
+/*
+ * MSVC has some preprocessor bugs in its expansion of __VA_ARGS__ during
+ * preprocessing. To work around this, we take all potential extra arguments in
+ * a var-args functions. Since a varargs macro needs at least one argument in
+ * the "...", we accept the format string there, and require that the first
+ * argument in this "..." is a const char *.
+ */
+static inline void
+log_impl_varargs(const char *name, ...) {
+ char buf[JEMALLOC_LOG_BUFSIZE];
+ va_list ap;
+
+ va_start(ap, name);
+ const char *format = va_arg(ap, const char *);
+ size_t dst_offset = 0;
+ dst_offset += malloc_snprintf(buf, JEMALLOC_LOG_BUFSIZE, "%s: ", name);
+ dst_offset += malloc_vsnprintf(buf + dst_offset,
+ JEMALLOC_LOG_BUFSIZE - dst_offset, format, ap);
+ dst_offset += malloc_snprintf(buf + dst_offset,
+ JEMALLOC_LOG_BUFSIZE - dst_offset, "\n");
+ va_end(ap);
+
+ malloc_write(buf);
+}
+
+/* Call as log("log.var.str", "format_string %d", arg_for_format_string); */
+#define LOG(log_var_str, ...) \
+do { \
+ static log_var_t log_var = LOG_VAR_INIT(log_var_str); \
+ log_do_begin(log_var) \
+ log_impl_varargs((log_var).name, __VA_ARGS__); \
+ log_do_end(log_var) \
+} while (0)
+
+#endif /* JEMALLOC_INTERNAL_LOG_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/malloc_io.h b/deps/jemalloc/include/jemalloc/internal/malloc_io.h
new file mode 100644
index 000000000..bfe556b52
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/malloc_io.h
@@ -0,0 +1,102 @@
+#ifndef JEMALLOC_INTERNAL_MALLOC_IO_H
+#define JEMALLOC_INTERNAL_MALLOC_IO_H
+
+#ifdef _WIN32
+# ifdef _WIN64
+# define FMT64_PREFIX "ll"
+# define FMTPTR_PREFIX "ll"
+# else
+# define FMT64_PREFIX "ll"
+# define FMTPTR_PREFIX ""
+# endif
+# define FMTd32 "d"
+# define FMTu32 "u"
+# define FMTx32 "x"
+# define FMTd64 FMT64_PREFIX "d"
+# define FMTu64 FMT64_PREFIX "u"
+# define FMTx64 FMT64_PREFIX "x"
+# define FMTdPTR FMTPTR_PREFIX "d"
+# define FMTuPTR FMTPTR_PREFIX "u"
+# define FMTxPTR FMTPTR_PREFIX "x"
+#else
+# include <inttypes.h>
+# define FMTd32 PRId32
+# define FMTu32 PRIu32
+# define FMTx32 PRIx32
+# define FMTd64 PRId64
+# define FMTu64 PRIu64
+# define FMTx64 PRIx64
+# define FMTdPTR PRIdPTR
+# define FMTuPTR PRIuPTR
+# define FMTxPTR PRIxPTR
+#endif
+
+/* Size of stack-allocated buffer passed to buferror(). */
+#define BUFERROR_BUF 64
+
+/*
+ * Size of stack-allocated buffer used by malloc_{,v,vc}printf(). This must be
+ * large enough for all possible uses within jemalloc.
+ */
+#define MALLOC_PRINTF_BUFSIZE 4096
+
+int buferror(int err, char *buf, size_t buflen);
+uintmax_t malloc_strtoumax(const char *restrict nptr, char **restrict endptr,
+ int base);
+void malloc_write(const char *s);
+
+/*
+ * malloc_vsnprintf() supports a subset of snprintf(3) that avoids floating
+ * point math.
+ */
+size_t malloc_vsnprintf(char *str, size_t size, const char *format,
+ va_list ap);
+size_t malloc_snprintf(char *str, size_t size, const char *format, ...)
+ JEMALLOC_FORMAT_PRINTF(3, 4);
+/*
+ * The caller can set write_cb and cbopaque to null to choose to print with the
+ * je_malloc_message hook.
+ */
+void malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
+ const char *format, va_list ap);
+void malloc_cprintf(void (*write_cb)(void *, const char *), void *cbopaque,
+ const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4);
+void malloc_printf(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
+
+static inline ssize_t
+malloc_write_fd(int fd, const void *buf, size_t count) {
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_write)
+ /*
+ * Use syscall(2) rather than write(2) when possible in order to avoid
+ * the possibility of memory allocation within libc. This is necessary
+ * on FreeBSD; most operating systems do not have this problem though.
+ *
+ * syscall() returns long or int, depending on platform, so capture the
+ * result in the widest plausible type to avoid compiler warnings.
+ */
+ long result = syscall(SYS_write, fd, buf, count);
+#else
+ ssize_t result = (ssize_t)write(fd, buf,
+#ifdef _WIN32
+ (unsigned int)
+#endif
+ count);
+#endif
+ return (ssize_t)result;
+}
+
+static inline ssize_t
+malloc_read_fd(int fd, void *buf, size_t count) {
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_read)
+ long result = syscall(SYS_read, fd, buf, count);
+#else
+ ssize_t result = read(fd, buf,
+#ifdef _WIN32
+ (unsigned int)
+#endif
+ count);
+#endif
+ return (ssize_t)result;
+}
+
+#endif /* JEMALLOC_INTERNAL_MALLOC_IO_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/mb.h b/deps/jemalloc/include/jemalloc/internal/mb.h
deleted file mode 100644
index 3cfa78729..000000000
--- a/deps/jemalloc/include/jemalloc/internal/mb.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-void mb_write(void);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_MB_C_))
-#ifdef __i386__
-/*
- * According to the Intel Architecture Software Developer's Manual, current
- * processors execute instructions in order from the perspective of other
- * processors in a multiprocessor system, but 1) Intel reserves the right to
- * change that, and 2) the compiler's optimizer could re-order instructions if
- * there weren't some form of barrier. Therefore, even if running on an
- * architecture that does not need memory barriers (everything through at least
- * i686), an "optimizer barrier" is necessary.
- */
-JEMALLOC_INLINE void
-mb_write(void)
-{
-
-# if 0
- /* This is a true memory barrier. */
- asm volatile ("pusha;"
- "xor %%eax,%%eax;"
- "cpuid;"
- "popa;"
- : /* Outputs. */
- : /* Inputs. */
- : "memory" /* Clobbers. */
- );
-#else
- /*
- * This is hopefully enough to keep the compiler from reordering
- * instructions around this one.
- */
- asm volatile ("nop;"
- : /* Outputs. */
- : /* Inputs. */
- : "memory" /* Clobbers. */
- );
-#endif
-}
-#elif (defined(__amd64__) || defined(__x86_64__))
-JEMALLOC_INLINE void
-mb_write(void)
-{
-
- asm volatile ("sfence"
- : /* Outputs. */
- : /* Inputs. */
- : "memory" /* Clobbers. */
- );
-}
-#elif defined(__powerpc__)
-JEMALLOC_INLINE void
-mb_write(void)
-{
-
- asm volatile ("eieio"
- : /* Outputs. */
- : /* Inputs. */
- : "memory" /* Clobbers. */
- );
-}
-#elif defined(__sparc64__)
-JEMALLOC_INLINE void
-mb_write(void)
-{
-
- asm volatile ("membar #StoreStore"
- : /* Outputs. */
- : /* Inputs. */
- : "memory" /* Clobbers. */
- );
-}
-#elif defined(__tile__)
-JEMALLOC_INLINE void
-mb_write(void)
-{
-
- __sync_synchronize();
-}
-#else
-/*
- * This is much slower than a simple memory barrier, but the semantics of mutex
- * unlock make this work.
- */
-JEMALLOC_INLINE void
-mb_write(void)
-{
- malloc_mutex_t mtx;
-
- malloc_mutex_init(&mtx);
- malloc_mutex_lock(&mtx);
- malloc_mutex_unlock(&mtx);
-}
-#endif
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/mutex.h b/deps/jemalloc/include/jemalloc/internal/mutex.h
index f051f2917..6520c2512 100644
--- a/deps/jemalloc/include/jemalloc/internal/mutex.h
+++ b/deps/jemalloc/include/jemalloc/internal/mutex.h
@@ -1,49 +1,123 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_MUTEX_H
+#define JEMALLOC_INTERNAL_MUTEX_H
-typedef struct malloc_mutex_s malloc_mutex_t;
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/mutex_prof.h"
+#include "jemalloc/internal/tsd.h"
+#include "jemalloc/internal/witness.h"
+
+typedef enum {
+ /* Can only acquire one mutex of a given witness rank at a time. */
+ malloc_mutex_rank_exclusive,
+ /*
+ * Can acquire multiple mutexes of the same witness rank, but in
+ * address-ascending order only.
+ */
+ malloc_mutex_address_ordered
+} malloc_mutex_lock_order_t;
+typedef struct malloc_mutex_s malloc_mutex_t;
+struct malloc_mutex_s {
+ union {
+ struct {
+ /*
+ * prof_data is defined first to reduce cacheline
+ * bouncing: the data is not touched by the mutex holder
+ * during unlocking, while might be modified by
+ * contenders. Having it before the mutex itself could
+ * avoid prefetching a modified cacheline (for the
+ * unlocking thread).
+ */
+ mutex_prof_data_t prof_data;
#ifdef _WIN32
-# define MALLOC_MUTEX_INITIALIZER
+# if _WIN32_WINNT >= 0x0600
+ SRWLOCK lock;
+# else
+ CRITICAL_SECTION lock;
+# endif
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ os_unfair_lock lock;
#elif (defined(JEMALLOC_OSSPIN))
-# define MALLOC_MUTEX_INITIALIZER {0}
+ OSSpinLock lock;
#elif (defined(JEMALLOC_MUTEX_INIT_CB))
-# define MALLOC_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER, NULL}
+ pthread_mutex_t lock;
+ malloc_mutex_t *postponed_next;
#else
-# if (defined(JEMALLOC_HAVE_PTHREAD_MUTEX_ADAPTIVE_NP) && \
- defined(PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP))
-# define MALLOC_MUTEX_TYPE PTHREAD_MUTEX_ADAPTIVE_NP
-# define MALLOC_MUTEX_INITIALIZER {PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP}
-# else
-# define MALLOC_MUTEX_TYPE PTHREAD_MUTEX_DEFAULT
-# define MALLOC_MUTEX_INITIALIZER {PTHREAD_MUTEX_INITIALIZER}
-# endif
+ pthread_mutex_t lock;
+#endif
+ };
+ /*
+ * We only touch witness when configured w/ debug. However we
+ * keep the field in a union when !debug so that we don't have
+ * to pollute the code base with #ifdefs, while avoid paying the
+ * memory cost.
+ */
+#if !defined(JEMALLOC_DEBUG)
+ witness_t witness;
+ malloc_mutex_lock_order_t lock_order;
#endif
+ };
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
+#if defined(JEMALLOC_DEBUG)
+ witness_t witness;
+ malloc_mutex_lock_order_t lock_order;
+#endif
+};
+
+/*
+ * Based on benchmark results, a fixed spin with this amount of retries works
+ * well for our critical sections.
+ */
+#define MALLOC_MUTEX_MAX_SPIN 250
-struct malloc_mutex_s {
#ifdef _WIN32
# if _WIN32_WINNT >= 0x0600
- SRWLOCK lock;
+# define MALLOC_MUTEX_LOCK(m) AcquireSRWLockExclusive(&(m)->lock)
+# define MALLOC_MUTEX_UNLOCK(m) ReleaseSRWLockExclusive(&(m)->lock)
+# define MALLOC_MUTEX_TRYLOCK(m) (!TryAcquireSRWLockExclusive(&(m)->lock))
# else
- CRITICAL_SECTION lock;
+# define MALLOC_MUTEX_LOCK(m) EnterCriticalSection(&(m)->lock)
+# define MALLOC_MUTEX_UNLOCK(m) LeaveCriticalSection(&(m)->lock)
+# define MALLOC_MUTEX_TRYLOCK(m) (!TryEnterCriticalSection(&(m)->lock))
# endif
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+# define MALLOC_MUTEX_LOCK(m) os_unfair_lock_lock(&(m)->lock)
+# define MALLOC_MUTEX_UNLOCK(m) os_unfair_lock_unlock(&(m)->lock)
+# define MALLOC_MUTEX_TRYLOCK(m) (!os_unfair_lock_trylock(&(m)->lock))
#elif (defined(JEMALLOC_OSSPIN))
- OSSpinLock lock;
-#elif (defined(JEMALLOC_MUTEX_INIT_CB))
- pthread_mutex_t lock;
- malloc_mutex_t *postponed_next;
+# define MALLOC_MUTEX_LOCK(m) OSSpinLockLock(&(m)->lock)
+# define MALLOC_MUTEX_UNLOCK(m) OSSpinLockUnlock(&(m)->lock)
+# define MALLOC_MUTEX_TRYLOCK(m) (!OSSpinLockTry(&(m)->lock))
#else
- pthread_mutex_t lock;
+# define MALLOC_MUTEX_LOCK(m) pthread_mutex_lock(&(m)->lock)
+# define MALLOC_MUTEX_UNLOCK(m) pthread_mutex_unlock(&(m)->lock)
+# define MALLOC_MUTEX_TRYLOCK(m) (pthread_mutex_trylock(&(m)->lock) != 0)
#endif
-};
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
+#define LOCK_PROF_DATA_INITIALIZER \
+ {NSTIME_ZERO_INITIALIZER, NSTIME_ZERO_INITIALIZER, 0, 0, 0, \
+ ATOMIC_INIT(0), 0, NULL, 0}
+
+#ifdef _WIN32
+# define MALLOC_MUTEX_INITIALIZER
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+# define MALLOC_MUTEX_INITIALIZER \
+ {{{LOCK_PROF_DATA_INITIALIZER, OS_UNFAIR_LOCK_INIT}}, \
+ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)}
+#elif (defined(JEMALLOC_OSSPIN))
+# define MALLOC_MUTEX_INITIALIZER \
+ {{{LOCK_PROF_DATA_INITIALIZER, 0}}, \
+ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)}
+#elif (defined(JEMALLOC_MUTEX_INIT_CB))
+# define MALLOC_MUTEX_INITIALIZER \
+ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, NULL}}, \
+ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)}
+#else
+# define MALLOC_MUTEX_TYPE PTHREAD_MUTEX_DEFAULT
+# define MALLOC_MUTEX_INITIALIZER \
+ {{{LOCK_PROF_DATA_INITIALIZER, PTHREAD_MUTEX_INITIALIZER}}, \
+ WITNESS_INITIALIZER("mutex", WITNESS_RANK_OMIT)}
+#endif
#ifdef JEMALLOC_LAZY_LOCK
extern bool isthreaded;
@@ -52,60 +126,123 @@ extern bool isthreaded;
# define isthreaded true
#endif
-bool malloc_mutex_init(malloc_mutex_t *mutex);
-void malloc_mutex_prefork(malloc_mutex_t *mutex);
-void malloc_mutex_postfork_parent(malloc_mutex_t *mutex);
-void malloc_mutex_postfork_child(malloc_mutex_t *mutex);
-bool mutex_boot(void);
+bool malloc_mutex_init(malloc_mutex_t *mutex, const char *name,
+ witness_rank_t rank, malloc_mutex_lock_order_t lock_order);
+void malloc_mutex_prefork(tsdn_t *tsdn, malloc_mutex_t *mutex);
+void malloc_mutex_postfork_parent(tsdn_t *tsdn, malloc_mutex_t *mutex);
+void malloc_mutex_postfork_child(tsdn_t *tsdn, malloc_mutex_t *mutex);
+bool malloc_mutex_boot(void);
+void malloc_mutex_prof_data_reset(tsdn_t *tsdn, malloc_mutex_t *mutex);
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+void malloc_mutex_lock_slow(malloc_mutex_t *mutex);
-#ifndef JEMALLOC_ENABLE_INLINE
-void malloc_mutex_lock(malloc_mutex_t *mutex);
-void malloc_mutex_unlock(malloc_mutex_t *mutex);
-#endif
+static inline void
+malloc_mutex_lock_final(malloc_mutex_t *mutex) {
+ MALLOC_MUTEX_LOCK(mutex);
+}
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_MUTEX_C_))
-JEMALLOC_INLINE void
-malloc_mutex_lock(malloc_mutex_t *mutex)
-{
+static inline bool
+malloc_mutex_trylock_final(malloc_mutex_t *mutex) {
+ return MALLOC_MUTEX_TRYLOCK(mutex);
+}
+
+static inline void
+mutex_owner_stats_update(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ if (config_stats) {
+ mutex_prof_data_t *data = &mutex->prof_data;
+ data->n_lock_ops++;
+ if (data->prev_owner != tsdn) {
+ data->prev_owner = tsdn;
+ data->n_owner_switches++;
+ }
+ }
+}
+/* Trylock: return false if the lock is successfully acquired. */
+static inline bool
+malloc_mutex_trylock(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
if (isthreaded) {
-#ifdef _WIN32
-# if _WIN32_WINNT >= 0x0600
- AcquireSRWLockExclusive(&mutex->lock);
-# else
- EnterCriticalSection(&mutex->lock);
-# endif
-#elif (defined(JEMALLOC_OSSPIN))
- OSSpinLockLock(&mutex->lock);
-#else
- pthread_mutex_lock(&mutex->lock);
-#endif
+ if (malloc_mutex_trylock_final(mutex)) {
+ return true;
+ }
+ mutex_owner_stats_update(tsdn, mutex);
}
+ witness_lock(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
+
+ return false;
}
-JEMALLOC_INLINE void
-malloc_mutex_unlock(malloc_mutex_t *mutex)
-{
+/* Aggregate lock prof data. */
+static inline void
+malloc_mutex_prof_merge(mutex_prof_data_t *sum, mutex_prof_data_t *data) {
+ nstime_add(&sum->tot_wait_time, &data->tot_wait_time);
+ if (nstime_compare(&sum->max_wait_time, &data->max_wait_time) < 0) {
+ nstime_copy(&sum->max_wait_time, &data->max_wait_time);
+ }
+
+ sum->n_wait_times += data->n_wait_times;
+ sum->n_spin_acquired += data->n_spin_acquired;
+
+ if (sum->max_n_thds < data->max_n_thds) {
+ sum->max_n_thds = data->max_n_thds;
+ }
+ uint32_t cur_n_waiting_thds = atomic_load_u32(&sum->n_waiting_thds,
+ ATOMIC_RELAXED);
+ uint32_t new_n_waiting_thds = cur_n_waiting_thds + atomic_load_u32(
+ &data->n_waiting_thds, ATOMIC_RELAXED);
+ atomic_store_u32(&sum->n_waiting_thds, new_n_waiting_thds,
+ ATOMIC_RELAXED);
+ sum->n_owner_switches += data->n_owner_switches;
+ sum->n_lock_ops += data->n_lock_ops;
+}
+static inline void
+malloc_mutex_lock(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
if (isthreaded) {
-#ifdef _WIN32
-# if _WIN32_WINNT >= 0x0600
- ReleaseSRWLockExclusive(&mutex->lock);
-# else
- LeaveCriticalSection(&mutex->lock);
-# endif
-#elif (defined(JEMALLOC_OSSPIN))
- OSSpinLockUnlock(&mutex->lock);
-#else
- pthread_mutex_unlock(&mutex->lock);
-#endif
+ if (malloc_mutex_trylock_final(mutex)) {
+ malloc_mutex_lock_slow(mutex);
+ }
+ mutex_owner_stats_update(tsdn, mutex);
}
+ witness_lock(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
+}
+
+static inline void
+malloc_mutex_unlock(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ witness_unlock(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
+ if (isthreaded) {
+ MALLOC_MUTEX_UNLOCK(mutex);
+ }
+}
+
+static inline void
+malloc_mutex_assert_owner(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ witness_assert_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
+}
+
+static inline void
+malloc_mutex_assert_not_owner(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ witness_assert_not_owner(tsdn_witness_tsdp_get(tsdn), &mutex->witness);
+}
+
+/* Copy the prof data from mutex for processing. */
+static inline void
+malloc_mutex_prof_read(tsdn_t *tsdn, mutex_prof_data_t *data,
+ malloc_mutex_t *mutex) {
+ mutex_prof_data_t *source = &mutex->prof_data;
+ /* Can only read holding the mutex. */
+ malloc_mutex_assert_owner(tsdn, mutex);
+
+ /*
+ * Not *really* allowed (we shouldn't be doing non-atomic loads of
+ * atomic data), but the mutex protection makes this safe, and writing
+ * a member-for-member copy is tedious for this situation.
+ */
+ *data = *source;
+ /* n_wait_thds is not reported (modified w/o locking). */
+ atomic_store_u32(&data->n_waiting_thds, 0, ATOMIC_RELAXED);
}
-#endif
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_MUTEX_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/mutex_pool.h b/deps/jemalloc/include/jemalloc/internal/mutex_pool.h
new file mode 100644
index 000000000..726cece90
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/mutex_pool.h
@@ -0,0 +1,94 @@
+#ifndef JEMALLOC_INTERNAL_MUTEX_POOL_H
+#define JEMALLOC_INTERNAL_MUTEX_POOL_H
+
+#include "jemalloc/internal/hash.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/witness.h"
+
+/* We do mod reductions by this value, so it should be kept a power of 2. */
+#define MUTEX_POOL_SIZE 256
+
+typedef struct mutex_pool_s mutex_pool_t;
+struct mutex_pool_s {
+ malloc_mutex_t mutexes[MUTEX_POOL_SIZE];
+};
+
+bool mutex_pool_init(mutex_pool_t *pool, const char *name, witness_rank_t rank);
+
+/* Internal helper - not meant to be called outside this module. */
+static inline malloc_mutex_t *
+mutex_pool_mutex(mutex_pool_t *pool, uintptr_t key) {
+ size_t hash_result[2];
+ hash(&key, sizeof(key), 0xd50dcc1b, hash_result);
+ return &pool->mutexes[hash_result[0] % MUTEX_POOL_SIZE];
+}
+
+static inline void
+mutex_pool_assert_not_held(tsdn_t *tsdn, mutex_pool_t *pool) {
+ for (int i = 0; i < MUTEX_POOL_SIZE; i++) {
+ malloc_mutex_assert_not_owner(tsdn, &pool->mutexes[i]);
+ }
+}
+
+/*
+ * Note that a mutex pool doesn't work exactly the way an embdedded mutex would.
+ * You're not allowed to acquire mutexes in the pool one at a time. You have to
+ * acquire all the mutexes you'll need in a single function call, and then
+ * release them all in a single function call.
+ */
+
+static inline void
+mutex_pool_lock(tsdn_t *tsdn, mutex_pool_t *pool, uintptr_t key) {
+ mutex_pool_assert_not_held(tsdn, pool);
+
+ malloc_mutex_t *mutex = mutex_pool_mutex(pool, key);
+ malloc_mutex_lock(tsdn, mutex);
+}
+
+static inline void
+mutex_pool_unlock(tsdn_t *tsdn, mutex_pool_t *pool, uintptr_t key) {
+ malloc_mutex_t *mutex = mutex_pool_mutex(pool, key);
+ malloc_mutex_unlock(tsdn, mutex);
+
+ mutex_pool_assert_not_held(tsdn, pool);
+}
+
+static inline void
+mutex_pool_lock2(tsdn_t *tsdn, mutex_pool_t *pool, uintptr_t key1,
+ uintptr_t key2) {
+ mutex_pool_assert_not_held(tsdn, pool);
+
+ malloc_mutex_t *mutex1 = mutex_pool_mutex(pool, key1);
+ malloc_mutex_t *mutex2 = mutex_pool_mutex(pool, key2);
+ if ((uintptr_t)mutex1 < (uintptr_t)mutex2) {
+ malloc_mutex_lock(tsdn, mutex1);
+ malloc_mutex_lock(tsdn, mutex2);
+ } else if ((uintptr_t)mutex1 == (uintptr_t)mutex2) {
+ malloc_mutex_lock(tsdn, mutex1);
+ } else {
+ malloc_mutex_lock(tsdn, mutex2);
+ malloc_mutex_lock(tsdn, mutex1);
+ }
+}
+
+static inline void
+mutex_pool_unlock2(tsdn_t *tsdn, mutex_pool_t *pool, uintptr_t key1,
+ uintptr_t key2) {
+ malloc_mutex_t *mutex1 = mutex_pool_mutex(pool, key1);
+ malloc_mutex_t *mutex2 = mutex_pool_mutex(pool, key2);
+ if (mutex1 == mutex2) {
+ malloc_mutex_unlock(tsdn, mutex1);
+ } else {
+ malloc_mutex_unlock(tsdn, mutex1);
+ malloc_mutex_unlock(tsdn, mutex2);
+ }
+
+ mutex_pool_assert_not_held(tsdn, pool);
+}
+
+static inline void
+mutex_pool_assert_owner(tsdn_t *tsdn, mutex_pool_t *pool, uintptr_t key) {
+ malloc_mutex_assert_owner(tsdn, mutex_pool_mutex(pool, key));
+}
+
+#endif /* JEMALLOC_INTERNAL_MUTEX_POOL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/mutex_prof.h b/deps/jemalloc/include/jemalloc/internal/mutex_prof.h
new file mode 100644
index 000000000..ce183d335
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/mutex_prof.h
@@ -0,0 +1,99 @@
+#ifndef JEMALLOC_INTERNAL_MUTEX_PROF_H
+#define JEMALLOC_INTERNAL_MUTEX_PROF_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/nstime.h"
+#include "jemalloc/internal/tsd_types.h"
+
+#define MUTEX_PROF_GLOBAL_MUTEXES \
+ OP(background_thread) \
+ OP(ctl) \
+ OP(prof)
+
+typedef enum {
+#define OP(mtx) global_prof_mutex_##mtx,
+ MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+ mutex_prof_num_global_mutexes
+} mutex_prof_global_ind_t;
+
+#define MUTEX_PROF_ARENA_MUTEXES \
+ OP(large) \
+ OP(extent_avail) \
+ OP(extents_dirty) \
+ OP(extents_muzzy) \
+ OP(extents_retained) \
+ OP(decay_dirty) \
+ OP(decay_muzzy) \
+ OP(base) \
+ OP(tcache_list)
+
+typedef enum {
+#define OP(mtx) arena_prof_mutex_##mtx,
+ MUTEX_PROF_ARENA_MUTEXES
+#undef OP
+ mutex_prof_num_arena_mutexes
+} mutex_prof_arena_ind_t;
+
+#define MUTEX_PROF_UINT64_COUNTERS \
+ OP(num_ops, uint64_t, "n_lock_ops") \
+ OP(num_wait, uint64_t, "n_waiting") \
+ OP(num_spin_acq, uint64_t, "n_spin_acq") \
+ OP(num_owner_switch, uint64_t, "n_owner_switch") \
+ OP(total_wait_time, uint64_t, "total_wait_ns") \
+ OP(max_wait_time, uint64_t, "max_wait_ns")
+
+#define MUTEX_PROF_UINT32_COUNTERS \
+ OP(max_num_thds, uint32_t, "max_n_thds")
+
+#define MUTEX_PROF_COUNTERS \
+ MUTEX_PROF_UINT64_COUNTERS \
+ MUTEX_PROF_UINT32_COUNTERS
+
+#define OP(counter, type, human) mutex_counter_##counter,
+
+#define COUNTER_ENUM(counter_list, t) \
+ typedef enum { \
+ counter_list \
+ mutex_prof_num_##t##_counters \
+ } mutex_prof_##t##_counter_ind_t;
+
+COUNTER_ENUM(MUTEX_PROF_UINT64_COUNTERS, uint64_t)
+COUNTER_ENUM(MUTEX_PROF_UINT32_COUNTERS, uint32_t)
+
+#undef COUNTER_ENUM
+#undef OP
+
+typedef struct {
+ /*
+ * Counters touched on the slow path, i.e. when there is lock
+ * contention. We update them once we have the lock.
+ */
+ /* Total time (in nano seconds) spent waiting on this mutex. */
+ nstime_t tot_wait_time;
+ /* Max time (in nano seconds) spent on a single lock operation. */
+ nstime_t max_wait_time;
+ /* # of times have to wait for this mutex (after spinning). */
+ uint64_t n_wait_times;
+ /* # of times acquired the mutex through local spinning. */
+ uint64_t n_spin_acquired;
+ /* Max # of threads waiting for the mutex at the same time. */
+ uint32_t max_n_thds;
+ /* Current # of threads waiting on the lock. Atomic synced. */
+ atomic_u32_t n_waiting_thds;
+
+ /*
+ * Data touched on the fast path. These are modified right after we
+ * grab the lock, so it's placed closest to the end (i.e. right before
+ * the lock) so that we have a higher chance of them being on the same
+ * cacheline.
+ */
+ /* # of times the mutex holder is different than the previous one. */
+ uint64_t n_owner_switches;
+ /* Previous mutex holder, to facilitate n_owner_switches. */
+ tsdn_t *prev_owner;
+ /* # of lock() operations in total. */
+ uint64_t n_lock_ops;
+} mutex_prof_data_t;
+
+#endif /* JEMALLOC_INTERNAL_MUTEX_PROF_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/nstime.h b/deps/jemalloc/include/jemalloc/internal/nstime.h
new file mode 100644
index 000000000..17c177c7f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/nstime.h
@@ -0,0 +1,34 @@
+#ifndef JEMALLOC_INTERNAL_NSTIME_H
+#define JEMALLOC_INTERNAL_NSTIME_H
+
+/* Maximum supported number of seconds (~584 years). */
+#define NSTIME_SEC_MAX KQU(18446744072)
+#define NSTIME_ZERO_INITIALIZER {0}
+
+typedef struct {
+ uint64_t ns;
+} nstime_t;
+
+void nstime_init(nstime_t *time, uint64_t ns);
+void nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec);
+uint64_t nstime_ns(const nstime_t *time);
+uint64_t nstime_sec(const nstime_t *time);
+uint64_t nstime_msec(const nstime_t *time);
+uint64_t nstime_nsec(const nstime_t *time);
+void nstime_copy(nstime_t *time, const nstime_t *source);
+int nstime_compare(const nstime_t *a, const nstime_t *b);
+void nstime_add(nstime_t *time, const nstime_t *addend);
+void nstime_iadd(nstime_t *time, uint64_t addend);
+void nstime_subtract(nstime_t *time, const nstime_t *subtrahend);
+void nstime_isubtract(nstime_t *time, uint64_t subtrahend);
+void nstime_imultiply(nstime_t *time, uint64_t multiplier);
+void nstime_idivide(nstime_t *time, uint64_t divisor);
+uint64_t nstime_divide(const nstime_t *time, const nstime_t *divisor);
+
+typedef bool (nstime_monotonic_t)(void);
+extern nstime_monotonic_t *JET_MUTABLE nstime_monotonic;
+
+typedef bool (nstime_update_t)(nstime_t *);
+extern nstime_update_t *JET_MUTABLE nstime_update;
+
+#endif /* JEMALLOC_INTERNAL_NSTIME_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/pages.h b/deps/jemalloc/include/jemalloc/internal/pages.h
index da7eb9686..7dae633af 100644
--- a/deps/jemalloc/include/jemalloc/internal/pages.h
+++ b/deps/jemalloc/include/jemalloc/internal/pages.h
@@ -1,26 +1,88 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_PAGES_EXTERNS_H
+#define JEMALLOC_INTERNAL_PAGES_EXTERNS_H
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
+/* Page size. LG_PAGE is determined by the configure script. */
+#ifdef PAGE_MASK
+# undef PAGE_MASK
+#endif
+#define PAGE ((size_t)(1U << LG_PAGE))
+#define PAGE_MASK ((size_t)(PAGE - 1))
+/* Return the page base address for the page containing address a. */
+#define PAGE_ADDR2BASE(a) \
+ ((void *)((uintptr_t)(a) & ~PAGE_MASK))
+/* Return the smallest pagesize multiple that is >= s. */
+#define PAGE_CEILING(s) \
+ (((s) + PAGE_MASK) & ~PAGE_MASK)
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
+/* Huge page size. LG_HUGEPAGE is determined by the configure script. */
+#define HUGEPAGE ((size_t)(1U << LG_HUGEPAGE))
+#define HUGEPAGE_MASK ((size_t)(HUGEPAGE - 1))
+/* Return the huge page base address for the huge page containing address a. */
+#define HUGEPAGE_ADDR2BASE(a) \
+ ((void *)((uintptr_t)(a) & ~HUGEPAGE_MASK))
+/* Return the smallest pagesize multiple that is >= s. */
+#define HUGEPAGE_CEILING(s) \
+ (((s) + HUGEPAGE_MASK) & ~HUGEPAGE_MASK)
-void *pages_map(void *addr, size_t size);
-void pages_unmap(void *addr, size_t size);
-void *pages_trim(void *addr, size_t alloc_size, size_t leadsize,
- size_t size);
-bool pages_commit(void *addr, size_t size);
-bool pages_decommit(void *addr, size_t size);
-bool pages_purge(void *addr, size_t size);
+/* PAGES_CAN_PURGE_LAZY is defined if lazy purging is supported. */
+#if defined(_WIN32) || defined(JEMALLOC_PURGE_MADVISE_FREE)
+# define PAGES_CAN_PURGE_LAZY
+#endif
+/*
+ * PAGES_CAN_PURGE_FORCED is defined if forced purging is supported.
+ *
+ * The only supported way to hard-purge on Windows is to decommit and then
+ * re-commit, but doing so is racy, and if re-commit fails it's a pain to
+ * propagate the "poisoned" memory state. Since we typically decommit as the
+ * next step after purging on Windows anyway, there's no point in adding such
+ * complexity.
+ */
+#if !defined(_WIN32) && ((defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \
+ defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS)) || \
+ defined(JEMALLOC_MAPS_COALESCE))
+# define PAGES_CAN_PURGE_FORCED
+#endif
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+static const bool pages_can_purge_lazy =
+#ifdef PAGES_CAN_PURGE_LAZY
+ true
+#else
+ false
+#endif
+ ;
+static const bool pages_can_purge_forced =
+#ifdef PAGES_CAN_PURGE_FORCED
+ true
+#else
+ false
+#endif
+ ;
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+typedef enum {
+ thp_mode_default = 0, /* Do not change hugepage settings. */
+ thp_mode_always = 1, /* Always set MADV_HUGEPAGE. */
+ thp_mode_never = 2, /* Always set MADV_NOHUGEPAGE. */
+ thp_mode_names_limit = 3, /* Used for option processing. */
+ thp_mode_not_supported = 3 /* No THP support detected. */
+} thp_mode_t;
+
+#define THP_MODE_DEFAULT thp_mode_default
+extern thp_mode_t opt_thp;
+extern thp_mode_t init_system_thp_mode; /* Initial system wide state. */
+extern const char *thp_mode_names[];
+
+void *pages_map(void *addr, size_t size, size_t alignment, bool *commit);
+void pages_unmap(void *addr, size_t size);
+bool pages_commit(void *addr, size_t size);
+bool pages_decommit(void *addr, size_t size);
+bool pages_purge_lazy(void *addr, size_t size);
+bool pages_purge_forced(void *addr, size_t size);
+bool pages_huge(void *addr, size_t size);
+bool pages_nohuge(void *addr, size_t size);
+bool pages_dontdump(void *addr, size_t size);
+bool pages_dodump(void *addr, size_t size);
+bool pages_boot(void);
+void pages_set_thp_state (void *ptr, size_t size);
+
+#endif /* JEMALLOC_INTERNAL_PAGES_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/ph.h b/deps/jemalloc/include/jemalloc/internal/ph.h
new file mode 100644
index 000000000..84d6778a9
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/ph.h
@@ -0,0 +1,391 @@
+/*
+ * A Pairing Heap implementation.
+ *
+ * "The Pairing Heap: A New Form of Self-Adjusting Heap"
+ * https://www.cs.cmu.edu/~sleator/papers/pairing-heaps.pdf
+ *
+ * With auxiliary twopass list, described in a follow on paper.
+ *
+ * "Pairing Heaps: Experiments and Analysis"
+ * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.106.2988&rep=rep1&type=pdf
+ *
+ *******************************************************************************
+ */
+
+#ifndef PH_H_
+#define PH_H_
+
+/* Node structure. */
+#define phn(a_type) \
+struct { \
+ a_type *phn_prev; \
+ a_type *phn_next; \
+ a_type *phn_lchild; \
+}
+
+/* Root structure. */
+#define ph(a_type) \
+struct { \
+ a_type *ph_root; \
+}
+
+/* Internal utility macros. */
+#define phn_lchild_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_lchild)
+#define phn_lchild_set(a_type, a_field, a_phn, a_lchild) do { \
+ a_phn->a_field.phn_lchild = a_lchild; \
+} while (0)
+
+#define phn_next_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_next)
+#define phn_prev_set(a_type, a_field, a_phn, a_prev) do { \
+ a_phn->a_field.phn_prev = a_prev; \
+} while (0)
+
+#define phn_prev_get(a_type, a_field, a_phn) \
+ (a_phn->a_field.phn_prev)
+#define phn_next_set(a_type, a_field, a_phn, a_next) do { \
+ a_phn->a_field.phn_next = a_next; \
+} while (0)
+
+#define phn_merge_ordered(a_type, a_field, a_phn0, a_phn1, a_cmp) do { \
+ a_type *phn0child; \
+ \
+ assert(a_phn0 != NULL); \
+ assert(a_phn1 != NULL); \
+ assert(a_cmp(a_phn0, a_phn1) <= 0); \
+ \
+ phn_prev_set(a_type, a_field, a_phn1, a_phn0); \
+ phn0child = phn_lchild_get(a_type, a_field, a_phn0); \
+ phn_next_set(a_type, a_field, a_phn1, phn0child); \
+ if (phn0child != NULL) { \
+ phn_prev_set(a_type, a_field, phn0child, a_phn1); \
+ } \
+ phn_lchild_set(a_type, a_field, a_phn0, a_phn1); \
+} while (0)
+
+#define phn_merge(a_type, a_field, a_phn0, a_phn1, a_cmp, r_phn) do { \
+ if (a_phn0 == NULL) { \
+ r_phn = a_phn1; \
+ } else if (a_phn1 == NULL) { \
+ r_phn = a_phn0; \
+ } else if (a_cmp(a_phn0, a_phn1) < 0) { \
+ phn_merge_ordered(a_type, a_field, a_phn0, a_phn1, \
+ a_cmp); \
+ r_phn = a_phn0; \
+ } else { \
+ phn_merge_ordered(a_type, a_field, a_phn1, a_phn0, \
+ a_cmp); \
+ r_phn = a_phn1; \
+ } \
+} while (0)
+
+#define ph_merge_siblings(a_type, a_field, a_phn, a_cmp, r_phn) do { \
+ a_type *head = NULL; \
+ a_type *tail = NULL; \
+ a_type *phn0 = a_phn; \
+ a_type *phn1 = phn_next_get(a_type, a_field, phn0); \
+ \
+ /* \
+ * Multipass merge, wherein the first two elements of a FIFO \
+ * are repeatedly merged, and each result is appended to the \
+ * singly linked FIFO, until the FIFO contains only a single \
+ * element. We start with a sibling list but no reference to \
+ * its tail, so we do a single pass over the sibling list to \
+ * populate the FIFO. \
+ */ \
+ if (phn1 != NULL) { \
+ a_type *phnrest = phn_next_get(a_type, a_field, phn1); \
+ if (phnrest != NULL) { \
+ phn_prev_set(a_type, a_field, phnrest, NULL); \
+ } \
+ phn_prev_set(a_type, a_field, phn0, NULL); \
+ phn_next_set(a_type, a_field, phn0, NULL); \
+ phn_prev_set(a_type, a_field, phn1, NULL); \
+ phn_next_set(a_type, a_field, phn1, NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, a_cmp, phn0); \
+ head = tail = phn0; \
+ phn0 = phnrest; \
+ while (phn0 != NULL) { \
+ phn1 = phn_next_get(a_type, a_field, phn0); \
+ if (phn1 != NULL) { \
+ phnrest = phn_next_get(a_type, a_field, \
+ phn1); \
+ if (phnrest != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phnrest, NULL); \
+ } \
+ phn_prev_set(a_type, a_field, phn0, \
+ NULL); \
+ phn_next_set(a_type, a_field, phn0, \
+ NULL); \
+ phn_prev_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_next_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, \
+ a_cmp, phn0); \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = phnrest; \
+ } else { \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = NULL; \
+ } \
+ } \
+ phn0 = head; \
+ phn1 = phn_next_get(a_type, a_field, phn0); \
+ if (phn1 != NULL) { \
+ while (true) { \
+ head = phn_next_get(a_type, a_field, \
+ phn1); \
+ assert(phn_prev_get(a_type, a_field, \
+ phn0) == NULL); \
+ phn_next_set(a_type, a_field, phn0, \
+ NULL); \
+ assert(phn_prev_get(a_type, a_field, \
+ phn1) == NULL); \
+ phn_next_set(a_type, a_field, phn1, \
+ NULL); \
+ phn_merge(a_type, a_field, phn0, phn1, \
+ a_cmp, phn0); \
+ if (head == NULL) { \
+ break; \
+ } \
+ phn_next_set(a_type, a_field, tail, \
+ phn0); \
+ tail = phn0; \
+ phn0 = head; \
+ phn1 = phn_next_get(a_type, a_field, \
+ phn0); \
+ } \
+ } \
+ } \
+ r_phn = phn0; \
+} while (0)
+
+#define ph_merge_aux(a_type, a_field, a_ph, a_cmp) do { \
+ a_type *phn = phn_next_get(a_type, a_field, a_ph->ph_root); \
+ if (phn != NULL) { \
+ phn_prev_set(a_type, a_field, a_ph->ph_root, NULL); \
+ phn_next_set(a_type, a_field, a_ph->ph_root, NULL); \
+ phn_prev_set(a_type, a_field, phn, NULL); \
+ ph_merge_siblings(a_type, a_field, phn, a_cmp, phn); \
+ assert(phn_next_get(a_type, a_field, phn) == NULL); \
+ phn_merge(a_type, a_field, a_ph->ph_root, phn, a_cmp, \
+ a_ph->ph_root); \
+ } \
+} while (0)
+
+#define ph_merge_children(a_type, a_field, a_phn, a_cmp, r_phn) do { \
+ a_type *lchild = phn_lchild_get(a_type, a_field, a_phn); \
+ if (lchild == NULL) { \
+ r_phn = NULL; \
+ } else { \
+ ph_merge_siblings(a_type, a_field, lchild, a_cmp, \
+ r_phn); \
+ } \
+} while (0)
+
+/*
+ * The ph_proto() macro generates function prototypes that correspond to the
+ * functions generated by an equivalently parameterized call to ph_gen().
+ */
+#define ph_proto(a_attr, a_prefix, a_ph_type, a_type) \
+a_attr void a_prefix##new(a_ph_type *ph); \
+a_attr bool a_prefix##empty(a_ph_type *ph); \
+a_attr a_type *a_prefix##first(a_ph_type *ph); \
+a_attr a_type *a_prefix##any(a_ph_type *ph); \
+a_attr void a_prefix##insert(a_ph_type *ph, a_type *phn); \
+a_attr a_type *a_prefix##remove_first(a_ph_type *ph); \
+a_attr a_type *a_prefix##remove_any(a_ph_type *ph); \
+a_attr void a_prefix##remove(a_ph_type *ph, a_type *phn);
+
+/*
+ * The ph_gen() macro generates a type-specific pairing heap implementation,
+ * based on the above cpp macros.
+ */
+#define ph_gen(a_attr, a_prefix, a_ph_type, a_type, a_field, a_cmp) \
+a_attr void \
+a_prefix##new(a_ph_type *ph) { \
+ memset(ph, 0, sizeof(ph(a_type))); \
+} \
+a_attr bool \
+a_prefix##empty(a_ph_type *ph) { \
+ return (ph->ph_root == NULL); \
+} \
+a_attr a_type * \
+a_prefix##first(a_ph_type *ph) { \
+ if (ph->ph_root == NULL) { \
+ return NULL; \
+ } \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ return ph->ph_root; \
+} \
+a_attr a_type * \
+a_prefix##any(a_ph_type *ph) { \
+ if (ph->ph_root == NULL) { \
+ return NULL; \
+ } \
+ a_type *aux = phn_next_get(a_type, a_field, ph->ph_root); \
+ if (aux != NULL) { \
+ return aux; \
+ } \
+ return ph->ph_root; \
+} \
+a_attr void \
+a_prefix##insert(a_ph_type *ph, a_type *phn) { \
+ memset(&phn->a_field, 0, sizeof(phn(a_type))); \
+ \
+ /* \
+ * Treat the root as an aux list during insertion, and lazily \
+ * merge during a_prefix##remove_first(). For elements that \
+ * are inserted, then removed via a_prefix##remove() before the \
+ * aux list is ever processed, this makes insert/remove \
+ * constant-time, whereas eager merging would make insert \
+ * O(log n). \
+ */ \
+ if (ph->ph_root == NULL) { \
+ ph->ph_root = phn; \
+ } else { \
+ phn_next_set(a_type, a_field, phn, phn_next_get(a_type, \
+ a_field, ph->ph_root)); \
+ if (phn_next_get(a_type, a_field, ph->ph_root) != \
+ NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, ph->ph_root), \
+ phn); \
+ } \
+ phn_prev_set(a_type, a_field, phn, ph->ph_root); \
+ phn_next_set(a_type, a_field, ph->ph_root, phn); \
+ } \
+} \
+a_attr a_type * \
+a_prefix##remove_first(a_ph_type *ph) { \
+ a_type *ret; \
+ \
+ if (ph->ph_root == NULL) { \
+ return NULL; \
+ } \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ \
+ ret = ph->ph_root; \
+ \
+ ph_merge_children(a_type, a_field, ph->ph_root, a_cmp, \
+ ph->ph_root); \
+ \
+ return ret; \
+} \
+a_attr a_type * \
+a_prefix##remove_any(a_ph_type *ph) { \
+ /* \
+ * Remove the most recently inserted aux list element, or the \
+ * root if the aux list is empty. This has the effect of \
+ * behaving as a LIFO (and insertion/removal is therefore \
+ * constant-time) if a_prefix##[remove_]first() are never \
+ * called. \
+ */ \
+ if (ph->ph_root == NULL) { \
+ return NULL; \
+ } \
+ a_type *ret = phn_next_get(a_type, a_field, ph->ph_root); \
+ if (ret != NULL) { \
+ a_type *aux = phn_next_get(a_type, a_field, ret); \
+ phn_next_set(a_type, a_field, ph->ph_root, aux); \
+ if (aux != NULL) { \
+ phn_prev_set(a_type, a_field, aux, \
+ ph->ph_root); \
+ } \
+ return ret; \
+ } \
+ ret = ph->ph_root; \
+ ph_merge_children(a_type, a_field, ph->ph_root, a_cmp, \
+ ph->ph_root); \
+ return ret; \
+} \
+a_attr void \
+a_prefix##remove(a_ph_type *ph, a_type *phn) { \
+ a_type *replace, *parent; \
+ \
+ if (ph->ph_root == phn) { \
+ /* \
+ * We can delete from aux list without merging it, but \
+ * we need to merge if we are dealing with the root \
+ * node and it has children. \
+ */ \
+ if (phn_lchild_get(a_type, a_field, phn) == NULL) { \
+ ph->ph_root = phn_next_get(a_type, a_field, \
+ phn); \
+ if (ph->ph_root != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ ph->ph_root, NULL); \
+ } \
+ return; \
+ } \
+ ph_merge_aux(a_type, a_field, ph, a_cmp); \
+ if (ph->ph_root == phn) { \
+ ph_merge_children(a_type, a_field, ph->ph_root, \
+ a_cmp, ph->ph_root); \
+ return; \
+ } \
+ } \
+ \
+ /* Get parent (if phn is leftmost child) before mutating. */ \
+ if ((parent = phn_prev_get(a_type, a_field, phn)) != NULL) { \
+ if (phn_lchild_get(a_type, a_field, parent) != phn) { \
+ parent = NULL; \
+ } \
+ } \
+ /* Find a possible replacement node, and link to parent. */ \
+ ph_merge_children(a_type, a_field, phn, a_cmp, replace); \
+ /* Set next/prev for sibling linked list. */ \
+ if (replace != NULL) { \
+ if (parent != NULL) { \
+ phn_prev_set(a_type, a_field, replace, parent); \
+ phn_lchild_set(a_type, a_field, parent, \
+ replace); \
+ } else { \
+ phn_prev_set(a_type, a_field, replace, \
+ phn_prev_get(a_type, a_field, phn)); \
+ if (phn_prev_get(a_type, a_field, phn) != \
+ NULL) { \
+ phn_next_set(a_type, a_field, \
+ phn_prev_get(a_type, a_field, phn), \
+ replace); \
+ } \
+ } \
+ phn_next_set(a_type, a_field, replace, \
+ phn_next_get(a_type, a_field, phn)); \
+ if (phn_next_get(a_type, a_field, phn) != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, phn), \
+ replace); \
+ } \
+ } else { \
+ if (parent != NULL) { \
+ a_type *next = phn_next_get(a_type, a_field, \
+ phn); \
+ phn_lchild_set(a_type, a_field, parent, next); \
+ if (next != NULL) { \
+ phn_prev_set(a_type, a_field, next, \
+ parent); \
+ } \
+ } else { \
+ assert(phn_prev_get(a_type, a_field, phn) != \
+ NULL); \
+ phn_next_set(a_type, a_field, \
+ phn_prev_get(a_type, a_field, phn), \
+ phn_next_get(a_type, a_field, phn)); \
+ } \
+ if (phn_next_get(a_type, a_field, phn) != NULL) { \
+ phn_prev_set(a_type, a_field, \
+ phn_next_get(a_type, a_field, phn), \
+ phn_prev_get(a_type, a_field, phn)); \
+ } \
+ } \
+}
+
+#endif /* PH_H_ */
diff --git a/deps/jemalloc/include/jemalloc/internal/private_namespace.sh b/deps/jemalloc/include/jemalloc/internal/private_namespace.sh
index cd25eb306..6ef1346a3 100755
--- a/deps/jemalloc/include/jemalloc/internal/private_namespace.sh
+++ b/deps/jemalloc/include/jemalloc/internal/private_namespace.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-for symbol in `cat $1` ; do
- echo "#define ${symbol} JEMALLOC_N(${symbol})"
+for symbol in `cat "$@"` ; do
+ echo "#define ${symbol} JEMALLOC_N(${symbol})"
done
diff --git a/deps/jemalloc/include/jemalloc/internal/private_symbols.sh b/deps/jemalloc/include/jemalloc/internal/private_symbols.sh
new file mode 100755
index 000000000..442a259fd
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/private_symbols.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+#
+# Generate private_symbols[_jet].awk.
+#
+# Usage: private_symbols.sh <sym_prefix> <sym>*
+#
+# <sym_prefix> is typically "" or "_".
+
+sym_prefix=$1
+shift
+
+cat <<EOF
+#!/usr/bin/env awk -f
+
+BEGIN {
+ sym_prefix = "${sym_prefix}"
+ split("\\
+EOF
+
+for public_sym in "$@" ; do
+ cat <<EOF
+ ${sym_prefix}${public_sym} \\
+EOF
+done
+
+cat <<"EOF"
+ ", exported_symbol_names)
+ # Store exported symbol names as keys in exported_symbols.
+ for (i in exported_symbol_names) {
+ exported_symbols[exported_symbol_names[i]] = 1
+ }
+}
+
+# Process 'nm -a <c_source.o>' output.
+#
+# Handle lines like:
+# 0000000000000008 D opt_junk
+# 0000000000007574 T malloc_initialized
+(NF == 3 && $2 ~ /^[ABCDGRSTVW]$/ && !($3 in exported_symbols) && $3 ~ /^[A-Za-z0-9_]+$/) {
+ print substr($3, 1+length(sym_prefix), length($3)-length(sym_prefix))
+}
+
+# Process 'dumpbin /SYMBOLS <c_source.obj>' output.
+#
+# Handle lines like:
+# 353 00008098 SECT4 notype External | opt_junk
+# 3F1 00000000 SECT7 notype () External | malloc_initialized
+($3 ~ /^SECT[0-9]+/ && $(NF-2) == "External" && !($NF in exported_symbols)) {
+ print $NF
+}
+EOF
diff --git a/deps/jemalloc/include/jemalloc/internal/private_symbols.txt b/deps/jemalloc/include/jemalloc/internal/private_symbols.txt
deleted file mode 100644
index a90021aa6..000000000
--- a/deps/jemalloc/include/jemalloc/internal/private_symbols.txt
+++ /dev/null
@@ -1,499 +0,0 @@
-a0dalloc
-a0get
-a0malloc
-arena_aalloc
-arena_alloc_junk_small
-arena_bin_index
-arena_bin_info
-arena_bitselm_get
-arena_boot
-arena_choose
-arena_choose_hard
-arena_chunk_alloc_huge
-arena_chunk_cache_maybe_insert
-arena_chunk_cache_maybe_remove
-arena_chunk_dalloc_huge
-arena_chunk_ralloc_huge_expand
-arena_chunk_ralloc_huge_shrink
-arena_chunk_ralloc_huge_similar
-arena_cleanup
-arena_dalloc
-arena_dalloc_bin
-arena_dalloc_bin_junked_locked
-arena_dalloc_junk_large
-arena_dalloc_junk_small
-arena_dalloc_large
-arena_dalloc_large_junked_locked
-arena_dalloc_small
-arena_dss_prec_get
-arena_dss_prec_set
-arena_get
-arena_get_hard
-arena_init
-arena_lg_dirty_mult_default_get
-arena_lg_dirty_mult_default_set
-arena_lg_dirty_mult_get
-arena_lg_dirty_mult_set
-arena_malloc
-arena_malloc_large
-arena_malloc_small
-arena_mapbits_allocated_get
-arena_mapbits_binind_get
-arena_mapbits_decommitted_get
-arena_mapbits_dirty_get
-arena_mapbits_get
-arena_mapbits_internal_set
-arena_mapbits_large_binind_set
-arena_mapbits_large_get
-arena_mapbits_large_set
-arena_mapbits_large_size_get
-arena_mapbitsp_get
-arena_mapbitsp_read
-arena_mapbitsp_write
-arena_mapbits_size_decode
-arena_mapbits_size_encode
-arena_mapbits_small_runind_get
-arena_mapbits_small_set
-arena_mapbits_unallocated_set
-arena_mapbits_unallocated_size_get
-arena_mapbits_unallocated_size_set
-arena_mapbits_unzeroed_get
-arena_maxrun
-arena_maybe_purge
-arena_metadata_allocated_add
-arena_metadata_allocated_get
-arena_metadata_allocated_sub
-arena_migrate
-arena_miscelm_get
-arena_miscelm_to_pageind
-arena_miscelm_to_rpages
-arena_nbound
-arena_new
-arena_node_alloc
-arena_node_dalloc
-arena_palloc
-arena_postfork_child
-arena_postfork_parent
-arena_prefork
-arena_prof_accum
-arena_prof_accum_impl
-arena_prof_accum_locked
-arena_prof_promoted
-arena_prof_tctx_get
-arena_prof_tctx_reset
-arena_prof_tctx_set
-arena_ptr_small_binind_get
-arena_purge_all
-arena_quarantine_junk_small
-arena_ralloc
-arena_ralloc_junk_large
-arena_ralloc_no_move
-arena_rd_to_miscelm
-arena_redzone_corruption
-arena_run_regind
-arena_run_to_miscelm
-arena_salloc
-arenas_cache_bypass_cleanup
-arenas_cache_cleanup
-arena_sdalloc
-arena_stats_merge
-arena_tcache_fill_small
-atomic_add_p
-atomic_add_u
-atomic_add_uint32
-atomic_add_uint64
-atomic_add_z
-atomic_cas_p
-atomic_cas_u
-atomic_cas_uint32
-atomic_cas_uint64
-atomic_cas_z
-atomic_sub_p
-atomic_sub_u
-atomic_sub_uint32
-atomic_sub_uint64
-atomic_sub_z
-base_alloc
-base_boot
-base_postfork_child
-base_postfork_parent
-base_prefork
-base_stats_get
-bitmap_full
-bitmap_get
-bitmap_info_init
-bitmap_info_ngroups
-bitmap_init
-bitmap_set
-bitmap_sfu
-bitmap_size
-bitmap_unset
-bootstrap_calloc
-bootstrap_free
-bootstrap_malloc
-bt_init
-buferror
-chunk_alloc_base
-chunk_alloc_cache
-chunk_alloc_dss
-chunk_alloc_mmap
-chunk_alloc_wrapper
-chunk_boot
-chunk_dalloc_arena
-chunk_dalloc_cache
-chunk_dalloc_mmap
-chunk_dalloc_wrapper
-chunk_deregister
-chunk_dss_boot
-chunk_dss_postfork_child
-chunk_dss_postfork_parent
-chunk_dss_prec_get
-chunk_dss_prec_set
-chunk_dss_prefork
-chunk_hooks_default
-chunk_hooks_get
-chunk_hooks_set
-chunk_in_dss
-chunk_lookup
-chunk_npages
-chunk_postfork_child
-chunk_postfork_parent
-chunk_prefork
-chunk_purge_arena
-chunk_purge_wrapper
-chunk_register
-chunksize
-chunksize_mask
-chunks_rtree
-ckh_count
-ckh_delete
-ckh_insert
-ckh_iter
-ckh_new
-ckh_pointer_hash
-ckh_pointer_keycomp
-ckh_remove
-ckh_search
-ckh_string_hash
-ckh_string_keycomp
-ctl_boot
-ctl_bymib
-ctl_byname
-ctl_nametomib
-ctl_postfork_child
-ctl_postfork_parent
-ctl_prefork
-dss_prec_names
-extent_node_achunk_get
-extent_node_achunk_set
-extent_node_addr_get
-extent_node_addr_set
-extent_node_arena_get
-extent_node_arena_set
-extent_node_dirty_insert
-extent_node_dirty_linkage_init
-extent_node_dirty_remove
-extent_node_init
-extent_node_prof_tctx_get
-extent_node_prof_tctx_set
-extent_node_size_get
-extent_node_size_set
-extent_node_zeroed_get
-extent_node_zeroed_set
-extent_tree_ad_empty
-extent_tree_ad_first
-extent_tree_ad_insert
-extent_tree_ad_iter
-extent_tree_ad_iter_recurse
-extent_tree_ad_iter_start
-extent_tree_ad_last
-extent_tree_ad_new
-extent_tree_ad_next
-extent_tree_ad_nsearch
-extent_tree_ad_prev
-extent_tree_ad_psearch
-extent_tree_ad_remove
-extent_tree_ad_reverse_iter
-extent_tree_ad_reverse_iter_recurse
-extent_tree_ad_reverse_iter_start
-extent_tree_ad_search
-extent_tree_szad_empty
-extent_tree_szad_first
-extent_tree_szad_insert
-extent_tree_szad_iter
-extent_tree_szad_iter_recurse
-extent_tree_szad_iter_start
-extent_tree_szad_last
-extent_tree_szad_new
-extent_tree_szad_next
-extent_tree_szad_nsearch
-extent_tree_szad_prev
-extent_tree_szad_psearch
-extent_tree_szad_remove
-extent_tree_szad_reverse_iter
-extent_tree_szad_reverse_iter_recurse
-extent_tree_szad_reverse_iter_start
-extent_tree_szad_search
-get_errno
-hash
-hash_fmix_32
-hash_fmix_64
-hash_get_block_32
-hash_get_block_64
-hash_rotl_32
-hash_rotl_64
-hash_x64_128
-hash_x86_128
-hash_x86_32
-huge_aalloc
-huge_dalloc
-huge_dalloc_junk
-huge_malloc
-huge_palloc
-huge_prof_tctx_get
-huge_prof_tctx_reset
-huge_prof_tctx_set
-huge_ralloc
-huge_ralloc_no_move
-huge_salloc
-iaalloc
-iallocztm
-icalloc
-icalloct
-idalloc
-idalloct
-idalloctm
-imalloc
-imalloct
-index2size
-index2size_compute
-index2size_lookup
-index2size_tab
-in_valgrind
-ipalloc
-ipalloct
-ipallocztm
-iqalloc
-iralloc
-iralloct
-iralloct_realign
-isalloc
-isdalloct
-isqalloc
-isthreaded
-ivsalloc
-ixalloc
-jemalloc_postfork_child
-jemalloc_postfork_parent
-jemalloc_prefork
-large_maxclass
-lg_floor
-malloc_cprintf
-malloc_mutex_init
-malloc_mutex_lock
-malloc_mutex_postfork_child
-malloc_mutex_postfork_parent
-malloc_mutex_prefork
-malloc_mutex_unlock
-malloc_printf
-malloc_snprintf
-malloc_strtoumax
-malloc_tsd_boot0
-malloc_tsd_boot1
-malloc_tsd_cleanup_register
-malloc_tsd_dalloc
-malloc_tsd_malloc
-malloc_tsd_no_cleanup
-malloc_vcprintf
-malloc_vsnprintf
-malloc_write
-map_bias
-map_misc_offset
-mb_write
-mutex_boot
-narenas_cache_cleanup
-narenas_total_get
-ncpus
-nhbins
-opt_abort
-opt_dss
-opt_junk
-opt_junk_alloc
-opt_junk_free
-opt_lg_chunk
-opt_lg_dirty_mult
-opt_lg_prof_interval
-opt_lg_prof_sample
-opt_lg_tcache_max
-opt_narenas
-opt_prof
-opt_prof_accum
-opt_prof_active
-opt_prof_final
-opt_prof_gdump
-opt_prof_leak
-opt_prof_prefix
-opt_prof_thread_active_init
-opt_quarantine
-opt_redzone
-opt_stats_print
-opt_tcache
-opt_utrace
-opt_xmalloc
-opt_zero
-p2rz
-pages_commit
-pages_decommit
-pages_map
-pages_purge
-pages_trim
-pages_unmap
-pow2_ceil
-prof_active_get
-prof_active_get_unlocked
-prof_active_set
-prof_alloc_prep
-prof_alloc_rollback
-prof_backtrace
-prof_boot0
-prof_boot1
-prof_boot2
-prof_dump_header
-prof_dump_open
-prof_free
-prof_free_sampled_object
-prof_gdump
-prof_gdump_get
-prof_gdump_get_unlocked
-prof_gdump_set
-prof_gdump_val
-prof_idump
-prof_interval
-prof_lookup
-prof_malloc
-prof_malloc_sample_object
-prof_mdump
-prof_postfork_child
-prof_postfork_parent
-prof_prefork
-prof_realloc
-prof_reset
-prof_sample_accum_update
-prof_sample_threshold_update
-prof_tctx_get
-prof_tctx_reset
-prof_tctx_set
-prof_tdata_cleanup
-prof_tdata_get
-prof_tdata_init
-prof_tdata_reinit
-prof_thread_active_get
-prof_thread_active_init_get
-prof_thread_active_init_set
-prof_thread_active_set
-prof_thread_name_get
-prof_thread_name_set
-quarantine
-quarantine_alloc_hook
-quarantine_alloc_hook_work
-quarantine_cleanup
-register_zone
-rtree_child_read
-rtree_child_read_hard
-rtree_child_tryread
-rtree_delete
-rtree_get
-rtree_new
-rtree_node_valid
-rtree_set
-rtree_start_level
-rtree_subkey
-rtree_subtree_read
-rtree_subtree_read_hard
-rtree_subtree_tryread
-rtree_val_read
-rtree_val_write
-s2u
-s2u_compute
-s2u_lookup
-sa2u
-set_errno
-size2index
-size2index_compute
-size2index_lookup
-size2index_tab
-stats_cactive
-stats_cactive_add
-stats_cactive_get
-stats_cactive_sub
-stats_print
-tcache_alloc_easy
-tcache_alloc_large
-tcache_alloc_small
-tcache_alloc_small_hard
-tcache_arena_associate
-tcache_arena_dissociate
-tcache_arena_reassociate
-tcache_bin_flush_large
-tcache_bin_flush_small
-tcache_bin_info
-tcache_boot
-tcache_cleanup
-tcache_create
-tcache_dalloc_large
-tcache_dalloc_small
-tcache_enabled_cleanup
-tcache_enabled_get
-tcache_enabled_set
-tcache_event
-tcache_event_hard
-tcache_flush
-tcache_get
-tcache_get_hard
-tcache_maxclass
-tcaches
-tcache_salloc
-tcaches_create
-tcaches_destroy
-tcaches_flush
-tcaches_get
-tcache_stats_merge
-thread_allocated_cleanup
-thread_deallocated_cleanup
-tsd_arena_get
-tsd_arena_set
-tsd_boot
-tsd_boot0
-tsd_boot1
-tsd_booted
-tsd_cleanup
-tsd_cleanup_wrapper
-tsd_fetch
-tsd_get
-tsd_wrapper_get
-tsd_wrapper_set
-tsd_initialized
-tsd_init_check_recursion
-tsd_init_finish
-tsd_init_head
-tsd_nominal
-tsd_quarantine_get
-tsd_quarantine_set
-tsd_set
-tsd_tcache_enabled_get
-tsd_tcache_enabled_set
-tsd_tcache_get
-tsd_tcache_set
-tsd_tls
-tsd_tsd
-tsd_prof_tdata_get
-tsd_prof_tdata_set
-tsd_thread_allocated_get
-tsd_thread_allocated_set
-tsd_thread_deallocated_get
-tsd_thread_deallocated_set
-u2rz
-valgrind_freelike_block
-valgrind_make_mem_defined
-valgrind_make_mem_noaccess
-valgrind_make_mem_undefined
diff --git a/deps/jemalloc/include/jemalloc/internal/private_unnamespace.sh b/deps/jemalloc/include/jemalloc/internal/private_unnamespace.sh
deleted file mode 100755
index 23fed8e80..000000000
--- a/deps/jemalloc/include/jemalloc/internal/private_unnamespace.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/sh
-
-for symbol in `cat $1` ; do
- echo "#undef ${symbol}"
-done
diff --git a/deps/jemalloc/include/jemalloc/internal/prng.h b/deps/jemalloc/include/jemalloc/internal/prng.h
index 216d0ef47..15cc2d18f 100644
--- a/deps/jemalloc/include/jemalloc/internal/prng.h
+++ b/deps/jemalloc/include/jemalloc/internal/prng.h
@@ -1,5 +1,8 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_PRNG_H
+#define JEMALLOC_INTERNAL_PRNG_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/bit_util.h"
/*
* Simple linear congruential pseudo-random number generator:
@@ -18,43 +21,165 @@
* proportional to bit position. For example, the lowest bit has a cycle of 2,
* the next has a cycle of 4, etc. For this reason, we prefer to use the upper
* bits.
- *
- * Macro parameters:
- * uint32_t r : Result.
- * unsigned lg_range : (0..32], number of least significant bits to return.
- * uint32_t state : Seed value.
- * const uint32_t a, c : See above discussion.
*/
-#define prng32(r, lg_range, state, a, c) do { \
- assert((lg_range) > 0); \
- assert((lg_range) <= 32); \
- \
- r = (state * (a)) + (c); \
- state = r; \
- r >>= (32 - (lg_range)); \
-} while (false)
-
-/* Same as prng32(), but 64 bits of pseudo-randomness, using uint64_t. */
-#define prng64(r, lg_range, state, a, c) do { \
- assert((lg_range) > 0); \
- assert((lg_range) <= 64); \
- \
- r = (state * (a)) + (c); \
- state = r; \
- r >>= (64 - (lg_range)); \
-} while (false)
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-#endif /* JEMALLOC_H_STRUCTS */
/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#endif /* JEMALLOC_H_EXTERNS */
+/* INTERNAL DEFINITIONS -- IGNORE */
/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+#define PRNG_A_32 UINT32_C(1103515241)
+#define PRNG_C_32 UINT32_C(12347)
+
+#define PRNG_A_64 UINT64_C(6364136223846793005)
+#define PRNG_C_64 UINT64_C(1442695040888963407)
+
+JEMALLOC_ALWAYS_INLINE uint32_t
+prng_state_next_u32(uint32_t state) {
+ return (state * PRNG_A_32) + PRNG_C_32;
+}
+
+JEMALLOC_ALWAYS_INLINE uint64_t
+prng_state_next_u64(uint64_t state) {
+ return (state * PRNG_A_64) + PRNG_C_64;
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+prng_state_next_zu(size_t state) {
+#if LG_SIZEOF_PTR == 2
+ return (state * PRNG_A_32) + PRNG_C_32;
+#elif LG_SIZEOF_PTR == 3
+ return (state * PRNG_A_64) + PRNG_C_64;
+#else
+#error Unsupported pointer size
+#endif
+}
-#endif /* JEMALLOC_H_INLINES */
/******************************************************************************/
+/* BEGIN PUBLIC API */
+/******************************************************************************/
+
+/*
+ * The prng_lg_range functions give a uniform int in the half-open range [0,
+ * 2**lg_range). If atomic is true, they do so safely from multiple threads.
+ * Multithreaded 64-bit prngs aren't supported.
+ */
+
+JEMALLOC_ALWAYS_INLINE uint32_t
+prng_lg_range_u32(atomic_u32_t *state, unsigned lg_range, bool atomic) {
+ uint32_t ret, state0, state1;
+
+ assert(lg_range > 0);
+ assert(lg_range <= 32);
+
+ state0 = atomic_load_u32(state, ATOMIC_RELAXED);
+
+ if (atomic) {
+ do {
+ state1 = prng_state_next_u32(state0);
+ } while (!atomic_compare_exchange_weak_u32(state, &state0,
+ state1, ATOMIC_RELAXED, ATOMIC_RELAXED));
+ } else {
+ state1 = prng_state_next_u32(state0);
+ atomic_store_u32(state, state1, ATOMIC_RELAXED);
+ }
+ ret = state1 >> (32 - lg_range);
+
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE uint64_t
+prng_lg_range_u64(uint64_t *state, unsigned lg_range) {
+ uint64_t ret, state1;
+
+ assert(lg_range > 0);
+ assert(lg_range <= 64);
+
+ state1 = prng_state_next_u64(*state);
+ *state = state1;
+ ret = state1 >> (64 - lg_range);
+
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+prng_lg_range_zu(atomic_zu_t *state, unsigned lg_range, bool atomic) {
+ size_t ret, state0, state1;
+
+ assert(lg_range > 0);
+ assert(lg_range <= ZU(1) << (3 + LG_SIZEOF_PTR));
+
+ state0 = atomic_load_zu(state, ATOMIC_RELAXED);
+
+ if (atomic) {
+ do {
+ state1 = prng_state_next_zu(state0);
+ } while (atomic_compare_exchange_weak_zu(state, &state0,
+ state1, ATOMIC_RELAXED, ATOMIC_RELAXED));
+ } else {
+ state1 = prng_state_next_zu(state0);
+ atomic_store_zu(state, state1, ATOMIC_RELAXED);
+ }
+ ret = state1 >> ((ZU(1) << (3 + LG_SIZEOF_PTR)) - lg_range);
+
+ return ret;
+}
+
+/*
+ * The prng_range functions behave like the prng_lg_range, but return a result
+ * in [0, range) instead of [0, 2**lg_range).
+ */
+
+JEMALLOC_ALWAYS_INLINE uint32_t
+prng_range_u32(atomic_u32_t *state, uint32_t range, bool atomic) {
+ uint32_t ret;
+ unsigned lg_range;
+
+ assert(range > 1);
+
+ /* Compute the ceiling of lg(range). */
+ lg_range = ffs_u32(pow2_ceil_u32(range)) - 1;
+
+ /* Generate a result in [0..range) via repeated trial. */
+ do {
+ ret = prng_lg_range_u32(state, lg_range, atomic);
+ } while (ret >= range);
+
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE uint64_t
+prng_range_u64(uint64_t *state, uint64_t range) {
+ uint64_t ret;
+ unsigned lg_range;
+
+ assert(range > 1);
+
+ /* Compute the ceiling of lg(range). */
+ lg_range = ffs_u64(pow2_ceil_u64(range)) - 1;
+
+ /* Generate a result in [0..range) via repeated trial. */
+ do {
+ ret = prng_lg_range_u64(state, lg_range);
+ } while (ret >= range);
+
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+prng_range_zu(atomic_zu_t *state, size_t range, bool atomic) {
+ size_t ret;
+ unsigned lg_range;
+
+ assert(range > 1);
+
+ /* Compute the ceiling of lg(range). */
+ lg_range = ffs_u64(pow2_ceil_u64(range)) - 1;
+
+ /* Generate a result in [0..range) via repeated trial. */
+ do {
+ ret = prng_lg_range_zu(state, lg_range, atomic);
+ } while (ret >= range);
+
+ return ret;
+}
+
+#endif /* JEMALLOC_INTERNAL_PRNG_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/prof.h b/deps/jemalloc/include/jemalloc/internal/prof.h
deleted file mode 100644
index e5198c3e8..000000000
--- a/deps/jemalloc/include/jemalloc/internal/prof.h
+++ /dev/null
@@ -1,545 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct prof_bt_s prof_bt_t;
-typedef struct prof_cnt_s prof_cnt_t;
-typedef struct prof_tctx_s prof_tctx_t;
-typedef struct prof_gctx_s prof_gctx_t;
-typedef struct prof_tdata_s prof_tdata_t;
-
-/* Option defaults. */
-#ifdef JEMALLOC_PROF
-# define PROF_PREFIX_DEFAULT "jeprof"
-#else
-# define PROF_PREFIX_DEFAULT ""
-#endif
-#define LG_PROF_SAMPLE_DEFAULT 19
-#define LG_PROF_INTERVAL_DEFAULT -1
-
-/*
- * Hard limit on stack backtrace depth. The version of prof_backtrace() that
- * is based on __builtin_return_address() necessarily has a hard-coded number
- * of backtrace frame handlers, and should be kept in sync with this setting.
- */
-#define PROF_BT_MAX 128
-
-/* Initial hash table size. */
-#define PROF_CKH_MINITEMS 64
-
-/* Size of memory buffer to use when writing dump files. */
-#define PROF_DUMP_BUFSIZE 65536
-
-/* Size of stack-allocated buffer used by prof_printf(). */
-#define PROF_PRINTF_BUFSIZE 128
-
-/*
- * Number of mutexes shared among all gctx's. No space is allocated for these
- * unless profiling is enabled, so it's okay to over-provision.
- */
-#define PROF_NCTX_LOCKS 1024
-
-/*
- * Number of mutexes shared among all tdata's. No space is allocated for these
- * unless profiling is enabled, so it's okay to over-provision.
- */
-#define PROF_NTDATA_LOCKS 256
-
-/*
- * prof_tdata pointers close to NULL are used to encode state information that
- * is used for cleaning up during thread shutdown.
- */
-#define PROF_TDATA_STATE_REINCARNATED ((prof_tdata_t *)(uintptr_t)1)
-#define PROF_TDATA_STATE_PURGATORY ((prof_tdata_t *)(uintptr_t)2)
-#define PROF_TDATA_STATE_MAX PROF_TDATA_STATE_PURGATORY
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-struct prof_bt_s {
- /* Backtrace, stored as len program counters. */
- void **vec;
- unsigned len;
-};
-
-#ifdef JEMALLOC_PROF_LIBGCC
-/* Data structure passed to libgcc _Unwind_Backtrace() callback functions. */
-typedef struct {
- prof_bt_t *bt;
- unsigned max;
-} prof_unwind_data_t;
-#endif
-
-struct prof_cnt_s {
- /* Profiling counters. */
- uint64_t curobjs;
- uint64_t curbytes;
- uint64_t accumobjs;
- uint64_t accumbytes;
-};
-
-typedef enum {
- prof_tctx_state_initializing,
- prof_tctx_state_nominal,
- prof_tctx_state_dumping,
- prof_tctx_state_purgatory /* Dumper must finish destroying. */
-} prof_tctx_state_t;
-
-struct prof_tctx_s {
- /* Thread data for thread that performed the allocation. */
- prof_tdata_t *tdata;
-
- /*
- * Copy of tdata->thr_{uid,discrim}, necessary because tdata may be
- * defunct during teardown.
- */
- uint64_t thr_uid;
- uint64_t thr_discrim;
-
- /* Profiling counters, protected by tdata->lock. */
- prof_cnt_t cnts;
-
- /* Associated global context. */
- prof_gctx_t *gctx;
-
- /*
- * UID that distinguishes multiple tctx's created by the same thread,
- * but coexisting in gctx->tctxs. There are two ways that such
- * coexistence can occur:
- * - A dumper thread can cause a tctx to be retained in the purgatory
- * state.
- * - Although a single "producer" thread must create all tctx's which
- * share the same thr_uid, multiple "consumers" can each concurrently
- * execute portions of prof_tctx_destroy(). prof_tctx_destroy() only
- * gets called once each time cnts.cur{objs,bytes} drop to 0, but this
- * threshold can be hit again before the first consumer finishes
- * executing prof_tctx_destroy().
- */
- uint64_t tctx_uid;
-
- /* Linkage into gctx's tctxs. */
- rb_node(prof_tctx_t) tctx_link;
-
- /*
- * True during prof_alloc_prep()..prof_malloc_sample_object(), prevents
- * sample vs destroy race.
- */
- bool prepared;
-
- /* Current dump-related state, protected by gctx->lock. */
- prof_tctx_state_t state;
-
- /*
- * Copy of cnts snapshotted during early dump phase, protected by
- * dump_mtx.
- */
- prof_cnt_t dump_cnts;
-};
-typedef rb_tree(prof_tctx_t) prof_tctx_tree_t;
-
-struct prof_gctx_s {
- /* Protects nlimbo, cnt_summed, and tctxs. */
- malloc_mutex_t *lock;
-
- /*
- * Number of threads that currently cause this gctx to be in a state of
- * limbo due to one of:
- * - Initializing this gctx.
- * - Initializing per thread counters associated with this gctx.
- * - Preparing to destroy this gctx.
- * - Dumping a heap profile that includes this gctx.
- * nlimbo must be 1 (single destroyer) in order to safely destroy the
- * gctx.
- */
- unsigned nlimbo;
-
- /*
- * Tree of profile counters, one for each thread that has allocated in
- * this context.
- */
- prof_tctx_tree_t tctxs;
-
- /* Linkage for tree of contexts to be dumped. */
- rb_node(prof_gctx_t) dump_link;
-
- /* Temporary storage for summation during dump. */
- prof_cnt_t cnt_summed;
-
- /* Associated backtrace. */
- prof_bt_t bt;
-
- /* Backtrace vector, variable size, referred to by bt. */
- void *vec[1];
-};
-typedef rb_tree(prof_gctx_t) prof_gctx_tree_t;
-
-struct prof_tdata_s {
- malloc_mutex_t *lock;
-
- /* Monotonically increasing unique thread identifier. */
- uint64_t thr_uid;
-
- /*
- * Monotonically increasing discriminator among tdata structures
- * associated with the same thr_uid.
- */
- uint64_t thr_discrim;
-
- /* Included in heap profile dumps if non-NULL. */
- char *thread_name;
-
- bool attached;
- bool expired;
-
- rb_node(prof_tdata_t) tdata_link;
-
- /*
- * Counter used to initialize prof_tctx_t's tctx_uid. No locking is
- * necessary when incrementing this field, because only one thread ever
- * does so.
- */
- uint64_t tctx_uid_next;
-
- /*
- * Hash of (prof_bt_t *)-->(prof_tctx_t *). Each thread tracks
- * backtraces for which it has non-zero allocation/deallocation counters
- * associated with thread-specific prof_tctx_t objects. Other threads
- * may write to prof_tctx_t contents when freeing associated objects.
- */
- ckh_t bt2tctx;
-
- /* Sampling state. */
- uint64_t prng_state;
- uint64_t bytes_until_sample;
-
- /* State used to avoid dumping while operating on prof internals. */
- bool enq;
- bool enq_idump;
- bool enq_gdump;
-
- /*
- * Set to true during an early dump phase for tdata's which are
- * currently being dumped. New threads' tdata's have this initialized
- * to false so that they aren't accidentally included in later dump
- * phases.
- */
- bool dumping;
-
- /*
- * True if profiling is active for this tdata's thread
- * (thread.prof.active mallctl).
- */
- bool active;
-
- /* Temporary storage for summation during dump. */
- prof_cnt_t cnt_summed;
-
- /* Backtrace vector, used for calls to prof_backtrace(). */
- void *vec[PROF_BT_MAX];
-};
-typedef rb_tree(prof_tdata_t) prof_tdata_tree_t;
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-extern bool opt_prof;
-extern bool opt_prof_active;
-extern bool opt_prof_thread_active_init;
-extern size_t opt_lg_prof_sample; /* Mean bytes between samples. */
-extern ssize_t opt_lg_prof_interval; /* lg(prof_interval). */
-extern bool opt_prof_gdump; /* High-water memory dumping. */
-extern bool opt_prof_final; /* Final profile dumping. */
-extern bool opt_prof_leak; /* Dump leak summary at exit. */
-extern bool opt_prof_accum; /* Report cumulative bytes. */
-extern char opt_prof_prefix[
- /* Minimize memory bloat for non-prof builds. */
-#ifdef JEMALLOC_PROF
- PATH_MAX +
-#endif
- 1];
-
-/* Accessed via prof_active_[gs]et{_unlocked,}(). */
-extern bool prof_active;
-
-/* Accessed via prof_gdump_[gs]et{_unlocked,}(). */
-extern bool prof_gdump_val;
-
-/*
- * Profile dump interval, measured in bytes allocated. Each arena triggers a
- * profile dump when it reaches this threshold. The effect is that the
- * interval between profile dumps averages prof_interval, though the actual
- * interval between dumps will tend to be sporadic, and the interval will be a
- * maximum of approximately (prof_interval * narenas).
- */
-extern uint64_t prof_interval;
-
-/*
- * Initialized as opt_lg_prof_sample, and potentially modified during profiling
- * resets.
- */
-extern size_t lg_prof_sample;
-
-void prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx, bool updated);
-void prof_malloc_sample_object(const void *ptr, size_t usize,
- prof_tctx_t *tctx);
-void prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_tctx_t *tctx);
-void bt_init(prof_bt_t *bt, void **vec);
-void prof_backtrace(prof_bt_t *bt);
-prof_tctx_t *prof_lookup(tsd_t *tsd, prof_bt_t *bt);
-#ifdef JEMALLOC_JET
-size_t prof_tdata_count(void);
-size_t prof_bt_count(void);
-const prof_cnt_t *prof_cnt_all(void);
-typedef int (prof_dump_open_t)(bool, const char *);
-extern prof_dump_open_t *prof_dump_open;
-typedef bool (prof_dump_header_t)(bool, const prof_cnt_t *);
-extern prof_dump_header_t *prof_dump_header;
-#endif
-void prof_idump(void);
-bool prof_mdump(const char *filename);
-void prof_gdump(void);
-prof_tdata_t *prof_tdata_init(tsd_t *tsd);
-prof_tdata_t *prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata);
-void prof_reset(tsd_t *tsd, size_t lg_sample);
-void prof_tdata_cleanup(tsd_t *tsd);
-const char *prof_thread_name_get(void);
-bool prof_active_get(void);
-bool prof_active_set(bool active);
-int prof_thread_name_set(tsd_t *tsd, const char *thread_name);
-bool prof_thread_active_get(void);
-bool prof_thread_active_set(bool active);
-bool prof_thread_active_init_get(void);
-bool prof_thread_active_init_set(bool active_init);
-bool prof_gdump_get(void);
-bool prof_gdump_set(bool active);
-void prof_boot0(void);
-void prof_boot1(void);
-bool prof_boot2(void);
-void prof_prefork(void);
-void prof_postfork_parent(void);
-void prof_postfork_child(void);
-void prof_sample_threshold_update(prof_tdata_t *tdata);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-bool prof_active_get_unlocked(void);
-bool prof_gdump_get_unlocked(void);
-prof_tdata_t *prof_tdata_get(tsd_t *tsd, bool create);
-bool prof_sample_accum_update(tsd_t *tsd, size_t usize, bool commit,
- prof_tdata_t **tdata_out);
-prof_tctx_t *prof_alloc_prep(tsd_t *tsd, size_t usize, bool prof_active,
- bool update);
-prof_tctx_t *prof_tctx_get(const void *ptr);
-void prof_tctx_set(const void *ptr, size_t usize, prof_tctx_t *tctx);
-void prof_tctx_reset(const void *ptr, size_t usize, const void *old_ptr,
- prof_tctx_t *tctx);
-void prof_malloc_sample_object(const void *ptr, size_t usize,
- prof_tctx_t *tctx);
-void prof_malloc(const void *ptr, size_t usize, prof_tctx_t *tctx);
-void prof_realloc(tsd_t *tsd, const void *ptr, size_t usize,
- prof_tctx_t *tctx, bool prof_active, bool updated, const void *old_ptr,
- size_t old_usize, prof_tctx_t *old_tctx);
-void prof_free(tsd_t *tsd, const void *ptr, size_t usize);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_PROF_C_))
-JEMALLOC_ALWAYS_INLINE bool
-prof_active_get_unlocked(void)
-{
-
- /*
- * Even if opt_prof is true, sampling can be temporarily disabled by
- * setting prof_active to false. No locking is used when reading
- * prof_active in the fast path, so there are no guarantees regarding
- * how long it will take for all threads to notice state changes.
- */
- return (prof_active);
-}
-
-JEMALLOC_ALWAYS_INLINE bool
-prof_gdump_get_unlocked(void)
-{
-
- /*
- * No locking is used when reading prof_gdump_val in the fast path, so
- * there are no guarantees regarding how long it will take for all
- * threads to notice state changes.
- */
- return (prof_gdump_val);
-}
-
-JEMALLOC_ALWAYS_INLINE prof_tdata_t *
-prof_tdata_get(tsd_t *tsd, bool create)
-{
- prof_tdata_t *tdata;
-
- cassert(config_prof);
-
- tdata = tsd_prof_tdata_get(tsd);
- if (create) {
- if (unlikely(tdata == NULL)) {
- if (tsd_nominal(tsd)) {
- tdata = prof_tdata_init(tsd);
- tsd_prof_tdata_set(tsd, tdata);
- }
- } else if (unlikely(tdata->expired)) {
- tdata = prof_tdata_reinit(tsd, tdata);
- tsd_prof_tdata_set(tsd, tdata);
- }
- assert(tdata == NULL || tdata->attached);
- }
-
- return (tdata);
-}
-
-JEMALLOC_ALWAYS_INLINE prof_tctx_t *
-prof_tctx_get(const void *ptr)
-{
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- return (arena_prof_tctx_get(ptr));
-}
-
-JEMALLOC_ALWAYS_INLINE void
-prof_tctx_set(const void *ptr, size_t usize, prof_tctx_t *tctx)
-{
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- arena_prof_tctx_set(ptr, usize, tctx);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-prof_tctx_reset(const void *ptr, size_t usize, const void *old_ptr,
- prof_tctx_t *old_tctx)
-{
-
- cassert(config_prof);
- assert(ptr != NULL);
-
- arena_prof_tctx_reset(ptr, usize, old_ptr, old_tctx);
-}
-
-JEMALLOC_ALWAYS_INLINE bool
-prof_sample_accum_update(tsd_t *tsd, size_t usize, bool update,
- prof_tdata_t **tdata_out)
-{
- prof_tdata_t *tdata;
-
- cassert(config_prof);
-
- tdata = prof_tdata_get(tsd, true);
- if ((uintptr_t)tdata <= (uintptr_t)PROF_TDATA_STATE_MAX)
- tdata = NULL;
-
- if (tdata_out != NULL)
- *tdata_out = tdata;
-
- if (tdata == NULL)
- return (true);
-
- if (tdata->bytes_until_sample >= usize) {
- if (update)
- tdata->bytes_until_sample -= usize;
- return (true);
- } else {
- /* Compute new sample threshold. */
- if (update)
- prof_sample_threshold_update(tdata);
- return (!tdata->active);
- }
-}
-
-JEMALLOC_ALWAYS_INLINE prof_tctx_t *
-prof_alloc_prep(tsd_t *tsd, size_t usize, bool prof_active, bool update)
-{
- prof_tctx_t *ret;
- prof_tdata_t *tdata;
- prof_bt_t bt;
-
- assert(usize == s2u(usize));
-
- if (!prof_active || likely(prof_sample_accum_update(tsd, usize, update,
- &tdata)))
- ret = (prof_tctx_t *)(uintptr_t)1U;
- else {
- bt_init(&bt, tdata->vec);
- prof_backtrace(&bt);
- ret = prof_lookup(tsd, &bt);
- }
-
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-prof_malloc(const void *ptr, size_t usize, prof_tctx_t *tctx)
-{
-
- cassert(config_prof);
- assert(ptr != NULL);
- assert(usize == isalloc(ptr, true));
-
- if (unlikely((uintptr_t)tctx > (uintptr_t)1U))
- prof_malloc_sample_object(ptr, usize, tctx);
- else
- prof_tctx_set(ptr, usize, (prof_tctx_t *)(uintptr_t)1U);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-prof_realloc(tsd_t *tsd, const void *ptr, size_t usize, prof_tctx_t *tctx,
- bool prof_active, bool updated, const void *old_ptr, size_t old_usize,
- prof_tctx_t *old_tctx)
-{
- bool sampled, old_sampled;
-
- cassert(config_prof);
- assert(ptr != NULL || (uintptr_t)tctx <= (uintptr_t)1U);
-
- if (prof_active && !updated && ptr != NULL) {
- assert(usize == isalloc(ptr, true));
- if (prof_sample_accum_update(tsd, usize, true, NULL)) {
- /*
- * Don't sample. The usize passed to prof_alloc_prep()
- * was larger than what actually got allocated, so a
- * backtrace was captured for this allocation, even
- * though its actual usize was insufficient to cross the
- * sample threshold.
- */
- tctx = (prof_tctx_t *)(uintptr_t)1U;
- }
- }
-
- sampled = ((uintptr_t)tctx > (uintptr_t)1U);
- old_sampled = ((uintptr_t)old_tctx > (uintptr_t)1U);
-
- if (unlikely(sampled))
- prof_malloc_sample_object(ptr, usize, tctx);
- else
- prof_tctx_reset(ptr, usize, old_ptr, old_tctx);
-
- if (unlikely(old_sampled))
- prof_free_sampled_object(tsd, old_usize, old_tctx);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-prof_free(tsd_t *tsd, const void *ptr, size_t usize)
-{
- prof_tctx_t *tctx = prof_tctx_get(ptr);
-
- cassert(config_prof);
- assert(usize == isalloc(ptr, true));
-
- if (unlikely((uintptr_t)tctx > (uintptr_t)1U))
- prof_free_sampled_object(tsd, usize, tctx);
-}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/prof_externs.h b/deps/jemalloc/include/jemalloc/internal/prof_externs.h
new file mode 100644
index 000000000..04348696f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/prof_externs.h
@@ -0,0 +1,92 @@
+#ifndef JEMALLOC_INTERNAL_PROF_EXTERNS_H
+#define JEMALLOC_INTERNAL_PROF_EXTERNS_H
+
+#include "jemalloc/internal/mutex.h"
+
+extern malloc_mutex_t bt2gctx_mtx;
+
+extern bool opt_prof;
+extern bool opt_prof_active;
+extern bool opt_prof_thread_active_init;
+extern size_t opt_lg_prof_sample; /* Mean bytes between samples. */
+extern ssize_t opt_lg_prof_interval; /* lg(prof_interval). */
+extern bool opt_prof_gdump; /* High-water memory dumping. */
+extern bool opt_prof_final; /* Final profile dumping. */
+extern bool opt_prof_leak; /* Dump leak summary at exit. */
+extern bool opt_prof_accum; /* Report cumulative bytes. */
+extern char opt_prof_prefix[
+ /* Minimize memory bloat for non-prof builds. */
+#ifdef JEMALLOC_PROF
+ PATH_MAX +
+#endif
+ 1];
+
+/* Accessed via prof_active_[gs]et{_unlocked,}(). */
+extern bool prof_active;
+
+/* Accessed via prof_gdump_[gs]et{_unlocked,}(). */
+extern bool prof_gdump_val;
+
+/*
+ * Profile dump interval, measured in bytes allocated. Each arena triggers a
+ * profile dump when it reaches this threshold. The effect is that the
+ * interval between profile dumps averages prof_interval, though the actual
+ * interval between dumps will tend to be sporadic, and the interval will be a
+ * maximum of approximately (prof_interval * narenas).
+ */
+extern uint64_t prof_interval;
+
+/*
+ * Initialized as opt_lg_prof_sample, and potentially modified during profiling
+ * resets.
+ */
+extern size_t lg_prof_sample;
+
+void prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx, bool updated);
+void prof_malloc_sample_object(tsdn_t *tsdn, const void *ptr, size_t usize,
+ prof_tctx_t *tctx);
+void prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_tctx_t *tctx);
+void bt_init(prof_bt_t *bt, void **vec);
+void prof_backtrace(prof_bt_t *bt);
+prof_tctx_t *prof_lookup(tsd_t *tsd, prof_bt_t *bt);
+#ifdef JEMALLOC_JET
+size_t prof_tdata_count(void);
+size_t prof_bt_count(void);
+#endif
+typedef int (prof_dump_open_t)(bool, const char *);
+extern prof_dump_open_t *JET_MUTABLE prof_dump_open;
+
+typedef bool (prof_dump_header_t)(tsdn_t *, bool, const prof_cnt_t *);
+extern prof_dump_header_t *JET_MUTABLE prof_dump_header;
+#ifdef JEMALLOC_JET
+void prof_cnt_all(uint64_t *curobjs, uint64_t *curbytes, uint64_t *accumobjs,
+ uint64_t *accumbytes);
+#endif
+bool prof_accum_init(tsdn_t *tsdn, prof_accum_t *prof_accum);
+void prof_idump(tsdn_t *tsdn);
+bool prof_mdump(tsd_t *tsd, const char *filename);
+void prof_gdump(tsdn_t *tsdn);
+prof_tdata_t *prof_tdata_init(tsd_t *tsd);
+prof_tdata_t *prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata);
+void prof_reset(tsd_t *tsd, size_t lg_sample);
+void prof_tdata_cleanup(tsd_t *tsd);
+bool prof_active_get(tsdn_t *tsdn);
+bool prof_active_set(tsdn_t *tsdn, bool active);
+const char *prof_thread_name_get(tsd_t *tsd);
+int prof_thread_name_set(tsd_t *tsd, const char *thread_name);
+bool prof_thread_active_get(tsd_t *tsd);
+bool prof_thread_active_set(tsd_t *tsd, bool active);
+bool prof_thread_active_init_get(tsdn_t *tsdn);
+bool prof_thread_active_init_set(tsdn_t *tsdn, bool active_init);
+bool prof_gdump_get(tsdn_t *tsdn);
+bool prof_gdump_set(tsdn_t *tsdn, bool active);
+void prof_boot0(void);
+void prof_boot1(void);
+bool prof_boot2(tsd_t *tsd);
+void prof_prefork0(tsdn_t *tsdn);
+void prof_prefork1(tsdn_t *tsdn);
+void prof_postfork_parent(tsdn_t *tsdn);
+void prof_postfork_child(tsdn_t *tsdn);
+void prof_sample_threshold_update(prof_tdata_t *tdata);
+
+#endif /* JEMALLOC_INTERNAL_PROF_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/prof_inlines_a.h b/deps/jemalloc/include/jemalloc/internal/prof_inlines_a.h
new file mode 100644
index 000000000..a6efb4851
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/prof_inlines_a.h
@@ -0,0 +1,83 @@
+#ifndef JEMALLOC_INTERNAL_PROF_INLINES_A_H
+#define JEMALLOC_INTERNAL_PROF_INLINES_A_H
+
+#include "jemalloc/internal/mutex.h"
+
+static inline bool
+prof_accum_add(tsdn_t *tsdn, prof_accum_t *prof_accum, uint64_t accumbytes) {
+ cassert(config_prof);
+
+ bool overflow;
+ uint64_t a0, a1;
+
+ /*
+ * If the application allocates fast enough (and/or if idump is slow
+ * enough), extreme overflow here (a1 >= prof_interval * 2) can cause
+ * idump trigger coalescing. This is an intentional mechanism that
+ * avoids rate-limiting allocation.
+ */
+#ifdef JEMALLOC_ATOMIC_U64
+ a0 = atomic_load_u64(&prof_accum->accumbytes, ATOMIC_RELAXED);
+ do {
+ a1 = a0 + accumbytes;
+ assert(a1 >= a0);
+ overflow = (a1 >= prof_interval);
+ if (overflow) {
+ a1 %= prof_interval;
+ }
+ } while (!atomic_compare_exchange_weak_u64(&prof_accum->accumbytes, &a0,
+ a1, ATOMIC_RELAXED, ATOMIC_RELAXED));
+#else
+ malloc_mutex_lock(tsdn, &prof_accum->mtx);
+ a0 = prof_accum->accumbytes;
+ a1 = a0 + accumbytes;
+ overflow = (a1 >= prof_interval);
+ if (overflow) {
+ a1 %= prof_interval;
+ }
+ prof_accum->accumbytes = a1;
+ malloc_mutex_unlock(tsdn, &prof_accum->mtx);
+#endif
+ return overflow;
+}
+
+static inline void
+prof_accum_cancel(tsdn_t *tsdn, prof_accum_t *prof_accum, size_t usize) {
+ cassert(config_prof);
+
+ /*
+ * Cancel out as much of the excessive prof_accumbytes increase as
+ * possible without underflowing. Interval-triggered dumps occur
+ * slightly more often than intended as a result of incomplete
+ * canceling.
+ */
+ uint64_t a0, a1;
+#ifdef JEMALLOC_ATOMIC_U64
+ a0 = atomic_load_u64(&prof_accum->accumbytes, ATOMIC_RELAXED);
+ do {
+ a1 = (a0 >= LARGE_MINCLASS - usize) ? a0 - (LARGE_MINCLASS -
+ usize) : 0;
+ } while (!atomic_compare_exchange_weak_u64(&prof_accum->accumbytes, &a0,
+ a1, ATOMIC_RELAXED, ATOMIC_RELAXED));
+#else
+ malloc_mutex_lock(tsdn, &prof_accum->mtx);
+ a0 = prof_accum->accumbytes;
+ a1 = (a0 >= LARGE_MINCLASS - usize) ? a0 - (LARGE_MINCLASS - usize) :
+ 0;
+ prof_accum->accumbytes = a1;
+ malloc_mutex_unlock(tsdn, &prof_accum->mtx);
+#endif
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+prof_active_get_unlocked(void) {
+ /*
+ * Even if opt_prof is true, sampling can be temporarily disabled by
+ * setting prof_active to false. No locking is used when reading
+ * prof_active in the fast path, so there are no guarantees regarding
+ * how long it will take for all threads to notice state changes.
+ */
+ return prof_active;
+}
+
+#endif /* JEMALLOC_INTERNAL_PROF_INLINES_A_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/prof_inlines_b.h b/deps/jemalloc/include/jemalloc/internal/prof_inlines_b.h
new file mode 100644
index 000000000..6ff465ad7
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/prof_inlines_b.h
@@ -0,0 +1,206 @@
+#ifndef JEMALLOC_INTERNAL_PROF_INLINES_B_H
+#define JEMALLOC_INTERNAL_PROF_INLINES_B_H
+
+#include "jemalloc/internal/sz.h"
+
+JEMALLOC_ALWAYS_INLINE bool
+prof_gdump_get_unlocked(void) {
+ /*
+ * No locking is used when reading prof_gdump_val in the fast path, so
+ * there are no guarantees regarding how long it will take for all
+ * threads to notice state changes.
+ */
+ return prof_gdump_val;
+}
+
+JEMALLOC_ALWAYS_INLINE prof_tdata_t *
+prof_tdata_get(tsd_t *tsd, bool create) {
+ prof_tdata_t *tdata;
+
+ cassert(config_prof);
+
+ tdata = tsd_prof_tdata_get(tsd);
+ if (create) {
+ if (unlikely(tdata == NULL)) {
+ if (tsd_nominal(tsd)) {
+ tdata = prof_tdata_init(tsd);
+ tsd_prof_tdata_set(tsd, tdata);
+ }
+ } else if (unlikely(tdata->expired)) {
+ tdata = prof_tdata_reinit(tsd, tdata);
+ tsd_prof_tdata_set(tsd, tdata);
+ }
+ assert(tdata == NULL || tdata->attached);
+ }
+
+ return tdata;
+}
+
+JEMALLOC_ALWAYS_INLINE prof_tctx_t *
+prof_tctx_get(tsdn_t *tsdn, const void *ptr, alloc_ctx_t *alloc_ctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ return arena_prof_tctx_get(tsdn, ptr, alloc_ctx);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+prof_tctx_set(tsdn_t *tsdn, const void *ptr, size_t usize,
+ alloc_ctx_t *alloc_ctx, prof_tctx_t *tctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ arena_prof_tctx_set(tsdn, ptr, usize, alloc_ctx, tctx);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+prof_tctx_reset(tsdn_t *tsdn, const void *ptr, prof_tctx_t *tctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+
+ arena_prof_tctx_reset(tsdn, ptr, tctx);
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+prof_sample_accum_update(tsd_t *tsd, size_t usize, bool update,
+ prof_tdata_t **tdata_out) {
+ prof_tdata_t *tdata;
+
+ cassert(config_prof);
+
+ tdata = prof_tdata_get(tsd, true);
+ if (unlikely((uintptr_t)tdata <= (uintptr_t)PROF_TDATA_STATE_MAX)) {
+ tdata = NULL;
+ }
+
+ if (tdata_out != NULL) {
+ *tdata_out = tdata;
+ }
+
+ if (unlikely(tdata == NULL)) {
+ return true;
+ }
+
+ if (likely(tdata->bytes_until_sample >= usize)) {
+ if (update) {
+ tdata->bytes_until_sample -= usize;
+ }
+ return true;
+ } else {
+ if (tsd_reentrancy_level_get(tsd) > 0) {
+ return true;
+ }
+ /* Compute new sample threshold. */
+ if (update) {
+ prof_sample_threshold_update(tdata);
+ }
+ return !tdata->active;
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE prof_tctx_t *
+prof_alloc_prep(tsd_t *tsd, size_t usize, bool prof_active, bool update) {
+ prof_tctx_t *ret;
+ prof_tdata_t *tdata;
+ prof_bt_t bt;
+
+ assert(usize == sz_s2u(usize));
+
+ if (!prof_active || likely(prof_sample_accum_update(tsd, usize, update,
+ &tdata))) {
+ ret = (prof_tctx_t *)(uintptr_t)1U;
+ } else {
+ bt_init(&bt, tdata->vec);
+ prof_backtrace(&bt);
+ ret = prof_lookup(tsd, &bt);
+ }
+
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+prof_malloc(tsdn_t *tsdn, const void *ptr, size_t usize, alloc_ctx_t *alloc_ctx,
+ prof_tctx_t *tctx) {
+ cassert(config_prof);
+ assert(ptr != NULL);
+ assert(usize == isalloc(tsdn, ptr));
+
+ if (unlikely((uintptr_t)tctx > (uintptr_t)1U)) {
+ prof_malloc_sample_object(tsdn, ptr, usize, tctx);
+ } else {
+ prof_tctx_set(tsdn, ptr, usize, alloc_ctx,
+ (prof_tctx_t *)(uintptr_t)1U);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void
+prof_realloc(tsd_t *tsd, const void *ptr, size_t usize, prof_tctx_t *tctx,
+ bool prof_active, bool updated, const void *old_ptr, size_t old_usize,
+ prof_tctx_t *old_tctx) {
+ bool sampled, old_sampled, moved;
+
+ cassert(config_prof);
+ assert(ptr != NULL || (uintptr_t)tctx <= (uintptr_t)1U);
+
+ if (prof_active && !updated && ptr != NULL) {
+ assert(usize == isalloc(tsd_tsdn(tsd), ptr));
+ if (prof_sample_accum_update(tsd, usize, true, NULL)) {
+ /*
+ * Don't sample. The usize passed to prof_alloc_prep()
+ * was larger than what actually got allocated, so a
+ * backtrace was captured for this allocation, even
+ * though its actual usize was insufficient to cross the
+ * sample threshold.
+ */
+ prof_alloc_rollback(tsd, tctx, true);
+ tctx = (prof_tctx_t *)(uintptr_t)1U;
+ }
+ }
+
+ sampled = ((uintptr_t)tctx > (uintptr_t)1U);
+ old_sampled = ((uintptr_t)old_tctx > (uintptr_t)1U);
+ moved = (ptr != old_ptr);
+
+ if (unlikely(sampled)) {
+ prof_malloc_sample_object(tsd_tsdn(tsd), ptr, usize, tctx);
+ } else if (moved) {
+ prof_tctx_set(tsd_tsdn(tsd), ptr, usize, NULL,
+ (prof_tctx_t *)(uintptr_t)1U);
+ } else if (unlikely(old_sampled)) {
+ /*
+ * prof_tctx_set() would work for the !moved case as well, but
+ * prof_tctx_reset() is slightly cheaper, and the proper thing
+ * to do here in the presence of explicit knowledge re: moved
+ * state.
+ */
+ prof_tctx_reset(tsd_tsdn(tsd), ptr, tctx);
+ } else {
+ assert((uintptr_t)prof_tctx_get(tsd_tsdn(tsd), ptr, NULL) ==
+ (uintptr_t)1U);
+ }
+
+ /*
+ * The prof_free_sampled_object() call must come after the
+ * prof_malloc_sample_object() call, because tctx and old_tctx may be
+ * the same, in which case reversing the call order could cause the tctx
+ * to be prematurely destroyed as a side effect of momentarily zeroed
+ * counters.
+ */
+ if (unlikely(old_sampled)) {
+ prof_free_sampled_object(tsd, old_usize, old_tctx);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void
+prof_free(tsd_t *tsd, const void *ptr, size_t usize, alloc_ctx_t *alloc_ctx) {
+ prof_tctx_t *tctx = prof_tctx_get(tsd_tsdn(tsd), ptr, alloc_ctx);
+
+ cassert(config_prof);
+ assert(usize == isalloc(tsd_tsdn(tsd), ptr));
+
+ if (unlikely((uintptr_t)tctx > (uintptr_t)1U)) {
+ prof_free_sampled_object(tsd, usize, tctx);
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_PROF_INLINES_B_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/prof_structs.h b/deps/jemalloc/include/jemalloc/internal/prof_structs.h
new file mode 100644
index 000000000..0d58ae100
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/prof_structs.h
@@ -0,0 +1,201 @@
+#ifndef JEMALLOC_INTERNAL_PROF_STRUCTS_H
+#define JEMALLOC_INTERNAL_PROF_STRUCTS_H
+
+#include "jemalloc/internal/ckh.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/prng.h"
+#include "jemalloc/internal/rb.h"
+
+struct prof_bt_s {
+ /* Backtrace, stored as len program counters. */
+ void **vec;
+ unsigned len;
+};
+
+#ifdef JEMALLOC_PROF_LIBGCC
+/* Data structure passed to libgcc _Unwind_Backtrace() callback functions. */
+typedef struct {
+ prof_bt_t *bt;
+ unsigned max;
+} prof_unwind_data_t;
+#endif
+
+struct prof_accum_s {
+#ifndef JEMALLOC_ATOMIC_U64
+ malloc_mutex_t mtx;
+ uint64_t accumbytes;
+#else
+ atomic_u64_t accumbytes;
+#endif
+};
+
+struct prof_cnt_s {
+ /* Profiling counters. */
+ uint64_t curobjs;
+ uint64_t curbytes;
+ uint64_t accumobjs;
+ uint64_t accumbytes;
+};
+
+typedef enum {
+ prof_tctx_state_initializing,
+ prof_tctx_state_nominal,
+ prof_tctx_state_dumping,
+ prof_tctx_state_purgatory /* Dumper must finish destroying. */
+} prof_tctx_state_t;
+
+struct prof_tctx_s {
+ /* Thread data for thread that performed the allocation. */
+ prof_tdata_t *tdata;
+
+ /*
+ * Copy of tdata->thr_{uid,discrim}, necessary because tdata may be
+ * defunct during teardown.
+ */
+ uint64_t thr_uid;
+ uint64_t thr_discrim;
+
+ /* Profiling counters, protected by tdata->lock. */
+ prof_cnt_t cnts;
+
+ /* Associated global context. */
+ prof_gctx_t *gctx;
+
+ /*
+ * UID that distinguishes multiple tctx's created by the same thread,
+ * but coexisting in gctx->tctxs. There are two ways that such
+ * coexistence can occur:
+ * - A dumper thread can cause a tctx to be retained in the purgatory
+ * state.
+ * - Although a single "producer" thread must create all tctx's which
+ * share the same thr_uid, multiple "consumers" can each concurrently
+ * execute portions of prof_tctx_destroy(). prof_tctx_destroy() only
+ * gets called once each time cnts.cur{objs,bytes} drop to 0, but this
+ * threshold can be hit again before the first consumer finishes
+ * executing prof_tctx_destroy().
+ */
+ uint64_t tctx_uid;
+
+ /* Linkage into gctx's tctxs. */
+ rb_node(prof_tctx_t) tctx_link;
+
+ /*
+ * True during prof_alloc_prep()..prof_malloc_sample_object(), prevents
+ * sample vs destroy race.
+ */
+ bool prepared;
+
+ /* Current dump-related state, protected by gctx->lock. */
+ prof_tctx_state_t state;
+
+ /*
+ * Copy of cnts snapshotted during early dump phase, protected by
+ * dump_mtx.
+ */
+ prof_cnt_t dump_cnts;
+};
+typedef rb_tree(prof_tctx_t) prof_tctx_tree_t;
+
+struct prof_gctx_s {
+ /* Protects nlimbo, cnt_summed, and tctxs. */
+ malloc_mutex_t *lock;
+
+ /*
+ * Number of threads that currently cause this gctx to be in a state of
+ * limbo due to one of:
+ * - Initializing this gctx.
+ * - Initializing per thread counters associated with this gctx.
+ * - Preparing to destroy this gctx.
+ * - Dumping a heap profile that includes this gctx.
+ * nlimbo must be 1 (single destroyer) in order to safely destroy the
+ * gctx.
+ */
+ unsigned nlimbo;
+
+ /*
+ * Tree of profile counters, one for each thread that has allocated in
+ * this context.
+ */
+ prof_tctx_tree_t tctxs;
+
+ /* Linkage for tree of contexts to be dumped. */
+ rb_node(prof_gctx_t) dump_link;
+
+ /* Temporary storage for summation during dump. */
+ prof_cnt_t cnt_summed;
+
+ /* Associated backtrace. */
+ prof_bt_t bt;
+
+ /* Backtrace vector, variable size, referred to by bt. */
+ void *vec[1];
+};
+typedef rb_tree(prof_gctx_t) prof_gctx_tree_t;
+
+struct prof_tdata_s {
+ malloc_mutex_t *lock;
+
+ /* Monotonically increasing unique thread identifier. */
+ uint64_t thr_uid;
+
+ /*
+ * Monotonically increasing discriminator among tdata structures
+ * associated with the same thr_uid.
+ */
+ uint64_t thr_discrim;
+
+ /* Included in heap profile dumps if non-NULL. */
+ char *thread_name;
+
+ bool attached;
+ bool expired;
+
+ rb_node(prof_tdata_t) tdata_link;
+
+ /*
+ * Counter used to initialize prof_tctx_t's tctx_uid. No locking is
+ * necessary when incrementing this field, because only one thread ever
+ * does so.
+ */
+ uint64_t tctx_uid_next;
+
+ /*
+ * Hash of (prof_bt_t *)-->(prof_tctx_t *). Each thread tracks
+ * backtraces for which it has non-zero allocation/deallocation counters
+ * associated with thread-specific prof_tctx_t objects. Other threads
+ * may write to prof_tctx_t contents when freeing associated objects.
+ */
+ ckh_t bt2tctx;
+
+ /* Sampling state. */
+ uint64_t prng_state;
+ uint64_t bytes_until_sample;
+
+ /* State used to avoid dumping while operating on prof internals. */
+ bool enq;
+ bool enq_idump;
+ bool enq_gdump;
+
+ /*
+ * Set to true during an early dump phase for tdata's which are
+ * currently being dumped. New threads' tdata's have this initialized
+ * to false so that they aren't accidentally included in later dump
+ * phases.
+ */
+ bool dumping;
+
+ /*
+ * True if profiling is active for this tdata's thread
+ * (thread.prof.active mallctl).
+ */
+ bool active;
+
+ /* Temporary storage for summation during dump. */
+ prof_cnt_t cnt_summed;
+
+ /* Backtrace vector, used for calls to prof_backtrace(). */
+ void *vec[PROF_BT_MAX];
+};
+typedef rb_tree(prof_tdata_t) prof_tdata_tree_t;
+
+#endif /* JEMALLOC_INTERNAL_PROF_STRUCTS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/prof_types.h b/deps/jemalloc/include/jemalloc/internal/prof_types.h
new file mode 100644
index 000000000..1eff995ec
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/prof_types.h
@@ -0,0 +1,56 @@
+#ifndef JEMALLOC_INTERNAL_PROF_TYPES_H
+#define JEMALLOC_INTERNAL_PROF_TYPES_H
+
+typedef struct prof_bt_s prof_bt_t;
+typedef struct prof_accum_s prof_accum_t;
+typedef struct prof_cnt_s prof_cnt_t;
+typedef struct prof_tctx_s prof_tctx_t;
+typedef struct prof_gctx_s prof_gctx_t;
+typedef struct prof_tdata_s prof_tdata_t;
+
+/* Option defaults. */
+#ifdef JEMALLOC_PROF
+# define PROF_PREFIX_DEFAULT "jeprof"
+#else
+# define PROF_PREFIX_DEFAULT ""
+#endif
+#define LG_PROF_SAMPLE_DEFAULT 19
+#define LG_PROF_INTERVAL_DEFAULT -1
+
+/*
+ * Hard limit on stack backtrace depth. The version of prof_backtrace() that
+ * is based on __builtin_return_address() necessarily has a hard-coded number
+ * of backtrace frame handlers, and should be kept in sync with this setting.
+ */
+#define PROF_BT_MAX 128
+
+/* Initial hash table size. */
+#define PROF_CKH_MINITEMS 64
+
+/* Size of memory buffer to use when writing dump files. */
+#define PROF_DUMP_BUFSIZE 65536
+
+/* Size of stack-allocated buffer used by prof_printf(). */
+#define PROF_PRINTF_BUFSIZE 128
+
+/*
+ * Number of mutexes shared among all gctx's. No space is allocated for these
+ * unless profiling is enabled, so it's okay to over-provision.
+ */
+#define PROF_NCTX_LOCKS 1024
+
+/*
+ * Number of mutexes shared among all tdata's. No space is allocated for these
+ * unless profiling is enabled, so it's okay to over-provision.
+ */
+#define PROF_NTDATA_LOCKS 256
+
+/*
+ * prof_tdata pointers close to NULL are used to encode state information that
+ * is used for cleaning up during thread shutdown.
+ */
+#define PROF_TDATA_STATE_REINCARNATED ((prof_tdata_t *)(uintptr_t)1)
+#define PROF_TDATA_STATE_PURGATORY ((prof_tdata_t *)(uintptr_t)2)
+#define PROF_TDATA_STATE_MAX PROF_TDATA_STATE_PURGATORY
+
+#endif /* JEMALLOC_INTERNAL_PROF_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/public_namespace.sh b/deps/jemalloc/include/jemalloc/internal/public_namespace.sh
index 362109f71..4d415ba01 100755
--- a/deps/jemalloc/include/jemalloc/internal/public_namespace.sh
+++ b/deps/jemalloc/include/jemalloc/internal/public_namespace.sh
@@ -2,5 +2,5 @@
for nm in `cat $1` ; do
n=`echo ${nm} |tr ':' ' ' |awk '{print $1}'`
- echo "#define je_${n} JEMALLOC_N(${n})"
+ echo "#define je_${n} JEMALLOC_N(${n})"
done
diff --git a/deps/jemalloc/include/jemalloc/internal/ql.h b/deps/jemalloc/include/jemalloc/internal/ql.h
index 1834bb855..802904077 100644
--- a/deps/jemalloc/include/jemalloc/internal/ql.h
+++ b/deps/jemalloc/include/jemalloc/internal/ql.h
@@ -1,59 +1,64 @@
+#ifndef JEMALLOC_INTERNAL_QL_H
+#define JEMALLOC_INTERNAL_QL_H
+
+#include "jemalloc/internal/qr.h"
+
/* List definitions. */
-#define ql_head(a_type) \
+#define ql_head(a_type) \
struct { \
a_type *qlh_first; \
}
-#define ql_head_initializer(a_head) {NULL}
+#define ql_head_initializer(a_head) {NULL}
-#define ql_elm(a_type) qr(a_type)
+#define ql_elm(a_type) qr(a_type)
/* List functions. */
-#define ql_new(a_head) do { \
+#define ql_new(a_head) do { \
(a_head)->qlh_first = NULL; \
} while (0)
-#define ql_elm_new(a_elm, a_field) qr_new((a_elm), a_field)
+#define ql_elm_new(a_elm, a_field) qr_new((a_elm), a_field)
-#define ql_first(a_head) ((a_head)->qlh_first)
+#define ql_first(a_head) ((a_head)->qlh_first)
-#define ql_last(a_head, a_field) \
+#define ql_last(a_head, a_field) \
((ql_first(a_head) != NULL) \
? qr_prev(ql_first(a_head), a_field) : NULL)
-#define ql_next(a_head, a_elm, a_field) \
+#define ql_next(a_head, a_elm, a_field) \
((ql_last(a_head, a_field) != (a_elm)) \
? qr_next((a_elm), a_field) : NULL)
-#define ql_prev(a_head, a_elm, a_field) \
+#define ql_prev(a_head, a_elm, a_field) \
((ql_first(a_head) != (a_elm)) ? qr_prev((a_elm), a_field) \
: NULL)
-#define ql_before_insert(a_head, a_qlelm, a_elm, a_field) do { \
+#define ql_before_insert(a_head, a_qlelm, a_elm, a_field) do { \
qr_before_insert((a_qlelm), (a_elm), a_field); \
if (ql_first(a_head) == (a_qlelm)) { \
ql_first(a_head) = (a_elm); \
} \
} while (0)
-#define ql_after_insert(a_qlelm, a_elm, a_field) \
+#define ql_after_insert(a_qlelm, a_elm, a_field) \
qr_after_insert((a_qlelm), (a_elm), a_field)
-#define ql_head_insert(a_head, a_elm, a_field) do { \
+#define ql_head_insert(a_head, a_elm, a_field) do { \
if (ql_first(a_head) != NULL) { \
qr_before_insert(ql_first(a_head), (a_elm), a_field); \
} \
ql_first(a_head) = (a_elm); \
} while (0)
-#define ql_tail_insert(a_head, a_elm, a_field) do { \
+#define ql_tail_insert(a_head, a_elm, a_field) do { \
if (ql_first(a_head) != NULL) { \
qr_before_insert(ql_first(a_head), (a_elm), a_field); \
} \
ql_first(a_head) = qr_next((a_elm), a_field); \
} while (0)
-#define ql_remove(a_head, a_elm, a_field) do { \
+#define ql_remove(a_head, a_elm, a_field) do { \
if (ql_first(a_head) == (a_elm)) { \
ql_first(a_head) = qr_next(ql_first(a_head), a_field); \
} \
@@ -64,18 +69,20 @@ struct { \
} \
} while (0)
-#define ql_head_remove(a_head, a_type, a_field) do { \
+#define ql_head_remove(a_head, a_type, a_field) do { \
a_type *t = ql_first(a_head); \
ql_remove((a_head), t, a_field); \
} while (0)
-#define ql_tail_remove(a_head, a_type, a_field) do { \
+#define ql_tail_remove(a_head, a_type, a_field) do { \
a_type *t = ql_last(a_head, a_field); \
ql_remove((a_head), t, a_field); \
} while (0)
-#define ql_foreach(a_var, a_head, a_field) \
+#define ql_foreach(a_var, a_head, a_field) \
qr_foreach((a_var), ql_first(a_head), a_field)
-#define ql_reverse_foreach(a_var, a_head, a_field) \
+#define ql_reverse_foreach(a_var, a_head, a_field) \
qr_reverse_foreach((a_var), ql_first(a_head), a_field)
+
+#endif /* JEMALLOC_INTERNAL_QL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/qr.h b/deps/jemalloc/include/jemalloc/internal/qr.h
index 0fbaec25e..1e1056b38 100644
--- a/deps/jemalloc/include/jemalloc/internal/qr.h
+++ b/deps/jemalloc/include/jemalloc/internal/qr.h
@@ -1,38 +1,39 @@
+#ifndef JEMALLOC_INTERNAL_QR_H
+#define JEMALLOC_INTERNAL_QR_H
+
/* Ring definitions. */
-#define qr(a_type) \
+#define qr(a_type) \
struct { \
a_type *qre_next; \
a_type *qre_prev; \
}
/* Ring functions. */
-#define qr_new(a_qr, a_field) do { \
+#define qr_new(a_qr, a_field) do { \
(a_qr)->a_field.qre_next = (a_qr); \
(a_qr)->a_field.qre_prev = (a_qr); \
} while (0)
-#define qr_next(a_qr, a_field) ((a_qr)->a_field.qre_next)
+#define qr_next(a_qr, a_field) ((a_qr)->a_field.qre_next)
-#define qr_prev(a_qr, a_field) ((a_qr)->a_field.qre_prev)
+#define qr_prev(a_qr, a_field) ((a_qr)->a_field.qre_prev)
-#define qr_before_insert(a_qrelm, a_qr, a_field) do { \
+#define qr_before_insert(a_qrelm, a_qr, a_field) do { \
(a_qr)->a_field.qre_prev = (a_qrelm)->a_field.qre_prev; \
(a_qr)->a_field.qre_next = (a_qrelm); \
(a_qr)->a_field.qre_prev->a_field.qre_next = (a_qr); \
(a_qrelm)->a_field.qre_prev = (a_qr); \
} while (0)
-#define qr_after_insert(a_qrelm, a_qr, a_field) \
- do \
- { \
+#define qr_after_insert(a_qrelm, a_qr, a_field) do { \
(a_qr)->a_field.qre_next = (a_qrelm)->a_field.qre_next; \
(a_qr)->a_field.qre_prev = (a_qrelm); \
(a_qr)->a_field.qre_next->a_field.qre_prev = (a_qr); \
(a_qrelm)->a_field.qre_next = (a_qr); \
- } while (0)
+} while (0)
-#define qr_meld(a_qr_a, a_qr_b, a_field) do { \
- void *t; \
+#define qr_meld(a_qr_a, a_qr_b, a_type, a_field) do { \
+ a_type *t; \
(a_qr_a)->a_field.qre_prev->a_field.qre_next = (a_qr_b); \
(a_qr_b)->a_field.qre_prev->a_field.qre_next = (a_qr_a); \
t = (a_qr_a)->a_field.qre_prev; \
@@ -44,10 +45,10 @@ struct { \
* qr_meld() and qr_split() are functionally equivalent, so there's no need to
* have two copies of the code.
*/
-#define qr_split(a_qr_a, a_qr_b, a_field) \
- qr_meld((a_qr_a), (a_qr_b), a_field)
+#define qr_split(a_qr_a, a_qr_b, a_type, a_field) \
+ qr_meld((a_qr_a), (a_qr_b), a_type, a_field)
-#define qr_remove(a_qr, a_field) do { \
+#define qr_remove(a_qr, a_field) do { \
(a_qr)->a_field.qre_prev->a_field.qre_next \
= (a_qr)->a_field.qre_next; \
(a_qr)->a_field.qre_next->a_field.qre_prev \
@@ -56,14 +57,16 @@ struct { \
(a_qr)->a_field.qre_prev = (a_qr); \
} while (0)
-#define qr_foreach(var, a_qr, a_field) \
+#define qr_foreach(var, a_qr, a_field) \
for ((var) = (a_qr); \
(var) != NULL; \
(var) = (((var)->a_field.qre_next != (a_qr)) \
? (var)->a_field.qre_next : NULL))
-#define qr_reverse_foreach(var, a_qr, a_field) \
+#define qr_reverse_foreach(var, a_qr, a_field) \
for ((var) = ((a_qr) != NULL) ? qr_prev(a_qr, a_field) : NULL; \
(var) != NULL; \
(var) = (((var) != (a_qr)) \
? (var)->a_field.qre_prev : NULL))
+
+#endif /* JEMALLOC_INTERNAL_QR_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/quarantine.h b/deps/jemalloc/include/jemalloc/internal/quarantine.h
deleted file mode 100644
index ae607399f..000000000
--- a/deps/jemalloc/include/jemalloc/internal/quarantine.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct quarantine_obj_s quarantine_obj_t;
-typedef struct quarantine_s quarantine_t;
-
-/* Default per thread quarantine size if valgrind is enabled. */
-#define JEMALLOC_VALGRIND_QUARANTINE_DEFAULT (ZU(1) << 24)
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-struct quarantine_obj_s {
- void *ptr;
- size_t usize;
-};
-
-struct quarantine_s {
- size_t curbytes;
- size_t curobjs;
- size_t first;
-#define LG_MAXOBJS_INIT 10
- size_t lg_maxobjs;
- quarantine_obj_t objs[1]; /* Dynamically sized ring buffer. */
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void quarantine_alloc_hook_work(tsd_t *tsd);
-void quarantine(tsd_t *tsd, void *ptr);
-void quarantine_cleanup(tsd_t *tsd);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-void quarantine_alloc_hook(void);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_QUARANTINE_C_))
-JEMALLOC_ALWAYS_INLINE void
-quarantine_alloc_hook(void)
-{
- tsd_t *tsd;
-
- assert(config_fill && opt_quarantine);
-
- tsd = tsd_fetch();
- if (tsd_quarantine_get(tsd) == NULL)
- quarantine_alloc_hook_work(tsd);
-}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
-
diff --git a/deps/jemalloc/include/jemalloc/internal/rb.h b/deps/jemalloc/include/jemalloc/internal/rb.h
index 2ca8e5933..47fa5ca99 100644
--- a/deps/jemalloc/include/jemalloc/internal/rb.h
+++ b/deps/jemalloc/include/jemalloc/internal/rb.h
@@ -20,17 +20,21 @@
*/
#ifndef RB_H_
-#define RB_H_
+#define RB_H_
+
+#ifndef __PGI
+#define RB_COMPACT
+#endif
#ifdef RB_COMPACT
/* Node structure. */
-#define rb_node(a_type) \
+#define rb_node(a_type) \
struct { \
a_type *rbn_left; \
a_type *rbn_right_red; \
}
#else
-#define rb_node(a_type) \
+#define rb_node(a_type) \
struct { \
a_type *rbn_left; \
a_type *rbn_right; \
@@ -39,111 +43,116 @@ struct { \
#endif
/* Root structure. */
-#define rb_tree(a_type) \
+#define rb_tree(a_type) \
struct { \
a_type *rbt_root; \
- a_type rbt_nil; \
}
/* Left accessors. */
-#define rbtn_left_get(a_type, a_field, a_node) \
+#define rbtn_left_get(a_type, a_field, a_node) \
((a_node)->a_field.rbn_left)
-#define rbtn_left_set(a_type, a_field, a_node, a_left) do { \
+#define rbtn_left_set(a_type, a_field, a_node, a_left) do { \
(a_node)->a_field.rbn_left = a_left; \
} while (0)
#ifdef RB_COMPACT
/* Right accessors. */
-#define rbtn_right_get(a_type, a_field, a_node) \
+#define rbtn_right_get(a_type, a_field, a_node) \
((a_type *) (((intptr_t) (a_node)->a_field.rbn_right_red) \
& ((ssize_t)-2)))
-#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \
+#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \
(a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) a_right) \
| (((uintptr_t) (a_node)->a_field.rbn_right_red) & ((size_t)1))); \
} while (0)
/* Color accessors. */
-#define rbtn_red_get(a_type, a_field, a_node) \
+#define rbtn_red_get(a_type, a_field, a_node) \
((bool) (((uintptr_t) (a_node)->a_field.rbn_right_red) \
& ((size_t)1)))
-#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \
+#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \
(a_node)->a_field.rbn_right_red = (a_type *) ((((intptr_t) \
(a_node)->a_field.rbn_right_red) & ((ssize_t)-2)) \
| ((ssize_t)a_red)); \
} while (0)
-#define rbtn_red_set(a_type, a_field, a_node) do { \
+#define rbtn_red_set(a_type, a_field, a_node) do { \
(a_node)->a_field.rbn_right_red = (a_type *) (((uintptr_t) \
(a_node)->a_field.rbn_right_red) | ((size_t)1)); \
} while (0)
-#define rbtn_black_set(a_type, a_field, a_node) do { \
+#define rbtn_black_set(a_type, a_field, a_node) do { \
(a_node)->a_field.rbn_right_red = (a_type *) (((intptr_t) \
(a_node)->a_field.rbn_right_red) & ((ssize_t)-2)); \
} while (0)
+
+/* Node initializer. */
+#define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \
+ /* Bookkeeping bit cannot be used by node pointer. */ \
+ assert(((uintptr_t)(a_node) & 0x1) == 0); \
+ rbtn_left_set(a_type, a_field, (a_node), NULL); \
+ rbtn_right_set(a_type, a_field, (a_node), NULL); \
+ rbtn_red_set(a_type, a_field, (a_node)); \
+} while (0)
#else
/* Right accessors. */
-#define rbtn_right_get(a_type, a_field, a_node) \
+#define rbtn_right_get(a_type, a_field, a_node) \
((a_node)->a_field.rbn_right)
-#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \
+#define rbtn_right_set(a_type, a_field, a_node, a_right) do { \
(a_node)->a_field.rbn_right = a_right; \
} while (0)
/* Color accessors. */
-#define rbtn_red_get(a_type, a_field, a_node) \
+#define rbtn_red_get(a_type, a_field, a_node) \
((a_node)->a_field.rbn_red)
-#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \
+#define rbtn_color_set(a_type, a_field, a_node, a_red) do { \
(a_node)->a_field.rbn_red = (a_red); \
} while (0)
-#define rbtn_red_set(a_type, a_field, a_node) do { \
+#define rbtn_red_set(a_type, a_field, a_node) do { \
(a_node)->a_field.rbn_red = true; \
} while (0)
-#define rbtn_black_set(a_type, a_field, a_node) do { \
+#define rbtn_black_set(a_type, a_field, a_node) do { \
(a_node)->a_field.rbn_red = false; \
} while (0)
-#endif
/* Node initializer. */
-#define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \
- rbtn_left_set(a_type, a_field, (a_node), &(a_rbt)->rbt_nil); \
- rbtn_right_set(a_type, a_field, (a_node), &(a_rbt)->rbt_nil); \
+#define rbt_node_new(a_type, a_field, a_rbt, a_node) do { \
+ rbtn_left_set(a_type, a_field, (a_node), NULL); \
+ rbtn_right_set(a_type, a_field, (a_node), NULL); \
rbtn_red_set(a_type, a_field, (a_node)); \
} while (0)
+#endif
/* Tree initializer. */
-#define rb_new(a_type, a_field, a_rbt) do { \
- (a_rbt)->rbt_root = &(a_rbt)->rbt_nil; \
- rbt_node_new(a_type, a_field, a_rbt, &(a_rbt)->rbt_nil); \
- rbtn_black_set(a_type, a_field, &(a_rbt)->rbt_nil); \
+#define rb_new(a_type, a_field, a_rbt) do { \
+ (a_rbt)->rbt_root = NULL; \
} while (0)
/* Internal utility macros. */
-#define rbtn_first(a_type, a_field, a_rbt, a_root, r_node) do { \
+#define rbtn_first(a_type, a_field, a_rbt, a_root, r_node) do { \
(r_node) = (a_root); \
- if ((r_node) != &(a_rbt)->rbt_nil) { \
+ if ((r_node) != NULL) { \
for (; \
- rbtn_left_get(a_type, a_field, (r_node)) != &(a_rbt)->rbt_nil;\
+ rbtn_left_get(a_type, a_field, (r_node)) != NULL; \
(r_node) = rbtn_left_get(a_type, a_field, (r_node))) { \
} \
} \
} while (0)
-#define rbtn_last(a_type, a_field, a_rbt, a_root, r_node) do { \
+#define rbtn_last(a_type, a_field, a_rbt, a_root, r_node) do { \
(r_node) = (a_root); \
- if ((r_node) != &(a_rbt)->rbt_nil) { \
- for (; rbtn_right_get(a_type, a_field, (r_node)) != \
- &(a_rbt)->rbt_nil; (r_node) = rbtn_right_get(a_type, a_field, \
- (r_node))) { \
+ if ((r_node) != NULL) { \
+ for (; rbtn_right_get(a_type, a_field, (r_node)) != NULL; \
+ (r_node) = rbtn_right_get(a_type, a_field, (r_node))) { \
} \
} \
} while (0)
-#define rbtn_rotate_left(a_type, a_field, a_node, r_node) do { \
+#define rbtn_rotate_left(a_type, a_field, a_node, r_node) do { \
(r_node) = rbtn_right_get(a_type, a_field, (a_node)); \
rbtn_right_set(a_type, a_field, (a_node), \
rbtn_left_get(a_type, a_field, (r_node))); \
rbtn_left_set(a_type, a_field, (r_node), (a_node)); \
} while (0)
-#define rbtn_rotate_right(a_type, a_field, a_node, r_node) do { \
+#define rbtn_rotate_right(a_type, a_field, a_node, r_node) do { \
(r_node) = rbtn_left_get(a_type, a_field, (a_node)); \
rbtn_left_set(a_type, a_field, (a_node), \
rbtn_right_get(a_type, a_field, (r_node))); \
@@ -155,7 +164,7 @@ struct { \
* functions generated by an equivalently parameterized call to rb_gen().
*/
-#define rb_proto(a_attr, a_prefix, a_rbt_type, a_type) \
+#define rb_proto(a_attr, a_prefix, a_rbt_type, a_type) \
a_attr void \
a_prefix##new(a_rbt_type *rbtree); \
a_attr bool \
@@ -169,11 +178,11 @@ a_prefix##next(a_rbt_type *rbtree, a_type *node); \
a_attr a_type * \
a_prefix##prev(a_rbt_type *rbtree, a_type *node); \
a_attr a_type * \
-a_prefix##search(a_rbt_type *rbtree, a_type *key); \
+a_prefix##search(a_rbt_type *rbtree, const a_type *key); \
a_attr a_type * \
-a_prefix##nsearch(a_rbt_type *rbtree, a_type *key); \
+a_prefix##nsearch(a_rbt_type *rbtree, const a_type *key); \
a_attr a_type * \
-a_prefix##psearch(a_rbt_type *rbtree, a_type *key); \
+a_prefix##psearch(a_rbt_type *rbtree, const a_type *key); \
a_attr void \
a_prefix##insert(a_rbt_type *rbtree, a_type *node); \
a_attr void \
@@ -183,7 +192,10 @@ a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \
a_rbt_type *, a_type *, void *), void *arg); \
a_attr a_type * \
a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \
- a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg);
+ a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg); \
+a_attr void \
+a_prefix##destroy(a_rbt_type *rbtree, void (*cb)(a_type *, void *), \
+ void *arg);
/*
* The rb_gen() macro generates a type-specific red-black tree implementation,
@@ -254,7 +266,7 @@ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \
* last/first.
*
* static ex_node_t *
- * ex_search(ex_t *tree, ex_node_t *key);
+ * ex_search(ex_t *tree, const ex_node_t *key);
* Description: Search for node that matches key.
* Args:
* tree: Pointer to an initialized red-black tree object.
@@ -262,9 +274,9 @@ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \
* Ret: Node in tree that matches key, or NULL if no match.
*
* static ex_node_t *
- * ex_nsearch(ex_t *tree, ex_node_t *key);
+ * ex_nsearch(ex_t *tree, const ex_node_t *key);
* static ex_node_t *
- * ex_psearch(ex_t *tree, ex_node_t *key);
+ * ex_psearch(ex_t *tree, const ex_node_t *key);
* Description: Search for node that matches key. If no match is found,
* return what would be key's successor/predecessor, were
* key in tree.
@@ -312,44 +324,52 @@ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \
* arg : Opaque pointer passed to cb().
* Ret: NULL if iteration completed, or the non-NULL callback return value
* that caused termination of the iteration.
+ *
+ * static void
+ * ex_destroy(ex_t *tree, void (*cb)(ex_node_t *, void *), void *arg);
+ * Description: Iterate over the tree with post-order traversal, remove
+ * each node, and run the callback if non-null. This is
+ * used for destroying a tree without paying the cost to
+ * rebalance it. The tree must not be otherwise altered
+ * during traversal.
+ * Args:
+ * tree: Pointer to an initialized red-black tree object.
+ * cb : Callback function, which, if non-null, is called for each node
+ * during iteration. There is no way to stop iteration once it
+ * has begun.
+ * arg : Opaque pointer passed to cb().
*/
-#define rb_gen(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp) \
+#define rb_gen(a_attr, a_prefix, a_rbt_type, a_type, a_field, a_cmp) \
a_attr void \
a_prefix##new(a_rbt_type *rbtree) { \
rb_new(a_type, a_field, rbtree); \
} \
a_attr bool \
a_prefix##empty(a_rbt_type *rbtree) { \
- return (rbtree->rbt_root == &rbtree->rbt_nil); \
+ return (rbtree->rbt_root == NULL); \
} \
a_attr a_type * \
a_prefix##first(a_rbt_type *rbtree) { \
a_type *ret; \
rbtn_first(a_type, a_field, rbtree, rbtree->rbt_root, ret); \
- if (ret == &rbtree->rbt_nil) { \
- ret = NULL; \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
a_prefix##last(a_rbt_type *rbtree) { \
a_type *ret; \
rbtn_last(a_type, a_field, rbtree, rbtree->rbt_root, ret); \
- if (ret == &rbtree->rbt_nil) { \
- ret = NULL; \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
a_prefix##next(a_rbt_type *rbtree, a_type *node) { \
a_type *ret; \
- if (rbtn_right_get(a_type, a_field, node) != &rbtree->rbt_nil) { \
+ if (rbtn_right_get(a_type, a_field, node) != NULL) { \
rbtn_first(a_type, a_field, rbtree, rbtn_right_get(a_type, \
a_field, node), ret); \
} else { \
a_type *tnode = rbtree->rbt_root; \
- assert(tnode != &rbtree->rbt_nil); \
- ret = &rbtree->rbt_nil; \
+ assert(tnode != NULL); \
+ ret = NULL; \
while (true) { \
int cmp = (a_cmp)(node, tnode); \
if (cmp < 0) { \
@@ -360,24 +380,21 @@ a_prefix##next(a_rbt_type *rbtree, a_type *node) { \
} else { \
break; \
} \
- assert(tnode != &rbtree->rbt_nil); \
+ assert(tnode != NULL); \
} \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = (NULL); \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
a_prefix##prev(a_rbt_type *rbtree, a_type *node) { \
a_type *ret; \
- if (rbtn_left_get(a_type, a_field, node) != &rbtree->rbt_nil) { \
+ if (rbtn_left_get(a_type, a_field, node) != NULL) { \
rbtn_last(a_type, a_field, rbtree, rbtn_left_get(a_type, \
a_field, node), ret); \
} else { \
a_type *tnode = rbtree->rbt_root; \
- assert(tnode != &rbtree->rbt_nil); \
- ret = &rbtree->rbt_nil; \
+ assert(tnode != NULL); \
+ ret = NULL; \
while (true) { \
int cmp = (a_cmp)(node, tnode); \
if (cmp < 0) { \
@@ -388,20 +405,17 @@ a_prefix##prev(a_rbt_type *rbtree, a_type *node) { \
} else { \
break; \
} \
- assert(tnode != &rbtree->rbt_nil); \
+ assert(tnode != NULL); \
} \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = (NULL); \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
-a_prefix##search(a_rbt_type *rbtree, a_type *key) { \
+a_prefix##search(a_rbt_type *rbtree, const a_type *key) { \
a_type *ret; \
int cmp; \
ret = rbtree->rbt_root; \
- while (ret != &rbtree->rbt_nil \
+ while (ret != NULL \
&& (cmp = (a_cmp)(key, ret)) != 0) { \
if (cmp < 0) { \
ret = rbtn_left_get(a_type, a_field, ret); \
@@ -409,17 +423,14 @@ a_prefix##search(a_rbt_type *rbtree, a_type *key) { \
ret = rbtn_right_get(a_type, a_field, ret); \
} \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = (NULL); \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
-a_prefix##nsearch(a_rbt_type *rbtree, a_type *key) { \
+a_prefix##nsearch(a_rbt_type *rbtree, const a_type *key) { \
a_type *ret; \
a_type *tnode = rbtree->rbt_root; \
- ret = &rbtree->rbt_nil; \
- while (tnode != &rbtree->rbt_nil) { \
+ ret = NULL; \
+ while (tnode != NULL) { \
int cmp = (a_cmp)(key, tnode); \
if (cmp < 0) { \
ret = tnode; \
@@ -431,17 +442,14 @@ a_prefix##nsearch(a_rbt_type *rbtree, a_type *key) { \
break; \
} \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = (NULL); \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
-a_prefix##psearch(a_rbt_type *rbtree, a_type *key) { \
+a_prefix##psearch(a_rbt_type *rbtree, const a_type *key) { \
a_type *ret; \
a_type *tnode = rbtree->rbt_root; \
- ret = &rbtree->rbt_nil; \
- while (tnode != &rbtree->rbt_nil) { \
+ ret = NULL; \
+ while (tnode != NULL) { \
int cmp = (a_cmp)(key, tnode); \
if (cmp < 0) { \
tnode = rbtn_left_get(a_type, a_field, tnode); \
@@ -453,10 +461,7 @@ a_prefix##psearch(a_rbt_type *rbtree, a_type *key) { \
break; \
} \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = (NULL); \
- } \
- return (ret); \
+ return ret; \
} \
a_attr void \
a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \
@@ -467,7 +472,7 @@ a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \
rbt_node_new(a_type, a_field, rbtree, node); \
/* Wind. */ \
path->node = rbtree->rbt_root; \
- for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) { \
+ for (pathp = path; pathp->node != NULL; pathp++) { \
int cmp = pathp->cmp = a_cmp(node, pathp->node); \
assert(cmp != 0); \
if (cmp < 0) { \
@@ -487,7 +492,8 @@ a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \
rbtn_left_set(a_type, a_field, cnode, left); \
if (rbtn_red_get(a_type, a_field, left)) { \
a_type *leftleft = rbtn_left_get(a_type, a_field, left);\
- if (rbtn_red_get(a_type, a_field, leftleft)) { \
+ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \
+ leftleft)) { \
/* Fix up 4-node. */ \
a_type *tnode; \
rbtn_black_set(a_type, a_field, leftleft); \
@@ -502,7 +508,8 @@ a_prefix##insert(a_rbt_type *rbtree, a_type *node) { \
rbtn_right_set(a_type, a_field, cnode, right); \
if (rbtn_red_get(a_type, a_field, right)) { \
a_type *left = rbtn_left_get(a_type, a_field, cnode); \
- if (rbtn_red_get(a_type, a_field, left)) { \
+ if (left != NULL && rbtn_red_get(a_type, a_field, \
+ left)) { \
/* Split 4-node. */ \
rbtn_black_set(a_type, a_field, left); \
rbtn_black_set(a_type, a_field, right); \
@@ -535,7 +542,7 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
/* Wind. */ \
nodep = NULL; /* Silence compiler warning. */ \
path->node = rbtree->rbt_root; \
- for (pathp = path; pathp->node != &rbtree->rbt_nil; pathp++) { \
+ for (pathp = path; pathp->node != NULL; pathp++) { \
int cmp = pathp->cmp = a_cmp(node, pathp->node); \
if (cmp < 0) { \
pathp[1].node = rbtn_left_get(a_type, a_field, \
@@ -547,8 +554,7 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
/* Find node's successor, in preparation for swap. */ \
pathp->cmp = 1; \
nodep = pathp; \
- for (pathp++; pathp->node != &rbtree->rbt_nil; \
- pathp++) { \
+ for (pathp++; pathp->node != NULL; pathp++) { \
pathp->cmp = -1; \
pathp[1].node = rbtn_left_get(a_type, a_field, \
pathp->node); \
@@ -590,7 +596,7 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
} \
} else { \
a_type *left = rbtn_left_get(a_type, a_field, node); \
- if (left != &rbtree->rbt_nil) { \
+ if (left != NULL) { \
/* node has no successor, but it has a left child. */\
/* Splice node out, without losing the left child. */\
assert(!rbtn_red_get(a_type, a_field, node)); \
@@ -610,33 +616,32 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
return; \
} else if (pathp == path) { \
/* The tree only contained one node. */ \
- rbtree->rbt_root = &rbtree->rbt_nil; \
+ rbtree->rbt_root = NULL; \
return; \
} \
} \
if (rbtn_red_get(a_type, a_field, pathp->node)) { \
/* Prune red node, which requires no fixup. */ \
assert(pathp[-1].cmp < 0); \
- rbtn_left_set(a_type, a_field, pathp[-1].node, \
- &rbtree->rbt_nil); \
+ rbtn_left_set(a_type, a_field, pathp[-1].node, NULL); \
return; \
} \
/* The node to be pruned is black, so unwind until balance is */\
/* restored. */\
- pathp->node = &rbtree->rbt_nil; \
+ pathp->node = NULL; \
for (pathp--; (uintptr_t)pathp >= (uintptr_t)path; pathp--) { \
assert(pathp->cmp != 0); \
if (pathp->cmp < 0) { \
rbtn_left_set(a_type, a_field, pathp->node, \
pathp[1].node); \
- assert(!rbtn_red_get(a_type, a_field, pathp[1].node)); \
if (rbtn_red_get(a_type, a_field, pathp->node)) { \
a_type *right = rbtn_right_get(a_type, a_field, \
pathp->node); \
a_type *rightleft = rbtn_left_get(a_type, a_field, \
right); \
a_type *tnode; \
- if (rbtn_red_get(a_type, a_field, rightleft)) { \
+ if (rightleft != NULL && rbtn_red_get(a_type, a_field, \
+ rightleft)) { \
/* In the following diagrams, ||, //, and \\ */\
/* indicate the path to the removed node. */\
/* */\
@@ -679,7 +684,8 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
pathp->node); \
a_type *rightleft = rbtn_left_get(a_type, a_field, \
right); \
- if (rbtn_red_get(a_type, a_field, rightleft)) { \
+ if (rightleft != NULL && rbtn_red_get(a_type, a_field, \
+ rightleft)) { \
/* || */\
/* pathp(b) */\
/* // \ */\
@@ -733,7 +739,8 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
left); \
a_type *leftrightleft = rbtn_left_get(a_type, a_field, \
leftright); \
- if (rbtn_red_get(a_type, a_field, leftrightleft)) { \
+ if (leftrightleft != NULL && rbtn_red_get(a_type, \
+ a_field, leftrightleft)) { \
/* || */\
/* pathp(b) */\
/* / \\ */\
@@ -759,7 +766,7 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
/* (b) */\
/* / */\
/* (b) */\
- assert(leftright != &rbtree->rbt_nil); \
+ assert(leftright != NULL); \
rbtn_red_set(a_type, a_field, leftright); \
rbtn_rotate_right(a_type, a_field, pathp->node, \
tnode); \
@@ -782,7 +789,8 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
return; \
} else if (rbtn_red_get(a_type, a_field, pathp->node)) { \
a_type *leftleft = rbtn_left_get(a_type, a_field, left);\
- if (rbtn_red_get(a_type, a_field, leftleft)) { \
+ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \
+ leftleft)) { \
/* || */\
/* pathp(r) */\
/* / \\ */\
@@ -820,7 +828,8 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
} \
} else { \
a_type *leftleft = rbtn_left_get(a_type, a_field, left);\
- if (rbtn_red_get(a_type, a_field, leftleft)) { \
+ if (leftleft != NULL && rbtn_red_get(a_type, a_field, \
+ leftleft)) { \
/* || */\
/* pathp(b) */\
/* / \\ */\
@@ -866,17 +875,17 @@ a_prefix##remove(a_rbt_type *rbtree, a_type *node) { \
a_attr a_type * \
a_prefix##iter_recurse(a_rbt_type *rbtree, a_type *node, \
a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \
- if (node == &rbtree->rbt_nil) { \
- return (&rbtree->rbt_nil); \
+ if (node == NULL) { \
+ return NULL; \
} else { \
a_type *ret; \
if ((ret = a_prefix##iter_recurse(rbtree, rbtn_left_get(a_type, \
- a_field, node), cb, arg)) != &rbtree->rbt_nil \
- || (ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ a_field, node), cb, arg)) != NULL || (ret = cb(rbtree, node, \
+ arg)) != NULL) { \
+ return ret; \
} \
- return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
- a_field, node), cb, arg)); \
+ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
+ a_field, node), cb, arg); \
} \
} \
a_attr a_type * \
@@ -886,22 +895,22 @@ a_prefix##iter_start(a_rbt_type *rbtree, a_type *start, a_type *node, \
if (cmp < 0) { \
a_type *ret; \
if ((ret = a_prefix##iter_start(rbtree, start, \
- rbtn_left_get(a_type, a_field, node), cb, arg)) != \
- &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ rbtn_left_get(a_type, a_field, node), cb, arg)) != NULL || \
+ (ret = cb(rbtree, node, arg)) != NULL) { \
+ return ret; \
} \
- return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
- a_field, node), cb, arg)); \
+ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
+ a_field, node), cb, arg); \
} else if (cmp > 0) { \
- return (a_prefix##iter_start(rbtree, start, \
- rbtn_right_get(a_type, a_field, node), cb, arg)); \
+ return a_prefix##iter_start(rbtree, start, \
+ rbtn_right_get(a_type, a_field, node), cb, arg); \
} else { \
a_type *ret; \
if ((ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ return ret; \
} \
- return (a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
- a_field, node), cb, arg)); \
+ return a_prefix##iter_recurse(rbtree, rbtn_right_get(a_type, \
+ a_field, node), cb, arg); \
} \
} \
a_attr a_type * \
@@ -914,25 +923,22 @@ a_prefix##iter(a_rbt_type *rbtree, a_type *start, a_type *(*cb)( \
} else { \
ret = a_prefix##iter_recurse(rbtree, rbtree->rbt_root, cb, arg);\
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = NULL; \
- } \
- return (ret); \
+ return ret; \
} \
a_attr a_type * \
a_prefix##reverse_iter_recurse(a_rbt_type *rbtree, a_type *node, \
a_type *(*cb)(a_rbt_type *, a_type *, void *), void *arg) { \
- if (node == &rbtree->rbt_nil) { \
- return (&rbtree->rbt_nil); \
+ if (node == NULL) { \
+ return NULL; \
} else { \
a_type *ret; \
if ((ret = a_prefix##reverse_iter_recurse(rbtree, \
- rbtn_right_get(a_type, a_field, node), cb, arg)) != \
- &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ rbtn_right_get(a_type, a_field, node), cb, arg)) != NULL || \
+ (ret = cb(rbtree, node, arg)) != NULL) { \
+ return ret; \
} \
- return (a_prefix##reverse_iter_recurse(rbtree, \
- rbtn_left_get(a_type, a_field, node), cb, arg)); \
+ return a_prefix##reverse_iter_recurse(rbtree, \
+ rbtn_left_get(a_type, a_field, node), cb, arg); \
} \
} \
a_attr a_type * \
@@ -943,22 +949,22 @@ a_prefix##reverse_iter_start(a_rbt_type *rbtree, a_type *start, \
if (cmp > 0) { \
a_type *ret; \
if ((ret = a_prefix##reverse_iter_start(rbtree, start, \
- rbtn_right_get(a_type, a_field, node), cb, arg)) != \
- &rbtree->rbt_nil || (ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ rbtn_right_get(a_type, a_field, node), cb, arg)) != NULL || \
+ (ret = cb(rbtree, node, arg)) != NULL) { \
+ return ret; \
} \
- return (a_prefix##reverse_iter_recurse(rbtree, \
- rbtn_left_get(a_type, a_field, node), cb, arg)); \
+ return a_prefix##reverse_iter_recurse(rbtree, \
+ rbtn_left_get(a_type, a_field, node), cb, arg); \
} else if (cmp < 0) { \
- return (a_prefix##reverse_iter_start(rbtree, start, \
- rbtn_left_get(a_type, a_field, node), cb, arg)); \
+ return a_prefix##reverse_iter_start(rbtree, start, \
+ rbtn_left_get(a_type, a_field, node), cb, arg); \
} else { \
a_type *ret; \
if ((ret = cb(rbtree, node, arg)) != NULL) { \
- return (ret); \
+ return ret; \
} \
- return (a_prefix##reverse_iter_recurse(rbtree, \
- rbtn_left_get(a_type, a_field, node), cb, arg)); \
+ return a_prefix##reverse_iter_recurse(rbtree, \
+ rbtn_left_get(a_type, a_field, node), cb, arg); \
} \
} \
a_attr a_type * \
@@ -972,10 +978,29 @@ a_prefix##reverse_iter(a_rbt_type *rbtree, a_type *start, \
ret = a_prefix##reverse_iter_recurse(rbtree, rbtree->rbt_root, \
cb, arg); \
} \
- if (ret == &rbtree->rbt_nil) { \
- ret = NULL; \
+ return ret; \
+} \
+a_attr void \
+a_prefix##destroy_recurse(a_rbt_type *rbtree, a_type *node, void (*cb)( \
+ a_type *, void *), void *arg) { \
+ if (node == NULL) { \
+ return; \
} \
- return (ret); \
+ a_prefix##destroy_recurse(rbtree, rbtn_left_get(a_type, a_field, \
+ node), cb, arg); \
+ rbtn_left_set(a_type, a_field, (node), NULL); \
+ a_prefix##destroy_recurse(rbtree, rbtn_right_get(a_type, a_field, \
+ node), cb, arg); \
+ rbtn_right_set(a_type, a_field, (node), NULL); \
+ if (cb) { \
+ cb(node, arg); \
+ } \
+} \
+a_attr void \
+a_prefix##destroy(a_rbt_type *rbtree, void (*cb)(a_type *, void *), \
+ void *arg) { \
+ a_prefix##destroy_recurse(rbtree, rbtree->rbt_root, cb, arg); \
+ rbtree->rbt_root = NULL; \
}
#endif /* RB_H_ */
diff --git a/deps/jemalloc/include/jemalloc/internal/rtree.h b/deps/jemalloc/include/jemalloc/internal/rtree.h
index 28ae9d1dd..b59d33a80 100644
--- a/deps/jemalloc/include/jemalloc/internal/rtree.h
+++ b/deps/jemalloc/include/jemalloc/internal/rtree.h
@@ -1,74 +1,72 @@
+#ifndef JEMALLOC_INTERNAL_RTREE_H
+#define JEMALLOC_INTERNAL_RTREE_H
+
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree_tsd.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/tsd.h"
+
/*
* This radix tree implementation is tailored to the singular purpose of
- * associating metadata with chunks that are currently owned by jemalloc.
+ * associating metadata with extents that are currently owned by jemalloc.
*
*******************************************************************************
*/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct rtree_node_elm_s rtree_node_elm_t;
-typedef struct rtree_level_s rtree_level_t;
-typedef struct rtree_s rtree_t;
-/*
- * RTREE_BITS_PER_LEVEL must be a power of two that is no larger than the
- * machine address width.
- */
-#define LG_RTREE_BITS_PER_LEVEL 4
-#define RTREE_BITS_PER_LEVEL (ZU(1) << LG_RTREE_BITS_PER_LEVEL)
-#define RTREE_HEIGHT_MAX \
- ((ZU(1) << (LG_SIZEOF_PTR+3)) / RTREE_BITS_PER_LEVEL)
-
-/* Used for two-stage lock-free node initialization. */
-#define RTREE_NODE_INITIALIZING ((rtree_node_elm_t *)0x1)
-
-/*
- * The node allocation callback function's argument is the number of contiguous
- * rtree_node_elm_t structures to allocate, and the resulting memory must be
- * zeroed.
- */
-typedef rtree_node_elm_t *(rtree_node_alloc_t)(size_t);
-typedef void (rtree_node_dalloc_t)(rtree_node_elm_t *);
+/* Number of high insignificant bits. */
+#define RTREE_NHIB ((1U << (LG_SIZEOF_PTR+3)) - LG_VADDR)
+/* Number of low insigificant bits. */
+#define RTREE_NLIB LG_PAGE
+/* Number of significant bits. */
+#define RTREE_NSB (LG_VADDR - RTREE_NLIB)
+/* Number of levels in radix tree. */
+#if RTREE_NSB <= 10
+# define RTREE_HEIGHT 1
+#elif RTREE_NSB <= 36
+# define RTREE_HEIGHT 2
+#elif RTREE_NSB <= 52
+# define RTREE_HEIGHT 3
+#else
+# error Unsupported number of significant virtual address bits
+#endif
+/* Use compact leaf representation if virtual address encoding allows. */
+#if RTREE_NHIB >= LG_CEIL_NSIZES
+# define RTREE_LEAF_COMPACT
+#endif
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
+/* Needed for initialization only. */
+#define RTREE_LEAFKEY_INVALID ((uintptr_t)1)
+typedef struct rtree_node_elm_s rtree_node_elm_t;
struct rtree_node_elm_s {
- union {
- void *pun;
- rtree_node_elm_t *child;
- extent_node_t *val;
- };
+ atomic_p_t child; /* (rtree_{node,leaf}_elm_t *) */
};
-struct rtree_level_s {
+struct rtree_leaf_elm_s {
+#ifdef RTREE_LEAF_COMPACT
/*
- * A non-NULL subtree points to a subtree rooted along the hypothetical
- * path to the leaf node corresponding to key 0. Depending on what keys
- * have been used to store to the tree, an arbitrary combination of
- * subtree pointers may remain NULL.
- *
- * Suppose keys comprise 48 bits, and LG_RTREE_BITS_PER_LEVEL is 4.
- * This results in a 3-level tree, and the leftmost leaf can be directly
- * accessed via subtrees[2], the subtree prefixed by 0x0000 (excluding
- * 0x00000000) can be accessed via subtrees[1], and the remainder of the
- * tree can be accessed via subtrees[0].
+ * Single pointer-width field containing all three leaf element fields.
+ * For example, on a 64-bit x64 system with 48 significant virtual
+ * memory address bits, the index, extent, and slab fields are packed as
+ * such:
*
- * levels[0] : [<unused> | 0x0001******** | 0x0002******** | ...]
+ * x: index
+ * e: extent
+ * b: slab
*
- * levels[1] : [<unused> | 0x00000001**** | 0x00000002**** | ... ]
- *
- * levels[2] : [val(0x000000000000) | val(0x000000000001) | ...]
- *
- * This has practical implications on x64, which currently uses only the
- * lower 47 bits of virtual address space in userland, thus leaving
- * subtrees[0] unused and avoiding a level of tree traversal.
+ * 00000000 xxxxxxxx eeeeeeee [...] eeeeeeee eeee000b
*/
- union {
- void *subtree_pun;
- rtree_node_elm_t *subtree;
- };
+ atomic_p_t le_bits;
+#else
+ atomic_p_t le_extent; /* (extent_t *) */
+ atomic_u_t le_szind; /* (szind_t) */
+ atomic_b_t le_slab; /* (bool) */
+#endif
+};
+
+typedef struct rtree_level_s rtree_level_t;
+struct rtree_level_s {
/* Number of key bits distinguished by this level. */
unsigned bits;
/*
@@ -78,217 +76,417 @@ struct rtree_level_s {
unsigned cumbits;
};
+typedef struct rtree_s rtree_t;
struct rtree_s {
- rtree_node_alloc_t *alloc;
- rtree_node_dalloc_t *dalloc;
- unsigned height;
- /*
- * Precomputed table used to convert from the number of leading 0 key
- * bits to which subtree level to start at.
- */
- unsigned start_level[RTREE_HEIGHT_MAX];
- rtree_level_t levels[RTREE_HEIGHT_MAX];
+ malloc_mutex_t init_lock;
+ /* Number of elements based on rtree_levels[0].bits. */
+#if RTREE_HEIGHT > 1
+ rtree_node_elm_t root[1U << (RTREE_NSB/RTREE_HEIGHT)];
+#else
+ rtree_leaf_elm_t root[1U << (RTREE_NSB/RTREE_HEIGHT)];
+#endif
};
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-bool rtree_new(rtree_t *rtree, unsigned bits, rtree_node_alloc_t *alloc,
- rtree_node_dalloc_t *dalloc);
-void rtree_delete(rtree_t *rtree);
-rtree_node_elm_t *rtree_subtree_read_hard(rtree_t *rtree,
- unsigned level);
-rtree_node_elm_t *rtree_child_read_hard(rtree_t *rtree,
- rtree_node_elm_t *elm, unsigned level);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-unsigned rtree_start_level(rtree_t *rtree, uintptr_t key);
-uintptr_t rtree_subkey(rtree_t *rtree, uintptr_t key, unsigned level);
-
-bool rtree_node_valid(rtree_node_elm_t *node);
-rtree_node_elm_t *rtree_child_tryread(rtree_node_elm_t *elm);
-rtree_node_elm_t *rtree_child_read(rtree_t *rtree, rtree_node_elm_t *elm,
- unsigned level);
-extent_node_t *rtree_val_read(rtree_t *rtree, rtree_node_elm_t *elm,
- bool dependent);
-void rtree_val_write(rtree_t *rtree, rtree_node_elm_t *elm,
- const extent_node_t *val);
-rtree_node_elm_t *rtree_subtree_tryread(rtree_t *rtree, unsigned level);
-rtree_node_elm_t *rtree_subtree_read(rtree_t *rtree, unsigned level);
-
-extent_node_t *rtree_get(rtree_t *rtree, uintptr_t key, bool dependent);
-bool rtree_set(rtree_t *rtree, uintptr_t key, const extent_node_t *val);
+/*
+ * Split the bits into one to three partitions depending on number of
+ * significant bits. It the number of bits does not divide evenly into the
+ * number of levels, place one remainder bit per level starting at the leaf
+ * level.
+ */
+static const rtree_level_t rtree_levels[] = {
+#if RTREE_HEIGHT == 1
+ {RTREE_NSB, RTREE_NHIB + RTREE_NSB}
+#elif RTREE_HEIGHT == 2
+ {RTREE_NSB/2, RTREE_NHIB + RTREE_NSB/2},
+ {RTREE_NSB/2 + RTREE_NSB%2, RTREE_NHIB + RTREE_NSB}
+#elif RTREE_HEIGHT == 3
+ {RTREE_NSB/3, RTREE_NHIB + RTREE_NSB/3},
+ {RTREE_NSB/3 + RTREE_NSB%3/2,
+ RTREE_NHIB + RTREE_NSB/3*2 + RTREE_NSB%3/2},
+ {RTREE_NSB/3 + RTREE_NSB%3 - RTREE_NSB%3/2, RTREE_NHIB + RTREE_NSB}
+#else
+# error Unsupported rtree height
#endif
+};
+
+bool rtree_new(rtree_t *rtree, bool zeroed);
+
+typedef rtree_node_elm_t *(rtree_node_alloc_t)(tsdn_t *, rtree_t *, size_t);
+extern rtree_node_alloc_t *JET_MUTABLE rtree_node_alloc;
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_RTREE_C_))
-JEMALLOC_INLINE unsigned
-rtree_start_level(rtree_t *rtree, uintptr_t key)
-{
- unsigned start_level;
+typedef rtree_leaf_elm_t *(rtree_leaf_alloc_t)(tsdn_t *, rtree_t *, size_t);
+extern rtree_leaf_alloc_t *JET_MUTABLE rtree_leaf_alloc;
- if (unlikely(key == 0))
- return (rtree->height - 1);
+typedef void (rtree_node_dalloc_t)(tsdn_t *, rtree_t *, rtree_node_elm_t *);
+extern rtree_node_dalloc_t *JET_MUTABLE rtree_node_dalloc;
- start_level = rtree->start_level[lg_floor(key) >>
- LG_RTREE_BITS_PER_LEVEL];
- assert(start_level < rtree->height);
- return (start_level);
+typedef void (rtree_leaf_dalloc_t)(tsdn_t *, rtree_t *, rtree_leaf_elm_t *);
+extern rtree_leaf_dalloc_t *JET_MUTABLE rtree_leaf_dalloc;
+#ifdef JEMALLOC_JET
+void rtree_delete(tsdn_t *tsdn, rtree_t *rtree);
+#endif
+rtree_leaf_elm_t *rtree_leaf_elm_lookup_hard(tsdn_t *tsdn, rtree_t *rtree,
+ rtree_ctx_t *rtree_ctx, uintptr_t key, bool dependent, bool init_missing);
+
+JEMALLOC_ALWAYS_INLINE uintptr_t
+rtree_leafkey(uintptr_t key) {
+ unsigned ptrbits = ZU(1) << (LG_SIZEOF_PTR+3);
+ unsigned cumbits = (rtree_levels[RTREE_HEIGHT-1].cumbits -
+ rtree_levels[RTREE_HEIGHT-1].bits);
+ unsigned maskbits = ptrbits - cumbits;
+ uintptr_t mask = ~((ZU(1) << maskbits) - 1);
+ return (key & mask);
}
-JEMALLOC_INLINE uintptr_t
-rtree_subkey(rtree_t *rtree, uintptr_t key, unsigned level)
-{
+JEMALLOC_ALWAYS_INLINE size_t
+rtree_cache_direct_map(uintptr_t key) {
+ unsigned ptrbits = ZU(1) << (LG_SIZEOF_PTR+3);
+ unsigned cumbits = (rtree_levels[RTREE_HEIGHT-1].cumbits -
+ rtree_levels[RTREE_HEIGHT-1].bits);
+ unsigned maskbits = ptrbits - cumbits;
+ return (size_t)((key >> maskbits) & (RTREE_CTX_NCACHE - 1));
+}
- return ((key >> ((ZU(1) << (LG_SIZEOF_PTR+3)) -
- rtree->levels[level].cumbits)) & ((ZU(1) <<
- rtree->levels[level].bits) - 1));
+JEMALLOC_ALWAYS_INLINE uintptr_t
+rtree_subkey(uintptr_t key, unsigned level) {
+ unsigned ptrbits = ZU(1) << (LG_SIZEOF_PTR+3);
+ unsigned cumbits = rtree_levels[level].cumbits;
+ unsigned shiftbits = ptrbits - cumbits;
+ unsigned maskbits = rtree_levels[level].bits;
+ uintptr_t mask = (ZU(1) << maskbits) - 1;
+ return ((key >> shiftbits) & mask);
}
-JEMALLOC_INLINE bool
-rtree_node_valid(rtree_node_elm_t *node)
-{
+/*
+ * Atomic getters.
+ *
+ * dependent: Reading a value on behalf of a pointer to a valid allocation
+ * is guaranteed to be a clean read even without synchronization,
+ * because the rtree update became visible in memory before the
+ * pointer came into existence.
+ * !dependent: An arbitrary read, e.g. on behalf of ivsalloc(), may not be
+ * dependent on a previous rtree write, which means a stale read
+ * could result if synchronization were omitted here.
+ */
+# ifdef RTREE_LEAF_COMPACT
+JEMALLOC_ALWAYS_INLINE uintptr_t
+rtree_leaf_elm_bits_read(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm,
+ bool dependent) {
+ return (uintptr_t)atomic_load_p(&elm->le_bits, dependent
+ ? ATOMIC_RELAXED : ATOMIC_ACQUIRE);
+}
- return ((uintptr_t)node > (uintptr_t)RTREE_NODE_INITIALIZING);
+JEMALLOC_ALWAYS_INLINE extent_t *
+rtree_leaf_elm_bits_extent_get(uintptr_t bits) {
+# ifdef __aarch64__
+ /*
+ * aarch64 doesn't sign extend the highest virtual address bit to set
+ * the higher ones. Instead, the high bits gets zeroed.
+ */
+ uintptr_t high_bit_mask = ((uintptr_t)1 << LG_VADDR) - 1;
+ /* Mask off the slab bit. */
+ uintptr_t low_bit_mask = ~(uintptr_t)1;
+ uintptr_t mask = high_bit_mask & low_bit_mask;
+ return (extent_t *)(bits & mask);
+# else
+ /* Restore sign-extended high bits, mask slab bit. */
+ return (extent_t *)((uintptr_t)((intptr_t)(bits << RTREE_NHIB) >>
+ RTREE_NHIB) & ~((uintptr_t)0x1));
+# endif
}
-JEMALLOC_INLINE rtree_node_elm_t *
-rtree_child_tryread(rtree_node_elm_t *elm)
-{
- rtree_node_elm_t *child;
+JEMALLOC_ALWAYS_INLINE szind_t
+rtree_leaf_elm_bits_szind_get(uintptr_t bits) {
+ return (szind_t)(bits >> LG_VADDR);
+}
- /* Double-checked read (first read may be stale. */
- child = elm->child;
- if (!rtree_node_valid(child))
- child = atomic_read_p(&elm->pun);
- return (child);
+JEMALLOC_ALWAYS_INLINE bool
+rtree_leaf_elm_bits_slab_get(uintptr_t bits) {
+ return (bool)(bits & (uintptr_t)0x1);
}
-JEMALLOC_INLINE rtree_node_elm_t *
-rtree_child_read(rtree_t *rtree, rtree_node_elm_t *elm, unsigned level)
-{
- rtree_node_elm_t *child;
+# endif
+
+JEMALLOC_ALWAYS_INLINE extent_t *
+rtree_leaf_elm_extent_read(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, bool dependent) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, dependent);
+ return rtree_leaf_elm_bits_extent_get(bits);
+#else
+ extent_t *extent = (extent_t *)atomic_load_p(&elm->le_extent, dependent
+ ? ATOMIC_RELAXED : ATOMIC_ACQUIRE);
+ return extent;
+#endif
+}
- child = rtree_child_tryread(elm);
- if (unlikely(!rtree_node_valid(child)))
- child = rtree_child_read_hard(rtree, elm, level);
- return (child);
+JEMALLOC_ALWAYS_INLINE szind_t
+rtree_leaf_elm_szind_read(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, bool dependent) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, dependent);
+ return rtree_leaf_elm_bits_szind_get(bits);
+#else
+ return (szind_t)atomic_load_u(&elm->le_szind, dependent ? ATOMIC_RELAXED
+ : ATOMIC_ACQUIRE);
+#endif
}
-JEMALLOC_INLINE extent_node_t *
-rtree_val_read(rtree_t *rtree, rtree_node_elm_t *elm, bool dependent)
-{
-
- if (dependent) {
- /*
- * Reading a val on behalf of a pointer to a valid allocation is
- * guaranteed to be a clean read even without synchronization,
- * because the rtree update became visible in memory before the
- * pointer came into existence.
- */
- return (elm->val);
- } else {
- /*
- * An arbitrary read, e.g. on behalf of ivsalloc(), may not be
- * dependent on a previous rtree write, which means a stale read
- * could result if synchronization were omitted here.
- */
- return (atomic_read_p(&elm->pun));
- }
+JEMALLOC_ALWAYS_INLINE bool
+rtree_leaf_elm_slab_read(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, bool dependent) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, dependent);
+ return rtree_leaf_elm_bits_slab_get(bits);
+#else
+ return atomic_load_b(&elm->le_slab, dependent ? ATOMIC_RELAXED :
+ ATOMIC_ACQUIRE);
+#endif
}
-JEMALLOC_INLINE void
-rtree_val_write(rtree_t *rtree, rtree_node_elm_t *elm, const extent_node_t *val)
-{
+static inline void
+rtree_leaf_elm_extent_write(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, extent_t *extent) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t old_bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, true);
+ uintptr_t bits = ((uintptr_t)rtree_leaf_elm_bits_szind_get(old_bits) <<
+ LG_VADDR) | ((uintptr_t)extent & (((uintptr_t)0x1 << LG_VADDR) - 1))
+ | ((uintptr_t)rtree_leaf_elm_bits_slab_get(old_bits));
+ atomic_store_p(&elm->le_bits, (void *)bits, ATOMIC_RELEASE);
+#else
+ atomic_store_p(&elm->le_extent, extent, ATOMIC_RELEASE);
+#endif
+}
- atomic_write_p(&elm->pun, val);
+static inline void
+rtree_leaf_elm_szind_write(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, szind_t szind) {
+ assert(szind <= NSIZES);
+
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t old_bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm,
+ true);
+ uintptr_t bits = ((uintptr_t)szind << LG_VADDR) |
+ ((uintptr_t)rtree_leaf_elm_bits_extent_get(old_bits) &
+ (((uintptr_t)0x1 << LG_VADDR) - 1)) |
+ ((uintptr_t)rtree_leaf_elm_bits_slab_get(old_bits));
+ atomic_store_p(&elm->le_bits, (void *)bits, ATOMIC_RELEASE);
+#else
+ atomic_store_u(&elm->le_szind, szind, ATOMIC_RELEASE);
+#endif
}
-JEMALLOC_INLINE rtree_node_elm_t *
-rtree_subtree_tryread(rtree_t *rtree, unsigned level)
-{
- rtree_node_elm_t *subtree;
+static inline void
+rtree_leaf_elm_slab_write(UNUSED tsdn_t *tsdn, UNUSED rtree_t *rtree,
+ rtree_leaf_elm_t *elm, bool slab) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t old_bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm,
+ true);
+ uintptr_t bits = ((uintptr_t)rtree_leaf_elm_bits_szind_get(old_bits) <<
+ LG_VADDR) | ((uintptr_t)rtree_leaf_elm_bits_extent_get(old_bits) &
+ (((uintptr_t)0x1 << LG_VADDR) - 1)) | ((uintptr_t)slab);
+ atomic_store_p(&elm->le_bits, (void *)bits, ATOMIC_RELEASE);
+#else
+ atomic_store_b(&elm->le_slab, slab, ATOMIC_RELEASE);
+#endif
+}
- /* Double-checked read (first read may be stale. */
- subtree = rtree->levels[level].subtree;
- if (!rtree_node_valid(subtree))
- subtree = atomic_read_p(&rtree->levels[level].subtree_pun);
- return (subtree);
+static inline void
+rtree_leaf_elm_write(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *elm,
+ extent_t *extent, szind_t szind, bool slab) {
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t bits = ((uintptr_t)szind << LG_VADDR) |
+ ((uintptr_t)extent & (((uintptr_t)0x1 << LG_VADDR) - 1)) |
+ ((uintptr_t)slab);
+ atomic_store_p(&elm->le_bits, (void *)bits, ATOMIC_RELEASE);
+#else
+ rtree_leaf_elm_slab_write(tsdn, rtree, elm, slab);
+ rtree_leaf_elm_szind_write(tsdn, rtree, elm, szind);
+ /*
+ * Write extent last, since the element is atomically considered valid
+ * as soon as the extent field is non-NULL.
+ */
+ rtree_leaf_elm_extent_write(tsdn, rtree, elm, extent);
+#endif
}
-JEMALLOC_INLINE rtree_node_elm_t *
-rtree_subtree_read(rtree_t *rtree, unsigned level)
-{
- rtree_node_elm_t *subtree;
+static inline void
+rtree_leaf_elm_szind_slab_update(tsdn_t *tsdn, rtree_t *rtree,
+ rtree_leaf_elm_t *elm, szind_t szind, bool slab) {
+ assert(!slab || szind < NBINS);
- subtree = rtree_subtree_tryread(rtree, level);
- if (unlikely(!rtree_node_valid(subtree)))
- subtree = rtree_subtree_read_hard(rtree, level);
- return (subtree);
+ /*
+ * The caller implicitly assures that it is the only writer to the szind
+ * and slab fields, and that the extent field cannot currently change.
+ */
+ rtree_leaf_elm_slab_write(tsdn, rtree, elm, slab);
+ rtree_leaf_elm_szind_write(tsdn, rtree, elm, szind);
}
-JEMALLOC_INLINE extent_node_t *
-rtree_get(rtree_t *rtree, uintptr_t key, bool dependent)
-{
- uintptr_t subkey;
- unsigned i, start_level;
- rtree_node_elm_t *node, *child;
-
- start_level = rtree_start_level(rtree, key);
-
- for (i = start_level, node = rtree_subtree_tryread(rtree, start_level);
- /**/; i++, node = child) {
- if (!dependent && unlikely(!rtree_node_valid(node)))
- return (NULL);
- subkey = rtree_subkey(rtree, key, i);
- if (i == rtree->height - 1) {
- /*
- * node is a leaf, so it contains values rather than
- * child pointers.
- */
- return (rtree_val_read(rtree, &node[subkey],
- dependent));
- }
- assert(i < rtree->height - 1);
- child = rtree_child_tryread(&node[subkey]);
+JEMALLOC_ALWAYS_INLINE rtree_leaf_elm_t *
+rtree_leaf_elm_lookup(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent, bool init_missing) {
+ assert(key != 0);
+ assert(!dependent || !init_missing);
+
+ size_t slot = rtree_cache_direct_map(key);
+ uintptr_t leafkey = rtree_leafkey(key);
+ assert(leafkey != RTREE_LEAFKEY_INVALID);
+
+ /* Fast path: L1 direct mapped cache. */
+ if (likely(rtree_ctx->cache[slot].leafkey == leafkey)) {
+ rtree_leaf_elm_t *leaf = rtree_ctx->cache[slot].leaf;
+ assert(leaf != NULL);
+ uintptr_t subkey = rtree_subkey(key, RTREE_HEIGHT-1);
+ return &leaf[subkey];
+ }
+ /*
+ * Search the L2 LRU cache. On hit, swap the matching element into the
+ * slot in L1 cache, and move the position in L2 up by 1.
+ */
+#define RTREE_CACHE_CHECK_L2(i) do { \
+ if (likely(rtree_ctx->l2_cache[i].leafkey == leafkey)) { \
+ rtree_leaf_elm_t *leaf = rtree_ctx->l2_cache[i].leaf; \
+ assert(leaf != NULL); \
+ if (i > 0) { \
+ /* Bubble up by one. */ \
+ rtree_ctx->l2_cache[i].leafkey = \
+ rtree_ctx->l2_cache[i - 1].leafkey; \
+ rtree_ctx->l2_cache[i].leaf = \
+ rtree_ctx->l2_cache[i - 1].leaf; \
+ rtree_ctx->l2_cache[i - 1].leafkey = \
+ rtree_ctx->cache[slot].leafkey; \
+ rtree_ctx->l2_cache[i - 1].leaf = \
+ rtree_ctx->cache[slot].leaf; \
+ } else { \
+ rtree_ctx->l2_cache[0].leafkey = \
+ rtree_ctx->cache[slot].leafkey; \
+ rtree_ctx->l2_cache[0].leaf = \
+ rtree_ctx->cache[slot].leaf; \
+ } \
+ rtree_ctx->cache[slot].leafkey = leafkey; \
+ rtree_ctx->cache[slot].leaf = leaf; \
+ uintptr_t subkey = rtree_subkey(key, RTREE_HEIGHT-1); \
+ return &leaf[subkey]; \
+ } \
+} while (0)
+ /* Check the first cache entry. */
+ RTREE_CACHE_CHECK_L2(0);
+ /* Search the remaining cache elements. */
+ for (unsigned i = 1; i < RTREE_CTX_NCACHE_L2; i++) {
+ RTREE_CACHE_CHECK_L2(i);
}
- not_reached();
+#undef RTREE_CACHE_CHECK_L2
+
+ return rtree_leaf_elm_lookup_hard(tsdn, rtree, rtree_ctx, key,
+ dependent, init_missing);
}
-JEMALLOC_INLINE bool
-rtree_set(rtree_t *rtree, uintptr_t key, const extent_node_t *val)
-{
- uintptr_t subkey;
- unsigned i, start_level;
- rtree_node_elm_t *node, *child;
-
- start_level = rtree_start_level(rtree, key);
-
- node = rtree_subtree_read(rtree, start_level);
- if (node == NULL)
- return (true);
- for (i = start_level; /**/; i++, node = child) {
- subkey = rtree_subkey(rtree, key, i);
- if (i == rtree->height - 1) {
- /*
- * node is a leaf, so it contains values rather than
- * child pointers.
- */
- rtree_val_write(rtree, &node[subkey], val);
- return (false);
- }
- assert(i + 1 < rtree->height);
- child = rtree_child_read(rtree, &node[subkey], i);
- if (child == NULL)
- return (true);
+static inline bool
+rtree_write(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key,
+ extent_t *extent, szind_t szind, bool slab) {
+ /* Use rtree_clear() to set the extent to NULL. */
+ assert(extent != NULL);
+
+ rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx,
+ key, false, true);
+ if (elm == NULL) {
+ return true;
}
- not_reached();
+
+ assert(rtree_leaf_elm_extent_read(tsdn, rtree, elm, false) == NULL);
+ rtree_leaf_elm_write(tsdn, rtree, elm, extent, szind, slab);
+
+ return false;
}
+
+JEMALLOC_ALWAYS_INLINE rtree_leaf_elm_t *
+rtree_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx, uintptr_t key,
+ bool dependent) {
+ rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree, rtree_ctx,
+ key, dependent, false);
+ if (!dependent && elm == NULL) {
+ return NULL;
+ }
+ assert(elm != NULL);
+ return elm;
+}
+
+JEMALLOC_ALWAYS_INLINE extent_t *
+rtree_extent_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent) {
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key,
+ dependent);
+ if (!dependent && elm == NULL) {
+ return NULL;
+ }
+ return rtree_leaf_elm_extent_read(tsdn, rtree, elm, dependent);
+}
+
+JEMALLOC_ALWAYS_INLINE szind_t
+rtree_szind_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent) {
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key,
+ dependent);
+ if (!dependent && elm == NULL) {
+ return NSIZES;
+ }
+ return rtree_leaf_elm_szind_read(tsdn, rtree, elm, dependent);
+}
+
+/*
+ * rtree_slab_read() is intentionally omitted because slab is always read in
+ * conjunction with szind, which makes rtree_szind_slab_read() a better choice.
+ */
+
+JEMALLOC_ALWAYS_INLINE bool
+rtree_extent_szind_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent, extent_t **r_extent, szind_t *r_szind) {
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key,
+ dependent);
+ if (!dependent && elm == NULL) {
+ return true;
+ }
+ *r_extent = rtree_leaf_elm_extent_read(tsdn, rtree, elm, dependent);
+ *r_szind = rtree_leaf_elm_szind_read(tsdn, rtree, elm, dependent);
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+rtree_szind_slab_read(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent, szind_t *r_szind, bool *r_slab) {
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key,
+ dependent);
+ if (!dependent && elm == NULL) {
+ return true;
+ }
+#ifdef RTREE_LEAF_COMPACT
+ uintptr_t bits = rtree_leaf_elm_bits_read(tsdn, rtree, elm, dependent);
+ *r_szind = rtree_leaf_elm_bits_szind_get(bits);
+ *r_slab = rtree_leaf_elm_bits_slab_get(bits);
+#else
+ *r_szind = rtree_leaf_elm_szind_read(tsdn, rtree, elm, dependent);
+ *r_slab = rtree_leaf_elm_slab_read(tsdn, rtree, elm, dependent);
#endif
+ return false;
+}
+
+static inline void
+rtree_szind_slab_update(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, szind_t szind, bool slab) {
+ assert(!slab || szind < NBINS);
+
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key, true);
+ rtree_leaf_elm_szind_slab_update(tsdn, rtree, elm, szind, slab);
+}
+
+static inline void
+rtree_clear(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key) {
+ rtree_leaf_elm_t *elm = rtree_read(tsdn, rtree, rtree_ctx, key, true);
+ assert(rtree_leaf_elm_extent_read(tsdn, rtree, elm, false) !=
+ NULL);
+ rtree_leaf_elm_write(tsdn, rtree, elm, NULL, NSIZES, false);
+}
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_RTREE_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/rtree_tsd.h b/deps/jemalloc/include/jemalloc/internal/rtree_tsd.h
new file mode 100644
index 000000000..93a75173a
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/rtree_tsd.h
@@ -0,0 +1,50 @@
+#ifndef JEMALLOC_INTERNAL_RTREE_CTX_H
+#define JEMALLOC_INTERNAL_RTREE_CTX_H
+
+/*
+ * Number of leafkey/leaf pairs to cache in L1 and L2 level respectively. Each
+ * entry supports an entire leaf, so the cache hit rate is typically high even
+ * with a small number of entries. In rare cases extent activity will straddle
+ * the boundary between two leaf nodes. Furthermore, an arena may use a
+ * combination of dss and mmap. Note that as memory usage grows past the amount
+ * that this cache can directly cover, the cache will become less effective if
+ * locality of reference is low, but the consequence is merely cache misses
+ * while traversing the tree nodes.
+ *
+ * The L1 direct mapped cache offers consistent and low cost on cache hit.
+ * However collision could affect hit rate negatively. This is resolved by
+ * combining with a L2 LRU cache, which requires linear search and re-ordering
+ * on access but suffers no collision. Note that, the cache will itself suffer
+ * cache misses if made overly large, plus the cost of linear search in the LRU
+ * cache.
+ */
+#define RTREE_CTX_LG_NCACHE 4
+#define RTREE_CTX_NCACHE (1 << RTREE_CTX_LG_NCACHE)
+#define RTREE_CTX_NCACHE_L2 8
+
+/*
+ * Zero initializer required for tsd initialization only. Proper initialization
+ * done via rtree_ctx_data_init().
+ */
+#define RTREE_CTX_ZERO_INITIALIZER {{{0}}, {{0}}}
+
+
+typedef struct rtree_leaf_elm_s rtree_leaf_elm_t;
+
+typedef struct rtree_ctx_cache_elm_s rtree_ctx_cache_elm_t;
+struct rtree_ctx_cache_elm_s {
+ uintptr_t leafkey;
+ rtree_leaf_elm_t *leaf;
+};
+
+typedef struct rtree_ctx_s rtree_ctx_t;
+struct rtree_ctx_s {
+ /* Direct mapped cache. */
+ rtree_ctx_cache_elm_t cache[RTREE_CTX_NCACHE];
+ /* L2 LRU cache. */
+ rtree_ctx_cache_elm_t l2_cache[RTREE_CTX_NCACHE_L2];
+};
+
+void rtree_ctx_data_init(rtree_ctx_t *ctx);
+
+#endif /* JEMALLOC_INTERNAL_RTREE_CTX_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/size_classes.sh b/deps/jemalloc/include/jemalloc/internal/size_classes.sh
index fc82036d3..998994d09 100755
--- a/deps/jemalloc/include/jemalloc/internal/size_classes.sh
+++ b/deps/jemalloc/include/jemalloc/internal/size_classes.sh
@@ -40,6 +40,54 @@ lg() {
done
}
+lg_ceil() {
+ y=$1
+ lg ${y}; lg_floor=${lg_result}
+ pow2 ${lg_floor}; pow2_floor=${pow2_result}
+ if [ ${pow2_floor} -lt ${y} ] ; then
+ lg_ceil_result=$((${lg_floor} + 1))
+ else
+ lg_ceil_result=${lg_floor}
+ fi
+}
+
+reg_size_compute() {
+ lg_grp=$1
+ lg_delta=$2
+ ndelta=$3
+
+ pow2 ${lg_grp}; grp=${pow2_result}
+ pow2 ${lg_delta}; delta=${pow2_result}
+ reg_size=$((${grp} + ${delta}*${ndelta}))
+}
+
+slab_size() {
+ lg_p=$1
+ lg_grp=$2
+ lg_delta=$3
+ ndelta=$4
+
+ pow2 ${lg_p}; p=${pow2_result}
+ reg_size_compute ${lg_grp} ${lg_delta} ${ndelta}
+
+ # Compute smallest slab size that is an integer multiple of reg_size.
+ try_slab_size=${p}
+ try_nregs=$((${try_slab_size} / ${reg_size}))
+ perfect=0
+ while [ ${perfect} -eq 0 ] ; do
+ perfect_slab_size=${try_slab_size}
+ perfect_nregs=${try_nregs}
+
+ try_slab_size=$((${try_slab_size} + ${p}))
+ try_nregs=$((${try_slab_size} / ${reg_size}))
+ if [ ${perfect_slab_size} -eq $((${perfect_nregs} * ${reg_size})) ] ; then
+ perfect=1
+ fi
+ done
+
+ slab_size_pgs=$((${perfect_slab_size} / ${p}))
+}
+
size_class() {
index=$1
lg_grp=$2
@@ -48,6 +96,21 @@ size_class() {
lg_p=$5
lg_kmax=$6
+ if [ ${lg_delta} -ge ${lg_p} ] ; then
+ psz="yes"
+ else
+ pow2 ${lg_p}; p=${pow2_result}
+ pow2 ${lg_grp}; grp=${pow2_result}
+ pow2 ${lg_delta}; delta=${pow2_result}
+ sz=$((${grp} + ${delta} * ${ndelta}))
+ npgs=$((${sz} / ${p}))
+ if [ ${sz} -eq $((${npgs} * ${p})) ] ; then
+ psz="yes"
+ else
+ psz="no"
+ fi
+ fi
+
lg ${ndelta}; lg_ndelta=${lg_result}; pow2 ${lg_ndelta}
if [ ${pow2_result} -lt ${ndelta} ] ; then
rem="yes"
@@ -65,8 +128,10 @@ size_class() {
if [ ${lg_size} -lt $((${lg_p} + ${lg_g})) ] ; then
bin="yes"
+ slab_size ${lg_p} ${lg_grp} ${lg_delta} ${ndelta}; pgs=${slab_size_pgs}
else
bin="no"
+ pgs=0
fi
if [ ${lg_size} -lt ${lg_kmax} \
-o ${lg_size} -eq ${lg_kmax} -a ${rem} = "no" ] ; then
@@ -74,14 +139,16 @@ size_class() {
else
lg_delta_lookup="no"
fi
- printf ' SC(%3d, %6d, %8d, %6d, %3s, %2s) \\\n' ${index} ${lg_grp} ${lg_delta} ${ndelta} ${bin} ${lg_delta_lookup}
+ printf ' SC(%3d, %6d, %8d, %6d, %3s, %3s, %3d, %2s) \\\n' ${index} ${lg_grp} ${lg_delta} ${ndelta} ${psz} ${bin} ${pgs} ${lg_delta_lookup}
# Defined upon return:
- # - lg_delta_lookup (${lg_delta} or "no")
+ # - psz ("yes" or "no")
# - bin ("yes" or "no")
+ # - pgs
+ # - lg_delta_lookup (${lg_delta} or "no")
}
sep_line() {
- echo " \\"
+ echo " \\"
}
size_classes() {
@@ -94,13 +161,14 @@ size_classes() {
pow2 $((${lg_z} + 3)); ptr_bits=${pow2_result}
pow2 ${lg_g}; g=${pow2_result}
- echo "#define SIZE_CLASSES \\"
- echo " /* index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup */ \\"
+ echo "#define SIZE_CLASSES \\"
+ echo " /* index, lg_grp, lg_delta, ndelta, psz, bin, pgs, lg_delta_lookup */ \\"
ntbins=0
nlbins=0
lg_tiny_maxclass='"NA"'
nbins=0
+ npsizes=0
# Tiny size classes.
ndelta=0
@@ -112,6 +180,9 @@ size_classes() {
if [ ${lg_delta_lookup} != "no" ] ; then
nlbins=$((${index} + 1))
fi
+ if [ ${psz} = "yes" ] ; then
+ npsizes=$((${npsizes} + 1))
+ fi
if [ ${bin} != "no" ] ; then
nbins=$((${index} + 1))
fi
@@ -133,19 +204,25 @@ size_classes() {
index=$((${index} + 1))
lg_grp=$((${lg_grp} + 1))
lg_delta=$((${lg_delta} + 1))
+ if [ ${psz} = "yes" ] ; then
+ npsizes=$((${npsizes} + 1))
+ fi
fi
while [ ${ndelta} -lt ${g} ] ; do
size_class ${index} ${lg_grp} ${lg_delta} ${ndelta} ${lg_p} ${lg_kmax}
index=$((${index} + 1))
ndelta=$((${ndelta} + 1))
+ if [ ${psz} = "yes" ] ; then
+ npsizes=$((${npsizes} + 1))
+ fi
done
# All remaining groups.
lg_grp=$((${lg_grp} + ${lg_g}))
- while [ ${lg_grp} -lt ${ptr_bits} ] ; do
+ while [ ${lg_grp} -lt $((${ptr_bits} - 1)) ] ; do
sep_line
ndelta=1
- if [ ${lg_grp} -eq $((${ptr_bits} - 1)) ] ; then
+ if [ ${lg_grp} -eq $((${ptr_bits} - 2)) ] ; then
ndelta_limit=$((${g} - 1))
else
ndelta_limit=${g}
@@ -157,6 +234,9 @@ size_classes() {
# Final written value is correct:
lookup_maxclass="((((size_t)1) << ${lg_grp}) + (((size_t)${ndelta}) << ${lg_delta}))"
fi
+ if [ ${psz} = "yes" ] ; then
+ npsizes=$((${npsizes} + 1))
+ fi
if [ ${bin} != "no" ] ; then
nbins=$((${index} + 1))
# Final written value is correct:
@@ -168,7 +248,7 @@ size_classes() {
fi
fi
# Final written value is correct:
- huge_maxclass="((((size_t)1) << ${lg_grp}) + (((size_t)${ndelta}) << ${lg_delta}))"
+ large_maxclass="((((size_t)1) << ${lg_grp}) + (((size_t)${ndelta}) << ${lg_delta}))"
index=$((${index} + 1))
ndelta=$((${ndelta} + 1))
done
@@ -177,51 +257,61 @@ size_classes() {
done
echo
nsizes=${index}
+ lg_ceil ${nsizes}; lg_ceil_nsizes=${lg_ceil_result}
# Defined upon completion:
# - ntbins
# - nlbins
# - nbins
# - nsizes
+ # - lg_ceil_nsizes
+ # - npsizes
# - lg_tiny_maxclass
# - lookup_maxclass
# - small_maxclass
# - lg_large_minclass
- # - huge_maxclass
+ # - large_maxclass
}
cat <<EOF
+#ifndef JEMALLOC_INTERNAL_SIZE_CLASSES_H
+#define JEMALLOC_INTERNAL_SIZE_CLASSES_H
+
/* This file was automatically generated by size_classes.sh. */
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+
+#include "jemalloc/internal/jemalloc_internal_types.h"
/*
- * This header requires LG_SIZEOF_PTR, LG_TINY_MIN, LG_QUANTUM, and LG_PAGE to
- * be defined prior to inclusion, and it in turn defines:
+ * This header file defines:
*
* LG_SIZE_CLASS_GROUP: Lg of size class count for each size doubling.
- * SIZE_CLASSES: Complete table of
- * SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup)
- * tuples.
+ * LG_TINY_MIN: Lg of minimum size class to support.
+ * SIZE_CLASSES: Complete table of SC(index, lg_grp, lg_delta, ndelta, psz,
+ * bin, pgs, lg_delta_lookup) tuples.
* index: Size class index.
* lg_grp: Lg group base size (no deltas added).
* lg_delta: Lg delta to previous size class.
* ndelta: Delta multiplier. size == 1<<lg_grp + ndelta<<lg_delta
+ * psz: 'yes' if a multiple of the page size, 'no' otherwise.
* bin: 'yes' if a small bin size class, 'no' otherwise.
+ * pgs: Slab page count if a small bin size class, 0 otherwise.
* lg_delta_lookup: Same as lg_delta if a lookup table size class, 'no'
* otherwise.
* NTBINS: Number of tiny bins.
* NLBINS: Number of bins supported by the lookup table.
* NBINS: Number of small size class bins.
* NSIZES: Number of size classes.
+ * LG_CEIL_NSIZES: Number of bits required to store NSIZES.
+ * NPSIZES: Number of size classes that are a multiple of (1U << LG_PAGE).
* LG_TINY_MAXCLASS: Lg of maximum tiny size class.
* LOOKUP_MAXCLASS: Maximum size class included in lookup table.
* SMALL_MAXCLASS: Maximum small size class.
* LG_LARGE_MINCLASS: Lg of minimum large size class.
- * HUGE_MAXCLASS: Maximum (huge) size class.
+ * LARGE_MAXCLASS: Maximum (large) size class.
*/
-#define LG_SIZE_CLASS_GROUP ${lg_g}
+#define LG_SIZE_CLASS_GROUP ${lg_g}
+#define LG_TINY_MIN ${lg_tmin}
EOF
@@ -233,16 +323,19 @@ for lg_z in ${lg_zarr} ; do
for lg_p in ${lg_parr} ; do
echo "#if (LG_SIZEOF_PTR == ${lg_z} && LG_TINY_MIN == ${lg_t} && LG_QUANTUM == ${lg_q} && LG_PAGE == ${lg_p})"
size_classes ${lg_z} ${lg_q} ${lg_t} ${lg_p} ${lg_g}
- echo "#define SIZE_CLASSES_DEFINED"
- echo "#define NTBINS ${ntbins}"
- echo "#define NLBINS ${nlbins}"
- echo "#define NBINS ${nbins}"
- echo "#define NSIZES ${nsizes}"
- echo "#define LG_TINY_MAXCLASS ${lg_tiny_maxclass}"
- echo "#define LOOKUP_MAXCLASS ${lookup_maxclass}"
- echo "#define SMALL_MAXCLASS ${small_maxclass}"
- echo "#define LG_LARGE_MINCLASS ${lg_large_minclass}"
- echo "#define HUGE_MAXCLASS ${huge_maxclass}"
+ echo "#define SIZE_CLASSES_DEFINED"
+ echo "#define NTBINS ${ntbins}"
+ echo "#define NLBINS ${nlbins}"
+ echo "#define NBINS ${nbins}"
+ echo "#define NSIZES ${nsizes}"
+ echo "#define LG_CEIL_NSIZES ${lg_ceil_nsizes}"
+ echo "#define NPSIZES ${npsizes}"
+ echo "#define LG_TINY_MAXCLASS ${lg_tiny_maxclass}"
+ echo "#define LOOKUP_MAXCLASS ${lookup_maxclass}"
+ echo "#define SMALL_MAXCLASS ${small_maxclass}"
+ echo "#define LG_LARGE_MINCLASS ${lg_large_minclass}"
+ echo "#define LARGE_MINCLASS (ZU(1) << LG_LARGE_MINCLASS)"
+ echo "#define LARGE_MAXCLASS ${large_maxclass}"
echo "#endif"
echo
done
@@ -258,29 +351,11 @@ cat <<EOF
#undef SIZE_CLASSES_DEFINED
/*
* The size2index_tab lookup table uses uint8_t to encode each bin index, so we
- * cannot support more than 256 small size classes. Further constrain NBINS to
- * 255 since all small size classes, plus a "not small" size class must be
- * stored in 8 bits of arena_chunk_map_bits_t's bits field.
+ * cannot support more than 256 small size classes.
*/
-#if (NBINS > 255)
+#if (NBINS > 256)
# error "Too many small size classes"
#endif
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_SIZE_CLASSES_H */
EOF
diff --git a/deps/jemalloc/include/jemalloc/internal/smoothstep.h b/deps/jemalloc/include/jemalloc/internal/smoothstep.h
new file mode 100644
index 000000000..2e14430f5
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/smoothstep.h
@@ -0,0 +1,232 @@
+#ifndef JEMALLOC_INTERNAL_SMOOTHSTEP_H
+#define JEMALLOC_INTERNAL_SMOOTHSTEP_H
+
+/*
+ * This file was generated by the following command:
+ * sh smoothstep.sh smoother 200 24 3 15
+ */
+/******************************************************************************/
+
+/*
+ * This header defines a precomputed table based on the smoothstep family of
+ * sigmoidal curves (https://en.wikipedia.org/wiki/Smoothstep) that grow from 0
+ * to 1 in 0 <= x <= 1. The table is stored as integer fixed point values so
+ * that floating point math can be avoided.
+ *
+ * 3 2
+ * smoothstep(x) = -2x + 3x
+ *
+ * 5 4 3
+ * smootherstep(x) = 6x - 15x + 10x
+ *
+ * 7 6 5 4
+ * smootheststep(x) = -20x + 70x - 84x + 35x
+ */
+
+#define SMOOTHSTEP_VARIANT "smoother"
+#define SMOOTHSTEP_NSTEPS 200
+#define SMOOTHSTEP_BFP 24
+#define SMOOTHSTEP \
+ /* STEP(step, h, x, y) */ \
+ STEP( 1, UINT64_C(0x0000000000000014), 0.005, 0.000001240643750) \
+ STEP( 2, UINT64_C(0x00000000000000a5), 0.010, 0.000009850600000) \
+ STEP( 3, UINT64_C(0x0000000000000229), 0.015, 0.000032995181250) \
+ STEP( 4, UINT64_C(0x0000000000000516), 0.020, 0.000077619200000) \
+ STEP( 5, UINT64_C(0x00000000000009dc), 0.025, 0.000150449218750) \
+ STEP( 6, UINT64_C(0x00000000000010e8), 0.030, 0.000257995800000) \
+ STEP( 7, UINT64_C(0x0000000000001aa4), 0.035, 0.000406555756250) \
+ STEP( 8, UINT64_C(0x0000000000002777), 0.040, 0.000602214400000) \
+ STEP( 9, UINT64_C(0x00000000000037c2), 0.045, 0.000850847793750) \
+ STEP( 10, UINT64_C(0x0000000000004be6), 0.050, 0.001158125000000) \
+ STEP( 11, UINT64_C(0x000000000000643c), 0.055, 0.001529510331250) \
+ STEP( 12, UINT64_C(0x000000000000811f), 0.060, 0.001970265600000) \
+ STEP( 13, UINT64_C(0x000000000000a2e2), 0.065, 0.002485452368750) \
+ STEP( 14, UINT64_C(0x000000000000c9d8), 0.070, 0.003079934200000) \
+ STEP( 15, UINT64_C(0x000000000000f64f), 0.075, 0.003758378906250) \
+ STEP( 16, UINT64_C(0x0000000000012891), 0.080, 0.004525260800000) \
+ STEP( 17, UINT64_C(0x00000000000160e7), 0.085, 0.005384862943750) \
+ STEP( 18, UINT64_C(0x0000000000019f95), 0.090, 0.006341279400000) \
+ STEP( 19, UINT64_C(0x000000000001e4dc), 0.095, 0.007398417481250) \
+ STEP( 20, UINT64_C(0x00000000000230fc), 0.100, 0.008560000000000) \
+ STEP( 21, UINT64_C(0x0000000000028430), 0.105, 0.009829567518750) \
+ STEP( 22, UINT64_C(0x000000000002deb0), 0.110, 0.011210480600000) \
+ STEP( 23, UINT64_C(0x00000000000340b1), 0.115, 0.012705922056250) \
+ STEP( 24, UINT64_C(0x000000000003aa67), 0.120, 0.014318899200000) \
+ STEP( 25, UINT64_C(0x0000000000041c00), 0.125, 0.016052246093750) \
+ STEP( 26, UINT64_C(0x00000000000495a8), 0.130, 0.017908625800000) \
+ STEP( 27, UINT64_C(0x000000000005178b), 0.135, 0.019890532631250) \
+ STEP( 28, UINT64_C(0x000000000005a1cf), 0.140, 0.022000294400000) \
+ STEP( 29, UINT64_C(0x0000000000063498), 0.145, 0.024240074668750) \
+ STEP( 30, UINT64_C(0x000000000006d009), 0.150, 0.026611875000000) \
+ STEP( 31, UINT64_C(0x000000000007743f), 0.155, 0.029117537206250) \
+ STEP( 32, UINT64_C(0x0000000000082157), 0.160, 0.031758745600000) \
+ STEP( 33, UINT64_C(0x000000000008d76b), 0.165, 0.034537029243750) \
+ STEP( 34, UINT64_C(0x0000000000099691), 0.170, 0.037453764200000) \
+ STEP( 35, UINT64_C(0x00000000000a5edf), 0.175, 0.040510175781250) \
+ STEP( 36, UINT64_C(0x00000000000b3067), 0.180, 0.043707340800000) \
+ STEP( 37, UINT64_C(0x00000000000c0b38), 0.185, 0.047046189818750) \
+ STEP( 38, UINT64_C(0x00000000000cef5e), 0.190, 0.050527509400000) \
+ STEP( 39, UINT64_C(0x00000000000ddce6), 0.195, 0.054151944356250) \
+ STEP( 40, UINT64_C(0x00000000000ed3d8), 0.200, 0.057920000000000) \
+ STEP( 41, UINT64_C(0x00000000000fd439), 0.205, 0.061832044393750) \
+ STEP( 42, UINT64_C(0x000000000010de0e), 0.210, 0.065888310600000) \
+ STEP( 43, UINT64_C(0x000000000011f158), 0.215, 0.070088898931250) \
+ STEP( 44, UINT64_C(0x0000000000130e17), 0.220, 0.074433779200000) \
+ STEP( 45, UINT64_C(0x0000000000143448), 0.225, 0.078922792968750) \
+ STEP( 46, UINT64_C(0x00000000001563e7), 0.230, 0.083555655800000) \
+ STEP( 47, UINT64_C(0x0000000000169cec), 0.235, 0.088331959506250) \
+ STEP( 48, UINT64_C(0x000000000017df4f), 0.240, 0.093251174400000) \
+ STEP( 49, UINT64_C(0x0000000000192b04), 0.245, 0.098312651543750) \
+ STEP( 50, UINT64_C(0x00000000001a8000), 0.250, 0.103515625000000) \
+ STEP( 51, UINT64_C(0x00000000001bde32), 0.255, 0.108859214081250) \
+ STEP( 52, UINT64_C(0x00000000001d458b), 0.260, 0.114342425600000) \
+ STEP( 53, UINT64_C(0x00000000001eb5f8), 0.265, 0.119964156118750) \
+ STEP( 54, UINT64_C(0x0000000000202f65), 0.270, 0.125723194200000) \
+ STEP( 55, UINT64_C(0x000000000021b1bb), 0.275, 0.131618222656250) \
+ STEP( 56, UINT64_C(0x0000000000233ce3), 0.280, 0.137647820800000) \
+ STEP( 57, UINT64_C(0x000000000024d0c3), 0.285, 0.143810466693750) \
+ STEP( 58, UINT64_C(0x0000000000266d40), 0.290, 0.150104539400000) \
+ STEP( 59, UINT64_C(0x000000000028123d), 0.295, 0.156528321231250) \
+ STEP( 60, UINT64_C(0x000000000029bf9c), 0.300, 0.163080000000000) \
+ STEP( 61, UINT64_C(0x00000000002b753d), 0.305, 0.169757671268750) \
+ STEP( 62, UINT64_C(0x00000000002d32fe), 0.310, 0.176559340600000) \
+ STEP( 63, UINT64_C(0x00000000002ef8bc), 0.315, 0.183482925806250) \
+ STEP( 64, UINT64_C(0x000000000030c654), 0.320, 0.190526259200000) \
+ STEP( 65, UINT64_C(0x0000000000329b9f), 0.325, 0.197687089843750) \
+ STEP( 66, UINT64_C(0x0000000000347875), 0.330, 0.204963085800000) \
+ STEP( 67, UINT64_C(0x0000000000365cb0), 0.335, 0.212351836381250) \
+ STEP( 68, UINT64_C(0x0000000000384825), 0.340, 0.219850854400000) \
+ STEP( 69, UINT64_C(0x00000000003a3aa8), 0.345, 0.227457578418750) \
+ STEP( 70, UINT64_C(0x00000000003c340f), 0.350, 0.235169375000000) \
+ STEP( 71, UINT64_C(0x00000000003e342b), 0.355, 0.242983540956250) \
+ STEP( 72, UINT64_C(0x0000000000403ace), 0.360, 0.250897305600000) \
+ STEP( 73, UINT64_C(0x00000000004247c8), 0.365, 0.258907832993750) \
+ STEP( 74, UINT64_C(0x0000000000445ae9), 0.370, 0.267012224200000) \
+ STEP( 75, UINT64_C(0x0000000000467400), 0.375, 0.275207519531250) \
+ STEP( 76, UINT64_C(0x00000000004892d8), 0.380, 0.283490700800000) \
+ STEP( 77, UINT64_C(0x00000000004ab740), 0.385, 0.291858693568750) \
+ STEP( 78, UINT64_C(0x00000000004ce102), 0.390, 0.300308369400000) \
+ STEP( 79, UINT64_C(0x00000000004f0fe9), 0.395, 0.308836548106250) \
+ STEP( 80, UINT64_C(0x00000000005143bf), 0.400, 0.317440000000000) \
+ STEP( 81, UINT64_C(0x0000000000537c4d), 0.405, 0.326115448143750) \
+ STEP( 82, UINT64_C(0x000000000055b95b), 0.410, 0.334859570600000) \
+ STEP( 83, UINT64_C(0x000000000057fab1), 0.415, 0.343669002681250) \
+ STEP( 84, UINT64_C(0x00000000005a4015), 0.420, 0.352540339200000) \
+ STEP( 85, UINT64_C(0x00000000005c894e), 0.425, 0.361470136718750) \
+ STEP( 86, UINT64_C(0x00000000005ed622), 0.430, 0.370454915800000) \
+ STEP( 87, UINT64_C(0x0000000000612655), 0.435, 0.379491163256250) \
+ STEP( 88, UINT64_C(0x00000000006379ac), 0.440, 0.388575334400000) \
+ STEP( 89, UINT64_C(0x000000000065cfeb), 0.445, 0.397703855293750) \
+ STEP( 90, UINT64_C(0x00000000006828d6), 0.450, 0.406873125000000) \
+ STEP( 91, UINT64_C(0x00000000006a842f), 0.455, 0.416079517831250) \
+ STEP( 92, UINT64_C(0x00000000006ce1bb), 0.460, 0.425319385600000) \
+ STEP( 93, UINT64_C(0x00000000006f413a), 0.465, 0.434589059868750) \
+ STEP( 94, UINT64_C(0x000000000071a270), 0.470, 0.443884854200000) \
+ STEP( 95, UINT64_C(0x000000000074051d), 0.475, 0.453203066406250) \
+ STEP( 96, UINT64_C(0x0000000000766905), 0.480, 0.462539980800000) \
+ STEP( 97, UINT64_C(0x000000000078cde7), 0.485, 0.471891870443750) \
+ STEP( 98, UINT64_C(0x00000000007b3387), 0.490, 0.481254999400000) \
+ STEP( 99, UINT64_C(0x00000000007d99a4), 0.495, 0.490625624981250) \
+ STEP( 100, UINT64_C(0x0000000000800000), 0.500, 0.500000000000000) \
+ STEP( 101, UINT64_C(0x000000000082665b), 0.505, 0.509374375018750) \
+ STEP( 102, UINT64_C(0x000000000084cc78), 0.510, 0.518745000600000) \
+ STEP( 103, UINT64_C(0x0000000000873218), 0.515, 0.528108129556250) \
+ STEP( 104, UINT64_C(0x00000000008996fa), 0.520, 0.537460019200000) \
+ STEP( 105, UINT64_C(0x00000000008bfae2), 0.525, 0.546796933593750) \
+ STEP( 106, UINT64_C(0x00000000008e5d8f), 0.530, 0.556115145800000) \
+ STEP( 107, UINT64_C(0x000000000090bec5), 0.535, 0.565410940131250) \
+ STEP( 108, UINT64_C(0x0000000000931e44), 0.540, 0.574680614400000) \
+ STEP( 109, UINT64_C(0x0000000000957bd0), 0.545, 0.583920482168750) \
+ STEP( 110, UINT64_C(0x000000000097d729), 0.550, 0.593126875000000) \
+ STEP( 111, UINT64_C(0x00000000009a3014), 0.555, 0.602296144706250) \
+ STEP( 112, UINT64_C(0x00000000009c8653), 0.560, 0.611424665600000) \
+ STEP( 113, UINT64_C(0x00000000009ed9aa), 0.565, 0.620508836743750) \
+ STEP( 114, UINT64_C(0x0000000000a129dd), 0.570, 0.629545084200000) \
+ STEP( 115, UINT64_C(0x0000000000a376b1), 0.575, 0.638529863281250) \
+ STEP( 116, UINT64_C(0x0000000000a5bfea), 0.580, 0.647459660800000) \
+ STEP( 117, UINT64_C(0x0000000000a8054e), 0.585, 0.656330997318750) \
+ STEP( 118, UINT64_C(0x0000000000aa46a4), 0.590, 0.665140429400000) \
+ STEP( 119, UINT64_C(0x0000000000ac83b2), 0.595, 0.673884551856250) \
+ STEP( 120, UINT64_C(0x0000000000aebc40), 0.600, 0.682560000000000) \
+ STEP( 121, UINT64_C(0x0000000000b0f016), 0.605, 0.691163451893750) \
+ STEP( 122, UINT64_C(0x0000000000b31efd), 0.610, 0.699691630600000) \
+ STEP( 123, UINT64_C(0x0000000000b548bf), 0.615, 0.708141306431250) \
+ STEP( 124, UINT64_C(0x0000000000b76d27), 0.620, 0.716509299200000) \
+ STEP( 125, UINT64_C(0x0000000000b98c00), 0.625, 0.724792480468750) \
+ STEP( 126, UINT64_C(0x0000000000bba516), 0.630, 0.732987775800000) \
+ STEP( 127, UINT64_C(0x0000000000bdb837), 0.635, 0.741092167006250) \
+ STEP( 128, UINT64_C(0x0000000000bfc531), 0.640, 0.749102694400000) \
+ STEP( 129, UINT64_C(0x0000000000c1cbd4), 0.645, 0.757016459043750) \
+ STEP( 130, UINT64_C(0x0000000000c3cbf0), 0.650, 0.764830625000000) \
+ STEP( 131, UINT64_C(0x0000000000c5c557), 0.655, 0.772542421581250) \
+ STEP( 132, UINT64_C(0x0000000000c7b7da), 0.660, 0.780149145600000) \
+ STEP( 133, UINT64_C(0x0000000000c9a34f), 0.665, 0.787648163618750) \
+ STEP( 134, UINT64_C(0x0000000000cb878a), 0.670, 0.795036914200000) \
+ STEP( 135, UINT64_C(0x0000000000cd6460), 0.675, 0.802312910156250) \
+ STEP( 136, UINT64_C(0x0000000000cf39ab), 0.680, 0.809473740800000) \
+ STEP( 137, UINT64_C(0x0000000000d10743), 0.685, 0.816517074193750) \
+ STEP( 138, UINT64_C(0x0000000000d2cd01), 0.690, 0.823440659400000) \
+ STEP( 139, UINT64_C(0x0000000000d48ac2), 0.695, 0.830242328731250) \
+ STEP( 140, UINT64_C(0x0000000000d64063), 0.700, 0.836920000000000) \
+ STEP( 141, UINT64_C(0x0000000000d7edc2), 0.705, 0.843471678768750) \
+ STEP( 142, UINT64_C(0x0000000000d992bf), 0.710, 0.849895460600000) \
+ STEP( 143, UINT64_C(0x0000000000db2f3c), 0.715, 0.856189533306250) \
+ STEP( 144, UINT64_C(0x0000000000dcc31c), 0.720, 0.862352179200000) \
+ STEP( 145, UINT64_C(0x0000000000de4e44), 0.725, 0.868381777343750) \
+ STEP( 146, UINT64_C(0x0000000000dfd09a), 0.730, 0.874276805800000) \
+ STEP( 147, UINT64_C(0x0000000000e14a07), 0.735, 0.880035843881250) \
+ STEP( 148, UINT64_C(0x0000000000e2ba74), 0.740, 0.885657574400000) \
+ STEP( 149, UINT64_C(0x0000000000e421cd), 0.745, 0.891140785918750) \
+ STEP( 150, UINT64_C(0x0000000000e58000), 0.750, 0.896484375000000) \
+ STEP( 151, UINT64_C(0x0000000000e6d4fb), 0.755, 0.901687348456250) \
+ STEP( 152, UINT64_C(0x0000000000e820b0), 0.760, 0.906748825600000) \
+ STEP( 153, UINT64_C(0x0000000000e96313), 0.765, 0.911668040493750) \
+ STEP( 154, UINT64_C(0x0000000000ea9c18), 0.770, 0.916444344200000) \
+ STEP( 155, UINT64_C(0x0000000000ebcbb7), 0.775, 0.921077207031250) \
+ STEP( 156, UINT64_C(0x0000000000ecf1e8), 0.780, 0.925566220800000) \
+ STEP( 157, UINT64_C(0x0000000000ee0ea7), 0.785, 0.929911101068750) \
+ STEP( 158, UINT64_C(0x0000000000ef21f1), 0.790, 0.934111689400000) \
+ STEP( 159, UINT64_C(0x0000000000f02bc6), 0.795, 0.938167955606250) \
+ STEP( 160, UINT64_C(0x0000000000f12c27), 0.800, 0.942080000000000) \
+ STEP( 161, UINT64_C(0x0000000000f22319), 0.805, 0.945848055643750) \
+ STEP( 162, UINT64_C(0x0000000000f310a1), 0.810, 0.949472490600000) \
+ STEP( 163, UINT64_C(0x0000000000f3f4c7), 0.815, 0.952953810181250) \
+ STEP( 164, UINT64_C(0x0000000000f4cf98), 0.820, 0.956292659200000) \
+ STEP( 165, UINT64_C(0x0000000000f5a120), 0.825, 0.959489824218750) \
+ STEP( 166, UINT64_C(0x0000000000f6696e), 0.830, 0.962546235800000) \
+ STEP( 167, UINT64_C(0x0000000000f72894), 0.835, 0.965462970756250) \
+ STEP( 168, UINT64_C(0x0000000000f7dea8), 0.840, 0.968241254400000) \
+ STEP( 169, UINT64_C(0x0000000000f88bc0), 0.845, 0.970882462793750) \
+ STEP( 170, UINT64_C(0x0000000000f92ff6), 0.850, 0.973388125000000) \
+ STEP( 171, UINT64_C(0x0000000000f9cb67), 0.855, 0.975759925331250) \
+ STEP( 172, UINT64_C(0x0000000000fa5e30), 0.860, 0.977999705600000) \
+ STEP( 173, UINT64_C(0x0000000000fae874), 0.865, 0.980109467368750) \
+ STEP( 174, UINT64_C(0x0000000000fb6a57), 0.870, 0.982091374200000) \
+ STEP( 175, UINT64_C(0x0000000000fbe400), 0.875, 0.983947753906250) \
+ STEP( 176, UINT64_C(0x0000000000fc5598), 0.880, 0.985681100800000) \
+ STEP( 177, UINT64_C(0x0000000000fcbf4e), 0.885, 0.987294077943750) \
+ STEP( 178, UINT64_C(0x0000000000fd214f), 0.890, 0.988789519400000) \
+ STEP( 179, UINT64_C(0x0000000000fd7bcf), 0.895, 0.990170432481250) \
+ STEP( 180, UINT64_C(0x0000000000fdcf03), 0.900, 0.991440000000000) \
+ STEP( 181, UINT64_C(0x0000000000fe1b23), 0.905, 0.992601582518750) \
+ STEP( 182, UINT64_C(0x0000000000fe606a), 0.910, 0.993658720600000) \
+ STEP( 183, UINT64_C(0x0000000000fe9f18), 0.915, 0.994615137056250) \
+ STEP( 184, UINT64_C(0x0000000000fed76e), 0.920, 0.995474739200000) \
+ STEP( 185, UINT64_C(0x0000000000ff09b0), 0.925, 0.996241621093750) \
+ STEP( 186, UINT64_C(0x0000000000ff3627), 0.930, 0.996920065800000) \
+ STEP( 187, UINT64_C(0x0000000000ff5d1d), 0.935, 0.997514547631250) \
+ STEP( 188, UINT64_C(0x0000000000ff7ee0), 0.940, 0.998029734400000) \
+ STEP( 189, UINT64_C(0x0000000000ff9bc3), 0.945, 0.998470489668750) \
+ STEP( 190, UINT64_C(0x0000000000ffb419), 0.950, 0.998841875000000) \
+ STEP( 191, UINT64_C(0x0000000000ffc83d), 0.955, 0.999149152206250) \
+ STEP( 192, UINT64_C(0x0000000000ffd888), 0.960, 0.999397785600000) \
+ STEP( 193, UINT64_C(0x0000000000ffe55b), 0.965, 0.999593444243750) \
+ STEP( 194, UINT64_C(0x0000000000ffef17), 0.970, 0.999742004200000) \
+ STEP( 195, UINT64_C(0x0000000000fff623), 0.975, 0.999849550781250) \
+ STEP( 196, UINT64_C(0x0000000000fffae9), 0.980, 0.999922380800000) \
+ STEP( 197, UINT64_C(0x0000000000fffdd6), 0.985, 0.999967004818750) \
+ STEP( 198, UINT64_C(0x0000000000ffff5a), 0.990, 0.999990149400000) \
+ STEP( 199, UINT64_C(0x0000000000ffffeb), 0.995, 0.999998759356250) \
+ STEP( 200, UINT64_C(0x0000000001000000), 1.000, 1.000000000000000) \
+
+#endif /* JEMALLOC_INTERNAL_SMOOTHSTEP_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/smoothstep.sh b/deps/jemalloc/include/jemalloc/internal/smoothstep.sh
new file mode 100755
index 000000000..65de97bf4
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/smoothstep.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+#
+# Generate a discrete lookup table for a sigmoid function in the smoothstep
+# family (https://en.wikipedia.org/wiki/Smoothstep), where the lookup table
+# entries correspond to x in [1/nsteps, 2/nsteps, ..., nsteps/nsteps]. Encode
+# the entries using a binary fixed point representation.
+#
+# Usage: smoothstep.sh <variant> <nsteps> <bfp> <xprec> <yprec>
+#
+# <variant> is in {smooth, smoother, smoothest}.
+# <nsteps> must be greater than zero.
+# <bfp> must be in [0..62]; reasonable values are roughly [10..30].
+# <xprec> is x decimal precision.
+# <yprec> is y decimal precision.
+
+#set -x
+
+cmd="sh smoothstep.sh $*"
+variant=$1
+nsteps=$2
+bfp=$3
+xprec=$4
+yprec=$5
+
+case "${variant}" in
+ smooth)
+ ;;
+ smoother)
+ ;;
+ smoothest)
+ ;;
+ *)
+ echo "Unsupported variant"
+ exit 1
+ ;;
+esac
+
+smooth() {
+ step=$1
+ y=`echo ${yprec} k ${step} ${nsteps} / sx _2 lx 3 ^ '*' 3 lx 2 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'`
+ h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' `
+}
+
+smoother() {
+ step=$1
+ y=`echo ${yprec} k ${step} ${nsteps} / sx 6 lx 5 ^ '*' _15 lx 4 ^ '*' + 10 lx 3 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'`
+ h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' `
+}
+
+smoothest() {
+ step=$1
+ y=`echo ${yprec} k ${step} ${nsteps} / sx _20 lx 7 ^ '*' 70 lx 6 ^ '*' + _84 lx 5 ^ '*' + 35 lx 4 ^ '*' + p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'`
+ h=`echo ${yprec} k 2 ${bfp} ^ ${y} '*' p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g' | tr '.' ' ' | awk '{print $1}' `
+}
+
+cat <<EOF
+#ifndef JEMALLOC_INTERNAL_SMOOTHSTEP_H
+#define JEMALLOC_INTERNAL_SMOOTHSTEP_H
+
+/*
+ * This file was generated by the following command:
+ * $cmd
+ */
+/******************************************************************************/
+
+/*
+ * This header defines a precomputed table based on the smoothstep family of
+ * sigmoidal curves (https://en.wikipedia.org/wiki/Smoothstep) that grow from 0
+ * to 1 in 0 <= x <= 1. The table is stored as integer fixed point values so
+ * that floating point math can be avoided.
+ *
+ * 3 2
+ * smoothstep(x) = -2x + 3x
+ *
+ * 5 4 3
+ * smootherstep(x) = 6x - 15x + 10x
+ *
+ * 7 6 5 4
+ * smootheststep(x) = -20x + 70x - 84x + 35x
+ */
+
+#define SMOOTHSTEP_VARIANT "${variant}"
+#define SMOOTHSTEP_NSTEPS ${nsteps}
+#define SMOOTHSTEP_BFP ${bfp}
+#define SMOOTHSTEP \\
+ /* STEP(step, h, x, y) */ \\
+EOF
+
+s=1
+while [ $s -le $nsteps ] ; do
+ $variant ${s}
+ x=`echo ${xprec} k ${s} ${nsteps} / p | dc | tr -d '\\\\\n' | sed -e 's#^\.#0.#g'`
+ printf ' STEP(%4d, UINT64_C(0x%016x), %s, %s) \\\n' ${s} ${h} ${x} ${y}
+
+ s=$((s+1))
+done
+echo
+
+cat <<EOF
+#endif /* JEMALLOC_INTERNAL_SMOOTHSTEP_H */
+EOF
diff --git a/deps/jemalloc/include/jemalloc/internal/spin.h b/deps/jemalloc/include/jemalloc/internal/spin.h
new file mode 100644
index 000000000..22804c687
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/spin.h
@@ -0,0 +1,40 @@
+#ifndef JEMALLOC_INTERNAL_SPIN_H
+#define JEMALLOC_INTERNAL_SPIN_H
+
+#define SPIN_INITIALIZER {0U}
+
+typedef struct {
+ unsigned iteration;
+} spin_t;
+
+static inline void
+spin_cpu_spinwait() {
+# if HAVE_CPU_SPINWAIT
+ CPU_SPINWAIT;
+# else
+ volatile int x = 0;
+ x = x;
+# endif
+}
+
+static inline void
+spin_adaptive(spin_t *spin) {
+ volatile uint32_t i;
+
+ if (spin->iteration < 5) {
+ for (i = 0; i < (1U << spin->iteration); i++) {
+ spin_cpu_spinwait();
+ }
+ spin->iteration++;
+ } else {
+#ifdef _WIN32
+ SwitchToThread();
+#else
+ sched_yield();
+#endif
+ }
+}
+
+#undef SPIN_INLINE
+
+#endif /* JEMALLOC_INTERNAL_SPIN_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/stats.h b/deps/jemalloc/include/jemalloc/internal/stats.h
index c91dba99d..852e34269 100644
--- a/deps/jemalloc/include/jemalloc/internal/stats.h
+++ b/deps/jemalloc/include/jemalloc/internal/stats.h
@@ -1,183 +1,30 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct tcache_bin_stats_s tcache_bin_stats_t;
-typedef struct malloc_bin_stats_s malloc_bin_stats_t;
-typedef struct malloc_large_stats_s malloc_large_stats_t;
-typedef struct malloc_huge_stats_s malloc_huge_stats_t;
-typedef struct arena_stats_s arena_stats_t;
-typedef struct chunk_stats_s chunk_stats_t;
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-struct tcache_bin_stats_s {
- /*
- * Number of allocation requests that corresponded to the size of this
- * bin.
- */
- uint64_t nrequests;
-};
-
-struct malloc_bin_stats_s {
- /*
- * Total number of allocation/deallocation requests served directly by
- * the bin. Note that tcache may allocate an object, then recycle it
- * many times, resulting many increments to nrequests, but only one
- * each to nmalloc and ndalloc.
- */
- uint64_t nmalloc;
- uint64_t ndalloc;
-
- /*
- * Number of allocation requests that correspond to the size of this
- * bin. This includes requests served by tcache, though tcache only
- * periodically merges into this counter.
- */
- uint64_t nrequests;
-
- /*
- * Current number of regions of this size class, including regions
- * currently cached by tcache.
- */
- size_t curregs;
-
- /* Number of tcache fills from this bin. */
- uint64_t nfills;
-
- /* Number of tcache flushes to this bin. */
- uint64_t nflushes;
-
- /* Total number of runs created for this bin's size class. */
- uint64_t nruns;
-
- /*
- * Total number of runs reused by extracting them from the runs tree for
- * this bin's size class.
- */
- uint64_t reruns;
-
- /* Current number of runs in this bin. */
- size_t curruns;
-};
-
-struct malloc_large_stats_s {
- /*
- * Total number of allocation/deallocation requests served directly by
- * the arena. Note that tcache may allocate an object, then recycle it
- * many times, resulting many increments to nrequests, but only one
- * each to nmalloc and ndalloc.
- */
- uint64_t nmalloc;
- uint64_t ndalloc;
-
- /*
- * Number of allocation requests that correspond to this size class.
- * This includes requests served by tcache, though tcache only
- * periodically merges into this counter.
- */
- uint64_t nrequests;
-
- /*
- * Current number of runs of this size class, including runs currently
- * cached by tcache.
- */
- size_t curruns;
-};
-
-struct malloc_huge_stats_s {
- /*
- * Total number of allocation/deallocation requests served directly by
- * the arena.
- */
- uint64_t nmalloc;
- uint64_t ndalloc;
-
- /* Current number of (multi-)chunk allocations of this size class. */
- size_t curhchunks;
+#ifndef JEMALLOC_INTERNAL_STATS_H
+#define JEMALLOC_INTERNAL_STATS_H
+
+/* OPTION(opt, var_name, default, set_value_to) */
+#define STATS_PRINT_OPTIONS \
+ OPTION('J', json, false, true) \
+ OPTION('g', general, true, false) \
+ OPTION('m', merged, config_stats, false) \
+ OPTION('d', destroyed, config_stats, false) \
+ OPTION('a', unmerged, config_stats, false) \
+ OPTION('b', bins, true, false) \
+ OPTION('l', large, true, false) \
+ OPTION('x', mutex, true, false)
+
+enum {
+#define OPTION(o, v, d, s) stats_print_option_num_##v,
+ STATS_PRINT_OPTIONS
+#undef OPTION
+ stats_print_tot_num_options
};
-struct arena_stats_s {
- /* Number of bytes currently mapped. */
- size_t mapped;
+/* Options for stats_print. */
+extern bool opt_stats_print;
+extern char opt_stats_print_opts[stats_print_tot_num_options+1];
- /*
- * Total number of purge sweeps, total number of madvise calls made,
- * and total pages purged in order to keep dirty unused memory under
- * control.
- */
- uint64_t npurge;
- uint64_t nmadvise;
- uint64_t purged;
-
- /*
- * Number of bytes currently mapped purely for metadata purposes, and
- * number of bytes currently allocated for internal metadata.
- */
- size_t metadata_mapped;
- size_t metadata_allocated; /* Protected via atomic_*_z(). */
-
- /* Per-size-category statistics. */
- size_t allocated_large;
- uint64_t nmalloc_large;
- uint64_t ndalloc_large;
- uint64_t nrequests_large;
-
- size_t allocated_huge;
- uint64_t nmalloc_huge;
- uint64_t ndalloc_huge;
-
- /* One element for each large size class. */
- malloc_large_stats_t *lstats;
-
- /* One element for each huge size class. */
- malloc_huge_stats_t *hstats;
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-extern bool opt_stats_print;
-
-extern size_t stats_cactive;
-
-void stats_print(void (*write)(void *, const char *), void *cbopaque,
+/* Implements je_malloc_stats_print. */
+void stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
const char *opts);
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-size_t stats_cactive_get(void);
-void stats_cactive_add(size_t size);
-void stats_cactive_sub(size_t size);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_STATS_C_))
-JEMALLOC_INLINE size_t
-stats_cactive_get(void)
-{
-
- return (atomic_read_z(&stats_cactive));
-}
-
-JEMALLOC_INLINE void
-stats_cactive_add(size_t size)
-{
-
- atomic_add_z(&stats_cactive, size);
-}
-
-JEMALLOC_INLINE void
-stats_cactive_sub(size_t size)
-{
-
- atomic_sub_z(&stats_cactive, size);
-}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_STATS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/sz.h b/deps/jemalloc/include/jemalloc/internal/sz.h
new file mode 100644
index 000000000..979462898
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/sz.h
@@ -0,0 +1,317 @@
+#ifndef JEMALLOC_INTERNAL_SIZE_H
+#define JEMALLOC_INTERNAL_SIZE_H
+
+#include "jemalloc/internal/bit_util.h"
+#include "jemalloc/internal/pages.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/util.h"
+
+/*
+ * sz module: Size computations.
+ *
+ * Some abbreviations used here:
+ * p: Page
+ * ind: Index
+ * s, sz: Size
+ * u: Usable size
+ * a: Aligned
+ *
+ * These are not always used completely consistently, but should be enough to
+ * interpret function names. E.g. sz_psz2ind converts page size to page size
+ * index; sz_sa2u converts a (size, alignment) allocation request to the usable
+ * size that would result from such an allocation.
+ */
+
+/*
+ * sz_pind2sz_tab encodes the same information as could be computed by
+ * sz_pind2sz_compute().
+ */
+extern size_t const sz_pind2sz_tab[NPSIZES+1];
+/*
+ * sz_index2size_tab encodes the same information as could be computed (at
+ * unacceptable cost in some code paths) by sz_index2size_compute().
+ */
+extern size_t const sz_index2size_tab[NSIZES];
+/*
+ * sz_size2index_tab is a compact lookup table that rounds request sizes up to
+ * size classes. In order to reduce cache footprint, the table is compressed,
+ * and all accesses are via sz_size2index().
+ */
+extern uint8_t const sz_size2index_tab[];
+
+static const size_t sz_large_pad =
+#ifdef JEMALLOC_CACHE_OBLIVIOUS
+ PAGE
+#else
+ 0
+#endif
+ ;
+
+JEMALLOC_ALWAYS_INLINE pszind_t
+sz_psz2ind(size_t psz) {
+ if (unlikely(psz > LARGE_MAXCLASS)) {
+ return NPSIZES;
+ }
+ {
+ pszind_t x = lg_floor((psz<<1)-1);
+ pszind_t shift = (x < LG_SIZE_CLASS_GROUP + LG_PAGE) ? 0 : x -
+ (LG_SIZE_CLASS_GROUP + LG_PAGE);
+ pszind_t grp = shift << LG_SIZE_CLASS_GROUP;
+
+ pszind_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_PAGE + 1) ?
+ LG_PAGE : x - LG_SIZE_CLASS_GROUP - 1;
+
+ size_t delta_inverse_mask = ZU(-1) << lg_delta;
+ pszind_t mod = ((((psz-1) & delta_inverse_mask) >> lg_delta)) &
+ ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);
+
+ pszind_t ind = grp + mod;
+ return ind;
+ }
+}
+
+static inline size_t
+sz_pind2sz_compute(pszind_t pind) {
+ if (unlikely(pind == NPSIZES)) {
+ return LARGE_MAXCLASS + PAGE;
+ }
+ {
+ size_t grp = pind >> LG_SIZE_CLASS_GROUP;
+ size_t mod = pind & ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);
+
+ size_t grp_size_mask = ~((!!grp)-1);
+ size_t grp_size = ((ZU(1) << (LG_PAGE +
+ (LG_SIZE_CLASS_GROUP-1))) << grp) & grp_size_mask;
+
+ size_t shift = (grp == 0) ? 1 : grp;
+ size_t lg_delta = shift + (LG_PAGE-1);
+ size_t mod_size = (mod+1) << lg_delta;
+
+ size_t sz = grp_size + mod_size;
+ return sz;
+ }
+}
+
+static inline size_t
+sz_pind2sz_lookup(pszind_t pind) {
+ size_t ret = (size_t)sz_pind2sz_tab[pind];
+ assert(ret == sz_pind2sz_compute(pind));
+ return ret;
+}
+
+static inline size_t
+sz_pind2sz(pszind_t pind) {
+ assert(pind < NPSIZES+1);
+ return sz_pind2sz_lookup(pind);
+}
+
+static inline size_t
+sz_psz2u(size_t psz) {
+ if (unlikely(psz > LARGE_MAXCLASS)) {
+ return LARGE_MAXCLASS + PAGE;
+ }
+ {
+ size_t x = lg_floor((psz<<1)-1);
+ size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_PAGE + 1) ?
+ LG_PAGE : x - LG_SIZE_CLASS_GROUP - 1;
+ size_t delta = ZU(1) << lg_delta;
+ size_t delta_mask = delta - 1;
+ size_t usize = (psz + delta_mask) & ~delta_mask;
+ return usize;
+ }
+}
+
+static inline szind_t
+sz_size2index_compute(size_t size) {
+ if (unlikely(size > LARGE_MAXCLASS)) {
+ return NSIZES;
+ }
+#if (NTBINS != 0)
+ if (size <= (ZU(1) << LG_TINY_MAXCLASS)) {
+ szind_t lg_tmin = LG_TINY_MAXCLASS - NTBINS + 1;
+ szind_t lg_ceil = lg_floor(pow2_ceil_zu(size));
+ return (lg_ceil < lg_tmin ? 0 : lg_ceil - lg_tmin);
+ }
+#endif
+ {
+ szind_t x = lg_floor((size<<1)-1);
+ szind_t shift = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM) ? 0 :
+ x - (LG_SIZE_CLASS_GROUP + LG_QUANTUM);
+ szind_t grp = shift << LG_SIZE_CLASS_GROUP;
+
+ szind_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
+ ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
+
+ size_t delta_inverse_mask = ZU(-1) << lg_delta;
+ szind_t mod = ((((size-1) & delta_inverse_mask) >> lg_delta)) &
+ ((ZU(1) << LG_SIZE_CLASS_GROUP) - 1);
+
+ szind_t index = NTBINS + grp + mod;
+ return index;
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE szind_t
+sz_size2index_lookup(size_t size) {
+ assert(size <= LOOKUP_MAXCLASS);
+ {
+ szind_t ret = (sz_size2index_tab[(size-1) >> LG_TINY_MIN]);
+ assert(ret == sz_size2index_compute(size));
+ return ret;
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE szind_t
+sz_size2index(size_t size) {
+ assert(size > 0);
+ if (likely(size <= LOOKUP_MAXCLASS)) {
+ return sz_size2index_lookup(size);
+ }
+ return sz_size2index_compute(size);
+}
+
+static inline size_t
+sz_index2size_compute(szind_t index) {
+#if (NTBINS > 0)
+ if (index < NTBINS) {
+ return (ZU(1) << (LG_TINY_MAXCLASS - NTBINS + 1 + index));
+ }
+#endif
+ {
+ size_t reduced_index = index - NTBINS;
+ size_t grp = reduced_index >> LG_SIZE_CLASS_GROUP;
+ size_t mod = reduced_index & ((ZU(1) << LG_SIZE_CLASS_GROUP) -
+ 1);
+
+ size_t grp_size_mask = ~((!!grp)-1);
+ size_t grp_size = ((ZU(1) << (LG_QUANTUM +
+ (LG_SIZE_CLASS_GROUP-1))) << grp) & grp_size_mask;
+
+ size_t shift = (grp == 0) ? 1 : grp;
+ size_t lg_delta = shift + (LG_QUANTUM-1);
+ size_t mod_size = (mod+1) << lg_delta;
+
+ size_t usize = grp_size + mod_size;
+ return usize;
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+sz_index2size_lookup(szind_t index) {
+ size_t ret = (size_t)sz_index2size_tab[index];
+ assert(ret == sz_index2size_compute(index));
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+sz_index2size(szind_t index) {
+ assert(index < NSIZES);
+ return sz_index2size_lookup(index);
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+sz_s2u_compute(size_t size) {
+ if (unlikely(size > LARGE_MAXCLASS)) {
+ return 0;
+ }
+#if (NTBINS > 0)
+ if (size <= (ZU(1) << LG_TINY_MAXCLASS)) {
+ size_t lg_tmin = LG_TINY_MAXCLASS - NTBINS + 1;
+ size_t lg_ceil = lg_floor(pow2_ceil_zu(size));
+ return (lg_ceil < lg_tmin ? (ZU(1) << lg_tmin) :
+ (ZU(1) << lg_ceil));
+ }
+#endif
+ {
+ size_t x = lg_floor((size<<1)-1);
+ size_t lg_delta = (x < LG_SIZE_CLASS_GROUP + LG_QUANTUM + 1)
+ ? LG_QUANTUM : x - LG_SIZE_CLASS_GROUP - 1;
+ size_t delta = ZU(1) << lg_delta;
+ size_t delta_mask = delta - 1;
+ size_t usize = (size + delta_mask) & ~delta_mask;
+ return usize;
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE size_t
+sz_s2u_lookup(size_t size) {
+ size_t ret = sz_index2size_lookup(sz_size2index_lookup(size));
+
+ assert(ret == sz_s2u_compute(size));
+ return ret;
+}
+
+/*
+ * Compute usable size that would result from allocating an object with the
+ * specified size.
+ */
+JEMALLOC_ALWAYS_INLINE size_t
+sz_s2u(size_t size) {
+ assert(size > 0);
+ if (likely(size <= LOOKUP_MAXCLASS)) {
+ return sz_s2u_lookup(size);
+ }
+ return sz_s2u_compute(size);
+}
+
+/*
+ * Compute usable size that would result from allocating an object with the
+ * specified size and alignment.
+ */
+JEMALLOC_ALWAYS_INLINE size_t
+sz_sa2u(size_t size, size_t alignment) {
+ size_t usize;
+
+ assert(alignment != 0 && ((alignment - 1) & alignment) == 0);
+
+ /* Try for a small size class. */
+ if (size <= SMALL_MAXCLASS && alignment < PAGE) {
+ /*
+ * Round size up to the nearest multiple of alignment.
+ *
+ * This done, we can take advantage of the fact that for each
+ * small size class, every object is aligned at the smallest
+ * power of two that is non-zero in the base two representation
+ * of the size. For example:
+ *
+ * Size | Base 2 | Minimum alignment
+ * -----+----------+------------------
+ * 96 | 1100000 | 32
+ * 144 | 10100000 | 32
+ * 192 | 11000000 | 64
+ */
+ usize = sz_s2u(ALIGNMENT_CEILING(size, alignment));
+ if (usize < LARGE_MINCLASS) {
+ return usize;
+ }
+ }
+
+ /* Large size class. Beware of overflow. */
+
+ if (unlikely(alignment > LARGE_MAXCLASS)) {
+ return 0;
+ }
+
+ /* Make sure result is a large size class. */
+ if (size <= LARGE_MINCLASS) {
+ usize = LARGE_MINCLASS;
+ } else {
+ usize = sz_s2u(size);
+ if (usize < size) {
+ /* size_t overflow. */
+ return 0;
+ }
+ }
+
+ /*
+ * Calculate the multi-page mapping that large_palloc() would need in
+ * order to guarantee the alignment.
+ */
+ if (usize + sz_large_pad + PAGE_CEILING(alignment) - PAGE < usize) {
+ /* size_t overflow. */
+ return 0;
+ }
+ return usize;
+}
+
+#endif /* JEMALLOC_INTERNAL_SIZE_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tcache.h b/deps/jemalloc/include/jemalloc/internal/tcache.h
deleted file mode 100644
index 5079cd266..000000000
--- a/deps/jemalloc/include/jemalloc/internal/tcache.h
+++ /dev/null
@@ -1,426 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-typedef struct tcache_bin_info_s tcache_bin_info_t;
-typedef struct tcache_bin_s tcache_bin_t;
-typedef struct tcache_s tcache_t;
-typedef struct tcaches_s tcaches_t;
-
-/*
- * tcache pointers close to NULL are used to encode state information that is
- * used for two purposes: preventing thread caching on a per thread basis and
- * cleaning up during thread shutdown.
- */
-#define TCACHE_STATE_DISABLED ((tcache_t *)(uintptr_t)1)
-#define TCACHE_STATE_REINCARNATED ((tcache_t *)(uintptr_t)2)
-#define TCACHE_STATE_PURGATORY ((tcache_t *)(uintptr_t)3)
-#define TCACHE_STATE_MAX TCACHE_STATE_PURGATORY
-
-/*
- * Absolute minimum number of cache slots for each small bin.
- */
-#define TCACHE_NSLOTS_SMALL_MIN 20
-
-/*
- * Absolute maximum number of cache slots for each small bin in the thread
- * cache. This is an additional constraint beyond that imposed as: twice the
- * number of regions per run for this size class.
- *
- * This constant must be an even number.
- */
-#define TCACHE_NSLOTS_SMALL_MAX 200
-
-/* Number of cache slots for large size classes. */
-#define TCACHE_NSLOTS_LARGE 20
-
-/* (1U << opt_lg_tcache_max) is used to compute tcache_maxclass. */
-#define LG_TCACHE_MAXCLASS_DEFAULT 15
-
-/*
- * TCACHE_GC_SWEEP is the approximate number of allocation events between
- * full GC sweeps. Integer rounding may cause the actual number to be
- * slightly higher, since GC is performed incrementally.
- */
-#define TCACHE_GC_SWEEP 8192
-
-/* Number of tcache allocation/deallocation events between incremental GCs. */
-#define TCACHE_GC_INCR \
- ((TCACHE_GC_SWEEP / NBINS) + ((TCACHE_GC_SWEEP / NBINS == 0) ? 0 : 1))
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-typedef enum {
- tcache_enabled_false = 0, /* Enable cast to/from bool. */
- tcache_enabled_true = 1,
- tcache_enabled_default = 2
-} tcache_enabled_t;
-
-/*
- * Read-only information associated with each element of tcache_t's tbins array
- * is stored separately, mainly to reduce memory usage.
- */
-struct tcache_bin_info_s {
- unsigned ncached_max; /* Upper limit on ncached. */
-};
-
-struct tcache_bin_s {
- tcache_bin_stats_t tstats;
- int low_water; /* Min # cached since last GC. */
- unsigned lg_fill_div; /* Fill (ncached_max >> lg_fill_div). */
- unsigned ncached; /* # of cached objects. */
- void **avail; /* Stack of available objects. */
-};
-
-struct tcache_s {
- ql_elm(tcache_t) link; /* Used for aggregating stats. */
- uint64_t prof_accumbytes;/* Cleared after arena_prof_accum(). */
- unsigned ev_cnt; /* Event count since incremental GC. */
- szind_t next_gc_bin; /* Next bin to GC. */
- tcache_bin_t tbins[1]; /* Dynamically sized. */
- /*
- * The pointer stacks associated with tbins follow as a contiguous
- * array. During tcache initialization, the avail pointer in each
- * element of tbins is initialized to point to the proper offset within
- * this array.
- */
-};
-
-/* Linkage for list of available (previously used) explicit tcache IDs. */
-struct tcaches_s {
- union {
- tcache_t *tcache;
- tcaches_t *next;
- };
-};
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-extern bool opt_tcache;
-extern ssize_t opt_lg_tcache_max;
-
-extern tcache_bin_info_t *tcache_bin_info;
-
-/*
- * Number of tcache bins. There are NBINS small-object bins, plus 0 or more
- * large-object bins.
- */
-extern size_t nhbins;
-
-/* Maximum cached size class. */
-extern size_t tcache_maxclass;
-
-/*
- * Explicit tcaches, managed via the tcache.{create,flush,destroy} mallctls and
- * usable via the MALLOCX_TCACHE() flag. The automatic per thread tcaches are
- * completely disjoint from this data structure. tcaches starts off as a sparse
- * array, so it has no physical memory footprint until individual pages are
- * touched. This allows the entire array to be allocated the first time an
- * explicit tcache is created without a disproportionate impact on memory usage.
- */
-extern tcaches_t *tcaches;
-
-size_t tcache_salloc(const void *ptr);
-void tcache_event_hard(tsd_t *tsd, tcache_t *tcache);
-void *tcache_alloc_small_hard(tsd_t *tsd, arena_t *arena, tcache_t *tcache,
- tcache_bin_t *tbin, szind_t binind);
-void tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, tcache_bin_t *tbin,
- szind_t binind, unsigned rem);
-void tcache_bin_flush_large(tsd_t *tsd, tcache_bin_t *tbin, szind_t binind,
- unsigned rem, tcache_t *tcache);
-void tcache_arena_associate(tcache_t *tcache, arena_t *arena);
-void tcache_arena_reassociate(tcache_t *tcache, arena_t *oldarena,
- arena_t *newarena);
-void tcache_arena_dissociate(tcache_t *tcache, arena_t *arena);
-tcache_t *tcache_get_hard(tsd_t *tsd);
-tcache_t *tcache_create(tsd_t *tsd, arena_t *arena);
-void tcache_cleanup(tsd_t *tsd);
-void tcache_enabled_cleanup(tsd_t *tsd);
-void tcache_stats_merge(tcache_t *tcache, arena_t *arena);
-bool tcaches_create(tsd_t *tsd, unsigned *r_ind);
-void tcaches_flush(tsd_t *tsd, unsigned ind);
-void tcaches_destroy(tsd_t *tsd, unsigned ind);
-bool tcache_boot(void);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-void tcache_event(tsd_t *tsd, tcache_t *tcache);
-void tcache_flush(void);
-bool tcache_enabled_get(void);
-tcache_t *tcache_get(tsd_t *tsd, bool create);
-void tcache_enabled_set(bool enabled);
-void *tcache_alloc_easy(tcache_bin_t *tbin);
-void *tcache_alloc_small(tsd_t *tsd, arena_t *arena, tcache_t *tcache,
- size_t size, bool zero);
-void *tcache_alloc_large(tsd_t *tsd, arena_t *arena, tcache_t *tcache,
- size_t size, bool zero);
-void tcache_dalloc_small(tsd_t *tsd, tcache_t *tcache, void *ptr,
- szind_t binind);
-void tcache_dalloc_large(tsd_t *tsd, tcache_t *tcache, void *ptr,
- size_t size);
-tcache_t *tcaches_get(tsd_t *tsd, unsigned ind);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_TCACHE_C_))
-JEMALLOC_INLINE void
-tcache_flush(void)
-{
- tsd_t *tsd;
-
- cassert(config_tcache);
-
- tsd = tsd_fetch();
- tcache_cleanup(tsd);
-}
-
-JEMALLOC_INLINE bool
-tcache_enabled_get(void)
-{
- tsd_t *tsd;
- tcache_enabled_t tcache_enabled;
-
- cassert(config_tcache);
-
- tsd = tsd_fetch();
- tcache_enabled = tsd_tcache_enabled_get(tsd);
- if (tcache_enabled == tcache_enabled_default) {
- tcache_enabled = (tcache_enabled_t)opt_tcache;
- tsd_tcache_enabled_set(tsd, tcache_enabled);
- }
-
- return ((bool)tcache_enabled);
-}
-
-JEMALLOC_INLINE void
-tcache_enabled_set(bool enabled)
-{
- tsd_t *tsd;
- tcache_enabled_t tcache_enabled;
-
- cassert(config_tcache);
-
- tsd = tsd_fetch();
-
- tcache_enabled = (tcache_enabled_t)enabled;
- tsd_tcache_enabled_set(tsd, tcache_enabled);
-
- if (!enabled)
- tcache_cleanup(tsd);
-}
-
-JEMALLOC_ALWAYS_INLINE tcache_t *
-tcache_get(tsd_t *tsd, bool create)
-{
- tcache_t *tcache;
-
- if (!config_tcache)
- return (NULL);
-
- tcache = tsd_tcache_get(tsd);
- if (!create)
- return (tcache);
- if (unlikely(tcache == NULL) && tsd_nominal(tsd)) {
- tcache = tcache_get_hard(tsd);
- tsd_tcache_set(tsd, tcache);
- }
-
- return (tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-tcache_event(tsd_t *tsd, tcache_t *tcache)
-{
-
- if (TCACHE_GC_INCR == 0)
- return;
-
- tcache->ev_cnt++;
- assert(tcache->ev_cnt <= TCACHE_GC_INCR);
- if (unlikely(tcache->ev_cnt == TCACHE_GC_INCR))
- tcache_event_hard(tsd, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-tcache_alloc_easy(tcache_bin_t *tbin)
-{
- void *ret;
-
- if (unlikely(tbin->ncached == 0)) {
- tbin->low_water = -1;
- return (NULL);
- }
- tbin->ncached--;
- if (unlikely((int)tbin->ncached < tbin->low_water))
- tbin->low_water = tbin->ncached;
- ret = tbin->avail[tbin->ncached];
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-tcache_alloc_small(tsd_t *tsd, arena_t *arena, tcache_t *tcache, size_t size,
- bool zero)
-{
- void *ret;
- szind_t binind;
- size_t usize;
- tcache_bin_t *tbin;
-
- binind = size2index(size);
- assert(binind < NBINS);
- tbin = &tcache->tbins[binind];
- usize = index2size(binind);
- ret = tcache_alloc_easy(tbin);
- if (unlikely(ret == NULL)) {
- ret = tcache_alloc_small_hard(tsd, arena, tcache, tbin, binind);
- if (ret == NULL)
- return (NULL);
- }
- assert(tcache_salloc(ret) == usize);
-
- if (likely(!zero)) {
- if (config_fill) {
- if (unlikely(opt_junk_alloc)) {
- arena_alloc_junk_small(ret,
- &arena_bin_info[binind], false);
- } else if (unlikely(opt_zero))
- memset(ret, 0, usize);
- }
- } else {
- if (config_fill && unlikely(opt_junk_alloc)) {
- arena_alloc_junk_small(ret, &arena_bin_info[binind],
- true);
- }
- memset(ret, 0, usize);
- }
-
- if (config_stats)
- tbin->tstats.nrequests++;
- if (config_prof)
- tcache->prof_accumbytes += usize;
- tcache_event(tsd, tcache);
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void *
-tcache_alloc_large(tsd_t *tsd, arena_t *arena, tcache_t *tcache, size_t size,
- bool zero)
-{
- void *ret;
- szind_t binind;
- size_t usize;
- tcache_bin_t *tbin;
-
- binind = size2index(size);
- usize = index2size(binind);
- assert(usize <= tcache_maxclass);
- assert(binind < nhbins);
- tbin = &tcache->tbins[binind];
- ret = tcache_alloc_easy(tbin);
- if (unlikely(ret == NULL)) {
- /*
- * Only allocate one large object at a time, because it's quite
- * expensive to create one and not use it.
- */
- ret = arena_malloc_large(arena, usize, zero);
- if (ret == NULL)
- return (NULL);
- } else {
- if (config_prof && usize == LARGE_MINCLASS) {
- arena_chunk_t *chunk =
- (arena_chunk_t *)CHUNK_ADDR2BASE(ret);
- size_t pageind = (((uintptr_t)ret - (uintptr_t)chunk) >>
- LG_PAGE);
- arena_mapbits_large_binind_set(chunk, pageind,
- BININD_INVALID);
- }
- if (likely(!zero)) {
- if (config_fill) {
- if (unlikely(opt_junk_alloc))
- memset(ret, 0xa5, usize);
- else if (unlikely(opt_zero))
- memset(ret, 0, usize);
- }
- } else
- memset(ret, 0, usize);
-
- if (config_stats)
- tbin->tstats.nrequests++;
- if (config_prof)
- tcache->prof_accumbytes += usize;
- }
-
- tcache_event(tsd, tcache);
- return (ret);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-tcache_dalloc_small(tsd_t *tsd, tcache_t *tcache, void *ptr, szind_t binind)
-{
- tcache_bin_t *tbin;
- tcache_bin_info_t *tbin_info;
-
- assert(tcache_salloc(ptr) <= SMALL_MAXCLASS);
-
- if (config_fill && unlikely(opt_junk_free))
- arena_dalloc_junk_small(ptr, &arena_bin_info[binind]);
-
- tbin = &tcache->tbins[binind];
- tbin_info = &tcache_bin_info[binind];
- if (unlikely(tbin->ncached == tbin_info->ncached_max)) {
- tcache_bin_flush_small(tsd, tcache, tbin, binind,
- (tbin_info->ncached_max >> 1));
- }
- assert(tbin->ncached < tbin_info->ncached_max);
- tbin->avail[tbin->ncached] = ptr;
- tbin->ncached++;
-
- tcache_event(tsd, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE void
-tcache_dalloc_large(tsd_t *tsd, tcache_t *tcache, void *ptr, size_t size)
-{
- szind_t binind;
- tcache_bin_t *tbin;
- tcache_bin_info_t *tbin_info;
-
- assert((size & PAGE_MASK) == 0);
- assert(tcache_salloc(ptr) > SMALL_MAXCLASS);
- assert(tcache_salloc(ptr) <= tcache_maxclass);
-
- binind = size2index(size);
-
- if (config_fill && unlikely(opt_junk_free))
- arena_dalloc_junk_large(ptr, size);
-
- tbin = &tcache->tbins[binind];
- tbin_info = &tcache_bin_info[binind];
- if (unlikely(tbin->ncached == tbin_info->ncached_max)) {
- tcache_bin_flush_large(tsd, tbin, binind,
- (tbin_info->ncached_max >> 1), tcache);
- }
- assert(tbin->ncached < tbin_info->ncached_max);
- tbin->avail[tbin->ncached] = ptr;
- tbin->ncached++;
-
- tcache_event(tsd, tcache);
-}
-
-JEMALLOC_ALWAYS_INLINE tcache_t *
-tcaches_get(tsd_t *tsd, unsigned ind)
-{
- tcaches_t *elm = &tcaches[ind];
- if (unlikely(elm->tcache == NULL))
- elm->tcache = tcache_create(tsd, arena_choose(tsd, NULL));
- return (elm->tcache);
-}
-#endif
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
diff --git a/deps/jemalloc/include/jemalloc/internal/tcache_externs.h b/deps/jemalloc/include/jemalloc/internal/tcache_externs.h
new file mode 100644
index 000000000..790367bd4
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tcache_externs.h
@@ -0,0 +1,55 @@
+#ifndef JEMALLOC_INTERNAL_TCACHE_EXTERNS_H
+#define JEMALLOC_INTERNAL_TCACHE_EXTERNS_H
+
+#include "jemalloc/internal/size_classes.h"
+
+extern bool opt_tcache;
+extern ssize_t opt_lg_tcache_max;
+
+extern cache_bin_info_t *tcache_bin_info;
+
+/*
+ * Number of tcache bins. There are NBINS small-object bins, plus 0 or more
+ * large-object bins.
+ */
+extern unsigned nhbins;
+
+/* Maximum cached size class. */
+extern size_t tcache_maxclass;
+
+/*
+ * Explicit tcaches, managed via the tcache.{create,flush,destroy} mallctls and
+ * usable via the MALLOCX_TCACHE() flag. The automatic per thread tcaches are
+ * completely disjoint from this data structure. tcaches starts off as a sparse
+ * array, so it has no physical memory footprint until individual pages are
+ * touched. This allows the entire array to be allocated the first time an
+ * explicit tcache is created without a disproportionate impact on memory usage.
+ */
+extern tcaches_t *tcaches;
+
+size_t tcache_salloc(tsdn_t *tsdn, const void *ptr);
+void tcache_event_hard(tsd_t *tsd, tcache_t *tcache);
+void *tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache,
+ cache_bin_t *tbin, szind_t binind, bool *tcache_success);
+void tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *tbin,
+ szind_t binind, unsigned rem);
+void tcache_bin_flush_large(tsd_t *tsd, cache_bin_t *tbin, szind_t binind,
+ unsigned rem, tcache_t *tcache);
+void tcache_arena_reassociate(tsdn_t *tsdn, tcache_t *tcache,
+ arena_t *arena);
+tcache_t *tcache_create_explicit(tsd_t *tsd);
+void tcache_cleanup(tsd_t *tsd);
+void tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena);
+bool tcaches_create(tsd_t *tsd, unsigned *r_ind);
+void tcaches_flush(tsd_t *tsd, unsigned ind);
+void tcaches_destroy(tsd_t *tsd, unsigned ind);
+bool tcache_boot(tsdn_t *tsdn);
+void tcache_arena_associate(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena);
+void tcache_prefork(tsdn_t *tsdn);
+void tcache_postfork_parent(tsdn_t *tsdn);
+void tcache_postfork_child(tsdn_t *tsdn);
+void tcache_flush(tsd_t *tsd);
+bool tsd_tcache_data_init(tsd_t *tsd);
+bool tsd_tcache_enabled_data_init(tsd_t *tsd);
+
+#endif /* JEMALLOC_INTERNAL_TCACHE_EXTERNS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tcache_inlines.h b/deps/jemalloc/include/jemalloc/internal/tcache_inlines.h
new file mode 100644
index 000000000..0f6ab8cb5
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tcache_inlines.h
@@ -0,0 +1,223 @@
+#ifndef JEMALLOC_INTERNAL_TCACHE_INLINES_H
+#define JEMALLOC_INTERNAL_TCACHE_INLINES_H
+
+#include "jemalloc/internal/bin.h"
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/sz.h"
+#include "jemalloc/internal/ticker.h"
+#include "jemalloc/internal/util.h"
+
+static inline bool
+tcache_enabled_get(tsd_t *tsd) {
+ return tsd_tcache_enabled_get(tsd);
+}
+
+static inline void
+tcache_enabled_set(tsd_t *tsd, bool enabled) {
+ bool was_enabled = tsd_tcache_enabled_get(tsd);
+
+ if (!was_enabled && enabled) {
+ tsd_tcache_data_init(tsd);
+ } else if (was_enabled && !enabled) {
+ tcache_cleanup(tsd);
+ }
+ /* Commit the state last. Above calls check current state. */
+ tsd_tcache_enabled_set(tsd, enabled);
+ tsd_slow_update(tsd);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tcache_event(tsd_t *tsd, tcache_t *tcache) {
+ if (TCACHE_GC_INCR == 0) {
+ return;
+ }
+
+ if (unlikely(ticker_tick(&tcache->gc_ticker))) {
+ tcache_event_hard(tsd, tcache);
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+tcache_alloc_small(tsd_t *tsd, arena_t *arena, tcache_t *tcache,
+ UNUSED size_t size, szind_t binind, bool zero, bool slow_path) {
+ void *ret;
+ cache_bin_t *bin;
+ bool tcache_success;
+ size_t usize JEMALLOC_CC_SILENCE_INIT(0);
+
+ assert(binind < NBINS);
+ bin = tcache_small_bin_get(tcache, binind);
+ ret = cache_bin_alloc_easy(bin, &tcache_success);
+ assert(tcache_success == (ret != NULL));
+ if (unlikely(!tcache_success)) {
+ bool tcache_hard_success;
+ arena = arena_choose(tsd, arena);
+ if (unlikely(arena == NULL)) {
+ return NULL;
+ }
+
+ ret = tcache_alloc_small_hard(tsd_tsdn(tsd), arena, tcache,
+ bin, binind, &tcache_hard_success);
+ if (tcache_hard_success == false) {
+ return NULL;
+ }
+ }
+
+ assert(ret);
+ /*
+ * Only compute usize if required. The checks in the following if
+ * statement are all static.
+ */
+ if (config_prof || (slow_path && config_fill) || unlikely(zero)) {
+ usize = sz_index2size(binind);
+ assert(tcache_salloc(tsd_tsdn(tsd), ret) == usize);
+ }
+
+ if (likely(!zero)) {
+ if (slow_path && config_fill) {
+ if (unlikely(opt_junk_alloc)) {
+ arena_alloc_junk_small(ret, &bin_infos[binind],
+ false);
+ } else if (unlikely(opt_zero)) {
+ memset(ret, 0, usize);
+ }
+ }
+ } else {
+ if (slow_path && config_fill && unlikely(opt_junk_alloc)) {
+ arena_alloc_junk_small(ret, &bin_infos[binind], true);
+ }
+ memset(ret, 0, usize);
+ }
+
+ if (config_stats) {
+ bin->tstats.nrequests++;
+ }
+ if (config_prof) {
+ tcache->prof_accumbytes += usize;
+ }
+ tcache_event(tsd, tcache);
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE void *
+tcache_alloc_large(tsd_t *tsd, arena_t *arena, tcache_t *tcache, size_t size,
+ szind_t binind, bool zero, bool slow_path) {
+ void *ret;
+ cache_bin_t *bin;
+ bool tcache_success;
+
+ assert(binind >= NBINS &&binind < nhbins);
+ bin = tcache_large_bin_get(tcache, binind);
+ ret = cache_bin_alloc_easy(bin, &tcache_success);
+ assert(tcache_success == (ret != NULL));
+ if (unlikely(!tcache_success)) {
+ /*
+ * Only allocate one large object at a time, because it's quite
+ * expensive to create one and not use it.
+ */
+ arena = arena_choose(tsd, arena);
+ if (unlikely(arena == NULL)) {
+ return NULL;
+ }
+
+ ret = large_malloc(tsd_tsdn(tsd), arena, sz_s2u(size), zero);
+ if (ret == NULL) {
+ return NULL;
+ }
+ } else {
+ size_t usize JEMALLOC_CC_SILENCE_INIT(0);
+
+ /* Only compute usize on demand */
+ if (config_prof || (slow_path && config_fill) ||
+ unlikely(zero)) {
+ usize = sz_index2size(binind);
+ assert(usize <= tcache_maxclass);
+ }
+
+ if (likely(!zero)) {
+ if (slow_path && config_fill) {
+ if (unlikely(opt_junk_alloc)) {
+ memset(ret, JEMALLOC_ALLOC_JUNK,
+ usize);
+ } else if (unlikely(opt_zero)) {
+ memset(ret, 0, usize);
+ }
+ }
+ } else {
+ memset(ret, 0, usize);
+ }
+
+ if (config_stats) {
+ bin->tstats.nrequests++;
+ }
+ if (config_prof) {
+ tcache->prof_accumbytes += usize;
+ }
+ }
+
+ tcache_event(tsd, tcache);
+ return ret;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tcache_dalloc_small(tsd_t *tsd, tcache_t *tcache, void *ptr, szind_t binind,
+ bool slow_path) {
+ cache_bin_t *bin;
+ cache_bin_info_t *bin_info;
+
+ assert(tcache_salloc(tsd_tsdn(tsd), ptr) <= SMALL_MAXCLASS);
+
+ if (slow_path && config_fill && unlikely(opt_junk_free)) {
+ arena_dalloc_junk_small(ptr, &bin_infos[binind]);
+ }
+
+ bin = tcache_small_bin_get(tcache, binind);
+ bin_info = &tcache_bin_info[binind];
+ if (unlikely(bin->ncached == bin_info->ncached_max)) {
+ tcache_bin_flush_small(tsd, tcache, bin, binind,
+ (bin_info->ncached_max >> 1));
+ }
+ assert(bin->ncached < bin_info->ncached_max);
+ bin->ncached++;
+ *(bin->avail - bin->ncached) = ptr;
+
+ tcache_event(tsd, tcache);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tcache_dalloc_large(tsd_t *tsd, tcache_t *tcache, void *ptr, szind_t binind,
+ bool slow_path) {
+ cache_bin_t *bin;
+ cache_bin_info_t *bin_info;
+
+ assert(tcache_salloc(tsd_tsdn(tsd), ptr) > SMALL_MAXCLASS);
+ assert(tcache_salloc(tsd_tsdn(tsd), ptr) <= tcache_maxclass);
+
+ if (slow_path && config_fill && unlikely(opt_junk_free)) {
+ large_dalloc_junk(ptr, sz_index2size(binind));
+ }
+
+ bin = tcache_large_bin_get(tcache, binind);
+ bin_info = &tcache_bin_info[binind];
+ if (unlikely(bin->ncached == bin_info->ncached_max)) {
+ tcache_bin_flush_large(tsd, bin, binind,
+ (bin_info->ncached_max >> 1), tcache);
+ }
+ assert(bin->ncached < bin_info->ncached_max);
+ bin->ncached++;
+ *(bin->avail - bin->ncached) = ptr;
+
+ tcache_event(tsd, tcache);
+}
+
+JEMALLOC_ALWAYS_INLINE tcache_t *
+tcaches_get(tsd_t *tsd, unsigned ind) {
+ tcaches_t *elm = &tcaches[ind];
+ if (unlikely(elm->tcache == NULL)) {
+ elm->tcache = tcache_create_explicit(tsd);
+ }
+ return elm->tcache;
+}
+
+#endif /* JEMALLOC_INTERNAL_TCACHE_INLINES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tcache_structs.h b/deps/jemalloc/include/jemalloc/internal/tcache_structs.h
new file mode 100644
index 000000000..07b738705
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tcache_structs.h
@@ -0,0 +1,61 @@
+#ifndef JEMALLOC_INTERNAL_TCACHE_STRUCTS_H
+#define JEMALLOC_INTERNAL_TCACHE_STRUCTS_H
+
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/cache_bin.h"
+#include "jemalloc/internal/ticker.h"
+
+struct tcache_s {
+ /*
+ * To minimize our cache-footprint, we put the frequently accessed data
+ * together at the start of this struct.
+ */
+
+ /* Cleared after arena_prof_accum(). */
+ uint64_t prof_accumbytes;
+ /* Drives incremental GC. */
+ ticker_t gc_ticker;
+ /*
+ * The pointer stacks associated with bins follow as a contiguous array.
+ * During tcache initialization, the avail pointer in each element of
+ * tbins is initialized to point to the proper offset within this array.
+ */
+ cache_bin_t bins_small[NBINS];
+
+ /*
+ * This data is less hot; we can be a little less careful with our
+ * footprint here.
+ */
+ /* Lets us track all the tcaches in an arena. */
+ ql_elm(tcache_t) link;
+ /*
+ * The descriptor lets the arena find our cache bins without seeing the
+ * tcache definition. This enables arenas to aggregate stats across
+ * tcaches without having a tcache dependency.
+ */
+ cache_bin_array_descriptor_t cache_bin_array_descriptor;
+
+ /* The arena this tcache is associated with. */
+ arena_t *arena;
+ /* Next bin to GC. */
+ szind_t next_gc_bin;
+ /* For small bins, fill (ncached_max >> lg_fill_div). */
+ uint8_t lg_fill_div[NBINS];
+ /*
+ * We put the cache bins for large size classes at the end of the
+ * struct, since some of them might not get used. This might end up
+ * letting us avoid touching an extra page if we don't have to.
+ */
+ cache_bin_t bins_large[NSIZES-NBINS];
+};
+
+/* Linkage for list of available (previously used) explicit tcache IDs. */
+struct tcaches_s {
+ union {
+ tcache_t *tcache;
+ tcaches_t *next;
+ };
+};
+
+#endif /* JEMALLOC_INTERNAL_TCACHE_STRUCTS_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tcache_types.h b/deps/jemalloc/include/jemalloc/internal/tcache_types.h
new file mode 100644
index 000000000..e49bc9d79
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tcache_types.h
@@ -0,0 +1,56 @@
+#ifndef JEMALLOC_INTERNAL_TCACHE_TYPES_H
+#define JEMALLOC_INTERNAL_TCACHE_TYPES_H
+
+#include "jemalloc/internal/size_classes.h"
+
+typedef struct tcache_s tcache_t;
+typedef struct tcaches_s tcaches_t;
+
+/*
+ * tcache pointers close to NULL are used to encode state information that is
+ * used for two purposes: preventing thread caching on a per thread basis and
+ * cleaning up during thread shutdown.
+ */
+#define TCACHE_STATE_DISABLED ((tcache_t *)(uintptr_t)1)
+#define TCACHE_STATE_REINCARNATED ((tcache_t *)(uintptr_t)2)
+#define TCACHE_STATE_PURGATORY ((tcache_t *)(uintptr_t)3)
+#define TCACHE_STATE_MAX TCACHE_STATE_PURGATORY
+
+/*
+ * Absolute minimum number of cache slots for each small bin.
+ */
+#define TCACHE_NSLOTS_SMALL_MIN 20
+
+/*
+ * Absolute maximum number of cache slots for each small bin in the thread
+ * cache. This is an additional constraint beyond that imposed as: twice the
+ * number of regions per slab for this size class.
+ *
+ * This constant must be an even number.
+ */
+#define TCACHE_NSLOTS_SMALL_MAX 200
+
+/* Number of cache slots for large size classes. */
+#define TCACHE_NSLOTS_LARGE 20
+
+/* (1U << opt_lg_tcache_max) is used to compute tcache_maxclass. */
+#define LG_TCACHE_MAXCLASS_DEFAULT 15
+
+/*
+ * TCACHE_GC_SWEEP is the approximate number of allocation events between
+ * full GC sweeps. Integer rounding may cause the actual number to be
+ * slightly higher, since GC is performed incrementally.
+ */
+#define TCACHE_GC_SWEEP 8192
+
+/* Number of tcache allocation/deallocation events between incremental GCs. */
+#define TCACHE_GC_INCR \
+ ((TCACHE_GC_SWEEP / NBINS) + ((TCACHE_GC_SWEEP / NBINS == 0) ? 0 : 1))
+
+/* Used in TSD static initializer only. Real init in tcache_data_init(). */
+#define TCACHE_ZERO_INITIALIZER {0}
+
+/* Used in TSD static initializer only. Will be initialized to opt_tcache. */
+#define TCACHE_ENABLED_ZERO_INITIALIZER false
+
+#endif /* JEMALLOC_INTERNAL_TCACHE_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/ticker.h b/deps/jemalloc/include/jemalloc/internal/ticker.h
new file mode 100644
index 000000000..4b3604708
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/ticker.h
@@ -0,0 +1,78 @@
+#ifndef JEMALLOC_INTERNAL_TICKER_H
+#define JEMALLOC_INTERNAL_TICKER_H
+
+#include "jemalloc/internal/util.h"
+
+/**
+ * A ticker makes it easy to count-down events until some limit. You
+ * ticker_init the ticker to trigger every nticks events. You then notify it
+ * that an event has occurred with calls to ticker_tick (or that nticks events
+ * have occurred with a call to ticker_ticks), which will return true (and reset
+ * the counter) if the countdown hit zero.
+ */
+
+typedef struct {
+ int32_t tick;
+ int32_t nticks;
+} ticker_t;
+
+static inline void
+ticker_init(ticker_t *ticker, int32_t nticks) {
+ ticker->tick = nticks;
+ ticker->nticks = nticks;
+}
+
+static inline void
+ticker_copy(ticker_t *ticker, const ticker_t *other) {
+ *ticker = *other;
+}
+
+static inline int32_t
+ticker_read(const ticker_t *ticker) {
+ return ticker->tick;
+}
+
+/*
+ * Not intended to be a public API. Unfortunately, on x86, neither gcc nor
+ * clang seems smart enough to turn
+ * ticker->tick -= nticks;
+ * if (unlikely(ticker->tick < 0)) {
+ * fixup ticker
+ * return true;
+ * }
+ * return false;
+ * into
+ * subq %nticks_reg, (%ticker_reg)
+ * js fixup ticker
+ *
+ * unless we force "fixup ticker" out of line. In that case, gcc gets it right,
+ * but clang now does worse than before. So, on x86 with gcc, we force it out
+ * of line, but otherwise let the inlining occur. Ordinarily this wouldn't be
+ * worth the hassle, but this is on the fast path of both malloc and free (via
+ * tcache_event).
+ */
+#if defined(__GNUC__) && !defined(__clang__) \
+ && (defined(__x86_64__) || defined(__i386__))
+JEMALLOC_NOINLINE
+#endif
+static bool
+ticker_fixup(ticker_t *ticker) {
+ ticker->tick = ticker->nticks;
+ return true;
+}
+
+static inline bool
+ticker_ticks(ticker_t *ticker, int32_t nticks) {
+ ticker->tick -= nticks;
+ if (unlikely(ticker->tick < 0)) {
+ return ticker_fixup(ticker);
+ }
+ return false;
+}
+
+static inline bool
+ticker_tick(ticker_t *ticker) {
+ return ticker_ticks(ticker, 1);
+}
+
+#endif /* JEMALLOC_INTERNAL_TICKER_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd.h b/deps/jemalloc/include/jemalloc/internal/tsd.h
index eed7aa013..0b9841aa7 100644
--- a/deps/jemalloc/include/jemalloc/internal/tsd.h
+++ b/deps/jemalloc/include/jemalloc/internal/tsd.h
@@ -1,665 +1,326 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_TSD_H
+#define JEMALLOC_INTERNAL_TSD_H
-/* Maximum number of malloc_tsd users with cleanup functions. */
-#define MALLOC_TSD_CLEANUPS_MAX 2
-
-typedef bool (*malloc_tsd_cleanup_t)(void);
-
-#if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \
- !defined(_WIN32))
-typedef struct tsd_init_block_s tsd_init_block_t;
-typedef struct tsd_init_head_s tsd_init_head_t;
-#endif
-
-typedef struct tsd_s tsd_t;
-
-typedef enum {
- tsd_state_uninitialized,
- tsd_state_nominal,
- tsd_state_purgatory,
- tsd_state_reincarnated
-} tsd_state_t;
+#include "jemalloc/internal/arena_types.h"
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/jemalloc_internal_externs.h"
+#include "jemalloc/internal/prof_types.h"
+#include "jemalloc/internal/ql.h"
+#include "jemalloc/internal/rtree_tsd.h"
+#include "jemalloc/internal/tcache_types.h"
+#include "jemalloc/internal/tcache_structs.h"
+#include "jemalloc/internal/util.h"
+#include "jemalloc/internal/witness.h"
/*
- * TLS/TSD-agnostic macro-based implementation of thread-specific data. There
- * are five macros that support (at least) three use cases: file-private,
- * library-private, and library-private inlined. Following is an example
- * library-private tsd variable:
- *
- * In example.h:
- * typedef struct {
- * int x;
- * int y;
- * } example_t;
- * #define EX_INITIALIZER JEMALLOC_CONCAT({0, 0})
- * malloc_tsd_types(example_, example_t)
- * malloc_tsd_protos(, example_, example_t)
- * malloc_tsd_externs(example_, example_t)
- * In example.c:
- * malloc_tsd_data(, example_, example_t, EX_INITIALIZER)
- * malloc_tsd_funcs(, example_, example_t, EX_INITIALIZER,
- * example_tsd_cleanup)
- *
- * The result is a set of generated functions, e.g.:
- *
- * bool example_tsd_boot(void) {...}
- * example_t *example_tsd_get() {...}
- * void example_tsd_set(example_t *val) {...}
- *
- * Note that all of the functions deal in terms of (a_type *) rather than
- * (a_type) so that it is possible to support non-pointer types (unlike
- * pthreads TSD). example_tsd_cleanup() is passed an (a_type *) pointer that is
- * cast to (void *). This means that the cleanup function needs to cast the
- * function argument to (a_type *), then dereference the resulting pointer to
- * access fields, e.g.
+ * Thread-Specific-Data layout
+ * --- data accessed on tcache fast path: state, rtree_ctx, stats, prof ---
+ * s: state
+ * e: tcache_enabled
+ * m: thread_allocated (config_stats)
+ * f: thread_deallocated (config_stats)
+ * p: prof_tdata (config_prof)
+ * c: rtree_ctx (rtree cache accessed on deallocation)
+ * t: tcache
+ * --- data not accessed on tcache fast path: arena-related fields ---
+ * d: arenas_tdata_bypass
+ * r: reentrancy_level
+ * x: narenas_tdata
+ * i: iarena
+ * a: arena
+ * o: arenas_tdata
+ * Loading TSD data is on the critical path of basically all malloc operations.
+ * In particular, tcache and rtree_ctx rely on hot CPU cache to be effective.
+ * Use a compact layout to reduce cache footprint.
+ * +--- 64-bit and 64B cacheline; 1B each letter; First byte on the left. ---+
+ * |---------------------------- 1st cacheline ----------------------------|
+ * | sedrxxxx mmmmmmmm ffffffff pppppppp [c * 32 ........ ........ .......] |
+ * |---------------------------- 2nd cacheline ----------------------------|
+ * | [c * 64 ........ ........ ........ ........ ........ ........ .......] |
+ * |---------------------------- 3nd cacheline ----------------------------|
+ * | [c * 32 ........ ........ .......] iiiiiiii aaaaaaaa oooooooo [t...... |
+ * +-------------------------------------------------------------------------+
+ * Note: the entire tcache is embedded into TSD and spans multiple cachelines.
*
- * void
- * example_tsd_cleanup(void *arg)
- * {
- * example_t *example = (example_t *)arg;
- *
- * example->x = 42;
- * [...]
- * if ([want the cleanup function to be called again])
- * example_tsd_set(example);
- * }
- *
- * If example_tsd_set() is called within example_tsd_cleanup(), it will be
- * called again. This is similar to how pthreads TSD destruction works, except
- * that pthreads only calls the cleanup function again if the value was set to
- * non-NULL.
+ * The last 3 members (i, a and o) before tcache isn't really needed on tcache
+ * fast path. However we have a number of unused tcache bins and witnesses
+ * (never touched unless config_debug) at the end of tcache, so we place them
+ * there to avoid breaking the cachelines and possibly paging in an extra page.
*/
-
-/* malloc_tsd_types(). */
-#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
-#define malloc_tsd_types(a_name, a_type)
-#elif (defined(JEMALLOC_TLS))
-#define malloc_tsd_types(a_name, a_type)
-#elif (defined(_WIN32))
-#define malloc_tsd_types(a_name, a_type) \
-typedef struct { \
- bool initialized; \
- a_type val; \
-} a_name##tsd_wrapper_t;
+#ifdef JEMALLOC_JET
+typedef void (*test_callback_t)(int *);
+# define MALLOC_TSD_TEST_DATA_INIT 0x72b65c10
+# define MALLOC_TEST_TSD \
+ O(test_data, int, int) \
+ O(test_callback, test_callback_t, int)
+# define MALLOC_TEST_TSD_INITIALIZER , MALLOC_TSD_TEST_DATA_INIT, NULL
#else
-#define malloc_tsd_types(a_name, a_type) \
-typedef struct { \
- bool initialized; \
- a_type val; \
-} a_name##tsd_wrapper_t;
+# define MALLOC_TEST_TSD
+# define MALLOC_TEST_TSD_INITIALIZER
#endif
-/* malloc_tsd_protos(). */
-#define malloc_tsd_protos(a_attr, a_name, a_type) \
-a_attr bool \
-a_name##tsd_boot0(void); \
-a_attr void \
-a_name##tsd_boot1(void); \
-a_attr bool \
-a_name##tsd_boot(void); \
-a_attr a_type * \
-a_name##tsd_get(void); \
-a_attr void \
-a_name##tsd_set(a_type *val);
-
-/* malloc_tsd_externs(). */
-#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
-#define malloc_tsd_externs(a_name, a_type) \
-extern __thread a_type a_name##tsd_tls; \
-extern __thread bool a_name##tsd_initialized; \
-extern bool a_name##tsd_booted;
-#elif (defined(JEMALLOC_TLS))
-#define malloc_tsd_externs(a_name, a_type) \
-extern __thread a_type a_name##tsd_tls; \
-extern pthread_key_t a_name##tsd_tsd; \
-extern bool a_name##tsd_booted;
-#elif (defined(_WIN32))
-#define malloc_tsd_externs(a_name, a_type) \
-extern DWORD a_name##tsd_tsd; \
-extern a_name##tsd_wrapper_t a_name##tsd_boot_wrapper; \
-extern bool a_name##tsd_booted;
-#else
-#define malloc_tsd_externs(a_name, a_type) \
-extern pthread_key_t a_name##tsd_tsd; \
-extern tsd_init_head_t a_name##tsd_init_head; \
-extern a_name##tsd_wrapper_t a_name##tsd_boot_wrapper; \
-extern bool a_name##tsd_booted;
-#endif
+/* O(name, type, nullable type */
+#define MALLOC_TSD \
+ O(tcache_enabled, bool, bool) \
+ O(arenas_tdata_bypass, bool, bool) \
+ O(reentrancy_level, int8_t, int8_t) \
+ O(narenas_tdata, uint32_t, uint32_t) \
+ O(offset_state, uint64_t, uint64_t) \
+ O(thread_allocated, uint64_t, uint64_t) \
+ O(thread_deallocated, uint64_t, uint64_t) \
+ O(prof_tdata, prof_tdata_t *, prof_tdata_t *) \
+ O(rtree_ctx, rtree_ctx_t, rtree_ctx_t) \
+ O(iarena, arena_t *, arena_t *) \
+ O(arena, arena_t *, arena_t *) \
+ O(arenas_tdata, arena_tdata_t *, arena_tdata_t *)\
+ O(tcache, tcache_t, tcache_t) \
+ O(witness_tsd, witness_tsd_t, witness_tsdn_t) \
+ MALLOC_TEST_TSD
-/* malloc_tsd_data(). */
-#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
-#define malloc_tsd_data(a_attr, a_name, a_type, a_initializer) \
-a_attr __thread a_type JEMALLOC_TLS_MODEL \
- a_name##tsd_tls = a_initializer; \
-a_attr __thread bool JEMALLOC_TLS_MODEL \
- a_name##tsd_initialized = false; \
-a_attr bool a_name##tsd_booted = false;
-#elif (defined(JEMALLOC_TLS))
-#define malloc_tsd_data(a_attr, a_name, a_type, a_initializer) \
-a_attr __thread a_type JEMALLOC_TLS_MODEL \
- a_name##tsd_tls = a_initializer; \
-a_attr pthread_key_t a_name##tsd_tsd; \
-a_attr bool a_name##tsd_booted = false;
-#elif (defined(_WIN32))
-#define malloc_tsd_data(a_attr, a_name, a_type, a_initializer) \
-a_attr DWORD a_name##tsd_tsd; \
-a_attr a_name##tsd_wrapper_t a_name##tsd_boot_wrapper = { \
- false, \
- a_initializer \
-}; \
-a_attr bool a_name##tsd_booted = false;
-#else
-#define malloc_tsd_data(a_attr, a_name, a_type, a_initializer) \
-a_attr pthread_key_t a_name##tsd_tsd; \
-a_attr tsd_init_head_t a_name##tsd_init_head = { \
- ql_head_initializer(blocks), \
- MALLOC_MUTEX_INITIALIZER \
-}; \
-a_attr a_name##tsd_wrapper_t a_name##tsd_boot_wrapper = { \
- false, \
- a_initializer \
-}; \
-a_attr bool a_name##tsd_booted = false;
-#endif
-
-/* malloc_tsd_funcs(). */
-#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
-#define malloc_tsd_funcs(a_attr, a_name, a_type, a_initializer, \
- a_cleanup) \
-/* Initialization/cleanup. */ \
-a_attr bool \
-a_name##tsd_cleanup_wrapper(void) \
-{ \
- \
- if (a_name##tsd_initialized) { \
- a_name##tsd_initialized = false; \
- a_cleanup(&a_name##tsd_tls); \
- } \
- return (a_name##tsd_initialized); \
-} \
-a_attr bool \
-a_name##tsd_boot0(void) \
-{ \
- \
- if (a_cleanup != malloc_tsd_no_cleanup) { \
- malloc_tsd_cleanup_register( \
- &a_name##tsd_cleanup_wrapper); \
- } \
- a_name##tsd_booted = true; \
- return (false); \
-} \
-a_attr void \
-a_name##tsd_boot1(void) \
-{ \
- \
- /* Do nothing. */ \
-} \
-a_attr bool \
-a_name##tsd_boot(void) \
-{ \
- \
- return (a_name##tsd_boot0()); \
-} \
-/* Get/set. */ \
-a_attr a_type * \
-a_name##tsd_get(void) \
-{ \
- \
- assert(a_name##tsd_booted); \
- return (&a_name##tsd_tls); \
-} \
-a_attr void \
-a_name##tsd_set(a_type *val) \
-{ \
- \
- assert(a_name##tsd_booted); \
- a_name##tsd_tls = (*val); \
- if (a_cleanup != malloc_tsd_no_cleanup) \
- a_name##tsd_initialized = true; \
-}
-#elif (defined(JEMALLOC_TLS))
-#define malloc_tsd_funcs(a_attr, a_name, a_type, a_initializer, \
- a_cleanup) \
-/* Initialization/cleanup. */ \
-a_attr bool \
-a_name##tsd_boot0(void) \
-{ \
- \
- if (a_cleanup != malloc_tsd_no_cleanup) { \
- if (pthread_key_create(&a_name##tsd_tsd, a_cleanup) != \
- 0) \
- return (true); \
- } \
- a_name##tsd_booted = true; \
- return (false); \
-} \
-a_attr void \
-a_name##tsd_boot1(void) \
-{ \
- \
- /* Do nothing. */ \
-} \
-a_attr bool \
-a_name##tsd_boot(void) \
-{ \
- \
- return (a_name##tsd_boot0()); \
-} \
-/* Get/set. */ \
-a_attr a_type * \
-a_name##tsd_get(void) \
-{ \
- \
- assert(a_name##tsd_booted); \
- return (&a_name##tsd_tls); \
-} \
-a_attr void \
-a_name##tsd_set(a_type *val) \
-{ \
- \
- assert(a_name##tsd_booted); \
- a_name##tsd_tls = (*val); \
- if (a_cleanup != malloc_tsd_no_cleanup) { \
- if (pthread_setspecific(a_name##tsd_tsd, \
- (void *)(&a_name##tsd_tls))) { \
- malloc_write("<jemalloc>: Error" \
- " setting TSD for "#a_name"\n"); \
- if (opt_abort) \
- abort(); \
- } \
- } \
-}
-#elif (defined(_WIN32))
-#define malloc_tsd_funcs(a_attr, a_name, a_type, a_initializer, \
- a_cleanup) \
-/* Initialization/cleanup. */ \
-a_attr bool \
-a_name##tsd_cleanup_wrapper(void) \
-{ \
- DWORD error = GetLastError(); \
- a_name##tsd_wrapper_t *wrapper = (a_name##tsd_wrapper_t *) \
- TlsGetValue(a_name##tsd_tsd); \
- SetLastError(error); \
- \
- if (wrapper == NULL) \
- return (false); \
- if (a_cleanup != malloc_tsd_no_cleanup && \
- wrapper->initialized) { \
- wrapper->initialized = false; \
- a_cleanup(&wrapper->val); \
- if (wrapper->initialized) { \
- /* Trigger another cleanup round. */ \
- return (true); \
- } \
- } \
- malloc_tsd_dalloc(wrapper); \
- return (false); \
-} \
-a_attr void \
-a_name##tsd_wrapper_set(a_name##tsd_wrapper_t *wrapper) \
-{ \
- \
- if (!TlsSetValue(a_name##tsd_tsd, (void *)wrapper)) { \
- malloc_write("<jemalloc>: Error setting" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } \
-} \
-a_attr a_name##tsd_wrapper_t * \
-a_name##tsd_wrapper_get(void) \
-{ \
- DWORD error = GetLastError(); \
- a_name##tsd_wrapper_t *wrapper = (a_name##tsd_wrapper_t *) \
- TlsGetValue(a_name##tsd_tsd); \
- SetLastError(error); \
- \
- if (unlikely(wrapper == NULL)) { \
- wrapper = (a_name##tsd_wrapper_t *) \
- malloc_tsd_malloc(sizeof(a_name##tsd_wrapper_t)); \
- if (wrapper == NULL) { \
- malloc_write("<jemalloc>: Error allocating" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } else { \
- wrapper->initialized = false; \
- wrapper->val = a_initializer; \
- } \
- a_name##tsd_wrapper_set(wrapper); \
- } \
- return (wrapper); \
-} \
-a_attr bool \
-a_name##tsd_boot0(void) \
-{ \
- \
- a_name##tsd_tsd = TlsAlloc(); \
- if (a_name##tsd_tsd == TLS_OUT_OF_INDEXES) \
- return (true); \
- if (a_cleanup != malloc_tsd_no_cleanup) { \
- malloc_tsd_cleanup_register( \
- &a_name##tsd_cleanup_wrapper); \
- } \
- a_name##tsd_wrapper_set(&a_name##tsd_boot_wrapper); \
- a_name##tsd_booted = true; \
- return (false); \
-} \
-a_attr void \
-a_name##tsd_boot1(void) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- wrapper = (a_name##tsd_wrapper_t *) \
- malloc_tsd_malloc(sizeof(a_name##tsd_wrapper_t)); \
- if (wrapper == NULL) { \
- malloc_write("<jemalloc>: Error allocating" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } \
- memcpy(wrapper, &a_name##tsd_boot_wrapper, \
- sizeof(a_name##tsd_wrapper_t)); \
- a_name##tsd_wrapper_set(wrapper); \
-} \
-a_attr bool \
-a_name##tsd_boot(void) \
-{ \
- \
- if (a_name##tsd_boot0()) \
- return (true); \
- a_name##tsd_boot1(); \
- return (false); \
-} \
-/* Get/set. */ \
-a_attr a_type * \
-a_name##tsd_get(void) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- \
- assert(a_name##tsd_booted); \
- wrapper = a_name##tsd_wrapper_get(); \
- return (&wrapper->val); \
-} \
-a_attr void \
-a_name##tsd_set(a_type *val) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- \
- assert(a_name##tsd_booted); \
- wrapper = a_name##tsd_wrapper_get(); \
- wrapper->val = *(val); \
- if (a_cleanup != malloc_tsd_no_cleanup) \
- wrapper->initialized = true; \
-}
-#else
-#define malloc_tsd_funcs(a_attr, a_name, a_type, a_initializer, \
- a_cleanup) \
-/* Initialization/cleanup. */ \
-a_attr void \
-a_name##tsd_cleanup_wrapper(void *arg) \
-{ \
- a_name##tsd_wrapper_t *wrapper = (a_name##tsd_wrapper_t *)arg; \
- \
- if (a_cleanup != malloc_tsd_no_cleanup && \
- wrapper->initialized) { \
- wrapper->initialized = false; \
- a_cleanup(&wrapper->val); \
- if (wrapper->initialized) { \
- /* Trigger another cleanup round. */ \
- if (pthread_setspecific(a_name##tsd_tsd, \
- (void *)wrapper)) { \
- malloc_write("<jemalloc>: Error" \
- " setting TSD for "#a_name"\n"); \
- if (opt_abort) \
- abort(); \
- } \
- return; \
- } \
- } \
- malloc_tsd_dalloc(wrapper); \
-} \
-a_attr void \
-a_name##tsd_wrapper_set(a_name##tsd_wrapper_t *wrapper) \
-{ \
- \
- if (pthread_setspecific(a_name##tsd_tsd, \
- (void *)wrapper)) { \
- malloc_write("<jemalloc>: Error setting" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } \
-} \
-a_attr a_name##tsd_wrapper_t * \
-a_name##tsd_wrapper_get(void) \
-{ \
- a_name##tsd_wrapper_t *wrapper = (a_name##tsd_wrapper_t *) \
- pthread_getspecific(a_name##tsd_tsd); \
- \
- if (unlikely(wrapper == NULL)) { \
- tsd_init_block_t block; \
- wrapper = tsd_init_check_recursion( \
- &a_name##tsd_init_head, &block); \
- if (wrapper) \
- return (wrapper); \
- wrapper = (a_name##tsd_wrapper_t *) \
- malloc_tsd_malloc(sizeof(a_name##tsd_wrapper_t)); \
- block.data = wrapper; \
- if (wrapper == NULL) { \
- malloc_write("<jemalloc>: Error allocating" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } else { \
- wrapper->initialized = false; \
- wrapper->val = a_initializer; \
- } \
- a_name##tsd_wrapper_set(wrapper); \
- tsd_init_finish(&a_name##tsd_init_head, &block); \
- } \
- return (wrapper); \
-} \
-a_attr bool \
-a_name##tsd_boot0(void) \
-{ \
- \
- if (pthread_key_create(&a_name##tsd_tsd, \
- a_name##tsd_cleanup_wrapper) != 0) \
- return (true); \
- a_name##tsd_wrapper_set(&a_name##tsd_boot_wrapper); \
- a_name##tsd_booted = true; \
- return (false); \
-} \
-a_attr void \
-a_name##tsd_boot1(void) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- wrapper = (a_name##tsd_wrapper_t *) \
- malloc_tsd_malloc(sizeof(a_name##tsd_wrapper_t)); \
- if (wrapper == NULL) { \
- malloc_write("<jemalloc>: Error allocating" \
- " TSD for "#a_name"\n"); \
- abort(); \
- } \
- memcpy(wrapper, &a_name##tsd_boot_wrapper, \
- sizeof(a_name##tsd_wrapper_t)); \
- a_name##tsd_wrapper_set(wrapper); \
-} \
-a_attr bool \
-a_name##tsd_boot(void) \
-{ \
- \
- if (a_name##tsd_boot0()) \
- return (true); \
- a_name##tsd_boot1(); \
- return (false); \
-} \
-/* Get/set. */ \
-a_attr a_type * \
-a_name##tsd_get(void) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- \
- assert(a_name##tsd_booted); \
- wrapper = a_name##tsd_wrapper_get(); \
- return (&wrapper->val); \
-} \
-a_attr void \
-a_name##tsd_set(a_type *val) \
-{ \
- a_name##tsd_wrapper_t *wrapper; \
- \
- assert(a_name##tsd_booted); \
- wrapper = a_name##tsd_wrapper_get(); \
- wrapper->val = *(val); \
- if (a_cleanup != malloc_tsd_no_cleanup) \
- wrapper->initialized = true; \
-}
-#endif
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \
- !defined(_WIN32))
-struct tsd_init_block_s {
- ql_elm(tsd_init_block_t) link;
- pthread_t thread;
- void *data;
-};
-struct tsd_init_head_s {
- ql_head(tsd_init_block_t) blocks;
- malloc_mutex_t lock;
-};
-#endif
-
-#define MALLOC_TSD \
-/* O(name, type) */ \
- O(tcache, tcache_t *) \
- O(thread_allocated, uint64_t) \
- O(thread_deallocated, uint64_t) \
- O(prof_tdata, prof_tdata_t *) \
- O(arena, arena_t *) \
- O(arenas_cache, arena_t **) \
- O(narenas_cache, unsigned) \
- O(arenas_cache_bypass, bool) \
- O(tcache_enabled, tcache_enabled_t) \
- O(quarantine, quarantine_t *) \
-
-#define TSD_INITIALIZER { \
+#define TSD_INITIALIZER { \
tsd_state_uninitialized, \
- NULL, \
+ TCACHE_ENABLED_ZERO_INITIALIZER, \
+ false, \
+ 0, \
+ 0, \
+ 0, \
0, \
0, \
NULL, \
+ RTREE_CTX_ZERO_INITIALIZER, \
NULL, \
NULL, \
- 0, \
- false, \
- tcache_enabled_default, \
- NULL \
+ NULL, \
+ TCACHE_ZERO_INITIALIZER, \
+ WITNESS_TSD_INITIALIZER \
+ MALLOC_TEST_TSD_INITIALIZER \
}
+enum {
+ tsd_state_nominal = 0, /* Common case --> jnz. */
+ tsd_state_nominal_slow = 1, /* Initialized but on slow path. */
+ /* the above 2 nominal states should be lower values. */
+ tsd_state_nominal_max = 1, /* used for comparison only. */
+ tsd_state_minimal_initialized = 2,
+ tsd_state_purgatory = 3,
+ tsd_state_reincarnated = 4,
+ tsd_state_uninitialized = 5
+};
+
+/* Manually limit tsd_state_t to a single byte. */
+typedef uint8_t tsd_state_t;
+
+/* The actual tsd. */
struct tsd_s {
+ /*
+ * The contents should be treated as totally opaque outside the tsd
+ * module. Access any thread-local state through the getters and
+ * setters below.
+ */
tsd_state_t state;
-#define O(n, t) \
- t n;
+#define O(n, t, nt) \
+ t use_a_getter_or_setter_instead_##n;
MALLOC_TSD
#undef O
};
-static const tsd_t tsd_initializer = TSD_INITIALIZER;
-
-malloc_tsd_types(, tsd_t)
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-void *malloc_tsd_malloc(size_t size);
-void malloc_tsd_dalloc(void *wrapper);
-void malloc_tsd_no_cleanup(void *arg);
-void malloc_tsd_cleanup_register(bool (*f)(void));
-bool malloc_tsd_boot0(void);
-void malloc_tsd_boot1(void);
-#if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \
- !defined(_WIN32))
-void *tsd_init_check_recursion(tsd_init_head_t *head,
- tsd_init_block_t *block);
-void tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block);
+/*
+ * Wrapper around tsd_t that makes it possible to avoid implicit conversion
+ * between tsd_t and tsdn_t, where tsdn_t is "nullable" and has to be
+ * explicitly converted to tsd_t, which is non-nullable.
+ */
+struct tsdn_s {
+ tsd_t tsd;
+};
+#define TSDN_NULL ((tsdn_t *)0)
+JEMALLOC_ALWAYS_INLINE tsdn_t *
+tsd_tsdn(tsd_t *tsd) {
+ return (tsdn_t *)tsd;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsdn_null(const tsdn_t *tsdn) {
+ return tsdn == NULL;
+}
+
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsdn_tsd(tsdn_t *tsdn) {
+ assert(!tsdn_null(tsdn));
+
+ return &tsdn->tsd;
+}
+
+void *malloc_tsd_malloc(size_t size);
+void malloc_tsd_dalloc(void *wrapper);
+void malloc_tsd_cleanup_register(bool (*f)(void));
+tsd_t *malloc_tsd_boot0(void);
+void malloc_tsd_boot1(void);
+void tsd_cleanup(void *arg);
+tsd_t *tsd_fetch_slow(tsd_t *tsd, bool internal);
+void tsd_slow_update(tsd_t *tsd);
+
+/*
+ * We put the platform-specific data declarations and inlines into their own
+ * header files to avoid cluttering this file. They define tsd_boot0,
+ * tsd_boot1, tsd_boot, tsd_booted_get, tsd_get_allocates, tsd_get, and tsd_set.
+ */
+#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
+#include "jemalloc/internal/tsd_malloc_thread_cleanup.h"
+#elif (defined(JEMALLOC_TLS))
+#include "jemalloc/internal/tsd_tls.h"
+#elif (defined(_WIN32))
+#include "jemalloc/internal/tsd_win.h"
+#else
+#include "jemalloc/internal/tsd_generic.h"
#endif
-void tsd_cleanup(void *arg);
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
+/*
+ * tsd_foop_get_unsafe(tsd) returns a pointer to the thread-local instance of
+ * foo. This omits some safety checks, and so can be used during tsd
+ * initialization and cleanup.
+ */
+#define O(n, t, nt) \
+JEMALLOC_ALWAYS_INLINE t * \
+tsd_##n##p_get_unsafe(tsd_t *tsd) { \
+ return &tsd->use_a_getter_or_setter_instead_##n; \
+}
+MALLOC_TSD
+#undef O
+
+/* tsd_foop_get(tsd) returns a pointer to the thread-local instance of foo. */
+#define O(n, t, nt) \
+JEMALLOC_ALWAYS_INLINE t * \
+tsd_##n##p_get(tsd_t *tsd) { \
+ assert(tsd->state == tsd_state_nominal || \
+ tsd->state == tsd_state_nominal_slow || \
+ tsd->state == tsd_state_reincarnated || \
+ tsd->state == tsd_state_minimal_initialized); \
+ return tsd_##n##p_get_unsafe(tsd); \
+}
+MALLOC_TSD
+#undef O
-#ifndef JEMALLOC_ENABLE_INLINE
-malloc_tsd_protos(JEMALLOC_ATTR(unused), , tsd_t)
+/*
+ * tsdn_foop_get(tsdn) returns either the thread-local instance of foo (if tsdn
+ * isn't NULL), or NULL (if tsdn is NULL), cast to the nullable pointer type.
+ */
+#define O(n, t, nt) \
+JEMALLOC_ALWAYS_INLINE nt * \
+tsdn_##n##p_get(tsdn_t *tsdn) { \
+ if (tsdn_null(tsdn)) { \
+ return NULL; \
+ } \
+ tsd_t *tsd = tsdn_tsd(tsdn); \
+ return (nt *)tsd_##n##p_get(tsd); \
+}
+MALLOC_TSD
+#undef O
-tsd_t *tsd_fetch(void);
-bool tsd_nominal(tsd_t *tsd);
-#define O(n, t) \
-t *tsd_##n##p_get(tsd_t *tsd); \
-t tsd_##n##_get(tsd_t *tsd); \
-void tsd_##n##_set(tsd_t *tsd, t n);
+/* tsd_foo_get(tsd) returns the value of the thread-local instance of foo. */
+#define O(n, t, nt) \
+JEMALLOC_ALWAYS_INLINE t \
+tsd_##n##_get(tsd_t *tsd) { \
+ return *tsd_##n##p_get(tsd); \
+}
MALLOC_TSD
#undef O
-#endif
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_TSD_C_))
-malloc_tsd_externs(, tsd_t)
-malloc_tsd_funcs(JEMALLOC_ALWAYS_INLINE, , tsd_t, tsd_initializer, tsd_cleanup)
+/* tsd_foo_set(tsd, val) updates the thread-local instance of foo to be val. */
+#define O(n, t, nt) \
+JEMALLOC_ALWAYS_INLINE void \
+tsd_##n##_set(tsd_t *tsd, t val) { \
+ assert(tsd->state != tsd_state_reincarnated && \
+ tsd->state != tsd_state_minimal_initialized); \
+ *tsd_##n##p_get(tsd) = val; \
+}
+MALLOC_TSD
+#undef O
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_assert_fast(tsd_t *tsd) {
+ assert(!malloc_slow && tsd_tcache_enabled_get(tsd) &&
+ tsd_reentrancy_level_get(tsd) == 0);
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_fast(tsd_t *tsd) {
+ bool fast = (tsd->state == tsd_state_nominal);
+ if (fast) {
+ tsd_assert_fast(tsd);
+ }
+
+ return fast;
+}
JEMALLOC_ALWAYS_INLINE tsd_t *
-tsd_fetch(void)
-{
- tsd_t *tsd = tsd_get();
+tsd_fetch_impl(bool init, bool minimal) {
+ tsd_t *tsd = tsd_get(init);
+
+ if (!init && tsd_get_allocates() && tsd == NULL) {
+ return NULL;
+ }
+ assert(tsd != NULL);
if (unlikely(tsd->state != tsd_state_nominal)) {
- if (tsd->state == tsd_state_uninitialized) {
- tsd->state = tsd_state_nominal;
- /* Trigger cleanup handler registration. */
- tsd_set(tsd);
- } else if (tsd->state == tsd_state_purgatory) {
- tsd->state = tsd_state_reincarnated;
- tsd_set(tsd);
- } else
- assert(tsd->state == tsd_state_reincarnated);
+ return tsd_fetch_slow(tsd, minimal);
}
+ assert(tsd_fast(tsd));
+ tsd_assert_fast(tsd);
+
+ return tsd;
+}
- return (tsd);
+/* Get a minimal TSD that requires no cleanup. See comments in free(). */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_fetch_min(void) {
+ return tsd_fetch_impl(true, true);
}
-JEMALLOC_INLINE bool
-tsd_nominal(tsd_t *tsd)
-{
+/* For internal background threads use only. */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_internal_fetch(void) {
+ tsd_t *tsd = tsd_fetch_min();
+ /* Use reincarnated state to prevent full initialization. */
+ tsd->state = tsd_state_reincarnated;
- return (tsd->state == tsd_state_nominal);
+ return tsd;
}
-#define O(n, t) \
-JEMALLOC_ALWAYS_INLINE t * \
-tsd_##n##p_get(tsd_t *tsd) \
-{ \
- \
- return (&tsd->n); \
-} \
- \
-JEMALLOC_ALWAYS_INLINE t \
-tsd_##n##_get(tsd_t *tsd) \
-{ \
- \
- return (*tsd_##n##p_get(tsd)); \
-} \
- \
-JEMALLOC_ALWAYS_INLINE void \
-tsd_##n##_set(tsd_t *tsd, t n) \
-{ \
- \
- assert(tsd->state == tsd_state_nominal); \
- tsd->n = n; \
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_fetch(void) {
+ return tsd_fetch_impl(true, false);
+}
+
+static inline bool
+tsd_nominal(tsd_t *tsd) {
+ return (tsd->state <= tsd_state_nominal_max);
+}
+
+JEMALLOC_ALWAYS_INLINE tsdn_t *
+tsdn_fetch(void) {
+ if (!tsd_booted_get()) {
+ return NULL;
+ }
+
+ return tsd_tsdn(tsd_fetch_impl(false, false));
+}
+
+JEMALLOC_ALWAYS_INLINE rtree_ctx_t *
+tsd_rtree_ctx(tsd_t *tsd) {
+ return tsd_rtree_ctxp_get(tsd);
+}
+
+JEMALLOC_ALWAYS_INLINE rtree_ctx_t *
+tsdn_rtree_ctx(tsdn_t *tsdn, rtree_ctx_t *fallback) {
+ /*
+ * If tsd cannot be accessed, initialize the fallback rtree_ctx and
+ * return a pointer to it.
+ */
+ if (unlikely(tsdn_null(tsdn))) {
+ rtree_ctx_data_init(fallback);
+ return fallback;
+ }
+ return tsd_rtree_ctx(tsdn_tsd(tsdn));
}
-MALLOC_TSD
-#undef O
-#endif
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#endif /* JEMALLOC_INTERNAL_TSD_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd_generic.h b/deps/jemalloc/include/jemalloc/internal/tsd_generic.h
new file mode 100644
index 000000000..1e52ef767
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tsd_generic.h
@@ -0,0 +1,157 @@
+#ifdef JEMALLOC_INTERNAL_TSD_GENERIC_H
+#error This file should be included only once, by tsd.h.
+#endif
+#define JEMALLOC_INTERNAL_TSD_GENERIC_H
+
+typedef struct tsd_init_block_s tsd_init_block_t;
+struct tsd_init_block_s {
+ ql_elm(tsd_init_block_t) link;
+ pthread_t thread;
+ void *data;
+};
+
+/* Defined in tsd.c, to allow the mutex headers to have tsd dependencies. */
+typedef struct tsd_init_head_s tsd_init_head_t;
+
+typedef struct {
+ bool initialized;
+ tsd_t val;
+} tsd_wrapper_t;
+
+void *tsd_init_check_recursion(tsd_init_head_t *head,
+ tsd_init_block_t *block);
+void tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block);
+
+extern pthread_key_t tsd_tsd;
+extern tsd_init_head_t tsd_init_head;
+extern tsd_wrapper_t tsd_boot_wrapper;
+extern bool tsd_booted;
+
+/* Initialization/cleanup. */
+JEMALLOC_ALWAYS_INLINE void
+tsd_cleanup_wrapper(void *arg) {
+ tsd_wrapper_t *wrapper = (tsd_wrapper_t *)arg;
+
+ if (wrapper->initialized) {
+ wrapper->initialized = false;
+ tsd_cleanup(&wrapper->val);
+ if (wrapper->initialized) {
+ /* Trigger another cleanup round. */
+ if (pthread_setspecific(tsd_tsd, (void *)wrapper) != 0)
+ {
+ malloc_write("<jemalloc>: Error setting TSD\n");
+ if (opt_abort) {
+ abort();
+ }
+ }
+ return;
+ }
+ }
+ malloc_tsd_dalloc(wrapper);
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_wrapper_set(tsd_wrapper_t *wrapper) {
+ if (pthread_setspecific(tsd_tsd, (void *)wrapper) != 0) {
+ malloc_write("<jemalloc>: Error setting TSD\n");
+ abort();
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE tsd_wrapper_t *
+tsd_wrapper_get(bool init) {
+ tsd_wrapper_t *wrapper = (tsd_wrapper_t *)pthread_getspecific(tsd_tsd);
+
+ if (init && unlikely(wrapper == NULL)) {
+ tsd_init_block_t block;
+ wrapper = (tsd_wrapper_t *)
+ tsd_init_check_recursion(&tsd_init_head, &block);
+ if (wrapper) {
+ return wrapper;
+ }
+ wrapper = (tsd_wrapper_t *)
+ malloc_tsd_malloc(sizeof(tsd_wrapper_t));
+ block.data = (void *)wrapper;
+ if (wrapper == NULL) {
+ malloc_write("<jemalloc>: Error allocating TSD\n");
+ abort();
+ } else {
+ wrapper->initialized = false;
+ tsd_t initializer = TSD_INITIALIZER;
+ wrapper->val = initializer;
+ }
+ tsd_wrapper_set(wrapper);
+ tsd_init_finish(&tsd_init_head, &block);
+ }
+ return wrapper;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot0(void) {
+ if (pthread_key_create(&tsd_tsd, tsd_cleanup_wrapper) != 0) {
+ return true;
+ }
+ tsd_wrapper_set(&tsd_boot_wrapper);
+ tsd_booted = true;
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_boot1(void) {
+ tsd_wrapper_t *wrapper;
+ wrapper = (tsd_wrapper_t *)malloc_tsd_malloc(sizeof(tsd_wrapper_t));
+ if (wrapper == NULL) {
+ malloc_write("<jemalloc>: Error allocating TSD\n");
+ abort();
+ }
+ tsd_boot_wrapper.initialized = false;
+ tsd_cleanup(&tsd_boot_wrapper.val);
+ wrapper->initialized = false;
+ tsd_t initializer = TSD_INITIALIZER;
+ wrapper->val = initializer;
+ tsd_wrapper_set(wrapper);
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot(void) {
+ if (tsd_boot0()) {
+ return true;
+ }
+ tsd_boot1();
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_booted_get(void) {
+ return tsd_booted;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_get_allocates(void) {
+ return true;
+}
+
+/* Get/set. */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_get(bool init) {
+ tsd_wrapper_t *wrapper;
+
+ assert(tsd_booted);
+ wrapper = tsd_wrapper_get(init);
+ if (tsd_get_allocates() && !init && wrapper == NULL) {
+ return NULL;
+ }
+ return &wrapper->val;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_set(tsd_t *val) {
+ tsd_wrapper_t *wrapper;
+
+ assert(tsd_booted);
+ wrapper = tsd_wrapper_get(true);
+ if (likely(&wrapper->val != val)) {
+ wrapper->val = *(val);
+ }
+ wrapper->initialized = true;
+}
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd_malloc_thread_cleanup.h b/deps/jemalloc/include/jemalloc/internal/tsd_malloc_thread_cleanup.h
new file mode 100644
index 000000000..beb467a67
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tsd_malloc_thread_cleanup.h
@@ -0,0 +1,60 @@
+#ifdef JEMALLOC_INTERNAL_TSD_MALLOC_THREAD_CLEANUP_H
+#error This file should be included only once, by tsd.h.
+#endif
+#define JEMALLOC_INTERNAL_TSD_MALLOC_THREAD_CLEANUP_H
+
+extern __thread tsd_t tsd_tls;
+extern __thread bool tsd_initialized;
+extern bool tsd_booted;
+
+/* Initialization/cleanup. */
+JEMALLOC_ALWAYS_INLINE bool
+tsd_cleanup_wrapper(void) {
+ if (tsd_initialized) {
+ tsd_initialized = false;
+ tsd_cleanup(&tsd_tls);
+ }
+ return tsd_initialized;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot0(void) {
+ malloc_tsd_cleanup_register(&tsd_cleanup_wrapper);
+ tsd_booted = true;
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_boot1(void) {
+ /* Do nothing. */
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot(void) {
+ return tsd_boot0();
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_booted_get(void) {
+ return tsd_booted;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_get_allocates(void) {
+ return false;
+}
+
+/* Get/set. */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_get(bool init) {
+ assert(tsd_booted);
+ return &tsd_tls;
+}
+JEMALLOC_ALWAYS_INLINE void
+tsd_set(tsd_t *val) {
+ assert(tsd_booted);
+ if (likely(&tsd_tls != val)) {
+ tsd_tls = (*val);
+ }
+ tsd_initialized = true;
+}
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd_tls.h b/deps/jemalloc/include/jemalloc/internal/tsd_tls.h
new file mode 100644
index 000000000..0de64b7b8
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tsd_tls.h
@@ -0,0 +1,59 @@
+#ifdef JEMALLOC_INTERNAL_TSD_TLS_H
+#error This file should be included only once, by tsd.h.
+#endif
+#define JEMALLOC_INTERNAL_TSD_TLS_H
+
+extern __thread tsd_t tsd_tls;
+extern pthread_key_t tsd_tsd;
+extern bool tsd_booted;
+
+/* Initialization/cleanup. */
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot0(void) {
+ if (pthread_key_create(&tsd_tsd, &tsd_cleanup) != 0) {
+ return true;
+ }
+ tsd_booted = true;
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_boot1(void) {
+ /* Do nothing. */
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot(void) {
+ return tsd_boot0();
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_booted_get(void) {
+ return tsd_booted;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_get_allocates(void) {
+ return false;
+}
+
+/* Get/set. */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_get(UNUSED bool init) {
+ assert(tsd_booted);
+ return &tsd_tls;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_set(tsd_t *val) {
+ assert(tsd_booted);
+ if (likely(&tsd_tls != val)) {
+ tsd_tls = (*val);
+ }
+ if (pthread_setspecific(tsd_tsd, (void *)(&tsd_tls)) != 0) {
+ malloc_write("<jemalloc>: Error setting tsd.\n");
+ if (opt_abort) {
+ abort();
+ }
+ }
+}
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd_types.h b/deps/jemalloc/include/jemalloc/internal/tsd_types.h
new file mode 100644
index 000000000..6200af61f
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tsd_types.h
@@ -0,0 +1,10 @@
+#ifndef JEMALLOC_INTERNAL_TSD_TYPES_H
+#define JEMALLOC_INTERNAL_TSD_TYPES_H
+
+#define MALLOC_TSD_CLEANUPS_MAX 2
+
+typedef struct tsd_s tsd_t;
+typedef struct tsdn_s tsdn_t;
+typedef bool (*malloc_tsd_cleanup_t)(void);
+
+#endif /* JEMALLOC_INTERNAL_TSD_TYPES_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/tsd_win.h b/deps/jemalloc/include/jemalloc/internal/tsd_win.h
new file mode 100644
index 000000000..cf30d18e3
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/tsd_win.h
@@ -0,0 +1,139 @@
+#ifdef JEMALLOC_INTERNAL_TSD_WIN_H
+#error This file should be included only once, by tsd.h.
+#endif
+#define JEMALLOC_INTERNAL_TSD_WIN_H
+
+typedef struct {
+ bool initialized;
+ tsd_t val;
+} tsd_wrapper_t;
+
+extern DWORD tsd_tsd;
+extern tsd_wrapper_t tsd_boot_wrapper;
+extern bool tsd_booted;
+
+/* Initialization/cleanup. */
+JEMALLOC_ALWAYS_INLINE bool
+tsd_cleanup_wrapper(void) {
+ DWORD error = GetLastError();
+ tsd_wrapper_t *wrapper = (tsd_wrapper_t *)TlsGetValue(tsd_tsd);
+ SetLastError(error);
+
+ if (wrapper == NULL) {
+ return false;
+ }
+
+ if (wrapper->initialized) {
+ wrapper->initialized = false;
+ tsd_cleanup(&wrapper->val);
+ if (wrapper->initialized) {
+ /* Trigger another cleanup round. */
+ return true;
+ }
+ }
+ malloc_tsd_dalloc(wrapper);
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_wrapper_set(tsd_wrapper_t *wrapper) {
+ if (!TlsSetValue(tsd_tsd, (void *)wrapper)) {
+ malloc_write("<jemalloc>: Error setting TSD\n");
+ abort();
+ }
+}
+
+JEMALLOC_ALWAYS_INLINE tsd_wrapper_t *
+tsd_wrapper_get(bool init) {
+ DWORD error = GetLastError();
+ tsd_wrapper_t *wrapper = (tsd_wrapper_t *) TlsGetValue(tsd_tsd);
+ SetLastError(error);
+
+ if (init && unlikely(wrapper == NULL)) {
+ wrapper = (tsd_wrapper_t *)
+ malloc_tsd_malloc(sizeof(tsd_wrapper_t));
+ if (wrapper == NULL) {
+ malloc_write("<jemalloc>: Error allocating TSD\n");
+ abort();
+ } else {
+ wrapper->initialized = false;
+ /* MSVC is finicky about aggregate initialization. */
+ tsd_t tsd_initializer = TSD_INITIALIZER;
+ wrapper->val = tsd_initializer;
+ }
+ tsd_wrapper_set(wrapper);
+ }
+ return wrapper;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot0(void) {
+ tsd_tsd = TlsAlloc();
+ if (tsd_tsd == TLS_OUT_OF_INDEXES) {
+ return true;
+ }
+ malloc_tsd_cleanup_register(&tsd_cleanup_wrapper);
+ tsd_wrapper_set(&tsd_boot_wrapper);
+ tsd_booted = true;
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_boot1(void) {
+ tsd_wrapper_t *wrapper;
+ wrapper = (tsd_wrapper_t *)
+ malloc_tsd_malloc(sizeof(tsd_wrapper_t));
+ if (wrapper == NULL) {
+ malloc_write("<jemalloc>: Error allocating TSD\n");
+ abort();
+ }
+ tsd_boot_wrapper.initialized = false;
+ tsd_cleanup(&tsd_boot_wrapper.val);
+ wrapper->initialized = false;
+ tsd_t initializer = TSD_INITIALIZER;
+ wrapper->val = initializer;
+ tsd_wrapper_set(wrapper);
+}
+JEMALLOC_ALWAYS_INLINE bool
+tsd_boot(void) {
+ if (tsd_boot0()) {
+ return true;
+ }
+ tsd_boot1();
+ return false;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_booted_get(void) {
+ return tsd_booted;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+tsd_get_allocates(void) {
+ return true;
+}
+
+/* Get/set. */
+JEMALLOC_ALWAYS_INLINE tsd_t *
+tsd_get(bool init) {
+ tsd_wrapper_t *wrapper;
+
+ assert(tsd_booted);
+ wrapper = tsd_wrapper_get(init);
+ if (tsd_get_allocates() && !init && wrapper == NULL) {
+ return NULL;
+ }
+ return &wrapper->val;
+}
+
+JEMALLOC_ALWAYS_INLINE void
+tsd_set(tsd_t *val) {
+ tsd_wrapper_t *wrapper;
+
+ assert(tsd_booted);
+ wrapper = tsd_wrapper_get(true);
+ if (likely(&wrapper->val != val)) {
+ wrapper->val = *(val);
+ }
+ wrapper->initialized = true;
+}
diff --git a/deps/jemalloc/include/jemalloc/internal/util.h b/deps/jemalloc/include/jemalloc/internal/util.h
index b2ea740fd..304cb545a 100644
--- a/deps/jemalloc/include/jemalloc/internal/util.h
+++ b/deps/jemalloc/include/jemalloc/internal/util.h
@@ -1,295 +1,50 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
+#ifndef JEMALLOC_INTERNAL_UTIL_H
+#define JEMALLOC_INTERNAL_UTIL_H
-#ifdef _WIN32
-# ifdef _WIN64
-# define FMT64_PREFIX "ll"
-# define FMTPTR_PREFIX "ll"
-# else
-# define FMT64_PREFIX "ll"
-# define FMTPTR_PREFIX ""
-# endif
-# define FMTd32 "d"
-# define FMTu32 "u"
-# define FMTx32 "x"
-# define FMTd64 FMT64_PREFIX "d"
-# define FMTu64 FMT64_PREFIX "u"
-# define FMTx64 FMT64_PREFIX "x"
-# define FMTdPTR FMTPTR_PREFIX "d"
-# define FMTuPTR FMTPTR_PREFIX "u"
-# define FMTxPTR FMTPTR_PREFIX "x"
-#else
-# include <inttypes.h>
-# define FMTd32 PRId32
-# define FMTu32 PRIu32
-# define FMTx32 PRIx32
-# define FMTd64 PRId64
-# define FMTu64 PRIu64
-# define FMTx64 PRIx64
-# define FMTdPTR PRIdPTR
-# define FMTuPTR PRIuPTR
-# define FMTxPTR PRIxPTR
-#endif
+#define UTIL_INLINE static inline
-/* Size of stack-allocated buffer passed to buferror(). */
-#define BUFERROR_BUF 64
-
-/*
- * Size of stack-allocated buffer used by malloc_{,v,vc}printf(). This must be
- * large enough for all possible uses within jemalloc.
- */
-#define MALLOC_PRINTF_BUFSIZE 4096
+/* Junk fill patterns. */
+#ifndef JEMALLOC_ALLOC_JUNK
+# define JEMALLOC_ALLOC_JUNK ((uint8_t)0xa5)
+#endif
+#ifndef JEMALLOC_FREE_JUNK
+# define JEMALLOC_FREE_JUNK ((uint8_t)0x5a)
+#endif
/*
* Wrap a cpp argument that contains commas such that it isn't broken up into
* multiple arguments.
*/
-#define JEMALLOC_ARG_CONCAT(...) __VA_ARGS__
+#define JEMALLOC_ARG_CONCAT(...) __VA_ARGS__
+
+/* cpp macro definition stringification. */
+#define STRINGIFY_HELPER(x) #x
+#define STRINGIFY(x) STRINGIFY_HELPER(x)
/*
* Silence compiler warnings due to uninitialized values. This is used
* wherever the compiler fails to recognize that the variable is never used
* uninitialized.
*/
-#ifdef JEMALLOC_CC_SILENCE
-# define JEMALLOC_CC_SILENCE_INIT(v) = v
-#else
-# define JEMALLOC_CC_SILENCE_INIT(v)
-#endif
-
-#define JEMALLOC_GNUC_PREREQ(major, minor) \
- (!defined(__clang__) && \
- (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
-#ifndef __has_builtin
-# define __has_builtin(builtin) (0)
-#endif
-#define JEMALLOC_CLANG_HAS_BUILTIN(builtin) \
- (defined(__clang__) && __has_builtin(builtin))
+#define JEMALLOC_CC_SILENCE_INIT(v) = v
#ifdef __GNUC__
-# define likely(x) __builtin_expect(!!(x), 1)
-# define unlikely(x) __builtin_expect(!!(x), 0)
-# if JEMALLOC_GNUC_PREREQ(4, 6) || \
- JEMALLOC_CLANG_HAS_BUILTIN(__builtin_unreachable)
-# define unreachable() __builtin_unreachable()
-# else
-# define unreachable()
-# endif
-#else
-# define likely(x) !!(x)
-# define unlikely(x) !!(x)
-# define unreachable()
-#endif
-
-/*
- * Define a custom assert() in order to reduce the chances of deadlock during
- * assertion failure.
- */
-#ifndef assert
-#define assert(e) do { \
- if (unlikely(config_debug && !(e))) { \
- malloc_printf( \
- "<jemalloc>: %s:%d: Failed assertion: \"%s\"\n", \
- __FILE__, __LINE__, #e); \
- abort(); \
- } \
-} while (0)
-#endif
-
-#ifndef not_reached
-#define not_reached() do { \
- if (config_debug) { \
- malloc_printf( \
- "<jemalloc>: %s:%d: Unreachable code reached\n", \
- __FILE__, __LINE__); \
- abort(); \
- } \
- unreachable(); \
-} while (0)
-#endif
-
-#ifndef not_implemented
-#define not_implemented() do { \
- if (config_debug) { \
- malloc_printf("<jemalloc>: %s:%d: Not implemented\n", \
- __FILE__, __LINE__); \
- abort(); \
- } \
-} while (0)
-#endif
-
-#ifndef assert_not_implemented
-#define assert_not_implemented(e) do { \
- if (unlikely(config_debug && !(e))) \
- not_implemented(); \
-} while (0)
-#endif
-
-/* Use to assert a particular configuration, e.g., cassert(config_debug). */
-#define cassert(c) do { \
- if (unlikely(!(c))) \
- not_reached(); \
-} while (0)
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-int buferror(int err, char *buf, size_t buflen);
-uintmax_t malloc_strtoumax(const char *restrict nptr,
- char **restrict endptr, int base);
-void malloc_write(const char *s);
-
-/*
- * malloc_vsnprintf() supports a subset of snprintf(3) that avoids floating
- * point math.
- */
-int malloc_vsnprintf(char *str, size_t size, const char *format,
- va_list ap);
-int malloc_snprintf(char *str, size_t size, const char *format, ...)
- JEMALLOC_FORMAT_PRINTF(3, 4);
-void malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
- const char *format, va_list ap);
-void malloc_cprintf(void (*write)(void *, const char *), void *cbopaque,
- const char *format, ...) JEMALLOC_FORMAT_PRINTF(3, 4);
-void malloc_printf(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#ifndef JEMALLOC_ENABLE_INLINE
-int jemalloc_ffsl(long bitmap);
-int jemalloc_ffs(int bitmap);
-size_t pow2_ceil(size_t x);
-size_t lg_floor(size_t x);
-void set_errno(int errnum);
-int get_errno(void);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(JEMALLOC_UTIL_C_))
-
-/* Sanity check. */
-#if !defined(JEMALLOC_INTERNAL_FFSL) || !defined(JEMALLOC_INTERNAL_FFS)
-# error Both JEMALLOC_INTERNAL_FFSL && JEMALLOC_INTERNAL_FFS should have been defined by configure
-#endif
-
-JEMALLOC_ALWAYS_INLINE int
-jemalloc_ffsl(long bitmap)
-{
-
- return (JEMALLOC_INTERNAL_FFSL(bitmap));
-}
-
-JEMALLOC_ALWAYS_INLINE int
-jemalloc_ffs(int bitmap)
-{
-
- return (JEMALLOC_INTERNAL_FFS(bitmap));
-}
-
-/* Compute the smallest power of 2 that is >= x. */
-JEMALLOC_INLINE size_t
-pow2_ceil(size_t x)
-{
-
- x--;
- x |= x >> 1;
- x |= x >> 2;
- x |= x >> 4;
- x |= x >> 8;
- x |= x >> 16;
-#if (LG_SIZEOF_PTR == 3)
- x |= x >> 32;
-#endif
- x++;
- return (x);
-}
-
-#if (defined(__i386__) || defined(__amd64__) || defined(__x86_64__))
-JEMALLOC_INLINE size_t
-lg_floor(size_t x)
-{
- size_t ret;
-
- assert(x != 0);
-
- asm ("bsr %1, %0"
- : "=r"(ret) // Outputs.
- : "r"(x) // Inputs.
- );
- return (ret);
-}
-#elif (defined(_MSC_VER))
-JEMALLOC_INLINE size_t
-lg_floor(size_t x)
-{
- unsigned long ret;
-
- assert(x != 0);
-
-#if (LG_SIZEOF_PTR == 3)
- _BitScanReverse64(&ret, x);
-#elif (LG_SIZEOF_PTR == 2)
- _BitScanReverse(&ret, x);
+# define likely(x) __builtin_expect(!!(x), 1)
+# define unlikely(x) __builtin_expect(!!(x), 0)
#else
-# error "Unsupported type sizes for lg_floor()"
+# define likely(x) !!(x)
+# define unlikely(x) !!(x)
#endif
- return (ret);
-}
-#elif (defined(JEMALLOC_HAVE_BUILTIN_CLZ))
-JEMALLOC_INLINE size_t
-lg_floor(size_t x)
-{
-
- assert(x != 0);
-#if (LG_SIZEOF_PTR == LG_SIZEOF_INT)
- return (((8 << LG_SIZEOF_PTR) - 1) - __builtin_clz(x));
-#elif (LG_SIZEOF_PTR == LG_SIZEOF_LONG)
- return (((8 << LG_SIZEOF_PTR) - 1) - __builtin_clzl(x));
-#else
-# error "Unsupported type sizes for lg_floor()"
+#if !defined(JEMALLOC_INTERNAL_UNREACHABLE)
+# error JEMALLOC_INTERNAL_UNREACHABLE should have been defined by configure
#endif
-}
-#else
-JEMALLOC_INLINE size_t
-lg_floor(size_t x)
-{
- assert(x != 0);
-
- x |= (x >> 1);
- x |= (x >> 2);
- x |= (x >> 4);
- x |= (x >> 8);
- x |= (x >> 16);
-#if (LG_SIZEOF_PTR == 3 && LG_SIZEOF_PTR == LG_SIZEOF_LONG)
- x |= (x >> 32);
- if (x == KZU(0xffffffffffffffff))
- return (63);
- x++;
- return (jemalloc_ffsl(x) - 2);
-#elif (LG_SIZEOF_PTR == 2)
- if (x == KZU(0xffffffff))
- return (31);
- x++;
- return (jemalloc_ffs(x) - 2);
-#else
-# error "Unsupported type sizes for lg_floor()"
-#endif
-}
-#endif
+#define unreachable() JEMALLOC_INTERNAL_UNREACHABLE()
/* Set error code. */
-JEMALLOC_INLINE void
-set_errno(int errnum)
-{
-
+UTIL_INLINE void
+set_errno(int errnum) {
#ifdef _WIN32
SetLastError(errnum);
#else
@@ -298,17 +53,15 @@ set_errno(int errnum)
}
/* Get last error code. */
-JEMALLOC_INLINE int
-get_errno(void)
-{
-
+UTIL_INLINE int
+get_errno(void) {
#ifdef _WIN32
- return (GetLastError());
+ return GetLastError();
#else
- return (errno);
+ return errno;
#endif
}
-#endif
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
+#undef UTIL_INLINE
+
+#endif /* JEMALLOC_INTERNAL_UTIL_H */
diff --git a/deps/jemalloc/include/jemalloc/internal/valgrind.h b/deps/jemalloc/include/jemalloc/internal/valgrind.h
deleted file mode 100644
index a3380df92..000000000
--- a/deps/jemalloc/include/jemalloc/internal/valgrind.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/******************************************************************************/
-#ifdef JEMALLOC_H_TYPES
-
-#ifdef JEMALLOC_VALGRIND
-#include <valgrind/valgrind.h>
-
-/*
- * The size that is reported to Valgrind must be consistent through a chain of
- * malloc..realloc..realloc calls. Request size isn't recorded anywhere in
- * jemalloc, so it is critical that all callers of these macros provide usize
- * rather than request size. As a result, buffer overflow detection is
- * technically weakened for the standard API, though it is generally accepted
- * practice to consider any extra bytes reported by malloc_usable_size() as
- * usable space.
- */
-#define JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(ptr, usize) do { \
- if (unlikely(in_valgrind)) \
- valgrind_make_mem_noaccess(ptr, usize); \
-} while (0)
-#define JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ptr, usize) do { \
- if (unlikely(in_valgrind)) \
- valgrind_make_mem_undefined(ptr, usize); \
-} while (0)
-#define JEMALLOC_VALGRIND_MAKE_MEM_DEFINED(ptr, usize) do { \
- if (unlikely(in_valgrind)) \
- valgrind_make_mem_defined(ptr, usize); \
-} while (0)
-/*
- * The VALGRIND_MALLOCLIKE_BLOCK() and VALGRIND_RESIZEINPLACE_BLOCK() macro
- * calls must be embedded in macros rather than in functions so that when
- * Valgrind reports errors, there are no extra stack frames in the backtraces.
- */
-#define JEMALLOC_VALGRIND_MALLOC(cond, ptr, usize, zero) do { \
- if (unlikely(in_valgrind && cond)) \
- VALGRIND_MALLOCLIKE_BLOCK(ptr, usize, p2rz(ptr), zero); \
-} while (0)
-#define JEMALLOC_VALGRIND_REALLOC(maybe_moved, ptr, usize, \
- ptr_maybe_null, old_ptr, old_usize, old_rzsize, old_ptr_maybe_null, \
- zero) do { \
- if (unlikely(in_valgrind)) { \
- size_t rzsize = p2rz(ptr); \
- \
- if (!maybe_moved || ptr == old_ptr) { \
- VALGRIND_RESIZEINPLACE_BLOCK(ptr, old_usize, \
- usize, rzsize); \
- if (zero && old_usize < usize) { \
- valgrind_make_mem_defined( \
- (void *)((uintptr_t)ptr + \
- old_usize), usize - old_usize); \
- } \
- } else { \
- if (!old_ptr_maybe_null || old_ptr != NULL) { \
- valgrind_freelike_block(old_ptr, \
- old_rzsize); \
- } \
- if (!ptr_maybe_null || ptr != NULL) { \
- size_t copy_size = (old_usize < usize) \
- ? old_usize : usize; \
- size_t tail_size = usize - copy_size; \
- VALGRIND_MALLOCLIKE_BLOCK(ptr, usize, \
- rzsize, false); \
- if (copy_size > 0) { \
- valgrind_make_mem_defined(ptr, \
- copy_size); \
- } \
- if (zero && tail_size > 0) { \
- valgrind_make_mem_defined( \
- (void *)((uintptr_t)ptr + \
- copy_size), tail_size); \
- } \
- } \
- } \
- } \
-} while (0)
-#define JEMALLOC_VALGRIND_FREE(ptr, rzsize) do { \
- if (unlikely(in_valgrind)) \
- valgrind_freelike_block(ptr, rzsize); \
-} while (0)
-#else
-#define RUNNING_ON_VALGRIND ((unsigned)0)
-#define JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(ptr, usize) do {} while (0)
-#define JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ptr, usize) do {} while (0)
-#define JEMALLOC_VALGRIND_MAKE_MEM_DEFINED(ptr, usize) do {} while (0)
-#define JEMALLOC_VALGRIND_MALLOC(cond, ptr, usize, zero) do {} while (0)
-#define JEMALLOC_VALGRIND_REALLOC(maybe_moved, ptr, usize, \
- ptr_maybe_null, old_ptr, old_usize, old_rzsize, old_ptr_maybe_null, \
- zero) do {} while (0)
-#define JEMALLOC_VALGRIND_FREE(ptr, rzsize) do {} while (0)
-#endif
-
-#endif /* JEMALLOC_H_TYPES */
-/******************************************************************************/
-#ifdef JEMALLOC_H_STRUCTS
-
-#endif /* JEMALLOC_H_STRUCTS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_EXTERNS
-
-#ifdef JEMALLOC_VALGRIND
-void valgrind_make_mem_noaccess(void *ptr, size_t usize);
-void valgrind_make_mem_undefined(void *ptr, size_t usize);
-void valgrind_make_mem_defined(void *ptr, size_t usize);
-void valgrind_freelike_block(void *ptr, size_t usize);
-#endif
-
-#endif /* JEMALLOC_H_EXTERNS */
-/******************************************************************************/
-#ifdef JEMALLOC_H_INLINES
-
-#endif /* JEMALLOC_H_INLINES */
-/******************************************************************************/
-
diff --git a/deps/jemalloc/include/jemalloc/internal/witness.h b/deps/jemalloc/include/jemalloc/internal/witness.h
new file mode 100644
index 000000000..7ace8ae4a
--- /dev/null
+++ b/deps/jemalloc/include/jemalloc/internal/witness.h
@@ -0,0 +1,346 @@
+#ifndef JEMALLOC_INTERNAL_WITNESS_H
+#define JEMALLOC_INTERNAL_WITNESS_H
+
+#include "jemalloc/internal/ql.h"
+
+/******************************************************************************/
+/* LOCK RANKS */
+/******************************************************************************/
+
+/*
+ * Witnesses with rank WITNESS_RANK_OMIT are completely ignored by the witness
+ * machinery.
+ */
+
+#define WITNESS_RANK_OMIT 0U
+
+#define WITNESS_RANK_MIN 1U
+
+#define WITNESS_RANK_INIT 1U
+#define WITNESS_RANK_CTL 1U
+#define WITNESS_RANK_TCACHES 2U
+#define WITNESS_RANK_ARENAS 3U
+
+#define WITNESS_RANK_BACKGROUND_THREAD_GLOBAL 4U
+
+#define WITNESS_RANK_PROF_DUMP 5U
+#define WITNESS_RANK_PROF_BT2GCTX 6U
+#define WITNESS_RANK_PROF_TDATAS 7U
+#define WITNESS_RANK_PROF_TDATA 8U
+#define WITNESS_RANK_PROF_GCTX 9U
+
+#define WITNESS_RANK_BACKGROUND_THREAD 10U
+
+/*
+ * Used as an argument to witness_assert_depth_to_rank() in order to validate
+ * depth excluding non-core locks with lower ranks. Since the rank argument to
+ * witness_assert_depth_to_rank() is inclusive rather than exclusive, this
+ * definition can have the same value as the minimally ranked core lock.
+ */
+#define WITNESS_RANK_CORE 11U
+
+#define WITNESS_RANK_DECAY 11U
+#define WITNESS_RANK_TCACHE_QL 12U
+#define WITNESS_RANK_EXTENT_GROW 13U
+#define WITNESS_RANK_EXTENTS 14U
+#define WITNESS_RANK_EXTENT_AVAIL 15U
+
+#define WITNESS_RANK_EXTENT_POOL 16U
+#define WITNESS_RANK_RTREE 17U
+#define WITNESS_RANK_BASE 18U
+#define WITNESS_RANK_ARENA_LARGE 19U
+
+#define WITNESS_RANK_LEAF 0xffffffffU
+#define WITNESS_RANK_BIN WITNESS_RANK_LEAF
+#define WITNESS_RANK_ARENA_STATS WITNESS_RANK_LEAF
+#define WITNESS_RANK_DSS WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_ACTIVE WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_ACCUM WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_DUMP_SEQ WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_GDUMP WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_NEXT_THR_UID WITNESS_RANK_LEAF
+#define WITNESS_RANK_PROF_THREAD_ACTIVE_INIT WITNESS_RANK_LEAF
+
+/******************************************************************************/
+/* PER-WITNESS DATA */
+/******************************************************************************/
+#if defined(JEMALLOC_DEBUG)
+# define WITNESS_INITIALIZER(name, rank) {name, rank, NULL, NULL, {NULL, NULL}}
+#else
+# define WITNESS_INITIALIZER(name, rank)
+#endif
+
+typedef struct witness_s witness_t;
+typedef unsigned witness_rank_t;
+typedef ql_head(witness_t) witness_list_t;
+typedef int witness_comp_t (const witness_t *, void *, const witness_t *,
+ void *);
+
+struct witness_s {
+ /* Name, used for printing lock order reversal messages. */
+ const char *name;
+
+ /*
+ * Witness rank, where 0 is lowest and UINT_MAX is highest. Witnesses
+ * must be acquired in order of increasing rank.
+ */
+ witness_rank_t rank;
+
+ /*
+ * If two witnesses are of equal rank and they have the samp comp
+ * function pointer, it is called as a last attempt to differentiate
+ * between witnesses of equal rank.
+ */
+ witness_comp_t *comp;
+
+ /* Opaque data, passed to comp(). */
+ void *opaque;
+
+ /* Linkage for thread's currently owned locks. */
+ ql_elm(witness_t) link;
+};
+
+/******************************************************************************/
+/* PER-THREAD DATA */
+/******************************************************************************/
+typedef struct witness_tsd_s witness_tsd_t;
+struct witness_tsd_s {
+ witness_list_t witnesses;
+ bool forking;
+};
+
+#define WITNESS_TSD_INITIALIZER { ql_head_initializer(witnesses), false }
+#define WITNESS_TSDN_NULL ((witness_tsdn_t *)0)
+
+/******************************************************************************/
+/* (PER-THREAD) NULLABILITY HELPERS */
+/******************************************************************************/
+typedef struct witness_tsdn_s witness_tsdn_t;
+struct witness_tsdn_s {
+ witness_tsd_t witness_tsd;
+};
+
+JEMALLOC_ALWAYS_INLINE witness_tsdn_t *
+witness_tsd_tsdn(witness_tsd_t *witness_tsd) {
+ return (witness_tsdn_t *)witness_tsd;
+}
+
+JEMALLOC_ALWAYS_INLINE bool
+witness_tsdn_null(witness_tsdn_t *witness_tsdn) {
+ return witness_tsdn == NULL;
+}
+
+JEMALLOC_ALWAYS_INLINE witness_tsd_t *
+witness_tsdn_tsd(witness_tsdn_t *witness_tsdn) {
+ assert(!witness_tsdn_null(witness_tsdn));
+ return &witness_tsdn->witness_tsd;
+}
+
+/******************************************************************************/
+/* API */
+/******************************************************************************/
+void witness_init(witness_t *witness, const char *name, witness_rank_t rank,
+ witness_comp_t *comp, void *opaque);
+
+typedef void (witness_lock_error_t)(const witness_list_t *, const witness_t *);
+extern witness_lock_error_t *JET_MUTABLE witness_lock_error;
+
+typedef void (witness_owner_error_t)(const witness_t *);
+extern witness_owner_error_t *JET_MUTABLE witness_owner_error;
+
+typedef void (witness_not_owner_error_t)(const witness_t *);
+extern witness_not_owner_error_t *JET_MUTABLE witness_not_owner_error;
+
+typedef void (witness_depth_error_t)(const witness_list_t *,
+ witness_rank_t rank_inclusive, unsigned depth);
+extern witness_depth_error_t *JET_MUTABLE witness_depth_error;
+
+void witnesses_cleanup(witness_tsd_t *witness_tsd);
+void witness_prefork(witness_tsd_t *witness_tsd);
+void witness_postfork_parent(witness_tsd_t *witness_tsd);
+void witness_postfork_child(witness_tsd_t *witness_tsd);
+
+/* Helper, not intended for direct use. */
+static inline bool
+witness_owner(witness_tsd_t *witness_tsd, const witness_t *witness) {
+ witness_list_t *witnesses;
+ witness_t *w;
+
+ cassert(config_debug);
+
+ witnesses = &witness_tsd->witnesses;
+ ql_foreach(w, witnesses, link) {
+ if (w == witness) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static inline void
+witness_assert_owner(witness_tsdn_t *witness_tsdn, const witness_t *witness) {
+ witness_tsd_t *witness_tsd;
+
+ if (!config_debug) {
+ return;
+ }
+
+ if (witness_tsdn_null(witness_tsdn)) {
+ return;
+ }
+ witness_tsd = witness_tsdn_tsd(witness_tsdn);
+ if (witness->rank == WITNESS_RANK_OMIT) {
+ return;
+ }
+
+ if (witness_owner(witness_tsd, witness)) {
+ return;
+ }
+ witness_owner_error(witness);
+}
+
+static inline void
+witness_assert_not_owner(witness_tsdn_t *witness_tsdn,
+ const witness_t *witness) {
+ witness_tsd_t *witness_tsd;
+ witness_list_t *witnesses;
+ witness_t *w;
+
+ if (!config_debug) {
+ return;
+ }
+
+ if (witness_tsdn_null(witness_tsdn)) {
+ return;
+ }
+ witness_tsd = witness_tsdn_tsd(witness_tsdn);
+ if (witness->rank == WITNESS_RANK_OMIT) {
+ return;
+ }
+
+ witnesses = &witness_tsd->witnesses;
+ ql_foreach(w, witnesses, link) {
+ if (w == witness) {
+ witness_not_owner_error(witness);
+ }
+ }
+}
+
+static inline void
+witness_assert_depth_to_rank(witness_tsdn_t *witness_tsdn,
+ witness_rank_t rank_inclusive, unsigned depth) {
+ witness_tsd_t *witness_tsd;
+ unsigned d;
+ witness_list_t *witnesses;
+ witness_t *w;
+
+ if (!config_debug) {
+ return;
+ }
+
+ if (witness_tsdn_null(witness_tsdn)) {
+ return;
+ }
+ witness_tsd = witness_tsdn_tsd(witness_tsdn);
+
+ d = 0;
+ witnesses = &witness_tsd->witnesses;
+ w = ql_last(witnesses, link);
+ if (w != NULL) {
+ ql_reverse_foreach(w, witnesses, link) {
+ if (w->rank < rank_inclusive) {
+ break;
+ }
+ d++;
+ }
+ }
+ if (d != depth) {
+ witness_depth_error(witnesses, rank_inclusive, depth);
+ }
+}
+
+static inline void
+witness_assert_depth(witness_tsdn_t *witness_tsdn, unsigned depth) {
+ witness_assert_depth_to_rank(witness_tsdn, WITNESS_RANK_MIN, depth);
+}
+
+static inline void
+witness_assert_lockless(witness_tsdn_t *witness_tsdn) {
+ witness_assert_depth(witness_tsdn, 0);
+}
+
+static inline void
+witness_lock(witness_tsdn_t *witness_tsdn, witness_t *witness) {
+ witness_tsd_t *witness_tsd;
+ witness_list_t *witnesses;
+ witness_t *w;
+
+ if (!config_debug) {
+ return;
+ }
+
+ if (witness_tsdn_null(witness_tsdn)) {
+ return;
+ }
+ witness_tsd = witness_tsdn_tsd(witness_tsdn);
+ if (witness->rank == WITNESS_RANK_OMIT) {
+ return;
+ }
+
+ witness_assert_not_owner(witness_tsdn, witness);
+
+ witnesses = &witness_tsd->witnesses;
+ w = ql_last(witnesses, link);
+ if (w == NULL) {
+ /* No other locks; do nothing. */
+ } else if (witness_tsd->forking && w->rank <= witness->rank) {
+ /* Forking, and relaxed ranking satisfied. */
+ } else if (w->rank > witness->rank) {
+ /* Not forking, rank order reversal. */
+ witness_lock_error(witnesses, witness);
+ } else if (w->rank == witness->rank && (w->comp == NULL || w->comp !=
+ witness->comp || w->comp(w, w->opaque, witness, witness->opaque) >
+ 0)) {
+ /*
+ * Missing/incompatible comparison function, or comparison
+ * function indicates rank order reversal.
+ */
+ witness_lock_error(witnesses, witness);
+ }
+
+ ql_elm_new(witness, link);
+ ql_tail_insert(witnesses, witness, link);
+}
+
+static inline void
+witness_unlock(witness_tsdn_t *witness_tsdn, witness_t *witness) {
+ witness_tsd_t *witness_tsd;
+ witness_list_t *witnesses;
+
+ if (!config_debug) {
+ return;
+ }
+
+ if (witness_tsdn_null(witness_tsdn)) {
+ return;
+ }
+ witness_tsd = witness_tsdn_tsd(witness_tsdn);
+ if (witness->rank == WITNESS_RANK_OMIT) {
+ return;
+ }
+
+ /*
+ * Check whether owner before removal, rather than relying on
+ * witness_assert_owner() to abort, so that unit tests can test this
+ * function's failure mode without causing undefined behavior.
+ */
+ if (witness_owner(witness_tsd, witness)) {
+ witnesses = &witness_tsd->witnesses;
+ ql_remove(witnesses, witness, link);
+ } else {
+ witness_assert_owner(witness_tsdn, witness);
+ }
+}
+
+#endif /* JEMALLOC_INTERNAL_WITNESS_H */
diff --git a/deps/jemalloc/include/jemalloc/jemalloc.sh b/deps/jemalloc/include/jemalloc/jemalloc.sh
index c085814f2..b19b1548b 100755
--- a/deps/jemalloc/include/jemalloc/jemalloc.sh
+++ b/deps/jemalloc/include/jemalloc/jemalloc.sh
@@ -4,7 +4,7 @@ objroot=$1
cat <<EOF
#ifndef JEMALLOC_H_
-#define JEMALLOC_H_
+#define JEMALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
@@ -15,7 +15,6 @@ for hdr in jemalloc_defs.h jemalloc_rename.h jemalloc_macros.h \
jemalloc_protos.h jemalloc_typedefs.h jemalloc_mangle.h ; do
cat "${objroot}include/jemalloc/${hdr}" \
| grep -v 'Generated from .* by configure\.' \
- | sed -e 's/^#define /#define /g' \
| sed -e 's/ $//g'
echo
done
diff --git a/deps/jemalloc/include/jemalloc/jemalloc_defs.h.in b/deps/jemalloc/include/jemalloc/jemalloc_defs.h.in
index ab13c3758..6d89435c2 100644
--- a/deps/jemalloc/include/jemalloc/jemalloc_defs.h.in
+++ b/deps/jemalloc/include/jemalloc/jemalloc_defs.h.in
@@ -33,5 +33,13 @@
*/
#undef JEMALLOC_USE_CXX_THROW
+#ifdef _MSC_VER
+# ifdef _WIN64
+# define LG_SIZEOF_PTR_WIN 3
+# else
+# define LG_SIZEOF_PTR_WIN 2
+# endif
+#endif
+
/* sizeof(void *) == 2^LG_SIZEOF_PTR. */
#undef LG_SIZEOF_PTR
diff --git a/deps/jemalloc/include/jemalloc/jemalloc_macros.h.in b/deps/jemalloc/include/jemalloc/jemalloc_macros.h.in
index a7028db34..daf9e571b 100644
--- a/deps/jemalloc/include/jemalloc/jemalloc_macros.h.in
+++ b/deps/jemalloc/include/jemalloc/jemalloc_macros.h.in
@@ -4,31 +4,51 @@
#include <limits.h>
#include <strings.h>
-#define JEMALLOC_VERSION "@jemalloc_version@"
-#define JEMALLOC_VERSION_MAJOR @jemalloc_version_major@
-#define JEMALLOC_VERSION_MINOR @jemalloc_version_minor@
-#define JEMALLOC_VERSION_BUGFIX @jemalloc_version_bugfix@
-#define JEMALLOC_VERSION_NREV @jemalloc_version_nrev@
-#define JEMALLOC_VERSION_GID "@jemalloc_version_gid@"
+#define JEMALLOC_VERSION "@jemalloc_version@"
+#define JEMALLOC_VERSION_MAJOR @jemalloc_version_major@
+#define JEMALLOC_VERSION_MINOR @jemalloc_version_minor@
+#define JEMALLOC_VERSION_BUGFIX @jemalloc_version_bugfix@
+#define JEMALLOC_VERSION_NREV @jemalloc_version_nrev@
+#define JEMALLOC_VERSION_GID "@jemalloc_version_gid@"
-# define MALLOCX_LG_ALIGN(la) (la)
-# if LG_SIZEOF_PTR == 2
-# define MALLOCX_ALIGN(a) (ffs(a)-1)
-# else
-# define MALLOCX_ALIGN(a) \
- ((a < (size_t)INT_MAX) ? ffs(a)-1 : ffs(a>>32)+31)
-# endif
-# define MALLOCX_ZERO ((int)0x40)
+#define MALLOCX_LG_ALIGN(la) ((int)(la))
+#if LG_SIZEOF_PTR == 2
+# define MALLOCX_ALIGN(a) ((int)(ffs((int)(a))-1))
+#else
+# define MALLOCX_ALIGN(a) \
+ ((int)(((size_t)(a) < (size_t)INT_MAX) ? ffs((int)(a))-1 : \
+ ffs((int)(((size_t)(a))>>32))+31))
+#endif
+#define MALLOCX_ZERO ((int)0x40)
/*
* Bias tcache index bits so that 0 encodes "automatic tcache management", and 1
* encodes MALLOCX_TCACHE_NONE.
*/
-# define MALLOCX_TCACHE(tc) ((int)(((tc)+2) << 8))
-# define MALLOCX_TCACHE_NONE MALLOCX_TCACHE(-1)
+#define MALLOCX_TCACHE(tc) ((int)(((tc)+2) << 8))
+#define MALLOCX_TCACHE_NONE MALLOCX_TCACHE(-1)
/*
* Bias arena index bits so that 0 encodes "use an automatically chosen arena".
*/
-# define MALLOCX_ARENA(a) ((int)(((a)+1) << 20))
+#define MALLOCX_ARENA(a) ((((int)(a))+1) << 20)
+
+/*
+ * Use as arena index in "arena.<i>.{purge,decay,dss}" and
+ * "stats.arenas.<i>.*" mallctl interfaces to select all arenas. This
+ * definition is intentionally specified in raw decimal format to support
+ * cpp-based string concatenation, e.g.
+ *
+ * #define STRINGIFY_HELPER(x) #x
+ * #define STRINGIFY(x) STRINGIFY_HELPER(x)
+ *
+ * mallctl("arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".purge", NULL, NULL, NULL,
+ * 0);
+ */
+#define MALLCTL_ARENAS_ALL 4096
+/*
+ * Use as arena index in "stats.arenas.<i>.*" mallctl interfaces to select
+ * destroyed arenas.
+ */
+#define MALLCTL_ARENAS_DESTROYED 4097
#if defined(__cplusplus) && defined(JEMALLOC_USE_CXX_THROW)
# define JEMALLOC_CXX_THROW throw()
@@ -36,32 +56,7 @@
# define JEMALLOC_CXX_THROW
#endif
-#ifdef JEMALLOC_HAVE_ATTR
-# define JEMALLOC_ATTR(s) __attribute__((s))
-# define JEMALLOC_ALIGNED(s) JEMALLOC_ATTR(aligned(s))
-# ifdef JEMALLOC_HAVE_ATTR_ALLOC_SIZE
-# define JEMALLOC_ALLOC_SIZE(s) JEMALLOC_ATTR(alloc_size(s))
-# define JEMALLOC_ALLOC_SIZE2(s1, s2) JEMALLOC_ATTR(alloc_size(s1, s2))
-# else
-# define JEMALLOC_ALLOC_SIZE(s)
-# define JEMALLOC_ALLOC_SIZE2(s1, s2)
-# endif
-# ifndef JEMALLOC_EXPORT
-# define JEMALLOC_EXPORT JEMALLOC_ATTR(visibility("default"))
-# endif
-# ifdef JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF
-# define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(gnu_printf, s, i))
-# elif defined(JEMALLOC_HAVE_ATTR_FORMAT_PRINTF)
-# define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(printf, s, i))
-# else
-# define JEMALLOC_FORMAT_PRINTF(s, i)
-# endif
-# define JEMALLOC_NOINLINE JEMALLOC_ATTR(noinline)
-# define JEMALLOC_NOTHROW JEMALLOC_ATTR(nothrow)
-# define JEMALLOC_SECTION(s) JEMALLOC_ATTR(section(s))
-# define JEMALLOC_RESTRICT_RETURN
-# define JEMALLOC_ALLOCATOR
-#elif _MSC_VER
+#if defined(_MSC_VER)
# define JEMALLOC_ATTR(s)
# define JEMALLOC_ALIGNED(s) __declspec(align(s))
# define JEMALLOC_ALLOC_SIZE(s)
@@ -87,6 +82,31 @@
# else
# define JEMALLOC_ALLOCATOR
# endif
+#elif defined(JEMALLOC_HAVE_ATTR)
+# define JEMALLOC_ATTR(s) __attribute__((s))
+# define JEMALLOC_ALIGNED(s) JEMALLOC_ATTR(aligned(s))
+# ifdef JEMALLOC_HAVE_ATTR_ALLOC_SIZE
+# define JEMALLOC_ALLOC_SIZE(s) JEMALLOC_ATTR(alloc_size(s))
+# define JEMALLOC_ALLOC_SIZE2(s1, s2) JEMALLOC_ATTR(alloc_size(s1, s2))
+# else
+# define JEMALLOC_ALLOC_SIZE(s)
+# define JEMALLOC_ALLOC_SIZE2(s1, s2)
+# endif
+# ifndef JEMALLOC_EXPORT
+# define JEMALLOC_EXPORT JEMALLOC_ATTR(visibility("default"))
+# endif
+# ifdef JEMALLOC_HAVE_ATTR_FORMAT_GNU_PRINTF
+# define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(gnu_printf, s, i))
+# elif defined(JEMALLOC_HAVE_ATTR_FORMAT_PRINTF)
+# define JEMALLOC_FORMAT_PRINTF(s, i) JEMALLOC_ATTR(format(printf, s, i))
+# else
+# define JEMALLOC_FORMAT_PRINTF(s, i)
+# endif
+# define JEMALLOC_NOINLINE JEMALLOC_ATTR(noinline)
+# define JEMALLOC_NOTHROW JEMALLOC_ATTR(nothrow)
+# define JEMALLOC_SECTION(s) JEMALLOC_ATTR(section(s))
+# define JEMALLOC_RESTRICT_RETURN
+# define JEMALLOC_ALLOCATOR
#else
# define JEMALLOC_ATTR(s)
# define JEMALLOC_ALIGNED(s)
diff --git a/deps/jemalloc/include/jemalloc/jemalloc_mangle.sh b/deps/jemalloc/include/jemalloc/jemalloc_mangle.sh
index df328b78d..c675bb469 100755
--- a/deps/jemalloc/include/jemalloc/jemalloc_mangle.sh
+++ b/deps/jemalloc/include/jemalloc/jemalloc_mangle.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/sh -eu
public_symbols_txt=$1
symbol_prefix=$2
diff --git a/deps/jemalloc/include/jemalloc/jemalloc_typedefs.h.in b/deps/jemalloc/include/jemalloc/jemalloc_typedefs.h.in
index fa7b350ad..1a5887430 100644
--- a/deps/jemalloc/include/jemalloc/jemalloc_typedefs.h.in
+++ b/deps/jemalloc/include/jemalloc/jemalloc_typedefs.h.in
@@ -1,57 +1,77 @@
+typedef struct extent_hooks_s extent_hooks_t;
+
/*
* void *
- * chunk_alloc(void *new_addr, size_t size, size_t alignment, bool *zero,
- * bool *commit, unsigned arena_ind);
+ * extent_alloc(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
+ * size_t alignment, bool *zero, bool *commit, unsigned arena_ind);
*/
-typedef void *(chunk_alloc_t)(void *, size_t, size_t, bool *, bool *, unsigned);
+typedef void *(extent_alloc_t)(extent_hooks_t *, void *, size_t, size_t, bool *,
+ bool *, unsigned);
/*
* bool
- * chunk_dalloc(void *chunk, size_t size, bool committed, unsigned arena_ind);
+ * extent_dalloc(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * bool committed, unsigned arena_ind);
*/
-typedef bool (chunk_dalloc_t)(void *, size_t, bool, unsigned);
+typedef bool (extent_dalloc_t)(extent_hooks_t *, void *, size_t, bool,
+ unsigned);
+
+/*
+ * void
+ * extent_destroy(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * bool committed, unsigned arena_ind);
+ */
+typedef void (extent_destroy_t)(extent_hooks_t *, void *, size_t, bool,
+ unsigned);
/*
* bool
- * chunk_commit(void *chunk, size_t size, size_t offset, size_t length,
- * unsigned arena_ind);
+ * extent_commit(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * size_t offset, size_t length, unsigned arena_ind);
*/
-typedef bool (chunk_commit_t)(void *, size_t, size_t, size_t, unsigned);
+typedef bool (extent_commit_t)(extent_hooks_t *, void *, size_t, size_t, size_t,
+ unsigned);
/*
* bool
- * chunk_decommit(void *chunk, size_t size, size_t offset, size_t length,
- * unsigned arena_ind);
+ * extent_decommit(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * size_t offset, size_t length, unsigned arena_ind);
*/
-typedef bool (chunk_decommit_t)(void *, size_t, size_t, size_t, unsigned);
+typedef bool (extent_decommit_t)(extent_hooks_t *, void *, size_t, size_t,
+ size_t, unsigned);
/*
* bool
- * chunk_purge(void *chunk, size_t size, size_t offset, size_t length,
- * unsigned arena_ind);
+ * extent_purge(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * size_t offset, size_t length, unsigned arena_ind);
*/
-typedef bool (chunk_purge_t)(void *, size_t, size_t, size_t, unsigned);
+typedef bool (extent_purge_t)(extent_hooks_t *, void *, size_t, size_t, size_t,
+ unsigned);
/*
* bool
- * chunk_split(void *chunk, size_t size, size_t size_a, size_t size_b,
- * bool committed, unsigned arena_ind);
+ * extent_split(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ * size_t size_a, size_t size_b, bool committed, unsigned arena_ind);
*/
-typedef bool (chunk_split_t)(void *, size_t, size_t, size_t, bool, unsigned);
+typedef bool (extent_split_t)(extent_hooks_t *, void *, size_t, size_t, size_t,
+ bool, unsigned);
/*
* bool
- * chunk_merge(void *chunk_a, size_t size_a, void *chunk_b, size_t size_b,
- * bool committed, unsigned arena_ind);
+ * extent_merge(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
+ * void *addr_b, size_t size_b, bool committed, unsigned arena_ind);
*/
-typedef bool (chunk_merge_t)(void *, size_t, void *, size_t, bool, unsigned);
-
-typedef struct {
- chunk_alloc_t *alloc;
- chunk_dalloc_t *dalloc;
- chunk_commit_t *commit;
- chunk_decommit_t *decommit;
- chunk_purge_t *purge;
- chunk_split_t *split;
- chunk_merge_t *merge;
-} chunk_hooks_t;
+typedef bool (extent_merge_t)(extent_hooks_t *, void *, size_t, void *, size_t,
+ bool, unsigned);
+
+struct extent_hooks_s {
+ extent_alloc_t *alloc;
+ extent_dalloc_t *dalloc;
+ extent_destroy_t *destroy;
+ extent_commit_t *commit;
+ extent_decommit_t *decommit;
+ extent_purge_t *purge_lazy;
+ extent_purge_t *purge_forced;
+ extent_split_t *split;
+ extent_merge_t *merge;
+};
diff --git a/deps/jemalloc/include/msvc_compat/strings.h b/deps/jemalloc/include/msvc_compat/strings.h
index f01ffdd18..996f256ce 100644
--- a/deps/jemalloc/include/msvc_compat/strings.h
+++ b/deps/jemalloc/include/msvc_compat/strings.h
@@ -6,22 +6,51 @@
#ifdef _MSC_VER
# include <intrin.h>
# pragma intrinsic(_BitScanForward)
-static __forceinline int ffsl(long x)
-{
+static __forceinline int ffsl(long x) {
unsigned long i;
- if (_BitScanForward(&i, x))
- return (i + 1);
- return (0);
+ if (_BitScanForward(&i, x)) {
+ return i + 1;
+ }
+ return 0;
}
-static __forceinline int ffs(int x)
-{
+static __forceinline int ffs(int x) {
+ return ffsl(x);
+}
+
+# ifdef _M_X64
+# pragma intrinsic(_BitScanForward64)
+# endif
- return (ffsl(x));
+static __forceinline int ffsll(unsigned __int64 x) {
+ unsigned long i;
+#ifdef _M_X64
+ if (_BitScanForward64(&i, x)) {
+ return i + 1;
+ }
+ return 0;
+#else
+// Fallback for 32-bit build where 64-bit version not available
+// assuming little endian
+ union {
+ unsigned __int64 ll;
+ unsigned long l[2];
+ } s;
+
+ s.ll = x;
+
+ if (_BitScanForward(&i, s.l[0])) {
+ return i + 1;
+ } else if(_BitScanForward(&i, s.l[1])) {
+ return i + 33;
+ }
+ return 0;
+#endif
}
#else
+# define ffsll(x) __builtin_ffsll(x)
# define ffsl(x) __builtin_ffsl(x)
# define ffs(x) __builtin_ffs(x)
#endif
diff --git a/deps/jemalloc/include/msvc_compat/windows_extra.h b/deps/jemalloc/include/msvc_compat/windows_extra.h
index 0c5e323ff..a6ebb9306 100644
--- a/deps/jemalloc/include/msvc_compat/windows_extra.h
+++ b/deps/jemalloc/include/msvc_compat/windows_extra.h
@@ -1,26 +1,6 @@
#ifndef MSVC_COMPAT_WINDOWS_EXTRA_H
-#define MSVC_COMPAT_WINDOWS_EXTRA_H
+#define MSVC_COMPAT_WINDOWS_EXTRA_H
-#ifndef ENOENT
-# define ENOENT ERROR_PATH_NOT_FOUND
-#endif
-#ifndef EINVAL
-# define EINVAL ERROR_BAD_ARGUMENTS
-#endif
-#ifndef EAGAIN
-# define EAGAIN ERROR_OUTOFMEMORY
-#endif
-#ifndef EPERM
-# define EPERM ERROR_WRITE_FAULT
-#endif
-#ifndef EFAULT
-# define EFAULT ERROR_INVALID_ADDRESS
-#endif
-#ifndef ENOMEM
-# define ENOMEM ERROR_NOT_ENOUGH_MEMORY
-#endif
-#ifndef ERANGE
-# define ERANGE ERROR_INVALID_DATA
-#endif
+#include <errno.h>
#endif /* MSVC_COMPAT_WINDOWS_EXTRA_H */
diff --git a/deps/jemalloc/jemalloc.pc.in b/deps/jemalloc/jemalloc.pc.in
index 1a3ad9b34..c428a86dc 100644
--- a/deps/jemalloc/jemalloc.pc.in
+++ b/deps/jemalloc/jemalloc.pc.in
@@ -6,7 +6,7 @@ install_suffix=@install_suffix@
Name: jemalloc
Description: A general purpose malloc(3) implementation that emphasizes fragmentation avoidance and scalable concurrency support.
-URL: http://www.canonware.com/jemalloc
-Version: @jemalloc_version@
+URL: http://jemalloc.net/
+Version: @jemalloc_version_major@.@jemalloc_version_minor@.@jemalloc_version_bugfix@_@jemalloc_version_nrev@
Cflags: -I${includedir}
Libs: -L${libdir} -ljemalloc${install_suffix}
diff --git a/deps/jemalloc/m4/ax_cxx_compile_stdcxx.m4 b/deps/jemalloc/m4/ax_cxx_compile_stdcxx.m4
new file mode 100644
index 000000000..2c18e49c5
--- /dev/null
+++ b/deps/jemalloc/m4/ax_cxx_compile_stdcxx.m4
@@ -0,0 +1,562 @@
+# ===========================================================================
+# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_CXX_COMPILE_STDCXX(VERSION, [ext|noext], [mandatory|optional])
+#
+# DESCRIPTION
+#
+# Check for baseline language coverage in the compiler for the specified
+# version of the C++ standard. If necessary, add switches to CXX and
+# CXXCPP to enable support. VERSION may be '11' (for the C++11 standard)
+# or '14' (for the C++14 standard).
+#
+# The second argument, if specified, indicates whether you insist on an
+# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g.
+# -std=c++11). If neither is specified, you get whatever works, with
+# preference for an extended mode.
+#
+# The third argument, if specified 'mandatory' or if left unspecified,
+# indicates that baseline support for the specified C++ standard is
+# required and that the macro should error out if no mode with that
+# support is found. If specified 'optional', then configuration proceeds
+# regardless, after defining HAVE_CXX${VERSION} if and only if a
+# supporting mode is found.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com>
+# Copyright (c) 2012 Zack Weinberg <zackw@panix.com>
+# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu>
+# Copyright (c) 2014, 2015 Google Inc.; contributed by Alexey Sokolov <sokolov@google.com>
+# Copyright (c) 2015 Paul Norman <penorman@mac.com>
+# Copyright (c) 2015 Moritz Klammler <moritz@klammler.eu>
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved. This file is offered as-is, without any
+# warranty.
+
+#serial 4
+
+dnl This macro is based on the code from the AX_CXX_COMPILE_STDCXX_11 macro
+dnl (serial version number 13).
+
+AC_DEFUN([AX_CXX_COMPILE_STDCXX], [dnl
+ m4_if([$1], [11], [],
+ [$1], [14], [],
+ [$1], [17], [m4_fatal([support for C++17 not yet implemented in AX_CXX_COMPILE_STDCXX])],
+ [m4_fatal([invalid first argument `$1' to AX_CXX_COMPILE_STDCXX])])dnl
+ m4_if([$2], [], [],
+ [$2], [ext], [],
+ [$2], [noext], [],
+ [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX])])dnl
+ m4_if([$3], [], [ax_cxx_compile_cxx$1_required=true],
+ [$3], [mandatory], [ax_cxx_compile_cxx$1_required=true],
+ [$3], [optional], [ax_cxx_compile_cxx$1_required=false],
+ [m4_fatal([invalid third argument `$3' to AX_CXX_COMPILE_STDCXX])])
+ AC_LANG_PUSH([C++])dnl
+ ac_success=no
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features by default,
+ ax_cv_cxx_compile_cxx$1,
+ [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [ax_cv_cxx_compile_cxx$1=yes],
+ [ax_cv_cxx_compile_cxx$1=no])])
+ if test x$ax_cv_cxx_compile_cxx$1 = xyes; then
+ ac_success=yes
+ fi
+
+ m4_if([$2], [noext], [], [dnl
+ if test x$ac_success = xno; then
+ for switch in -std=gnu++$1 -std=gnu++0x; do
+ cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+ $cachevar,
+ [ac_save_CXX="$CXX"
+ CXX="$CXX $switch"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [eval $cachevar=yes],
+ [eval $cachevar=no])
+ CXX="$ac_save_CXX"])
+ if eval test x\$$cachevar = xyes; then
+ CXX="$CXX $switch"
+ if test -n "$CXXCPP" ; then
+ CXXCPP="$CXXCPP $switch"
+ fi
+ ac_success=yes
+ break
+ fi
+ done
+ fi])
+
+ m4_if([$2], [ext], [], [dnl
+ if test x$ac_success = xno; then
+ dnl HP's aCC needs +std=c++11 according to:
+ dnl http://h21007.www2.hp.com/portal/download/files/unprot/aCxx/PDF_Release_Notes/769149-001.pdf
+ dnl Cray's crayCC needs "-h std=c++11"
+ for switch in -std=c++$1 -std=c++0x +std=c++$1 "-h std=c++$1"; do
+ cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx$1_$switch])
+ AC_CACHE_CHECK(whether $CXX supports C++$1 features with $switch,
+ $cachevar,
+ [ac_save_CXX="$CXX"
+ CXX="$CXX $switch"
+ AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_testbody_$1])],
+ [eval $cachevar=yes],
+ [eval $cachevar=no])
+ CXX="$ac_save_CXX"])
+ if eval test x\$$cachevar = xyes; then
+ CXX="$CXX $switch"
+ if test -n "$CXXCPP" ; then
+ CXXCPP="$CXXCPP $switch"
+ fi
+ ac_success=yes
+ break
+ fi
+ done
+ fi])
+ AC_LANG_POP([C++])
+ if test x$ax_cxx_compile_cxx$1_required = xtrue; then
+ if test x$ac_success = xno; then
+ AC_MSG_ERROR([*** A compiler with support for C++$1 language features is required.])
+ fi
+ fi
+ if test x$ac_success = xno; then
+ HAVE_CXX$1=0
+ AC_MSG_NOTICE([No compiler with C++$1 support was found])
+ else
+ HAVE_CXX$1=1
+ AC_DEFINE(HAVE_CXX$1,1,
+ [define if the compiler supports basic C++$1 syntax])
+ fi
+ AC_SUBST(HAVE_CXX$1)
+])
+
+
+dnl Test body for checking C++11 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_11],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+)
+
+
+dnl Test body for checking C++14 support
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_14],
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_11
+ _AX_CXX_COMPILE_STDCXX_testbody_new_in_14
+)
+
+
+dnl Tests for new features in C++11
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_11], [[
+
+// If the compiler admits that it is not ready for C++11, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201103L
+
+#error "This is not a C++11 compiler"
+
+#else
+
+namespace cxx11
+{
+
+ namespace test_static_assert
+ {
+
+ template <typename T>
+ struct check
+ {
+ static_assert(sizeof(int) <= sizeof(T), "not big enough");
+ };
+
+ }
+
+ namespace test_final_override
+ {
+
+ struct Base
+ {
+ virtual void f() {}
+ };
+
+ struct Derived : public Base
+ {
+ virtual void f() override {}
+ };
+
+ }
+
+ namespace test_double_right_angle_brackets
+ {
+
+ template < typename T >
+ struct check {};
+
+ typedef check<void> single_type;
+ typedef check<check<void>> double_type;
+ typedef check<check<check<void>>> triple_type;
+ typedef check<check<check<check<void>>>> quadruple_type;
+
+ }
+
+ namespace test_decltype
+ {
+
+ int
+ f()
+ {
+ int a = 1;
+ decltype(a) b = 2;
+ return a + b;
+ }
+
+ }
+
+ namespace test_type_deduction
+ {
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static const bool value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static const bool value = true;
+ };
+
+ template < typename T1, typename T2 >
+ auto
+ add(T1 a1, T2 a2) -> decltype(a1 + a2)
+ {
+ return a1 + a2;
+ }
+
+ int
+ test(const int c, volatile int v)
+ {
+ static_assert(is_same<int, decltype(0)>::value == true, "");
+ static_assert(is_same<int, decltype(c)>::value == false, "");
+ static_assert(is_same<int, decltype(v)>::value == false, "");
+ auto ac = c;
+ auto av = v;
+ auto sumi = ac + av + 'x';
+ auto sumf = ac + av + 1.0;
+ static_assert(is_same<int, decltype(ac)>::value == true, "");
+ static_assert(is_same<int, decltype(av)>::value == true, "");
+ static_assert(is_same<int, decltype(sumi)>::value == true, "");
+ static_assert(is_same<int, decltype(sumf)>::value == false, "");
+ static_assert(is_same<int, decltype(add(c, v))>::value == true, "");
+ return (sumf > 0.0) ? sumi : add(c, v);
+ }
+
+ }
+
+ namespace test_noexcept
+ {
+
+ int f() { return 0; }
+ int g() noexcept { return 0; }
+
+ static_assert(noexcept(f()) == false, "");
+ static_assert(noexcept(g()) == true, "");
+
+ }
+
+ namespace test_constexpr
+ {
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c_r(const CharT *const s, const unsigned long acc) noexcept
+ {
+ return *s ? strlen_c_r(s + 1, acc + 1) : acc;
+ }
+
+ template < typename CharT >
+ unsigned long constexpr
+ strlen_c(const CharT *const s) noexcept
+ {
+ return strlen_c_r(s, 0UL);
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("1") == 1UL, "");
+ static_assert(strlen_c("example") == 7UL, "");
+ static_assert(strlen_c("another\0example") == 7UL, "");
+
+ }
+
+ namespace test_rvalue_references
+ {
+
+ template < int N >
+ struct answer
+ {
+ static constexpr int value = N;
+ };
+
+ answer<1> f(int&) { return answer<1>(); }
+ answer<2> f(const int&) { return answer<2>(); }
+ answer<3> f(int&&) { return answer<3>(); }
+
+ void
+ test()
+ {
+ int i = 0;
+ const int c = 0;
+ static_assert(decltype(f(i))::value == 1, "");
+ static_assert(decltype(f(c))::value == 2, "");
+ static_assert(decltype(f(0))::value == 3, "");
+ }
+
+ }
+
+ namespace test_uniform_initialization
+ {
+
+ struct test
+ {
+ static const int zero {};
+ static const int one {1};
+ };
+
+ static_assert(test::zero == 0, "");
+ static_assert(test::one == 1, "");
+
+ }
+
+ namespace test_lambdas
+ {
+
+ void
+ test1()
+ {
+ auto lambda1 = [](){};
+ auto lambda2 = lambda1;
+ lambda1();
+ lambda2();
+ }
+
+ int
+ test2()
+ {
+ auto a = [](int i, int j){ return i + j; }(1, 2);
+ auto b = []() -> int { return '0'; }();
+ auto c = [=](){ return a + b; }();
+ auto d = [&](){ return c; }();
+ auto e = [a, &b](int x) mutable {
+ const auto identity = [](int y){ return y; };
+ for (auto i = 0; i < a; ++i)
+ a += b--;
+ return x + identity(a + b);
+ }(0);
+ return a + b + c + d + e;
+ }
+
+ int
+ test3()
+ {
+ const auto nullary = [](){ return 0; };
+ const auto unary = [](int x){ return x; };
+ using nullary_t = decltype(nullary);
+ using unary_t = decltype(unary);
+ const auto higher1st = [](nullary_t f){ return f(); };
+ const auto higher2nd = [unary](nullary_t f1){
+ return [unary, f1](unary_t f2){ return f2(unary(f1())); };
+ };
+ return higher1st(nullary) + higher2nd(nullary)(unary);
+ }
+
+ }
+
+ namespace test_variadic_templates
+ {
+
+ template <int...>
+ struct sum;
+
+ template <int N0, int... N1toN>
+ struct sum<N0, N1toN...>
+ {
+ static constexpr auto value = N0 + sum<N1toN...>::value;
+ };
+
+ template <>
+ struct sum<>
+ {
+ static constexpr auto value = 0;
+ };
+
+ static_assert(sum<>::value == 0, "");
+ static_assert(sum<1>::value == 1, "");
+ static_assert(sum<23>::value == 23, "");
+ static_assert(sum<1, 2>::value == 3, "");
+ static_assert(sum<5, 5, 11>::value == 21, "");
+ static_assert(sum<2, 3, 5, 7, 11, 13>::value == 41, "");
+
+ }
+
+ // http://stackoverflow.com/questions/13728184/template-aliases-and-sfinae
+ // Clang 3.1 fails with headers of libstd++ 4.8.3 when using std::function
+ // because of this.
+ namespace test_template_alias_sfinae
+ {
+
+ struct foo {};
+
+ template<typename T>
+ using member = typename T::member_type;
+
+ template<typename T>
+ void func(...) {}
+
+ template<typename T>
+ void func(member<T>*) {}
+
+ void test();
+
+ void test() { func<foo>(0); }
+
+ }
+
+} // namespace cxx11
+
+#endif // __cplusplus >= 201103L
+
+]])
+
+
+dnl Tests for new features in C++14
+
+m4_define([_AX_CXX_COMPILE_STDCXX_testbody_new_in_14], [[
+
+// If the compiler admits that it is not ready for C++14, why torture it?
+// Hopefully, this will speed up the test.
+
+#ifndef __cplusplus
+
+#error "This is not a C++ compiler"
+
+#elif __cplusplus < 201402L
+
+#error "This is not a C++14 compiler"
+
+#else
+
+namespace cxx14
+{
+
+ namespace test_polymorphic_lambdas
+ {
+
+ int
+ test()
+ {
+ const auto lambda = [](auto&&... args){
+ const auto istiny = [](auto x){
+ return (sizeof(x) == 1UL) ? 1 : 0;
+ };
+ const int aretiny[] = { istiny(args)... };
+ return aretiny[0];
+ };
+ return lambda(1, 1L, 1.0f, '1');
+ }
+
+ }
+
+ namespace test_binary_literals
+ {
+
+ constexpr auto ivii = 0b0000000000101010;
+ static_assert(ivii == 42, "wrong value");
+
+ }
+
+ namespace test_generalized_constexpr
+ {
+
+ template < typename CharT >
+ constexpr unsigned long
+ strlen_c(const CharT *const s) noexcept
+ {
+ auto length = 0UL;
+ for (auto p = s; *p; ++p)
+ ++length;
+ return length;
+ }
+
+ static_assert(strlen_c("") == 0UL, "");
+ static_assert(strlen_c("x") == 1UL, "");
+ static_assert(strlen_c("test") == 4UL, "");
+ static_assert(strlen_c("another\0test") == 7UL, "");
+
+ }
+
+ namespace test_lambda_init_capture
+ {
+
+ int
+ test()
+ {
+ auto x = 0;
+ const auto lambda1 = [a = x](int b){ return a + b; };
+ const auto lambda2 = [a = lambda1(x)](){ return a; };
+ return lambda2();
+ }
+
+ }
+
+ namespace test_digit_seperators
+ {
+
+ constexpr auto ten_million = 100'000'000;
+ static_assert(ten_million == 100000000, "");
+
+ }
+
+ namespace test_return_type_deduction
+ {
+
+ auto f(int& x) { return x; }
+ decltype(auto) g(int& x) { return x; }
+
+ template < typename T1, typename T2 >
+ struct is_same
+ {
+ static constexpr auto value = false;
+ };
+
+ template < typename T >
+ struct is_same<T, T>
+ {
+ static constexpr auto value = true;
+ };
+
+ int
+ test()
+ {
+ auto x = 0;
+ static_assert(is_same<int, decltype(f(x))>::value, "");
+ static_assert(is_same<int&, decltype(g(x))>::value, "");
+ return x;
+ }
+
+ }
+
+} // namespace cxx14
+
+#endif // __cplusplus >= 201402L
+
+]])
diff --git a/deps/jemalloc/msvc/ReadMe.txt b/deps/jemalloc/msvc/ReadMe.txt
new file mode 100644
index 000000000..633a7d49f
--- /dev/null
+++ b/deps/jemalloc/msvc/ReadMe.txt
@@ -0,0 +1,23 @@
+
+How to build jemalloc for Windows
+=================================
+
+1. Install Cygwin with at least the following packages:
+ * autoconf
+ * autogen
+ * gawk
+ * grep
+ * sed
+
+2. Install Visual Studio 2015 or 2017 with Visual C++
+
+3. Add Cygwin\bin to the PATH environment variable
+
+4. Open "x64 Native Tools Command Prompt for VS 2017"
+ (note: x86/x64 doesn't matter at this point)
+
+5. Generate header files:
+ sh -c "CC=cl ./autogen.sh"
+
+6. Now the project can be opened and built in Visual Studio:
+ msvc\jemalloc_vc2017.sln
diff --git a/deps/jemalloc/msvc/jemalloc_vc2015.sln b/deps/jemalloc/msvc/jemalloc_vc2015.sln
new file mode 100644
index 000000000..aedd5e5ea
--- /dev/null
+++ b/deps/jemalloc/msvc/jemalloc_vc2015.sln
@@ -0,0 +1,63 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70A99006-6DE9-472B-8F83-4CEE6C616DF3}"
+ ProjectSection(SolutionItems) = preProject
+ ReadMe.txt = ReadMe.txt
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jemalloc", "projects\vc2015\jemalloc\jemalloc.vcxproj", "{8D6BB292-9E1C-413D-9F98-4864BDC1514A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_threads", "projects\vc2015\test_threads\test_threads.vcxproj", "{09028CFD-4EB7-491D-869C-0708DB97ED44}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Debug-static|x64 = Debug-static|x64
+ Debug-static|x86 = Debug-static|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ Release-static|x64 = Release-static|x64
+ Release-static|x86 = Release-static|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.ActiveCfg = Debug|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.Build.0 = Debug|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.ActiveCfg = Debug|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.Build.0 = Debug|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.ActiveCfg = Debug-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.Build.0 = Debug-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.ActiveCfg = Debug-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.Build.0 = Debug-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.ActiveCfg = Release|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.Build.0 = Release|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.ActiveCfg = Release|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.Build.0 = Release|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.ActiveCfg = Release-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.Build.0 = Release-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.ActiveCfg = Release-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.Build.0 = Release-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.ActiveCfg = Debug|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.Build.0 = Debug|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.ActiveCfg = Debug|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.Build.0 = Debug|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.ActiveCfg = Debug-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.Build.0 = Debug-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.ActiveCfg = Debug-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.Build.0 = Debug-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.ActiveCfg = Release|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.Build.0 = Release|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.ActiveCfg = Release|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.Build.0 = Release|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.ActiveCfg = Release-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.Build.0 = Release-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.ActiveCfg = Release-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.Build.0 = Release-static|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/deps/jemalloc/msvc/jemalloc_vc2017.sln b/deps/jemalloc/msvc/jemalloc_vc2017.sln
new file mode 100644
index 000000000..c22fcb437
--- /dev/null
+++ b/deps/jemalloc/msvc/jemalloc_vc2017.sln
@@ -0,0 +1,63 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 14
+VisualStudioVersion = 14.0.24720.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70A99006-6DE9-472B-8F83-4CEE6C616DF3}"
+ ProjectSection(SolutionItems) = preProject
+ ReadMe.txt = ReadMe.txt
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jemalloc", "projects\vc2017\jemalloc\jemalloc.vcxproj", "{8D6BB292-9E1C-413D-9F98-4864BDC1514A}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_threads", "projects\vc2017\test_threads\test_threads.vcxproj", "{09028CFD-4EB7-491D-869C-0708DB97ED44}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Debug-static|x64 = Debug-static|x64
+ Debug-static|x86 = Debug-static|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ Release-static|x64 = Release-static|x64
+ Release-static|x86 = Release-static|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.ActiveCfg = Debug|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x64.Build.0 = Debug|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.ActiveCfg = Debug|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug|x86.Build.0 = Debug|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.ActiveCfg = Debug-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x64.Build.0 = Debug-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.ActiveCfg = Debug-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Debug-static|x86.Build.0 = Debug-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.ActiveCfg = Release|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x64.Build.0 = Release|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.ActiveCfg = Release|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release|x86.Build.0 = Release|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.ActiveCfg = Release-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x64.Build.0 = Release-static|x64
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.ActiveCfg = Release-static|Win32
+ {8D6BB292-9E1C-413D-9F98-4864BDC1514A}.Release-static|x86.Build.0 = Release-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.ActiveCfg = Debug|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x64.Build.0 = Debug|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.ActiveCfg = Debug|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug|x86.Build.0 = Debug|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.ActiveCfg = Debug-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x64.Build.0 = Debug-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.ActiveCfg = Debug-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Debug-static|x86.Build.0 = Debug-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.ActiveCfg = Release|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x64.Build.0 = Release|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.ActiveCfg = Release|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release|x86.Build.0 = Release|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.ActiveCfg = Release-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x64.Build.0 = Release-static|x64
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.ActiveCfg = Release-static|Win32
+ {09028CFD-4EB7-491D-869C-0708DB97ED44}.Release-static|x86.Build.0 = Release-static|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj b/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj
new file mode 100644
index 000000000..f7b175b0a
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj
@@ -0,0 +1,348 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug-static|Win32">
+ <Configuration>Debug-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug-static|x64">
+ <Configuration>Debug-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|Win32">
+ <Configuration>Release-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|x64">
+ <Configuration>Release-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\..\src\arena.c" />
+ <ClCompile Include="..\..\..\..\src\background_thread.c" />
+ <ClCompile Include="..\..\..\..\src\base.c" />
+ <ClCompile Include="..\..\..\..\src\bin.c" />
+ <ClCompile Include="..\..\..\..\src\bitmap.c" />
+ <ClCompile Include="..\..\..\..\src\ckh.c" />
+ <ClCompile Include="..\..\..\..\src\ctl.c" />
+ <ClCompile Include="..\..\..\..\src\div.c" />
+ <ClCompile Include="..\..\..\..\src\extent.c" />
+ <ClCompile Include="..\..\..\..\src\extent_dss.c" />
+ <ClCompile Include="..\..\..\..\src\extent_mmap.c" />
+ <ClCompile Include="..\..\..\..\src\hash.c" />
+ <ClCompile Include="..\..\..\..\src\hooks.c" />
+ <ClCompile Include="..\..\..\..\src\jemalloc.c" />
+ <ClCompile Include="..\..\..\..\src\large.c" />
+ <ClCompile Include="..\..\..\..\src\log.c" />
+ <ClCompile Include="..\..\..\..\src\malloc_io.c" />
+ <ClCompile Include="..\..\..\..\src\mutex.c" />
+ <ClCompile Include="..\..\..\..\src\mutex_pool.c" />
+ <ClCompile Include="..\..\..\..\src\nstime.c" />
+ <ClCompile Include="..\..\..\..\src\pages.c" />
+ <ClCompile Include="..\..\..\..\src\prng.c" />
+ <ClCompile Include="..\..\..\..\src\prof.c" />
+ <ClCompile Include="..\..\..\..\src\rtree.c" />
+ <ClCompile Include="..\..\..\..\src\stats.c" />
+ <ClCompile Include="..\..\..\..\src\sz.c" />
+ <ClCompile Include="..\..\..\..\src\tcache.c" />
+ <ClCompile Include="..\..\..\..\src\ticker.c" />
+ <ClCompile Include="..\..\..\..\src\tsd.c" />
+ <ClCompile Include="..\..\..\..\src\witness.c" />
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{8D6BB292-9E1C-413D-9F98-4864BDC1514A}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>jemalloc</RootNamespace>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)d</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-$(PlatformToolset)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-$(PlatformToolset)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)d</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <DebugInformationFormat>OldStyle</DebugInformationFormat>
+ <MinimalRebuild>false</MinimalRebuild>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <DebugInformationFormat>OldStyle</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj.filters b/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj.filters
new file mode 100644
index 000000000..11cfcd0be
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2015/jemalloc/jemalloc.vcxproj.filters
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\..\src\arena.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\background_thread.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\base.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\bitmap.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ckh.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ctl.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent_dss.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent_mmap.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\hash.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\hooks.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\jemalloc.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\large.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\malloc_io.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\mutex.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\mutex_pool.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\nstime.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\pages.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\prng.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\prof.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\rtree.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\stats.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\sz.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\tcache.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ticker.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\tsd.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\witness.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\log.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\bin.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\div.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj b/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj
new file mode 100644
index 000000000..325876d6e
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug-static|Win32">
+ <Configuration>Debug-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug-static|x64">
+ <Configuration>Debug-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|Win32">
+ <Configuration>Release-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|x64">
+ <Configuration>Release-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{09028CFD-4EB7-491D-869C-0708DB97ED44}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>test_threads</RootNamespace>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\test_threads\test_threads.cpp" />
+ <ClCompile Include="..\..\..\test_threads\test_threads_main.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\jemalloc\jemalloc.vcxproj">
+ <Project>{8d6bb292-9e1c-413d-9f98-4864bdc1514a}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\test_threads\test_threads.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj.filters b/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj.filters
new file mode 100644
index 000000000..fa4588fd8
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2015/test_threads/test_threads.vcxproj.filters
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\test_threads\test_threads.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\test_threads\test_threads_main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\test_threads\test_threads.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj b/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj
new file mode 100644
index 000000000..ed71de8a5
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj
@@ -0,0 +1,347 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug-static|Win32">
+ <Configuration>Debug-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug-static|x64">
+ <Configuration>Debug-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|Win32">
+ <Configuration>Release-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|x64">
+ <Configuration>Release-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\..\src\arena.c" />
+ <ClCompile Include="..\..\..\..\src\background_thread.c" />
+ <ClCompile Include="..\..\..\..\src\base.c" />
+ <ClCompile Include="..\..\..\..\src\bin.c" />
+ <ClCompile Include="..\..\..\..\src\bitmap.c" />
+ <ClCompile Include="..\..\..\..\src\ckh.c" />
+ <ClCompile Include="..\..\..\..\src\ctl.c" />
+ <ClCompile Include="..\..\..\..\src\div.c" />
+ <ClCompile Include="..\..\..\..\src\extent.c" />
+ <ClCompile Include="..\..\..\..\src\extent_dss.c" />
+ <ClCompile Include="..\..\..\..\src\extent_mmap.c" />
+ <ClCompile Include="..\..\..\..\src\hash.c" />
+ <ClCompile Include="..\..\..\..\src\hooks.c" />
+ <ClCompile Include="..\..\..\..\src\jemalloc.c" />
+ <ClCompile Include="..\..\..\..\src\large.c" />
+ <ClCompile Include="..\..\..\..\src\log.c" />
+ <ClCompile Include="..\..\..\..\src\malloc_io.c" />
+ <ClCompile Include="..\..\..\..\src\mutex.c" />
+ <ClCompile Include="..\..\..\..\src\mutex_pool.c" />
+ <ClCompile Include="..\..\..\..\src\nstime.c" />
+ <ClCompile Include="..\..\..\..\src\pages.c" />
+ <ClCompile Include="..\..\..\..\src\prng.c" />
+ <ClCompile Include="..\..\..\..\src\prof.c" />
+ <ClCompile Include="..\..\..\..\src\rtree.c" />
+ <ClCompile Include="..\..\..\..\src\stats.c" />
+ <ClCompile Include="..\..\..\..\src\sz.c" />
+ <ClCompile Include="..\..\..\..\src\tcache.c" />
+ <ClCompile Include="..\..\..\..\src\ticker.c" />
+ <ClCompile Include="..\..\..\..\src\tsd.c" />
+ <ClCompile Include="..\..\..\..\src\witness.c" />
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{8D6BB292-9E1C-413D-9F98-4864BDC1514A}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>jemalloc</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="Configuration">
+ <ConfigurationType>StaticLibrary</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)d</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-$(PlatformToolset)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-$(PlatformToolset)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)d</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <TargetName>$(ProjectName)-vc$(PlatformToolsetVersion)-$(Configuration)</TargetName>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;JEMALLOC_DEBUG;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;JEMALLOC_DEBUG;_REENTRANT;JEMALLOC_EXPORT=;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <DebugInformationFormat>OldStyle</DebugInformationFormat>
+ <MinimalRebuild>false</MinimalRebuild>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;_WINDLL;DLLEXPORT;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(OutputPath)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_NO_PRIVATE_NAMESPACE;_REENTRANT;JEMALLOC_EXPORT=;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ <DisableSpecificWarnings>4090;4146;4267;4334</DisableSpecificWarnings>
+ <DebugInformationFormat>OldStyle</DebugInformationFormat>
+ </ClCompile>
+ <Link>
+ <SubSystem>Windows</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ </Link>
+ </ItemDefinitionGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj.filters b/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj.filters
new file mode 100644
index 000000000..11cfcd0be
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2017/jemalloc/jemalloc.vcxproj.filters
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\..\src\arena.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\background_thread.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\base.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\bitmap.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ckh.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ctl.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent_dss.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\extent_mmap.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\hash.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\hooks.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\jemalloc.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\large.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\malloc_io.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\mutex.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\mutex_pool.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\nstime.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\pages.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\prng.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\prof.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\rtree.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\stats.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\sz.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\tcache.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\ticker.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\tsd.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\witness.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\log.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\bin.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\..\src\div.c">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj b/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj
new file mode 100644
index 000000000..c35b0f5aa
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj
@@ -0,0 +1,326 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug-static|Win32">
+ <Configuration>Debug-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug-static|x64">
+ <Configuration>Debug-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|Win32">
+ <Configuration>Release-static</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release-static|x64">
+ <Configuration>Release-static</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{09028CFD-4EB7-491D-869C-0708DB97ED44}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>test_threads</RootNamespace>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v141</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>MultiByte</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>true</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
+ <IntDir>$(Platform)\$(Configuration)\</IntDir>
+ <LinkIncremental>false</LinkIncremental>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>jemallocd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug-static|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <AdditionalDependencies>jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-$(PlatformToolset)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-static|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>JEMALLOC_EXPORT=;JEMALLOC_STATIC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <AdditionalIncludeDirectories>..\..\..\..\test\include;..\..\..\..\include;..\..\..\..\include\msvc_compat;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <AdditionalLibraryDirectories>$(SolutionDir)$(Platform)\$(Configuration)</AdditionalLibraryDirectories>
+ <AdditionalDependencies>jemalloc-vc$(PlatformToolsetVersion)-$(Configuration).lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\test_threads\test_threads.cpp" />
+ <ClCompile Include="..\..\..\test_threads\test_threads_main.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\jemalloc\jemalloc.vcxproj">
+ <Project>{8d6bb292-9e1c-413d-9f98-4864bdc1514a}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\test_threads\test_threads.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj.filters b/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj.filters
new file mode 100644
index 000000000..fa4588fd8
--- /dev/null
+++ b/deps/jemalloc/msvc/projects/vc2017/test_threads/test_threads.vcxproj.filters
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\test_threads\test_threads.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\test_threads\test_threads_main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="..\..\..\test_threads\test_threads.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/deps/jemalloc/msvc/test_threads/test_threads.cpp b/deps/jemalloc/msvc/test_threads/test_threads.cpp
new file mode 100644
index 000000000..92e316243
--- /dev/null
+++ b/deps/jemalloc/msvc/test_threads/test_threads.cpp
@@ -0,0 +1,88 @@
+// jemalloc C++ threaded test
+// Author: Rustam Abdullaev
+// Public Domain
+
+#include <atomic>
+#include <functional>
+#include <future>
+#include <random>
+#include <thread>
+#include <vector>
+#include <stdio.h>
+#include <jemalloc/jemalloc.h>
+
+using std::vector;
+using std::thread;
+using std::uniform_int_distribution;
+using std::minstd_rand;
+
+int test_threads() {
+ je_malloc_conf = "narenas:3";
+ int narenas = 0;
+ size_t sz = sizeof(narenas);
+ je_mallctl("opt.narenas", (void *)&narenas, &sz, NULL, 0);
+ if (narenas != 3) {
+ printf("Error: unexpected number of arenas: %d\n", narenas);
+ return 1;
+ }
+ static const int sizes[] = { 7, 16, 32, 60, 91, 100, 120, 144, 169, 199, 255, 400, 670, 900, 917, 1025, 3333, 5190, 13131, 49192, 99999, 123123, 255265, 2333111 };
+ static const int numSizes = (int)(sizeof(sizes) / sizeof(sizes[0]));
+ vector<thread> workers;
+ static const int numThreads = narenas + 1, numAllocsMax = 25, numIter1 = 50, numIter2 = 50;
+ je_malloc_stats_print(NULL, NULL, NULL);
+ size_t allocated1;
+ size_t sz1 = sizeof(allocated1);
+ je_mallctl("stats.active", (void *)&allocated1, &sz1, NULL, 0);
+ printf("\nPress Enter to start threads...\n");
+ getchar();
+ printf("Starting %d threads x %d x %d iterations...\n", numThreads, numIter1, numIter2);
+ for (int i = 0; i < numThreads; i++) {
+ workers.emplace_back([tid=i]() {
+ uniform_int_distribution<int> sizeDist(0, numSizes - 1);
+ minstd_rand rnd(tid * 17);
+ uint8_t* ptrs[numAllocsMax];
+ int ptrsz[numAllocsMax];
+ for (int i = 0; i < numIter1; ++i) {
+ thread t([&]() {
+ for (int i = 0; i < numIter2; ++i) {
+ const int numAllocs = numAllocsMax - sizeDist(rnd);
+ for (int j = 0; j < numAllocs; j += 64) {
+ const int x = sizeDist(rnd);
+ const int sz = sizes[x];
+ ptrsz[j] = sz;
+ ptrs[j] = (uint8_t*)je_malloc(sz);
+ if (!ptrs[j]) {
+ printf("Unable to allocate %d bytes in thread %d, iter %d, alloc %d. %d\n", sz, tid, i, j, x);
+ exit(1);
+ }
+ for (int k = 0; k < sz; k++)
+ ptrs[j][k] = tid + k;
+ }
+ for (int j = 0; j < numAllocs; j += 64) {
+ for (int k = 0, sz = ptrsz[j]; k < sz; k++)
+ if (ptrs[j][k] != (uint8_t)(tid + k)) {
+ printf("Memory error in thread %d, iter %d, alloc %d @ %d : %02X!=%02X\n", tid, i, j, k, ptrs[j][k], (uint8_t)(tid + k));
+ exit(1);
+ }
+ je_free(ptrs[j]);
+ }
+ }
+ });
+ t.join();
+ }
+ });
+ }
+ for (thread& t : workers) {
+ t.join();
+ }
+ je_malloc_stats_print(NULL, NULL, NULL);
+ size_t allocated2;
+ je_mallctl("stats.active", (void *)&allocated2, &sz1, NULL, 0);
+ size_t leaked = allocated2 - allocated1;
+ printf("\nDone. Leaked: %zd bytes\n", leaked);
+ bool failed = leaked > 65536; // in case C++ runtime allocated something (e.g. iostream locale or facet)
+ printf("\nTest %s!\n", (failed ? "FAILED" : "successful"));
+ printf("\nPress Enter to continue...\n");
+ getchar();
+ return failed ? 1 : 0;
+}
diff --git a/deps/jemalloc/msvc/test_threads/test_threads.h b/deps/jemalloc/msvc/test_threads/test_threads.h
new file mode 100644
index 000000000..64d0cdb33
--- /dev/null
+++ b/deps/jemalloc/msvc/test_threads/test_threads.h
@@ -0,0 +1,3 @@
+#pragma once
+
+int test_threads();
diff --git a/deps/jemalloc/msvc/test_threads/test_threads_main.cpp b/deps/jemalloc/msvc/test_threads/test_threads_main.cpp
new file mode 100644
index 000000000..0a022fba4
--- /dev/null
+++ b/deps/jemalloc/msvc/test_threads/test_threads_main.cpp
@@ -0,0 +1,11 @@
+#include "test_threads.h"
+#include <future>
+#include <functional>
+#include <chrono>
+
+using namespace std::chrono_literals;
+
+int main(int argc, char** argv) {
+ int rc = test_threads();
+ return rc;
+}
diff --git a/deps/jemalloc/run_tests.sh b/deps/jemalloc/run_tests.sh
new file mode 100755
index 000000000..b434f15b3
--- /dev/null
+++ b/deps/jemalloc/run_tests.sh
@@ -0,0 +1 @@
+$(dirname "$)")/scripts/gen_run_tests.py | bash
diff --git a/deps/jemalloc/scripts/gen_run_tests.py b/deps/jemalloc/scripts/gen_run_tests.py
new file mode 100755
index 000000000..a87ecffba
--- /dev/null
+++ b/deps/jemalloc/scripts/gen_run_tests.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+import sys
+from itertools import combinations
+from os import uname
+from multiprocessing import cpu_count
+
+# Later, we want to test extended vaddr support. Apparently, the "real" way of
+# checking this is flaky on OS X.
+bits_64 = sys.maxsize > 2**32
+
+nparallel = cpu_count() * 2
+
+uname = uname()[0]
+
+def powerset(items):
+ result = []
+ for i in xrange(len(items) + 1):
+ result += combinations(items, i)
+ return result
+
+possible_compilers = [('gcc', 'g++'), ('clang', 'clang++')]
+possible_compiler_opts = [
+ '-m32',
+]
+possible_config_opts = [
+ '--enable-debug',
+ '--enable-prof',
+ '--disable-stats',
+]
+if bits_64:
+ possible_config_opts.append('--with-lg-vaddr=56')
+
+possible_malloc_conf_opts = [
+ 'tcache:false',
+ 'dss:primary',
+ 'percpu_arena:percpu',
+ 'background_thread:true',
+]
+
+print 'set -e'
+print 'if [ -f Makefile ] ; then make relclean ; fi'
+print 'autoconf'
+print 'rm -rf run_tests.out'
+print 'mkdir run_tests.out'
+print 'cd run_tests.out'
+
+ind = 0
+for cc, cxx in possible_compilers:
+ for compiler_opts in powerset(possible_compiler_opts):
+ for config_opts in powerset(possible_config_opts):
+ for malloc_conf_opts in powerset(possible_malloc_conf_opts):
+ if cc is 'clang' \
+ and '-m32' in possible_compiler_opts \
+ and '--enable-prof' in config_opts:
+ continue
+ config_line = (
+ 'EXTRA_CFLAGS=-Werror EXTRA_CXXFLAGS=-Werror '
+ + 'CC="{} {}" '.format(cc, " ".join(compiler_opts))
+ + 'CXX="{} {}" '.format(cxx, " ".join(compiler_opts))
+ + '../../configure '
+ + " ".join(config_opts) + (' --with-malloc-conf=' +
+ ",".join(malloc_conf_opts) if len(malloc_conf_opts) > 0
+ else '')
+ )
+
+ # We don't want to test large vaddr spaces in 32-bit mode.
+ if ('-m32' in compiler_opts and '--with-lg-vaddr=56' in
+ config_opts):
+ continue
+
+ # Per CPU arenas are only supported on Linux.
+ linux_supported = ('percpu_arena:percpu' in malloc_conf_opts \
+ or 'background_thread:true' in malloc_conf_opts)
+ # Heap profiling and dss are not supported on OS X.
+ darwin_unsupported = ('--enable-prof' in config_opts or \
+ 'dss:primary' in malloc_conf_opts)
+ if (uname == 'Linux' and linux_supported) \
+ or (not linux_supported and (uname != 'Darwin' or \
+ not darwin_unsupported)):
+ print """cat <<EOF > run_test_%(ind)d.sh
+#!/bin/sh
+
+set -e
+
+abort() {
+ echo "==> Error" >> run_test.log
+ echo "Error; see run_tests.out/run_test_%(ind)d.out/run_test.log"
+ exit 255 # Special exit code tells xargs to terminate.
+}
+
+# Environment variables are not supported.
+run_cmd() {
+ echo "==> \$@" >> run_test.log
+ \$@ >> run_test.log 2>&1 || abort
+}
+
+echo "=> run_test_%(ind)d: %(config_line)s"
+mkdir run_test_%(ind)d.out
+cd run_test_%(ind)d.out
+
+echo "==> %(config_line)s" >> run_test.log
+%(config_line)s >> run_test.log 2>&1 || abort
+
+run_cmd make all tests
+run_cmd make check
+run_cmd make distclean
+EOF
+chmod 755 run_test_%(ind)d.sh""" % {'ind': ind, 'config_line': config_line}
+ ind += 1
+
+print 'for i in `seq 0 %(last_ind)d` ; do echo run_test_${i}.sh ; done | xargs -P %(nparallel)d -n 1 sh' % {'last_ind': ind-1, 'nparallel': nparallel}
diff --git a/deps/jemalloc/scripts/gen_travis.py b/deps/jemalloc/scripts/gen_travis.py
new file mode 100755
index 000000000..6dd39290c
--- /dev/null
+++ b/deps/jemalloc/scripts/gen_travis.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+
+from itertools import combinations
+
+travis_template = """\
+language: generic
+
+matrix:
+ include:
+%s
+
+before_script:
+ - autoconf
+ - ./configure ${COMPILER_FLAGS:+ \
+ CC="$CC $COMPILER_FLAGS" \
+ CXX="$CXX $COMPILER_FLAGS" } \
+ $CONFIGURE_FLAGS
+ - make -j3
+ - make -j3 tests
+
+script:
+ - make check
+"""
+
+# The 'default' configuration is gcc, on linux, with no compiler or configure
+# flags. We also test with clang, -m32, --enable-debug, --enable-prof,
+# --disable-stats, and --with-malloc-conf=tcache:false. To avoid abusing
+# travis though, we don't test all 2**7 = 128 possible combinations of these;
+# instead, we only test combinations of up to 2 'unusual' settings, under the
+# hope that bugs involving interactions of such settings are rare.
+# Things at once, for C(7, 0) + C(7, 1) + C(7, 2) = 29
+MAX_UNUSUAL_OPTIONS = 2
+
+os_default = 'linux'
+os_unusual = 'osx'
+
+compilers_default = 'CC=gcc CXX=g++'
+compilers_unusual = 'CC=clang CXX=clang++'
+
+compiler_flag_unusuals = ['-m32']
+
+configure_flag_unusuals = [
+ '--enable-debug',
+ '--enable-prof',
+ '--disable-stats',
+]
+
+malloc_conf_unusuals = [
+ 'tcache:false',
+ 'dss:primary',
+ 'percpu_arena:percpu',
+ 'background_thread:true',
+]
+
+all_unusuals = (
+ [os_unusual] + [compilers_unusual] + compiler_flag_unusuals
+ + configure_flag_unusuals + malloc_conf_unusuals
+)
+
+unusual_combinations_to_test = []
+for i in xrange(MAX_UNUSUAL_OPTIONS + 1):
+ unusual_combinations_to_test += combinations(all_unusuals, i)
+
+include_rows = ""
+for unusual_combination in unusual_combinations_to_test:
+ os = os_default
+ if os_unusual in unusual_combination:
+ os = os_unusual
+
+ compilers = compilers_default
+ if compilers_unusual in unusual_combination:
+ compilers = compilers_unusual
+
+ compiler_flags = [
+ x for x in unusual_combination if x in compiler_flag_unusuals]
+
+ configure_flags = [
+ x for x in unusual_combination if x in configure_flag_unusuals]
+
+ malloc_conf = [
+ x for x in unusual_combination if x in malloc_conf_unusuals]
+ # Filter out unsupported configurations on OS X.
+ if os == 'osx' and ('dss:primary' in malloc_conf or \
+ 'percpu_arena:percpu' in malloc_conf or 'background_thread:true' \
+ in malloc_conf):
+ continue
+ if len(malloc_conf) > 0:
+ configure_flags.append('--with-malloc-conf=' + ",".join(malloc_conf))
+
+ # Filter out an unsupported configuration - heap profiling on OS X.
+ if os == 'osx' and '--enable-prof' in configure_flags:
+ continue
+
+ # We get some spurious errors when -Warray-bounds is enabled.
+ env_string = ('{} COMPILER_FLAGS="{}" CONFIGURE_FLAGS="{}" '
+ 'EXTRA_CFLAGS="-Werror -Wno-array-bounds"').format(
+ compilers, " ".join(compiler_flags), " ".join(configure_flags))
+
+ include_rows += ' - os: %s\n' % os
+ include_rows += ' env: %s\n' % env_string
+ if '-m32' in unusual_combination and os == 'linux':
+ include_rows += ' addons:\n'
+ include_rows += ' apt:\n'
+ include_rows += ' packages:\n'
+ include_rows += ' - gcc-multilib\n'
+
+print travis_template % include_rows
diff --git a/deps/jemalloc/src/arena.c b/deps/jemalloc/src/arena.c
index 3081519cc..5d55bf1a0 100644
--- a/deps/jemalloc/src/arena.c
+++ b/deps/jemalloc/src/arena.c
@@ -1,21 +1,46 @@
-#define JEMALLOC_ARENA_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_ARENA_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/div.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/util.h"
/******************************************************************************/
/* Data. */
-ssize_t opt_lg_dirty_mult = LG_DIRTY_MULT_DEFAULT;
-static ssize_t lg_dirty_mult_default;
-arena_bin_info_t arena_bin_info[NBINS];
-
-size_t map_bias;
-size_t map_misc_offset;
-size_t arena_maxrun; /* Max run size for arenas. */
-size_t large_maxclass; /* Max large size class. */
-static size_t small_maxrun; /* Max run size used for small size classes. */
-static bool *small_run_tab; /* Valid small run page multiples. */
-unsigned nlclasses; /* Number of large size classes. */
-unsigned nhclasses; /* Number of huge size classes. */
+/*
+ * Define names for both unininitialized and initialized phases, so that
+ * options and mallctl processing are straightforward.
+ */
+const char *percpu_arena_mode_names[] = {
+ "percpu",
+ "phycpu",
+ "disabled",
+ "percpu",
+ "phycpu"
+};
+percpu_arena_mode_t opt_percpu_arena = PERCPU_ARENA_DEFAULT;
+
+ssize_t opt_dirty_decay_ms = DIRTY_DECAY_MS_DEFAULT;
+ssize_t opt_muzzy_decay_ms = MUZZY_DECAY_MS_DEFAULT;
+
+static atomic_zd_t dirty_decay_ms_default;
+static atomic_zd_t muzzy_decay_ms_default;
+
+const uint64_t h_steps[SMOOTHSTEP_NSTEPS] = {
+#define STEP(step, h, x, y) \
+ h,
+ SMOOTHSTEP
+#undef STEP
+};
+
+static div_info_t arena_binind_div_info[NBINS];
/******************************************************************************/
/*
@@ -23,2008 +48,1244 @@ unsigned nhclasses; /* Number of huge size classes. */
* definition.
*/
-static void arena_purge(arena_t *arena, bool all);
-static void arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty,
- bool cleaned, bool decommitted);
-static void arena_dalloc_bin_run(arena_t *arena, arena_chunk_t *chunk,
- arena_run_t *run, arena_bin_t *bin);
-static void arena_bin_lower_run(arena_t *arena, arena_chunk_t *chunk,
- arena_run_t *run, arena_bin_t *bin);
+static void arena_decay_to_limit(tsdn_t *tsdn, arena_t *arena,
+ arena_decay_t *decay, extents_t *extents, bool all, size_t npages_limit,
+ size_t npages_decay_max, bool is_background_thread);
+static bool arena_decay_dirty(tsdn_t *tsdn, arena_t *arena,
+ bool is_background_thread, bool all);
+static void arena_dalloc_bin_slab(tsdn_t *tsdn, arena_t *arena, extent_t *slab,
+ bin_t *bin);
+static void arena_bin_lower_slab(tsdn_t *tsdn, arena_t *arena, extent_t *slab,
+ bin_t *bin);
/******************************************************************************/
-#define CHUNK_MAP_KEY ((uintptr_t)0x1U)
-
-JEMALLOC_INLINE_C arena_chunk_map_misc_t *
-arena_miscelm_key_create(size_t size)
-{
-
- return ((arena_chunk_map_misc_t *)(arena_mapbits_size_encode(size) |
- CHUNK_MAP_KEY));
+void
+arena_basic_stats_merge(UNUSED tsdn_t *tsdn, arena_t *arena, unsigned *nthreads,
+ const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms,
+ size_t *nactive, size_t *ndirty, size_t *nmuzzy) {
+ *nthreads += arena_nthreads_get(arena, false);
+ *dss = dss_prec_names[arena_dss_prec_get(arena)];
+ *dirty_decay_ms = arena_dirty_decay_ms_get(arena);
+ *muzzy_decay_ms = arena_muzzy_decay_ms_get(arena);
+ *nactive += atomic_load_zu(&arena->nactive, ATOMIC_RELAXED);
+ *ndirty += extents_npages_get(&arena->extents_dirty);
+ *nmuzzy += extents_npages_get(&arena->extents_muzzy);
}
-JEMALLOC_INLINE_C bool
-arena_miscelm_is_key(const arena_chunk_map_misc_t *miscelm)
-{
+void
+arena_stats_merge(tsdn_t *tsdn, arena_t *arena, unsigned *nthreads,
+ const char **dss, ssize_t *dirty_decay_ms, ssize_t *muzzy_decay_ms,
+ size_t *nactive, size_t *ndirty, size_t *nmuzzy, arena_stats_t *astats,
+ bin_stats_t *bstats, arena_stats_large_t *lstats) {
+ cassert(config_stats);
- return (((uintptr_t)miscelm & CHUNK_MAP_KEY) != 0);
+ arena_basic_stats_merge(tsdn, arena, nthreads, dss, dirty_decay_ms,
+ muzzy_decay_ms, nactive, ndirty, nmuzzy);
+
+ size_t base_allocated, base_resident, base_mapped, metadata_thp;
+ base_stats_get(tsdn, arena->base, &base_allocated, &base_resident,
+ &base_mapped, &metadata_thp);
+
+ arena_stats_lock(tsdn, &arena->stats);
+
+ arena_stats_accum_zu(&astats->mapped, base_mapped
+ + arena_stats_read_zu(tsdn, &arena->stats, &arena->stats.mapped));
+ arena_stats_accum_zu(&astats->retained,
+ extents_npages_get(&arena->extents_retained) << LG_PAGE);
+
+ arena_stats_accum_u64(&astats->decay_dirty.npurge,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_dirty.npurge));
+ arena_stats_accum_u64(&astats->decay_dirty.nmadvise,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_dirty.nmadvise));
+ arena_stats_accum_u64(&astats->decay_dirty.purged,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_dirty.purged));
+
+ arena_stats_accum_u64(&astats->decay_muzzy.npurge,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_muzzy.npurge));
+ arena_stats_accum_u64(&astats->decay_muzzy.nmadvise,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_muzzy.nmadvise));
+ arena_stats_accum_u64(&astats->decay_muzzy.purged,
+ arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.decay_muzzy.purged));
+
+ arena_stats_accum_zu(&astats->base, base_allocated);
+ arena_stats_accum_zu(&astats->internal, arena_internal_get(arena));
+ arena_stats_accum_zu(&astats->metadata_thp, metadata_thp);
+ arena_stats_accum_zu(&astats->resident, base_resident +
+ (((atomic_load_zu(&arena->nactive, ATOMIC_RELAXED) +
+ extents_npages_get(&arena->extents_dirty) +
+ extents_npages_get(&arena->extents_muzzy)) << LG_PAGE)));
+
+ for (szind_t i = 0; i < NSIZES - NBINS; i++) {
+ uint64_t nmalloc = arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.lstats[i].nmalloc);
+ arena_stats_accum_u64(&lstats[i].nmalloc, nmalloc);
+ arena_stats_accum_u64(&astats->nmalloc_large, nmalloc);
+
+ uint64_t ndalloc = arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.lstats[i].ndalloc);
+ arena_stats_accum_u64(&lstats[i].ndalloc, ndalloc);
+ arena_stats_accum_u64(&astats->ndalloc_large, ndalloc);
+
+ uint64_t nrequests = arena_stats_read_u64(tsdn, &arena->stats,
+ &arena->stats.lstats[i].nrequests);
+ arena_stats_accum_u64(&lstats[i].nrequests,
+ nmalloc + nrequests);
+ arena_stats_accum_u64(&astats->nrequests_large,
+ nmalloc + nrequests);
+
+ assert(nmalloc >= ndalloc);
+ assert(nmalloc - ndalloc <= SIZE_T_MAX);
+ size_t curlextents = (size_t)(nmalloc - ndalloc);
+ lstats[i].curlextents += curlextents;
+ arena_stats_accum_zu(&astats->allocated_large,
+ curlextents * sz_index2size(NBINS + i));
+ }
+
+ arena_stats_unlock(tsdn, &arena->stats);
+
+ /* tcache_bytes counts currently cached bytes. */
+ atomic_store_zu(&astats->tcache_bytes, 0, ATOMIC_RELAXED);
+ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
+ cache_bin_array_descriptor_t *descriptor;
+ ql_foreach(descriptor, &arena->cache_bin_array_descriptor_ql, link) {
+ szind_t i = 0;
+ for (; i < NBINS; i++) {
+ cache_bin_t *tbin = &descriptor->bins_small[i];
+ arena_stats_accum_zu(&astats->tcache_bytes,
+ tbin->ncached * sz_index2size(i));
+ }
+ for (; i < nhbins; i++) {
+ cache_bin_t *tbin = &descriptor->bins_large[i];
+ arena_stats_accum_zu(&astats->tcache_bytes,
+ tbin->ncached * sz_index2size(i));
+ }
+ }
+ malloc_mutex_prof_read(tsdn,
+ &astats->mutex_prof_data[arena_prof_mutex_tcache_list],
+ &arena->tcache_ql_mtx);
+ malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx);
+
+#define READ_ARENA_MUTEX_PROF_DATA(mtx, ind) \
+ malloc_mutex_lock(tsdn, &arena->mtx); \
+ malloc_mutex_prof_read(tsdn, &astats->mutex_prof_data[ind], \
+ &arena->mtx); \
+ malloc_mutex_unlock(tsdn, &arena->mtx);
+
+ /* Gather per arena mutex profiling data. */
+ READ_ARENA_MUTEX_PROF_DATA(large_mtx, arena_prof_mutex_large);
+ READ_ARENA_MUTEX_PROF_DATA(extent_avail_mtx,
+ arena_prof_mutex_extent_avail)
+ READ_ARENA_MUTEX_PROF_DATA(extents_dirty.mtx,
+ arena_prof_mutex_extents_dirty)
+ READ_ARENA_MUTEX_PROF_DATA(extents_muzzy.mtx,
+ arena_prof_mutex_extents_muzzy)
+ READ_ARENA_MUTEX_PROF_DATA(extents_retained.mtx,
+ arena_prof_mutex_extents_retained)
+ READ_ARENA_MUTEX_PROF_DATA(decay_dirty.mtx,
+ arena_prof_mutex_decay_dirty)
+ READ_ARENA_MUTEX_PROF_DATA(decay_muzzy.mtx,
+ arena_prof_mutex_decay_muzzy)
+ READ_ARENA_MUTEX_PROF_DATA(base->mtx,
+ arena_prof_mutex_base)
+#undef READ_ARENA_MUTEX_PROF_DATA
+
+ nstime_copy(&astats->uptime, &arena->create_time);
+ nstime_update(&astats->uptime);
+ nstime_subtract(&astats->uptime, &arena->create_time);
+
+ for (szind_t i = 0; i < NBINS; i++) {
+ bin_stats_merge(tsdn, &bstats[i], &arena->bins[i]);
+ }
}
-#undef CHUNK_MAP_KEY
-
-JEMALLOC_INLINE_C size_t
-arena_miscelm_key_size_get(const arena_chunk_map_misc_t *miscelm)
-{
-
- assert(arena_miscelm_is_key(miscelm));
-
- return (arena_mapbits_size_decode((uintptr_t)miscelm));
+void
+arena_extents_dirty_dalloc(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extents_dalloc(tsdn, arena, r_extent_hooks, &arena->extents_dirty,
+ extent);
+ if (arena_dirty_decay_ms_get(arena) == 0) {
+ arena_decay_dirty(tsdn, arena, false, true);
+ } else {
+ arena_background_thread_inactivity_check(tsdn, arena, false);
+ }
}
-JEMALLOC_INLINE_C size_t
-arena_miscelm_size_get(arena_chunk_map_misc_t *miscelm)
-{
- arena_chunk_t *chunk;
- size_t pageind, mapbits;
+static void *
+arena_slab_reg_alloc(extent_t *slab, const bin_info_t *bin_info) {
+ void *ret;
+ arena_slab_data_t *slab_data = extent_slab_data_get(slab);
+ size_t regind;
- assert(!arena_miscelm_is_key(miscelm));
+ assert(extent_nfree_get(slab) > 0);
+ assert(!bitmap_full(slab_data->bitmap, &bin_info->bitmap_info));
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(miscelm);
- pageind = arena_miscelm_to_pageind(miscelm);
- mapbits = arena_mapbits_get(chunk, pageind);
- return (arena_mapbits_size_decode(mapbits));
+ regind = bitmap_sfu(slab_data->bitmap, &bin_info->bitmap_info);
+ ret = (void *)((uintptr_t)extent_addr_get(slab) +
+ (uintptr_t)(bin_info->reg_size * regind));
+ extent_nfree_dec(slab);
+ return ret;
}
-JEMALLOC_INLINE_C int
-arena_run_comp(arena_chunk_map_misc_t *a, arena_chunk_map_misc_t *b)
-{
- uintptr_t a_miscelm = (uintptr_t)a;
- uintptr_t b_miscelm = (uintptr_t)b;
-
- assert(a != NULL);
- assert(b != NULL);
-
- return ((a_miscelm > b_miscelm) - (a_miscelm < b_miscelm));
-}
+#ifndef JEMALLOC_JET
+static
+#endif
+size_t
+arena_slab_regind(extent_t *slab, szind_t binind, const void *ptr) {
+ size_t diff, regind;
-/* Generate red-black tree functions. */
-rb_gen(static UNUSED, arena_run_tree_, arena_run_tree_t, arena_chunk_map_misc_t,
- rb_link, arena_run_comp)
+ /* Freeing a pointer outside the slab can cause assertion failure. */
+ assert((uintptr_t)ptr >= (uintptr_t)extent_addr_get(slab));
+ assert((uintptr_t)ptr < (uintptr_t)extent_past_get(slab));
+ /* Freeing an interior pointer can cause assertion failure. */
+ assert(((uintptr_t)ptr - (uintptr_t)extent_addr_get(slab)) %
+ (uintptr_t)bin_infos[binind].reg_size == 0);
-static size_t
-run_quantize(size_t size)
-{
- size_t qsize;
+ diff = (size_t)((uintptr_t)ptr - (uintptr_t)extent_addr_get(slab));
- assert(size != 0);
- assert(size == PAGE_CEILING(size));
+ /* Avoid doing division with a variable divisor. */
+ regind = div_compute(&arena_binind_div_info[binind], diff);
- /* Don't change sizes that are valid small run sizes. */
- if (size <= small_maxrun && small_run_tab[size >> LG_PAGE])
- return (size);
+ assert(regind < bin_infos[binind].nregs);
- /*
- * Round down to the nearest run size that can actually be requested
- * during normal large allocation. Add large_pad so that cache index
- * randomization can offset the allocation from the page boundary.
- */
- qsize = index2size(size2index(size - large_pad + 1) - 1) + large_pad;
- if (qsize <= SMALL_MAXCLASS + large_pad)
- return (run_quantize(size - large_pad));
- assert(qsize <= size);
- return (qsize);
+ return regind;
}
-static size_t
-run_quantize_next(size_t size)
-{
- size_t large_run_size_next;
-
- assert(size != 0);
- assert(size == PAGE_CEILING(size));
+static void
+arena_slab_reg_dalloc(extent_t *slab, arena_slab_data_t *slab_data, void *ptr) {
+ szind_t binind = extent_szind_get(slab);
+ const bin_info_t *bin_info = &bin_infos[binind];
+ size_t regind = arena_slab_regind(slab, binind, ptr);
- /*
- * Return the next quantized size greater than the input size.
- * Quantized sizes comprise the union of run sizes that back small
- * region runs, and run sizes that back large regions with no explicit
- * alignment constraints.
- */
+ assert(extent_nfree_get(slab) < bin_info->nregs);
+ /* Freeing an unallocated pointer can cause assertion failure. */
+ assert(bitmap_get(slab_data->bitmap, &bin_info->bitmap_info, regind));
- if (size > SMALL_MAXCLASS) {
- large_run_size_next = PAGE_CEILING(index2size(size2index(size -
- large_pad) + 1) + large_pad);
- } else
- large_run_size_next = SIZE_T_MAX;
- if (size >= small_maxrun)
- return (large_run_size_next);
-
- while (true) {
- size += PAGE;
- assert(size <= small_maxrun);
- if (small_run_tab[size >> LG_PAGE]) {
- if (large_run_size_next < size)
- return (large_run_size_next);
- return (size);
- }
- }
+ bitmap_unset(slab_data->bitmap, &bin_info->bitmap_info, regind);
+ extent_nfree_inc(slab);
}
-static size_t
-run_quantize_first(size_t size)
-{
- size_t qsize = run_quantize(size);
+static void
+arena_nactive_add(arena_t *arena, size_t add_pages) {
+ atomic_fetch_add_zu(&arena->nactive, add_pages, ATOMIC_RELAXED);
+}
- if (qsize < size) {
- /*
- * Skip a quantization that may have an adequately large run,
- * because under-sized runs may be mixed in. This only happens
- * when an unusual size is requested, i.e. for aligned
- * allocation, and is just one of several places where linear
- * search would potentially find sufficiently aligned available
- * memory somewhere lower.
- */
- qsize = run_quantize_next(size);
- }
- return (qsize);
+static void
+arena_nactive_sub(arena_t *arena, size_t sub_pages) {
+ assert(atomic_load_zu(&arena->nactive, ATOMIC_RELAXED) >= sub_pages);
+ atomic_fetch_sub_zu(&arena->nactive, sub_pages, ATOMIC_RELAXED);
}
-JEMALLOC_INLINE_C int
-arena_avail_comp(arena_chunk_map_misc_t *a, arena_chunk_map_misc_t *b)
-{
- int ret;
- uintptr_t a_miscelm = (uintptr_t)a;
- size_t a_qsize = run_quantize(arena_miscelm_is_key(a) ?
- arena_miscelm_key_size_get(a) : arena_miscelm_size_get(a));
- size_t b_qsize = run_quantize(arena_miscelm_size_get(b));
+static void
+arena_large_malloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) {
+ szind_t index, hindex;
- /*
- * Compare based on quantized size rather than size, in order to sort
- * equally useful runs only by address.
- */
- ret = (a_qsize > b_qsize) - (a_qsize < b_qsize);
- if (ret == 0) {
- if (!arena_miscelm_is_key(a)) {
- uintptr_t b_miscelm = (uintptr_t)b;
+ cassert(config_stats);
- ret = (a_miscelm > b_miscelm) - (a_miscelm < b_miscelm);
- } else {
- /*
- * Treat keys as if they are lower than anything else.
- */
- ret = -1;
- }
+ if (usize < LARGE_MINCLASS) {
+ usize = LARGE_MINCLASS;
}
+ index = sz_size2index(usize);
+ hindex = (index >= NBINS) ? index - NBINS : 0;
- return (ret);
+ arena_stats_add_u64(tsdn, &arena->stats,
+ &arena->stats.lstats[hindex].nmalloc, 1);
}
-/* Generate red-black tree functions. */
-rb_gen(static UNUSED, arena_avail_tree_, arena_avail_tree_t,
- arena_chunk_map_misc_t, rb_link, arena_avail_comp)
-
static void
-arena_avail_insert(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
- size_t npages)
-{
+arena_large_dalloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t usize) {
+ szind_t index, hindex;
- assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
- LG_PAGE));
- arena_avail_tree_insert(&arena->runs_avail, arena_miscelm_get(chunk,
- pageind));
-}
+ cassert(config_stats);
-static void
-arena_avail_remove(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
- size_t npages)
-{
+ if (usize < LARGE_MINCLASS) {
+ usize = LARGE_MINCLASS;
+ }
+ index = sz_size2index(usize);
+ hindex = (index >= NBINS) ? index - NBINS : 0;
- assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
- LG_PAGE));
- arena_avail_tree_remove(&arena->runs_avail, arena_miscelm_get(chunk,
- pageind));
+ arena_stats_add_u64(tsdn, &arena->stats,
+ &arena->stats.lstats[hindex].ndalloc, 1);
}
static void
-arena_run_dirty_insert(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
- size_t npages)
-{
- arena_chunk_map_misc_t *miscelm = arena_miscelm_get(chunk, pageind);
-
- assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
- LG_PAGE));
- assert(arena_mapbits_dirty_get(chunk, pageind) == CHUNK_MAP_DIRTY);
- assert(arena_mapbits_dirty_get(chunk, pageind+npages-1) ==
- CHUNK_MAP_DIRTY);
-
- qr_new(&miscelm->rd, rd_link);
- qr_meld(&arena->runs_dirty, &miscelm->rd, rd_link);
- arena->ndirty += npages;
+arena_large_ralloc_stats_update(tsdn_t *tsdn, arena_t *arena, size_t oldusize,
+ size_t usize) {
+ arena_large_dalloc_stats_update(tsdn, arena, oldusize);
+ arena_large_malloc_stats_update(tsdn, arena, usize);
}
-static void
-arena_run_dirty_remove(arena_t *arena, arena_chunk_t *chunk, size_t pageind,
- size_t npages)
-{
- arena_chunk_map_misc_t *miscelm = arena_miscelm_get(chunk, pageind);
+extent_t *
+arena_extent_alloc_large(tsdn_t *tsdn, arena_t *arena, size_t usize,
+ size_t alignment, bool *zero) {
+ extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
- assert(npages == (arena_mapbits_unallocated_size_get(chunk, pageind) >>
- LG_PAGE));
- assert(arena_mapbits_dirty_get(chunk, pageind) == CHUNK_MAP_DIRTY);
- assert(arena_mapbits_dirty_get(chunk, pageind+npages-1) ==
- CHUNK_MAP_DIRTY);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
- qr_remove(&miscelm->rd, rd_link);
- assert(arena->ndirty >= npages);
- arena->ndirty -= npages;
-}
+ szind_t szind = sz_size2index(usize);
+ size_t mapped_add;
+ bool commit = true;
+ extent_t *extent = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_dirty, NULL, usize, sz_large_pad, alignment, false,
+ szind, zero, &commit);
+ if (extent == NULL) {
+ extent = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_muzzy, NULL, usize, sz_large_pad, alignment,
+ false, szind, zero, &commit);
+ }
+ size_t size = usize + sz_large_pad;
+ if (extent == NULL) {
+ extent = extent_alloc_wrapper(tsdn, arena, &extent_hooks, NULL,
+ usize, sz_large_pad, alignment, false, szind, zero,
+ &commit);
+ if (config_stats) {
+ /*
+ * extent may be NULL on OOM, but in that case
+ * mapped_add isn't used below, so there's no need to
+ * conditionlly set it to 0 here.
+ */
+ mapped_add = size;
+ }
+ } else if (config_stats) {
+ mapped_add = 0;
+ }
-static size_t
-arena_chunk_dirty_npages(const extent_node_t *node)
-{
+ if (extent != NULL) {
+ if (config_stats) {
+ arena_stats_lock(tsdn, &arena->stats);
+ arena_large_malloc_stats_update(tsdn, arena, usize);
+ if (mapped_add != 0) {
+ arena_stats_add_zu(tsdn, &arena->stats,
+ &arena->stats.mapped, mapped_add);
+ }
+ arena_stats_unlock(tsdn, &arena->stats);
+ }
+ arena_nactive_add(arena, size >> LG_PAGE);
+ }
- return (extent_node_size_get(node) >> LG_PAGE);
+ return extent;
}
void
-arena_chunk_cache_maybe_insert(arena_t *arena, extent_node_t *node, bool cache)
-{
-
- if (cache) {
- extent_node_dirty_linkage_init(node);
- extent_node_dirty_insert(node, &arena->runs_dirty,
- &arena->chunks_cache);
- arena->ndirty += arena_chunk_dirty_npages(node);
+arena_extent_dalloc_large_prep(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
+ if (config_stats) {
+ arena_stats_lock(tsdn, &arena->stats);
+ arena_large_dalloc_stats_update(tsdn, arena,
+ extent_usize_get(extent));
+ arena_stats_unlock(tsdn, &arena->stats);
}
+ arena_nactive_sub(arena, extent_size_get(extent) >> LG_PAGE);
}
void
-arena_chunk_cache_maybe_remove(arena_t *arena, extent_node_t *node, bool dirty)
-{
+arena_extent_ralloc_large_shrink(tsdn_t *tsdn, arena_t *arena, extent_t *extent,
+ size_t oldusize) {
+ size_t usize = extent_usize_get(extent);
+ size_t udiff = oldusize - usize;
- if (dirty) {
- extent_node_dirty_remove(node);
- assert(arena->ndirty >= arena_chunk_dirty_npages(node));
- arena->ndirty -= arena_chunk_dirty_npages(node);
+ if (config_stats) {
+ arena_stats_lock(tsdn, &arena->stats);
+ arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize);
+ arena_stats_unlock(tsdn, &arena->stats);
}
+ arena_nactive_sub(arena, udiff >> LG_PAGE);
}
-JEMALLOC_INLINE_C void *
-arena_run_reg_alloc(arena_run_t *run, arena_bin_info_t *bin_info)
-{
- void *ret;
- unsigned regind;
- arena_chunk_map_misc_t *miscelm;
- void *rpages;
-
- assert(run->nfree > 0);
- assert(!bitmap_full(run->bitmap, &bin_info->bitmap_info));
-
- regind = bitmap_sfu(run->bitmap, &bin_info->bitmap_info);
- miscelm = arena_run_to_miscelm(run);
- rpages = arena_miscelm_to_rpages(miscelm);
- ret = (void *)((uintptr_t)rpages + (uintptr_t)bin_info->reg0_offset +
- (uintptr_t)(bin_info->reg_interval * regind));
- run->nfree--;
- return (ret);
-}
-
-JEMALLOC_INLINE_C void
-arena_run_reg_dalloc(arena_run_t *run, void *ptr)
-{
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- size_t mapbits = arena_mapbits_get(chunk, pageind);
- szind_t binind = arena_ptr_small_binind_get(ptr, mapbits);
- arena_bin_info_t *bin_info = &arena_bin_info[binind];
- unsigned regind = arena_run_regind(run, bin_info, ptr);
-
- assert(run->nfree < bin_info->nregs);
- /* Freeing an interior pointer can cause assertion failure. */
- assert(((uintptr_t)ptr -
- ((uintptr_t)arena_miscelm_to_rpages(arena_run_to_miscelm(run)) +
- (uintptr_t)bin_info->reg0_offset)) %
- (uintptr_t)bin_info->reg_interval == 0);
- assert((uintptr_t)ptr >=
- (uintptr_t)arena_miscelm_to_rpages(arena_run_to_miscelm(run)) +
- (uintptr_t)bin_info->reg0_offset);
- /* Freeing an unallocated pointer can cause assertion failure. */
- assert(bitmap_get(run->bitmap, &bin_info->bitmap_info, regind));
-
- bitmap_unset(run->bitmap, &bin_info->bitmap_info, regind);
- run->nfree++;
-}
-
-JEMALLOC_INLINE_C void
-arena_run_zero(arena_chunk_t *chunk, size_t run_ind, size_t npages)
-{
-
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED((void *)((uintptr_t)chunk +
- (run_ind << LG_PAGE)), (npages << LG_PAGE));
- memset((void *)((uintptr_t)chunk + (run_ind << LG_PAGE)), 0,
- (npages << LG_PAGE));
-}
-
-JEMALLOC_INLINE_C void
-arena_run_page_mark_zeroed(arena_chunk_t *chunk, size_t run_ind)
-{
+void
+arena_extent_ralloc_large_expand(tsdn_t *tsdn, arena_t *arena, extent_t *extent,
+ size_t oldusize) {
+ size_t usize = extent_usize_get(extent);
+ size_t udiff = usize - oldusize;
- JEMALLOC_VALGRIND_MAKE_MEM_DEFINED((void *)((uintptr_t)chunk + (run_ind
- << LG_PAGE)), PAGE);
+ if (config_stats) {
+ arena_stats_lock(tsdn, &arena->stats);
+ arena_large_ralloc_stats_update(tsdn, arena, oldusize, usize);
+ arena_stats_unlock(tsdn, &arena->stats);
+ }
+ arena_nactive_add(arena, udiff >> LG_PAGE);
}
-JEMALLOC_INLINE_C void
-arena_run_page_validate_zeroed(arena_chunk_t *chunk, size_t run_ind)
-{
- size_t i;
- UNUSED size_t *p = (size_t *)((uintptr_t)chunk + (run_ind << LG_PAGE));
-
- arena_run_page_mark_zeroed(chunk, run_ind);
- for (i = 0; i < PAGE / sizeof(size_t); i++)
- assert(p[i] == 0);
+static ssize_t
+arena_decay_ms_read(arena_decay_t *decay) {
+ return atomic_load_zd(&decay->time_ms, ATOMIC_RELAXED);
}
static void
-arena_cactive_update(arena_t *arena, size_t add_pages, size_t sub_pages)
-{
-
- if (config_stats) {
- ssize_t cactive_diff = CHUNK_CEILING((arena->nactive + add_pages
- - sub_pages) << LG_PAGE) - CHUNK_CEILING(arena->nactive <<
- LG_PAGE);
- if (cactive_diff != 0)
- stats_cactive_add(cactive_diff);
- }
+arena_decay_ms_write(arena_decay_t *decay, ssize_t decay_ms) {
+ atomic_store_zd(&decay->time_ms, decay_ms, ATOMIC_RELAXED);
}
static void
-arena_run_split_remove(arena_t *arena, arena_chunk_t *chunk, size_t run_ind,
- size_t flag_dirty, size_t flag_decommitted, size_t need_pages)
-{
- size_t total_pages, rem_pages;
-
- assert(flag_dirty == 0 || flag_decommitted == 0);
-
- total_pages = arena_mapbits_unallocated_size_get(chunk, run_ind) >>
- LG_PAGE;
- assert(arena_mapbits_dirty_get(chunk, run_ind+total_pages-1) ==
- flag_dirty);
- assert(need_pages <= total_pages);
- rem_pages = total_pages - need_pages;
-
- arena_avail_remove(arena, chunk, run_ind, total_pages);
- if (flag_dirty != 0)
- arena_run_dirty_remove(arena, chunk, run_ind, total_pages);
- arena_cactive_update(arena, need_pages, 0);
- arena->nactive += need_pages;
-
- /* Keep track of trailing unused pages for later use. */
- if (rem_pages > 0) {
- size_t flags = flag_dirty | flag_decommitted;
- size_t flag_unzeroed_mask = (flags == 0) ? CHUNK_MAP_UNZEROED :
- 0;
-
- arena_mapbits_unallocated_set(chunk, run_ind+need_pages,
- (rem_pages << LG_PAGE), flags |
- (arena_mapbits_unzeroed_get(chunk, run_ind+need_pages) &
- flag_unzeroed_mask));
- arena_mapbits_unallocated_set(chunk, run_ind+total_pages-1,
- (rem_pages << LG_PAGE), flags |
- (arena_mapbits_unzeroed_get(chunk, run_ind+total_pages-1) &
- flag_unzeroed_mask));
- if (flag_dirty != 0) {
- arena_run_dirty_insert(arena, chunk, run_ind+need_pages,
- rem_pages);
- }
- arena_avail_insert(arena, chunk, run_ind+need_pages, rem_pages);
- }
-}
-
-static bool
-arena_run_split_large_helper(arena_t *arena, arena_run_t *run, size_t size,
- bool remove, bool zero)
-{
- arena_chunk_t *chunk;
- arena_chunk_map_misc_t *miscelm;
- size_t flag_dirty, flag_decommitted, run_ind, need_pages;
- size_t flag_unzeroed_mask;
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- miscelm = arena_run_to_miscelm(run);
- run_ind = arena_miscelm_to_pageind(miscelm);
- flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
- flag_decommitted = arena_mapbits_decommitted_get(chunk, run_ind);
- need_pages = (size >> LG_PAGE);
- assert(need_pages > 0);
-
- if (flag_decommitted != 0 && arena->chunk_hooks.commit(chunk, chunksize,
- run_ind << LG_PAGE, size, arena->ind))
- return (true);
-
- if (remove) {
- arena_run_split_remove(arena, chunk, run_ind, flag_dirty,
- flag_decommitted, need_pages);
- }
-
- if (zero) {
- if (flag_decommitted != 0) {
- /* The run is untouched, and therefore zeroed. */
- JEMALLOC_VALGRIND_MAKE_MEM_DEFINED((void
- *)((uintptr_t)chunk + (run_ind << LG_PAGE)),
- (need_pages << LG_PAGE));
- } else if (flag_dirty != 0) {
- /* The run is dirty, so all pages must be zeroed. */
- arena_run_zero(chunk, run_ind, need_pages);
- } else {
- /*
- * The run is clean, so some pages may be zeroed (i.e.
- * never before touched).
- */
- size_t i;
- for (i = 0; i < need_pages; i++) {
- if (arena_mapbits_unzeroed_get(chunk, run_ind+i)
- != 0)
- arena_run_zero(chunk, run_ind+i, 1);
- else if (config_debug) {
- arena_run_page_validate_zeroed(chunk,
- run_ind+i);
- } else {
- arena_run_page_mark_zeroed(chunk,
- run_ind+i);
- }
- }
- }
- } else {
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED((void *)((uintptr_t)chunk +
- (run_ind << LG_PAGE)), (need_pages << LG_PAGE));
- }
-
+arena_decay_deadline_init(arena_decay_t *decay) {
/*
- * Set the last element first, in case the run only contains one page
- * (i.e. both statements set the same element).
+ * Generate a new deadline that is uniformly random within the next
+ * epoch after the current one.
*/
- flag_unzeroed_mask = (flag_dirty | flag_decommitted) == 0 ?
- CHUNK_MAP_UNZEROED : 0;
- arena_mapbits_large_set(chunk, run_ind+need_pages-1, 0, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- run_ind+need_pages-1)));
- arena_mapbits_large_set(chunk, run_ind, size, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk, run_ind)));
- return (false);
-}
-
-static bool
-arena_run_split_large(arena_t *arena, arena_run_t *run, size_t size, bool zero)
-{
-
- return (arena_run_split_large_helper(arena, run, size, true, zero));
-}
-
-static bool
-arena_run_init_large(arena_t *arena, arena_run_t *run, size_t size, bool zero)
-{
-
- return (arena_run_split_large_helper(arena, run, size, false, zero));
-}
-
-static bool
-arena_run_split_small(arena_t *arena, arena_run_t *run, size_t size,
- szind_t binind)
-{
- arena_chunk_t *chunk;
- arena_chunk_map_misc_t *miscelm;
- size_t flag_dirty, flag_decommitted, run_ind, need_pages, i;
-
- assert(binind != BININD_INVALID);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- miscelm = arena_run_to_miscelm(run);
- run_ind = arena_miscelm_to_pageind(miscelm);
- flag_dirty = arena_mapbits_dirty_get(chunk, run_ind);
- flag_decommitted = arena_mapbits_decommitted_get(chunk, run_ind);
- need_pages = (size >> LG_PAGE);
- assert(need_pages > 0);
-
- if (flag_decommitted != 0 && arena->chunk_hooks.commit(chunk, chunksize,
- run_ind << LG_PAGE, size, arena->ind))
- return (true);
+ nstime_copy(&decay->deadline, &decay->epoch);
+ nstime_add(&decay->deadline, &decay->interval);
+ if (arena_decay_ms_read(decay) > 0) {
+ nstime_t jitter;
- arena_run_split_remove(arena, chunk, run_ind, flag_dirty,
- flag_decommitted, need_pages);
-
- for (i = 0; i < need_pages; i++) {
- size_t flag_unzeroed = arena_mapbits_unzeroed_get(chunk,
- run_ind+i);
- arena_mapbits_small_set(chunk, run_ind+i, i, binind,
- flag_unzeroed);
- if (config_debug && flag_dirty == 0 && flag_unzeroed == 0)
- arena_run_page_validate_zeroed(chunk, run_ind+i);
+ nstime_init(&jitter, prng_range_u64(&decay->jitter_state,
+ nstime_ns(&decay->interval)));
+ nstime_add(&decay->deadline, &jitter);
}
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED((void *)((uintptr_t)chunk +
- (run_ind << LG_PAGE)), (need_pages << LG_PAGE));
- return (false);
}
-static arena_chunk_t *
-arena_chunk_init_spare(arena_t *arena)
-{
- arena_chunk_t *chunk;
-
- assert(arena->spare != NULL);
-
- chunk = arena->spare;
- arena->spare = NULL;
-
- assert(arena_mapbits_allocated_get(chunk, map_bias) == 0);
- assert(arena_mapbits_allocated_get(chunk, chunk_npages-1) == 0);
- assert(arena_mapbits_unallocated_size_get(chunk, map_bias) ==
- arena_maxrun);
- assert(arena_mapbits_unallocated_size_get(chunk, chunk_npages-1) ==
- arena_maxrun);
- assert(arena_mapbits_dirty_get(chunk, map_bias) ==
- arena_mapbits_dirty_get(chunk, chunk_npages-1));
-
- return (chunk);
+static bool
+arena_decay_deadline_reached(const arena_decay_t *decay, const nstime_t *time) {
+ return (nstime_compare(&decay->deadline, time) <= 0);
}
-static bool
-arena_chunk_register(arena_t *arena, arena_chunk_t *chunk, bool zero)
-{
+static size_t
+arena_decay_backlog_npages_limit(const arena_decay_t *decay) {
+ uint64_t sum;
+ size_t npages_limit_backlog;
+ unsigned i;
/*
- * The extent node notion of "committed" doesn't directly apply to
- * arena chunks. Arbitrarily mark them as committed. The commit state
- * of runs is tracked individually, and upon chunk deallocation the
- * entire chunk is in a consistent commit state.
+ * For each element of decay_backlog, multiply by the corresponding
+ * fixed-point smoothstep decay factor. Sum the products, then divide
+ * to round down to the nearest whole number of pages.
*/
- extent_node_init(&chunk->node, arena, chunk, chunksize, zero, true);
- extent_node_achunk_set(&chunk->node, true);
- return (chunk_register(chunk, &chunk->node));
-}
-
-static arena_chunk_t *
-arena_chunk_alloc_internal_hard(arena_t *arena, chunk_hooks_t *chunk_hooks,
- bool *zero, bool *commit)
-{
- arena_chunk_t *chunk;
-
- malloc_mutex_unlock(&arena->lock);
-
- chunk = (arena_chunk_t *)chunk_alloc_wrapper(arena, chunk_hooks, NULL,
- chunksize, chunksize, zero, commit);
- if (chunk != NULL && !*commit) {
- /* Commit header. */
- if (chunk_hooks->commit(chunk, chunksize, 0, map_bias <<
- LG_PAGE, arena->ind)) {
- chunk_dalloc_wrapper(arena, chunk_hooks,
- (void *)chunk, chunksize, *commit);
- chunk = NULL;
- }
- }
- if (chunk != NULL && arena_chunk_register(arena, chunk, *zero)) {
- if (!*commit) {
- /* Undo commit of header. */
- chunk_hooks->decommit(chunk, chunksize, 0, map_bias <<
- LG_PAGE, arena->ind);
- }
- chunk_dalloc_wrapper(arena, chunk_hooks, (void *)chunk,
- chunksize, *commit);
- chunk = NULL;
+ sum = 0;
+ for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) {
+ sum += decay->backlog[i] * h_steps[i];
}
+ npages_limit_backlog = (size_t)(sum >> SMOOTHSTEP_BFP);
- malloc_mutex_lock(&arena->lock);
- return (chunk);
+ return npages_limit_backlog;
}
-static arena_chunk_t *
-arena_chunk_alloc_internal(arena_t *arena, bool *zero, bool *commit)
-{
- arena_chunk_t *chunk;
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
+static void
+arena_decay_backlog_update_last(arena_decay_t *decay, size_t current_npages) {
+ size_t npages_delta = (current_npages > decay->nunpurged) ?
+ current_npages - decay->nunpurged : 0;
+ decay->backlog[SMOOTHSTEP_NSTEPS-1] = npages_delta;
- chunk = chunk_alloc_cache(arena, &chunk_hooks, NULL, chunksize,
- chunksize, zero, true);
- if (chunk != NULL) {
- if (arena_chunk_register(arena, chunk, *zero)) {
- chunk_dalloc_cache(arena, &chunk_hooks, chunk,
- chunksize, true);
- return (NULL);
+ if (config_debug) {
+ if (current_npages > decay->ceil_npages) {
+ decay->ceil_npages = current_npages;
}
- *commit = true;
- }
- if (chunk == NULL) {
- chunk = arena_chunk_alloc_internal_hard(arena, &chunk_hooks,
- zero, commit);
- }
-
- if (config_stats && chunk != NULL) {
- arena->stats.mapped += chunksize;
- arena->stats.metadata_mapped += (map_bias << LG_PAGE);
- }
-
- return (chunk);
-}
-
-static arena_chunk_t *
-arena_chunk_init_hard(arena_t *arena)
-{
- arena_chunk_t *chunk;
- bool zero, commit;
- size_t flag_unzeroed, flag_decommitted, i;
-
- assert(arena->spare == NULL);
-
- zero = false;
- commit = false;
- chunk = arena_chunk_alloc_internal(arena, &zero, &commit);
- if (chunk == NULL)
- return (NULL);
-
- /*
- * Initialize the map to contain one maximal free untouched run. Mark
- * the pages as zeroed if chunk_alloc() returned a zeroed or decommitted
- * chunk.
- */
- flag_unzeroed = (zero || !commit) ? 0 : CHUNK_MAP_UNZEROED;
- flag_decommitted = commit ? 0 : CHUNK_MAP_DECOMMITTED;
- arena_mapbits_unallocated_set(chunk, map_bias, arena_maxrun,
- flag_unzeroed | flag_decommitted);
- /*
- * There is no need to initialize the internal page map entries unless
- * the chunk is not zeroed.
- */
- if (!zero) {
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(
- (void *)arena_bitselm_get(chunk, map_bias+1),
- (size_t)((uintptr_t) arena_bitselm_get(chunk,
- chunk_npages-1) - (uintptr_t)arena_bitselm_get(chunk,
- map_bias+1)));
- for (i = map_bias+1; i < chunk_npages-1; i++)
- arena_mapbits_internal_set(chunk, i, flag_unzeroed);
- } else {
- JEMALLOC_VALGRIND_MAKE_MEM_DEFINED((void
- *)arena_bitselm_get(chunk, map_bias+1), (size_t)((uintptr_t)
- arena_bitselm_get(chunk, chunk_npages-1) -
- (uintptr_t)arena_bitselm_get(chunk, map_bias+1)));
- if (config_debug) {
- for (i = map_bias+1; i < chunk_npages-1; i++) {
- assert(arena_mapbits_unzeroed_get(chunk, i) ==
- flag_unzeroed);
- }
+ size_t npages_limit = arena_decay_backlog_npages_limit(decay);
+ assert(decay->ceil_npages >= npages_limit);
+ if (decay->ceil_npages > npages_limit) {
+ decay->ceil_npages = npages_limit;
}
}
- arena_mapbits_unallocated_set(chunk, chunk_npages-1, arena_maxrun,
- flag_unzeroed);
-
- return (chunk);
-}
-
-static arena_chunk_t *
-arena_chunk_alloc(arena_t *arena)
-{
- arena_chunk_t *chunk;
-
- if (arena->spare != NULL)
- chunk = arena_chunk_init_spare(arena);
- else {
- chunk = arena_chunk_init_hard(arena);
- if (chunk == NULL)
- return (NULL);
- }
-
- /* Insert the run into the runs_avail tree. */
- arena_avail_insert(arena, chunk, map_bias, chunk_npages-map_bias);
-
- return (chunk);
}
static void
-arena_chunk_dalloc(arena_t *arena, arena_chunk_t *chunk)
-{
-
- assert(arena_mapbits_allocated_get(chunk, map_bias) == 0);
- assert(arena_mapbits_allocated_get(chunk, chunk_npages-1) == 0);
- assert(arena_mapbits_unallocated_size_get(chunk, map_bias) ==
- arena_maxrun);
- assert(arena_mapbits_unallocated_size_get(chunk, chunk_npages-1) ==
- arena_maxrun);
- assert(arena_mapbits_dirty_get(chunk, map_bias) ==
- arena_mapbits_dirty_get(chunk, chunk_npages-1));
- assert(arena_mapbits_decommitted_get(chunk, map_bias) ==
- arena_mapbits_decommitted_get(chunk, chunk_npages-1));
-
- /*
- * Remove run from the runs_avail tree, so that the arena does not use
- * it.
- */
- arena_avail_remove(arena, chunk, map_bias, chunk_npages-map_bias);
+arena_decay_backlog_update(arena_decay_t *decay, uint64_t nadvance_u64,
+ size_t current_npages) {
+ if (nadvance_u64 >= SMOOTHSTEP_NSTEPS) {
+ memset(decay->backlog, 0, (SMOOTHSTEP_NSTEPS-1) *
+ sizeof(size_t));
+ } else {
+ size_t nadvance_z = (size_t)nadvance_u64;
- if (arena->spare != NULL) {
- arena_chunk_t *spare = arena->spare;
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
- bool committed;
+ assert((uint64_t)nadvance_z == nadvance_u64);
- arena->spare = chunk;
- if (arena_mapbits_dirty_get(spare, map_bias) != 0) {
- arena_run_dirty_remove(arena, spare, map_bias,
- chunk_npages-map_bias);
+ memmove(decay->backlog, &decay->backlog[nadvance_z],
+ (SMOOTHSTEP_NSTEPS - nadvance_z) * sizeof(size_t));
+ if (nadvance_z > 1) {
+ memset(&decay->backlog[SMOOTHSTEP_NSTEPS -
+ nadvance_z], 0, (nadvance_z-1) * sizeof(size_t));
}
+ }
- chunk_deregister(spare, &spare->node);
-
- committed = (arena_mapbits_decommitted_get(spare, map_bias) ==
- 0);
- if (!committed) {
- /*
- * Decommit the header. Mark the chunk as decommitted
- * even if header decommit fails, since treating a
- * partially committed chunk as committed has a high
- * potential for causing later access of decommitted
- * memory.
- */
- chunk_hooks = chunk_hooks_get(arena);
- chunk_hooks.decommit(spare, chunksize, 0, map_bias <<
- LG_PAGE, arena->ind);
- }
-
- chunk_dalloc_cache(arena, &chunk_hooks, (void *)spare,
- chunksize, committed);
-
- if (config_stats) {
- arena->stats.mapped -= chunksize;
- arena->stats.metadata_mapped -= (map_bias << LG_PAGE);
- }
- } else
- arena->spare = chunk;
+ arena_decay_backlog_update_last(decay, current_npages);
}
static void
-arena_huge_malloc_stats_update(arena_t *arena, size_t usize)
-{
- szind_t index = size2index(usize) - nlclasses - NBINS;
-
- cassert(config_stats);
-
- arena->stats.nmalloc_huge++;
- arena->stats.allocated_huge += usize;
- arena->stats.hstats[index].nmalloc++;
- arena->stats.hstats[index].curhchunks++;
+arena_decay_try_purge(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, size_t current_npages, size_t npages_limit,
+ bool is_background_thread) {
+ if (current_npages > npages_limit) {
+ arena_decay_to_limit(tsdn, arena, decay, extents, false,
+ npages_limit, current_npages - npages_limit,
+ is_background_thread);
+ }
}
static void
-arena_huge_malloc_stats_update_undo(arena_t *arena, size_t usize)
-{
- szind_t index = size2index(usize) - nlclasses - NBINS;
+arena_decay_epoch_advance_helper(arena_decay_t *decay, const nstime_t *time,
+ size_t current_npages) {
+ assert(arena_decay_deadline_reached(decay, time));
- cassert(config_stats);
+ nstime_t delta;
+ nstime_copy(&delta, time);
+ nstime_subtract(&delta, &decay->epoch);
- arena->stats.nmalloc_huge--;
- arena->stats.allocated_huge -= usize;
- arena->stats.hstats[index].nmalloc--;
- arena->stats.hstats[index].curhchunks--;
-}
+ uint64_t nadvance_u64 = nstime_divide(&delta, &decay->interval);
+ assert(nadvance_u64 > 0);
-static void
-arena_huge_dalloc_stats_update(arena_t *arena, size_t usize)
-{
- szind_t index = size2index(usize) - nlclasses - NBINS;
+ /* Add nadvance_u64 decay intervals to epoch. */
+ nstime_copy(&delta, &decay->interval);
+ nstime_imultiply(&delta, nadvance_u64);
+ nstime_add(&decay->epoch, &delta);
- cassert(config_stats);
+ /* Set a new deadline. */
+ arena_decay_deadline_init(decay);
- arena->stats.ndalloc_huge++;
- arena->stats.allocated_huge -= usize;
- arena->stats.hstats[index].ndalloc++;
- arena->stats.hstats[index].curhchunks--;
+ /* Update the backlog. */
+ arena_decay_backlog_update(decay, nadvance_u64, current_npages);
}
static void
-arena_huge_dalloc_stats_update_undo(arena_t *arena, size_t usize)
-{
- szind_t index = size2index(usize) - nlclasses - NBINS;
-
- cassert(config_stats);
+arena_decay_epoch_advance(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, const nstime_t *time, bool is_background_thread) {
+ size_t current_npages = extents_npages_get(extents);
+ arena_decay_epoch_advance_helper(decay, time, current_npages);
- arena->stats.ndalloc_huge--;
- arena->stats.allocated_huge += usize;
- arena->stats.hstats[index].ndalloc--;
- arena->stats.hstats[index].curhchunks++;
-}
-
-static void
-arena_huge_ralloc_stats_update(arena_t *arena, size_t oldsize, size_t usize)
-{
+ size_t npages_limit = arena_decay_backlog_npages_limit(decay);
+ /* We may unlock decay->mtx when try_purge(). Finish logging first. */
+ decay->nunpurged = (npages_limit > current_npages) ? npages_limit :
+ current_npages;
- arena_huge_dalloc_stats_update(arena, oldsize);
- arena_huge_malloc_stats_update(arena, usize);
+ if (!background_thread_enabled() || is_background_thread) {
+ arena_decay_try_purge(tsdn, arena, decay, extents,
+ current_npages, npages_limit, is_background_thread);
+ }
}
static void
-arena_huge_ralloc_stats_update_undo(arena_t *arena, size_t oldsize,
- size_t usize)
-{
-
- arena_huge_dalloc_stats_update_undo(arena, oldsize);
- arena_huge_malloc_stats_update_undo(arena, usize);
-}
-
-extent_node_t *
-arena_node_alloc(arena_t *arena)
-{
- extent_node_t *node;
-
- malloc_mutex_lock(&arena->node_cache_mtx);
- node = ql_last(&arena->node_cache, ql_link);
- if (node == NULL) {
- malloc_mutex_unlock(&arena->node_cache_mtx);
- return (base_alloc(sizeof(extent_node_t)));
+arena_decay_reinit(arena_decay_t *decay, ssize_t decay_ms) {
+ arena_decay_ms_write(decay, decay_ms);
+ if (decay_ms > 0) {
+ nstime_init(&decay->interval, (uint64_t)decay_ms *
+ KQU(1000000));
+ nstime_idivide(&decay->interval, SMOOTHSTEP_NSTEPS);
}
- ql_tail_remove(&arena->node_cache, extent_node_t, ql_link);
- malloc_mutex_unlock(&arena->node_cache_mtx);
- return (node);
-}
-
-void
-arena_node_dalloc(arena_t *arena, extent_node_t *node)
-{
- malloc_mutex_lock(&arena->node_cache_mtx);
- ql_elm_new(node, ql_link);
- ql_tail_insert(&arena->node_cache, node, ql_link);
- malloc_mutex_unlock(&arena->node_cache_mtx);
+ nstime_init(&decay->epoch, 0);
+ nstime_update(&decay->epoch);
+ decay->jitter_state = (uint64_t)(uintptr_t)decay;
+ arena_decay_deadline_init(decay);
+ decay->nunpurged = 0;
+ memset(decay->backlog, 0, SMOOTHSTEP_NSTEPS * sizeof(size_t));
}
-static void *
-arena_chunk_alloc_huge_hard(arena_t *arena, chunk_hooks_t *chunk_hooks,
- size_t usize, size_t alignment, bool *zero, size_t csize)
-{
- void *ret;
- bool commit = true;
-
- ret = chunk_alloc_wrapper(arena, chunk_hooks, NULL, csize, alignment,
- zero, &commit);
- if (ret == NULL) {
- /* Revert optimistic stats updates. */
- malloc_mutex_lock(&arena->lock);
- if (config_stats) {
- arena_huge_malloc_stats_update_undo(arena, usize);
- arena->stats.mapped -= usize;
+static bool
+arena_decay_init(arena_decay_t *decay, ssize_t decay_ms,
+ arena_stats_decay_t *stats) {
+ if (config_debug) {
+ for (size_t i = 0; i < sizeof(arena_decay_t); i++) {
+ assert(((char *)decay)[i] == 0);
}
- arena->nactive -= (usize >> LG_PAGE);
- malloc_mutex_unlock(&arena->lock);
- }
-
- return (ret);
-}
-
-void *
-arena_chunk_alloc_huge(arena_t *arena, size_t usize, size_t alignment,
- bool *zero)
-{
- void *ret;
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
- size_t csize = CHUNK_CEILING(usize);
-
- malloc_mutex_lock(&arena->lock);
-
- /* Optimistically update stats. */
- if (config_stats) {
- arena_huge_malloc_stats_update(arena, usize);
- arena->stats.mapped += usize;
+ decay->ceil_npages = 0;
}
- arena->nactive += (usize >> LG_PAGE);
-
- ret = chunk_alloc_cache(arena, &chunk_hooks, NULL, csize, alignment,
- zero, true);
- malloc_mutex_unlock(&arena->lock);
- if (ret == NULL) {
- ret = arena_chunk_alloc_huge_hard(arena, &chunk_hooks, usize,
- alignment, zero, csize);
+ if (malloc_mutex_init(&decay->mtx, "decay", WITNESS_RANK_DECAY,
+ malloc_mutex_rank_exclusive)) {
+ return true;
}
-
- if (config_stats && ret != NULL)
- stats_cactive_add(usize);
- return (ret);
-}
-
-void
-arena_chunk_dalloc_huge(arena_t *arena, void *chunk, size_t usize)
-{
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
- size_t csize;
-
- csize = CHUNK_CEILING(usize);
- malloc_mutex_lock(&arena->lock);
+ decay->purging = false;
+ arena_decay_reinit(decay, decay_ms);
+ /* Memory is zeroed, so there is no need to clear stats. */
if (config_stats) {
- arena_huge_dalloc_stats_update(arena, usize);
- arena->stats.mapped -= usize;
- stats_cactive_sub(usize);
- }
- arena->nactive -= (usize >> LG_PAGE);
-
- chunk_dalloc_cache(arena, &chunk_hooks, chunk, csize, true);
- malloc_mutex_unlock(&arena->lock);
-}
-
-void
-arena_chunk_ralloc_huge_similar(arena_t *arena, void *chunk, size_t oldsize,
- size_t usize)
-{
-
- assert(CHUNK_CEILING(oldsize) == CHUNK_CEILING(usize));
- assert(oldsize != usize);
-
- malloc_mutex_lock(&arena->lock);
- if (config_stats)
- arena_huge_ralloc_stats_update(arena, oldsize, usize);
- if (oldsize < usize) {
- size_t udiff = usize - oldsize;
- arena->nactive += udiff >> LG_PAGE;
- if (config_stats)
- stats_cactive_add(udiff);
- } else {
- size_t udiff = oldsize - usize;
- arena->nactive -= udiff >> LG_PAGE;
- if (config_stats)
- stats_cactive_sub(udiff);
+ decay->stats = stats;
}
- malloc_mutex_unlock(&arena->lock);
+ return false;
}
-void
-arena_chunk_ralloc_huge_shrink(arena_t *arena, void *chunk, size_t oldsize,
- size_t usize)
-{
- size_t udiff = oldsize - usize;
- size_t cdiff = CHUNK_CEILING(oldsize) - CHUNK_CEILING(usize);
-
- malloc_mutex_lock(&arena->lock);
- if (config_stats) {
- arena_huge_ralloc_stats_update(arena, oldsize, usize);
- if (cdiff != 0) {
- arena->stats.mapped -= cdiff;
- stats_cactive_sub(udiff);
- }
+static bool
+arena_decay_ms_valid(ssize_t decay_ms) {
+ if (decay_ms < -1) {
+ return false;
}
- arena->nactive -= udiff >> LG_PAGE;
-
- if (cdiff != 0) {
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
- void *nchunk = (void *)((uintptr_t)chunk +
- CHUNK_CEILING(usize));
-
- chunk_dalloc_cache(arena, &chunk_hooks, nchunk, cdiff, true);
+ if (decay_ms == -1 || (uint64_t)decay_ms <= NSTIME_SEC_MAX *
+ KQU(1000)) {
+ return true;
}
- malloc_mutex_unlock(&arena->lock);
+ return false;
}
static bool
-arena_chunk_ralloc_huge_expand_hard(arena_t *arena, chunk_hooks_t *chunk_hooks,
- void *chunk, size_t oldsize, size_t usize, bool *zero, void *nchunk,
- size_t udiff, size_t cdiff)
-{
- bool err;
- bool commit = true;
-
- err = (chunk_alloc_wrapper(arena, chunk_hooks, nchunk, cdiff, chunksize,
- zero, &commit) == NULL);
- if (err) {
- /* Revert optimistic stats updates. */
- malloc_mutex_lock(&arena->lock);
- if (config_stats) {
- arena_huge_ralloc_stats_update_undo(arena, oldsize,
- usize);
- arena->stats.mapped -= cdiff;
+arena_maybe_decay(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, bool is_background_thread) {
+ malloc_mutex_assert_owner(tsdn, &decay->mtx);
+
+ /* Purge all or nothing if the option is disabled. */
+ ssize_t decay_ms = arena_decay_ms_read(decay);
+ if (decay_ms <= 0) {
+ if (decay_ms == 0) {
+ arena_decay_to_limit(tsdn, arena, decay, extents, false,
+ 0, extents_npages_get(extents),
+ is_background_thread);
}
- arena->nactive -= (udiff >> LG_PAGE);
- malloc_mutex_unlock(&arena->lock);
- } else if (chunk_hooks->merge(chunk, CHUNK_CEILING(oldsize), nchunk,
- cdiff, true, arena->ind)) {
- chunk_dalloc_arena(arena, chunk_hooks, nchunk, cdiff, *zero,
- true);
- err = true;
- }
- return (err);
-}
-
-bool
-arena_chunk_ralloc_huge_expand(arena_t *arena, void *chunk, size_t oldsize,
- size_t usize, bool *zero)
-{
- bool err;
- chunk_hooks_t chunk_hooks = chunk_hooks_get(arena);
- void *nchunk = (void *)((uintptr_t)chunk + CHUNK_CEILING(oldsize));
- size_t udiff = usize - oldsize;
- size_t cdiff = CHUNK_CEILING(usize) - CHUNK_CEILING(oldsize);
-
- malloc_mutex_lock(&arena->lock);
-
- /* Optimistically update stats. */
- if (config_stats) {
- arena_huge_ralloc_stats_update(arena, oldsize, usize);
- arena->stats.mapped += cdiff;
+ return false;
}
- arena->nactive += (udiff >> LG_PAGE);
- err = (chunk_alloc_cache(arena, &arena->chunk_hooks, nchunk, cdiff,
- chunksize, zero, true) == NULL);
- malloc_mutex_unlock(&arena->lock);
- if (err) {
- err = arena_chunk_ralloc_huge_expand_hard(arena, &chunk_hooks,
- chunk, oldsize, usize, zero, nchunk, udiff,
- cdiff);
- } else if (chunk_hooks.merge(chunk, CHUNK_CEILING(oldsize), nchunk,
- cdiff, true, arena->ind)) {
- chunk_dalloc_arena(arena, &chunk_hooks, nchunk, cdiff, *zero,
- true);
- err = true;
+ nstime_t time;
+ nstime_init(&time, 0);
+ nstime_update(&time);
+ if (unlikely(!nstime_monotonic() && nstime_compare(&decay->epoch, &time)
+ > 0)) {
+ /*
+ * Time went backwards. Move the epoch back in time and
+ * generate a new deadline, with the expectation that time
+ * typically flows forward for long enough periods of time that
+ * epochs complete. Unfortunately, this strategy is susceptible
+ * to clock jitter triggering premature epoch advances, but
+ * clock jitter estimation and compensation isn't feasible here
+ * because calls into this code are event-driven.
+ */
+ nstime_copy(&decay->epoch, &time);
+ arena_decay_deadline_init(decay);
+ } else {
+ /* Verify that time does not go backwards. */
+ assert(nstime_compare(&decay->epoch, &time) <= 0);
}
- if (config_stats && !err)
- stats_cactive_add(udiff);
- return (err);
-}
-
-/*
- * Do first-best-fit run selection, i.e. select the lowest run that best fits.
- * Run sizes are quantized, so not all candidate runs are necessarily exactly
- * the same size.
- */
-static arena_run_t *
-arena_run_first_best_fit(arena_t *arena, size_t size)
-{
- size_t search_size = run_quantize_first(size);
- arena_chunk_map_misc_t *key = arena_miscelm_key_create(search_size);
- arena_chunk_map_misc_t *miscelm =
- arena_avail_tree_nsearch(&arena->runs_avail, key);
- if (miscelm == NULL)
- return (NULL);
- return (&miscelm->run);
-}
-
-static arena_run_t *
-arena_run_alloc_large_helper(arena_t *arena, size_t size, bool zero)
-{
- arena_run_t *run = arena_run_first_best_fit(arena, s2u(size));
- if (run != NULL) {
- if (arena_run_split_large(arena, run, size, zero))
- run = NULL;
- }
- return (run);
-}
-
-static arena_run_t *
-arena_run_alloc_large(arena_t *arena, size_t size, bool zero)
-{
- arena_chunk_t *chunk;
- arena_run_t *run;
-
- assert(size <= arena_maxrun);
- assert(size == PAGE_CEILING(size));
-
- /* Search the arena's chunks for the lowest best fit. */
- run = arena_run_alloc_large_helper(arena, size, zero);
- if (run != NULL)
- return (run);
-
/*
- * No usable runs. Create a new chunk from which to allocate the run.
+ * If the deadline has been reached, advance to the current epoch and
+ * purge to the new limit if necessary. Note that dirty pages created
+ * during the current epoch are not subject to purge until a future
+ * epoch, so as a result purging only happens during epoch advances, or
+ * being triggered by background threads (scheduled event).
*/
- chunk = arena_chunk_alloc(arena);
- if (chunk != NULL) {
- run = &arena_miscelm_get(chunk, map_bias)->run;
- if (arena_run_split_large(arena, run, size, zero))
- run = NULL;
- return (run);
+ bool advance_epoch = arena_decay_deadline_reached(decay, &time);
+ if (advance_epoch) {
+ arena_decay_epoch_advance(tsdn, arena, decay, extents, &time,
+ is_background_thread);
+ } else if (is_background_thread) {
+ arena_decay_try_purge(tsdn, arena, decay, extents,
+ extents_npages_get(extents),
+ arena_decay_backlog_npages_limit(decay),
+ is_background_thread);
}
- /*
- * arena_chunk_alloc() failed, but another thread may have made
- * sufficient memory available while this one dropped arena->lock in
- * arena_chunk_alloc(), so search one more time.
- */
- return (arena_run_alloc_large_helper(arena, size, zero));
+ return advance_epoch;
}
-static arena_run_t *
-arena_run_alloc_small_helper(arena_t *arena, size_t size, szind_t binind)
-{
- arena_run_t *run = arena_run_first_best_fit(arena, size);
- if (run != NULL) {
- if (arena_run_split_small(arena, run, size, binind))
- run = NULL;
- }
- return (run);
+static ssize_t
+arena_decay_ms_get(arena_decay_t *decay) {
+ return arena_decay_ms_read(decay);
}
-static arena_run_t *
-arena_run_alloc_small(arena_t *arena, size_t size, szind_t binind)
-{
- arena_chunk_t *chunk;
- arena_run_t *run;
-
- assert(size <= arena_maxrun);
- assert(size == PAGE_CEILING(size));
- assert(binind != BININD_INVALID);
+ssize_t
+arena_dirty_decay_ms_get(arena_t *arena) {
+ return arena_decay_ms_get(&arena->decay_dirty);
+}
- /* Search the arena's chunks for the lowest best fit. */
- run = arena_run_alloc_small_helper(arena, size, binind);
- if (run != NULL)
- return (run);
+ssize_t
+arena_muzzy_decay_ms_get(arena_t *arena) {
+ return arena_decay_ms_get(&arena->decay_muzzy);
+}
- /*
- * No usable runs. Create a new chunk from which to allocate the run.
- */
- chunk = arena_chunk_alloc(arena);
- if (chunk != NULL) {
- run = &arena_miscelm_get(chunk, map_bias)->run;
- if (arena_run_split_small(arena, run, size, binind))
- run = NULL;
- return (run);
+static bool
+arena_decay_ms_set(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, ssize_t decay_ms) {
+ if (!arena_decay_ms_valid(decay_ms)) {
+ return true;
}
+ malloc_mutex_lock(tsdn, &decay->mtx);
/*
- * arena_chunk_alloc() failed, but another thread may have made
- * sufficient memory available while this one dropped arena->lock in
- * arena_chunk_alloc(), so search one more time.
+ * Restart decay backlog from scratch, which may cause many dirty pages
+ * to be immediately purged. It would conceptually be possible to map
+ * the old backlog onto the new backlog, but there is no justification
+ * for such complexity since decay_ms changes are intended to be
+ * infrequent, either between the {-1, 0, >0} states, or a one-time
+ * arbitrary change during initial arena configuration.
*/
- return (arena_run_alloc_small_helper(arena, size, binind));
-}
-
-static bool
-arena_lg_dirty_mult_valid(ssize_t lg_dirty_mult)
-{
+ arena_decay_reinit(decay, decay_ms);
+ arena_maybe_decay(tsdn, arena, decay, extents, false);
+ malloc_mutex_unlock(tsdn, &decay->mtx);
- return (lg_dirty_mult >= -1 && lg_dirty_mult < (ssize_t)(sizeof(size_t)
- << 3));
-}
-
-ssize_t
-arena_lg_dirty_mult_get(arena_t *arena)
-{
- ssize_t lg_dirty_mult;
-
- malloc_mutex_lock(&arena->lock);
- lg_dirty_mult = arena->lg_dirty_mult;
- malloc_mutex_unlock(&arena->lock);
-
- return (lg_dirty_mult);
+ return false;
}
bool
-arena_lg_dirty_mult_set(arena_t *arena, ssize_t lg_dirty_mult)
-{
-
- if (!arena_lg_dirty_mult_valid(lg_dirty_mult))
- return (true);
-
- malloc_mutex_lock(&arena->lock);
- arena->lg_dirty_mult = lg_dirty_mult;
- arena_maybe_purge(arena);
- malloc_mutex_unlock(&arena->lock);
-
- return (false);
+arena_dirty_decay_ms_set(tsdn_t *tsdn, arena_t *arena,
+ ssize_t decay_ms) {
+ return arena_decay_ms_set(tsdn, arena, &arena->decay_dirty,
+ &arena->extents_dirty, decay_ms);
}
-void
-arena_maybe_purge(arena_t *arena)
-{
-
- /* Don't purge if the option is disabled. */
- if (arena->lg_dirty_mult < 0)
- return;
- /* Don't recursively purge. */
- if (arena->purging)
- return;
- /*
- * Iterate, since preventing recursive purging could otherwise leave too
- * many dirty pages.
- */
- while (true) {
- size_t threshold = (arena->nactive >> arena->lg_dirty_mult);
- if (threshold < chunk_npages)
- threshold = chunk_npages;
- /*
- * Don't purge unless the number of purgeable pages exceeds the
- * threshold.
- */
- if (arena->ndirty <= threshold)
- return;
- arena_purge(arena, false);
- }
-}
-
-static size_t
-arena_dirty_count(arena_t *arena)
-{
- size_t ndirty = 0;
- arena_runs_dirty_link_t *rdelm;
- extent_node_t *chunkselm;
-
- for (rdelm = qr_next(&arena->runs_dirty, rd_link),
- chunkselm = qr_next(&arena->chunks_cache, cc_link);
- rdelm != &arena->runs_dirty; rdelm = qr_next(rdelm, rd_link)) {
- size_t npages;
-
- if (rdelm == &chunkselm->rd) {
- npages = extent_node_size_get(chunkselm) >> LG_PAGE;
- chunkselm = qr_next(chunkselm, cc_link);
- } else {
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(
- rdelm);
- arena_chunk_map_misc_t *miscelm =
- arena_rd_to_miscelm(rdelm);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
- assert(arena_mapbits_allocated_get(chunk, pageind) ==
- 0);
- assert(arena_mapbits_large_get(chunk, pageind) == 0);
- assert(arena_mapbits_dirty_get(chunk, pageind) != 0);
- npages = arena_mapbits_unallocated_size_get(chunk,
- pageind) >> LG_PAGE;
- }
- ndirty += npages;
- }
-
- return (ndirty);
+bool
+arena_muzzy_decay_ms_set(tsdn_t *tsdn, arena_t *arena,
+ ssize_t decay_ms) {
+ return arena_decay_ms_set(tsdn, arena, &arena->decay_muzzy,
+ &arena->extents_muzzy, decay_ms);
}
static size_t
-arena_compute_npurge(arena_t *arena, bool all)
-{
- size_t npurge;
+arena_stash_decayed(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, size_t npages_limit,
+ size_t npages_decay_max, extent_list_t *decay_extents) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
- /*
- * Compute the minimum number of pages that this thread should try to
- * purge.
- */
- if (!all) {
- size_t threshold = (arena->nactive >> arena->lg_dirty_mult);
- threshold = threshold < chunk_npages ? chunk_npages : threshold;
-
- npurge = arena->ndirty - threshold;
- } else
- npurge = arena->ndirty;
-
- return (npurge);
-}
-
-static size_t
-arena_stash_dirty(arena_t *arena, chunk_hooks_t *chunk_hooks, bool all,
- size_t npurge, arena_runs_dirty_link_t *purge_runs_sentinel,
- extent_node_t *purge_chunks_sentinel)
-{
- arena_runs_dirty_link_t *rdelm, *rdelm_next;
- extent_node_t *chunkselm;
+ /* Stash extents according to npages_limit. */
size_t nstashed = 0;
-
- /* Stash at least npurge pages. */
- for (rdelm = qr_next(&arena->runs_dirty, rd_link),
- chunkselm = qr_next(&arena->chunks_cache, cc_link);
- rdelm != &arena->runs_dirty; rdelm = rdelm_next) {
- size_t npages;
- rdelm_next = qr_next(rdelm, rd_link);
-
- if (rdelm == &chunkselm->rd) {
- extent_node_t *chunkselm_next;
- bool zero;
- UNUSED void *chunk;
-
- chunkselm_next = qr_next(chunkselm, cc_link);
- /*
- * Allocate. chunkselm remains valid due to the
- * dalloc_node=false argument to chunk_alloc_cache().
- */
- zero = false;
- chunk = chunk_alloc_cache(arena, chunk_hooks,
- extent_node_addr_get(chunkselm),
- extent_node_size_get(chunkselm), chunksize, &zero,
- false);
- assert(chunk == extent_node_addr_get(chunkselm));
- assert(zero == extent_node_zeroed_get(chunkselm));
- extent_node_dirty_insert(chunkselm, purge_runs_sentinel,
- purge_chunks_sentinel);
- npages = extent_node_size_get(chunkselm) >> LG_PAGE;
- chunkselm = chunkselm_next;
- } else {
- arena_chunk_t *chunk =
- (arena_chunk_t *)CHUNK_ADDR2BASE(rdelm);
- arena_chunk_map_misc_t *miscelm =
- arena_rd_to_miscelm(rdelm);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
- arena_run_t *run = &miscelm->run;
- size_t run_size =
- arena_mapbits_unallocated_size_get(chunk, pageind);
-
- npages = run_size >> LG_PAGE;
-
- assert(pageind + npages <= chunk_npages);
- assert(arena_mapbits_dirty_get(chunk, pageind) ==
- arena_mapbits_dirty_get(chunk, pageind+npages-1));
-
- /*
- * If purging the spare chunk's run, make it available
- * prior to allocation.
- */
- if (chunk == arena->spare)
- arena_chunk_alloc(arena);
-
- /* Temporarily allocate the free dirty run. */
- arena_run_split_large(arena, run, run_size, false);
- /* Stash. */
- if (false)
- qr_new(rdelm, rd_link); /* Redundant. */
- else {
- assert(qr_next(rdelm, rd_link) == rdelm);
- assert(qr_prev(rdelm, rd_link) == rdelm);
- }
- qr_meld(purge_runs_sentinel, rdelm, rd_link);
- }
-
- nstashed += npages;
- if (!all && nstashed >= npurge)
- break;
+ extent_t *extent;
+ while (nstashed < npages_decay_max &&
+ (extent = extents_evict(tsdn, arena, r_extent_hooks, extents,
+ npages_limit)) != NULL) {
+ extent_list_append(decay_extents, extent);
+ nstashed += extent_size_get(extent) >> LG_PAGE;
}
-
- return (nstashed);
+ return nstashed;
}
static size_t
-arena_purge_stashed(arena_t *arena, chunk_hooks_t *chunk_hooks,
- arena_runs_dirty_link_t *purge_runs_sentinel,
- extent_node_t *purge_chunks_sentinel)
-{
- size_t npurged, nmadvise;
- arena_runs_dirty_link_t *rdelm;
- extent_node_t *chunkselm;
-
- if (config_stats)
+arena_decay_stashed(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, arena_decay_t *decay, extents_t *extents,
+ bool all, extent_list_t *decay_extents, bool is_background_thread) {
+ UNUSED size_t nmadvise, nunmapped;
+ size_t npurged;
+
+ if (config_stats) {
nmadvise = 0;
+ nunmapped = 0;
+ }
npurged = 0;
- malloc_mutex_unlock(&arena->lock);
- for (rdelm = qr_next(purge_runs_sentinel, rd_link),
- chunkselm = qr_next(purge_chunks_sentinel, cc_link);
- rdelm != purge_runs_sentinel; rdelm = qr_next(rdelm, rd_link)) {
- size_t npages;
-
- if (rdelm == &chunkselm->rd) {
- /*
- * Don't actually purge the chunk here because 1)
- * chunkselm is embedded in the chunk and must remain
- * valid, and 2) we deallocate the chunk in
- * arena_unstash_purged(), where it is destroyed,
- * decommitted, or purged, depending on chunk
- * deallocation policy.
- */
- size_t size = extent_node_size_get(chunkselm);
- npages = size >> LG_PAGE;
- chunkselm = qr_next(chunkselm, cc_link);
- } else {
- size_t pageind, run_size, flag_unzeroed, flags, i;
- bool decommitted;
- arena_chunk_t *chunk =
- (arena_chunk_t *)CHUNK_ADDR2BASE(rdelm);
- arena_chunk_map_misc_t *miscelm =
- arena_rd_to_miscelm(rdelm);
- pageind = arena_miscelm_to_pageind(miscelm);
- run_size = arena_mapbits_large_size_get(chunk, pageind);
- npages = run_size >> LG_PAGE;
-
- assert(pageind + npages <= chunk_npages);
- assert(!arena_mapbits_decommitted_get(chunk, pageind));
- assert(!arena_mapbits_decommitted_get(chunk,
- pageind+npages-1));
- decommitted = !chunk_hooks->decommit(chunk, chunksize,
- pageind << LG_PAGE, npages << LG_PAGE, arena->ind);
- if (decommitted) {
- flag_unzeroed = 0;
- flags = CHUNK_MAP_DECOMMITTED;
- } else {
- flag_unzeroed = chunk_purge_wrapper(arena,
- chunk_hooks, chunk, chunksize, pageind <<
- LG_PAGE, run_size) ? CHUNK_MAP_UNZEROED : 0;
- flags = flag_unzeroed;
+ ssize_t muzzy_decay_ms = arena_muzzy_decay_ms_get(arena);
+ for (extent_t *extent = extent_list_first(decay_extents); extent !=
+ NULL; extent = extent_list_first(decay_extents)) {
+ if (config_stats) {
+ nmadvise++;
+ }
+ size_t npages = extent_size_get(extent) >> LG_PAGE;
+ npurged += npages;
+ extent_list_remove(decay_extents, extent);
+ switch (extents_state_get(extents)) {
+ case extent_state_active:
+ not_reached();
+ case extent_state_dirty:
+ if (!all && muzzy_decay_ms != 0 &&
+ !extent_purge_lazy_wrapper(tsdn, arena,
+ r_extent_hooks, extent, 0,
+ extent_size_get(extent))) {
+ extents_dalloc(tsdn, arena, r_extent_hooks,
+ &arena->extents_muzzy, extent);
+ arena_background_thread_inactivity_check(tsdn,
+ arena, is_background_thread);
+ break;
}
- arena_mapbits_large_set(chunk, pageind+npages-1, 0,
- flags);
- arena_mapbits_large_set(chunk, pageind, run_size,
- flags);
-
- /*
- * Set the unzeroed flag for internal pages, now that
- * chunk_purge_wrapper() has returned whether the pages
- * were zeroed as a side effect of purging. This chunk
- * map modification is safe even though the arena mutex
- * isn't currently owned by this thread, because the run
- * is marked as allocated, thus protecting it from being
- * modified by any other thread. As long as these
- * writes don't perturb the first and last elements'
- * CHUNK_MAP_ALLOCATED bits, behavior is well defined.
- */
- for (i = 1; i < npages-1; i++) {
- arena_mapbits_internal_set(chunk, pageind+i,
- flag_unzeroed);
+ /* Fall through. */
+ case extent_state_muzzy:
+ extent_dalloc_wrapper(tsdn, arena, r_extent_hooks,
+ extent);
+ if (config_stats) {
+ nunmapped += npages;
}
+ break;
+ case extent_state_retained:
+ default:
+ not_reached();
}
-
- npurged += npages;
- if (config_stats)
- nmadvise++;
}
- malloc_mutex_lock(&arena->lock);
if (config_stats) {
- arena->stats.nmadvise += nmadvise;
- arena->stats.purged += npurged;
+ arena_stats_lock(tsdn, &arena->stats);
+ arena_stats_add_u64(tsdn, &arena->stats, &decay->stats->npurge,
+ 1);
+ arena_stats_add_u64(tsdn, &arena->stats,
+ &decay->stats->nmadvise, nmadvise);
+ arena_stats_add_u64(tsdn, &arena->stats, &decay->stats->purged,
+ npurged);
+ arena_stats_sub_zu(tsdn, &arena->stats, &arena->stats.mapped,
+ nunmapped << LG_PAGE);
+ arena_stats_unlock(tsdn, &arena->stats);
}
- return (npurged);
+ return npurged;
}
+/*
+ * npages_limit: Decay at most npages_decay_max pages without violating the
+ * invariant: (extents_npages_get(extents) >= npages_limit). We need an upper
+ * bound on number of pages in order to prevent unbounded growth (namely in
+ * stashed), otherwise unbounded new pages could be added to extents during the
+ * current decay run, so that the purging thread never finishes.
+ */
static void
-arena_unstash_purged(arena_t *arena, chunk_hooks_t *chunk_hooks,
- arena_runs_dirty_link_t *purge_runs_sentinel,
- extent_node_t *purge_chunks_sentinel)
-{
- arena_runs_dirty_link_t *rdelm, *rdelm_next;
- extent_node_t *chunkselm;
-
- /* Deallocate chunks/runs. */
- for (rdelm = qr_next(purge_runs_sentinel, rd_link),
- chunkselm = qr_next(purge_chunks_sentinel, cc_link);
- rdelm != purge_runs_sentinel; rdelm = rdelm_next) {
- rdelm_next = qr_next(rdelm, rd_link);
- if (rdelm == &chunkselm->rd) {
- extent_node_t *chunkselm_next = qr_next(chunkselm,
- cc_link);
- void *addr = extent_node_addr_get(chunkselm);
- size_t size = extent_node_size_get(chunkselm);
- bool zeroed = extent_node_zeroed_get(chunkselm);
- bool committed = extent_node_committed_get(chunkselm);
- extent_node_dirty_remove(chunkselm);
- arena_node_dalloc(arena, chunkselm);
- chunkselm = chunkselm_next;
- chunk_dalloc_arena(arena, chunk_hooks, addr, size,
- zeroed, committed);
- } else {
- arena_chunk_t *chunk =
- (arena_chunk_t *)CHUNK_ADDR2BASE(rdelm);
- arena_chunk_map_misc_t *miscelm =
- arena_rd_to_miscelm(rdelm);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
- bool decommitted = (arena_mapbits_decommitted_get(chunk,
- pageind) != 0);
- arena_run_t *run = &miscelm->run;
- qr_remove(rdelm, rd_link);
- arena_run_dalloc(arena, run, false, true, decommitted);
- }
+arena_decay_to_limit(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, bool all, size_t npages_limit, size_t npages_decay_max,
+ bool is_background_thread) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 1);
+ malloc_mutex_assert_owner(tsdn, &decay->mtx);
+
+ if (decay->purging) {
+ return;
}
-}
+ decay->purging = true;
+ malloc_mutex_unlock(tsdn, &decay->mtx);
-static void
-arena_purge(arena_t *arena, bool all)
-{
- chunk_hooks_t chunk_hooks = chunk_hooks_get(arena);
- size_t npurge, npurgeable, npurged;
- arena_runs_dirty_link_t purge_runs_sentinel;
- extent_node_t purge_chunks_sentinel;
+ extent_hooks_t *extent_hooks = extent_hooks_get(arena);
- arena->purging = true;
+ extent_list_t decay_extents;
+ extent_list_init(&decay_extents);
- /*
- * Calls to arena_dirty_count() are disabled even for debug builds
- * because overhead grows nonlinearly as memory usage increases.
- */
- if (false && config_debug) {
- size_t ndirty = arena_dirty_count(arena);
- assert(ndirty == arena->ndirty);
+ size_t npurge = arena_stash_decayed(tsdn, arena, &extent_hooks, extents,
+ npages_limit, npages_decay_max, &decay_extents);
+ if (npurge != 0) {
+ UNUSED size_t npurged = arena_decay_stashed(tsdn, arena,
+ &extent_hooks, decay, extents, all, &decay_extents,
+ is_background_thread);
+ assert(npurged == npurge);
}
- assert((arena->nactive >> arena->lg_dirty_mult) < arena->ndirty || all);
-
- if (config_stats)
- arena->stats.npurge++;
-
- npurge = arena_compute_npurge(arena, all);
- qr_new(&purge_runs_sentinel, rd_link);
- extent_node_dirty_linkage_init(&purge_chunks_sentinel);
-
- npurgeable = arena_stash_dirty(arena, &chunk_hooks, all, npurge,
- &purge_runs_sentinel, &purge_chunks_sentinel);
- assert(npurgeable >= npurge);
- npurged = arena_purge_stashed(arena, &chunk_hooks, &purge_runs_sentinel,
- &purge_chunks_sentinel);
- assert(npurged == npurgeable);
- arena_unstash_purged(arena, &chunk_hooks, &purge_runs_sentinel,
- &purge_chunks_sentinel);
- arena->purging = false;
+ malloc_mutex_lock(tsdn, &decay->mtx);
+ decay->purging = false;
}
-void
-arena_purge_all(arena_t *arena)
-{
-
- malloc_mutex_lock(&arena->lock);
- arena_purge(arena, true);
- malloc_mutex_unlock(&arena->lock);
-}
-
-static void
-arena_run_coalesce(arena_t *arena, arena_chunk_t *chunk, size_t *p_size,
- size_t *p_run_ind, size_t *p_run_pages, size_t flag_dirty,
- size_t flag_decommitted)
-{
- size_t size = *p_size;
- size_t run_ind = *p_run_ind;
- size_t run_pages = *p_run_pages;
-
- /* Try to coalesce forward. */
- if (run_ind + run_pages < chunk_npages &&
- arena_mapbits_allocated_get(chunk, run_ind+run_pages) == 0 &&
- arena_mapbits_dirty_get(chunk, run_ind+run_pages) == flag_dirty &&
- arena_mapbits_decommitted_get(chunk, run_ind+run_pages) ==
- flag_decommitted) {
- size_t nrun_size = arena_mapbits_unallocated_size_get(chunk,
- run_ind+run_pages);
- size_t nrun_pages = nrun_size >> LG_PAGE;
-
- /*
- * Remove successor from runs_avail; the coalesced run is
- * inserted later.
- */
- assert(arena_mapbits_unallocated_size_get(chunk,
- run_ind+run_pages+nrun_pages-1) == nrun_size);
- assert(arena_mapbits_dirty_get(chunk,
- run_ind+run_pages+nrun_pages-1) == flag_dirty);
- assert(arena_mapbits_decommitted_get(chunk,
- run_ind+run_pages+nrun_pages-1) == flag_decommitted);
- arena_avail_remove(arena, chunk, run_ind+run_pages, nrun_pages);
-
- /*
- * If the successor is dirty, remove it from the set of dirty
- * pages.
- */
- if (flag_dirty != 0) {
- arena_run_dirty_remove(arena, chunk, run_ind+run_pages,
- nrun_pages);
- }
-
- size += nrun_size;
- run_pages += nrun_pages;
+static bool
+arena_decay_impl(tsdn_t *tsdn, arena_t *arena, arena_decay_t *decay,
+ extents_t *extents, bool is_background_thread, bool all) {
+ if (all) {
+ malloc_mutex_lock(tsdn, &decay->mtx);
+ arena_decay_to_limit(tsdn, arena, decay, extents, all, 0,
+ extents_npages_get(extents), is_background_thread);
+ malloc_mutex_unlock(tsdn, &decay->mtx);
- arena_mapbits_unallocated_size_set(chunk, run_ind, size);
- arena_mapbits_unallocated_size_set(chunk, run_ind+run_pages-1,
- size);
+ return false;
}
- /* Try to coalesce backward. */
- if (run_ind > map_bias && arena_mapbits_allocated_get(chunk,
- run_ind-1) == 0 && arena_mapbits_dirty_get(chunk, run_ind-1) ==
- flag_dirty && arena_mapbits_decommitted_get(chunk, run_ind-1) ==
- flag_decommitted) {
- size_t prun_size = arena_mapbits_unallocated_size_get(chunk,
- run_ind-1);
- size_t prun_pages = prun_size >> LG_PAGE;
-
- run_ind -= prun_pages;
-
- /*
- * Remove predecessor from runs_avail; the coalesced run is
- * inserted later.
- */
- assert(arena_mapbits_unallocated_size_get(chunk, run_ind) ==
- prun_size);
- assert(arena_mapbits_dirty_get(chunk, run_ind) == flag_dirty);
- assert(arena_mapbits_decommitted_get(chunk, run_ind) ==
- flag_decommitted);
- arena_avail_remove(arena, chunk, run_ind, prun_pages);
-
- /*
- * If the predecessor is dirty, remove it from the set of dirty
- * pages.
- */
- if (flag_dirty != 0) {
- arena_run_dirty_remove(arena, chunk, run_ind,
- prun_pages);
- }
+ if (malloc_mutex_trylock(tsdn, &decay->mtx)) {
+ /* No need to wait if another thread is in progress. */
+ return true;
+ }
- size += prun_size;
- run_pages += prun_pages;
+ bool epoch_advanced = arena_maybe_decay(tsdn, arena, decay, extents,
+ is_background_thread);
+ UNUSED size_t npages_new;
+ if (epoch_advanced) {
+ /* Backlog is updated on epoch advance. */
+ npages_new = decay->backlog[SMOOTHSTEP_NSTEPS-1];
+ }
+ malloc_mutex_unlock(tsdn, &decay->mtx);
- arena_mapbits_unallocated_size_set(chunk, run_ind, size);
- arena_mapbits_unallocated_size_set(chunk, run_ind+run_pages-1,
- size);
+ if (have_background_thread && background_thread_enabled() &&
+ epoch_advanced && !is_background_thread) {
+ background_thread_interval_check(tsdn, arena, decay,
+ npages_new);
}
- *p_size = size;
- *p_run_ind = run_ind;
- *p_run_pages = run_pages;
+ return false;
}
-static size_t
-arena_run_size_get(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
- size_t run_ind)
-{
- size_t size;
-
- assert(run_ind >= map_bias);
- assert(run_ind < chunk_npages);
-
- if (arena_mapbits_large_get(chunk, run_ind) != 0) {
- size = arena_mapbits_large_size_get(chunk, run_ind);
- assert(size == PAGE || arena_mapbits_large_size_get(chunk,
- run_ind+(size>>LG_PAGE)-1) == 0);
- } else {
- arena_bin_info_t *bin_info = &arena_bin_info[run->binind];
- size = bin_info->run_size;
- }
-
- return (size);
+static bool
+arena_decay_dirty(tsdn_t *tsdn, arena_t *arena, bool is_background_thread,
+ bool all) {
+ return arena_decay_impl(tsdn, arena, &arena->decay_dirty,
+ &arena->extents_dirty, is_background_thread, all);
}
static bool
-arena_run_decommit(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
- size_t run_ind = arena_miscelm_to_pageind(miscelm);
- size_t offset = run_ind << LG_PAGE;
- size_t length = arena_run_size_get(arena, chunk, run, run_ind);
+arena_decay_muzzy(tsdn_t *tsdn, arena_t *arena, bool is_background_thread,
+ bool all) {
+ return arena_decay_impl(tsdn, arena, &arena->decay_muzzy,
+ &arena->extents_muzzy, is_background_thread, all);
+}
- return (arena->chunk_hooks.decommit(chunk, chunksize, offset, length,
- arena->ind));
+void
+arena_decay(tsdn_t *tsdn, arena_t *arena, bool is_background_thread, bool all) {
+ if (arena_decay_dirty(tsdn, arena, is_background_thread, all)) {
+ return;
+ }
+ arena_decay_muzzy(tsdn, arena, is_background_thread, all);
}
static void
-arena_run_dalloc(arena_t *arena, arena_run_t *run, bool dirty, bool cleaned,
- bool decommitted)
-{
- arena_chunk_t *chunk;
- arena_chunk_map_misc_t *miscelm;
- size_t size, run_ind, run_pages, flag_dirty, flag_decommitted;
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- miscelm = arena_run_to_miscelm(run);
- run_ind = arena_miscelm_to_pageind(miscelm);
- assert(run_ind >= map_bias);
- assert(run_ind < chunk_npages);
- size = arena_run_size_get(arena, chunk, run, run_ind);
- run_pages = (size >> LG_PAGE);
- arena_cactive_update(arena, 0, run_pages);
- arena->nactive -= run_pages;
+arena_slab_dalloc(tsdn_t *tsdn, arena_t *arena, extent_t *slab) {
+ arena_nactive_sub(arena, extent_size_get(slab) >> LG_PAGE);
- /*
- * The run is dirty if the caller claims to have dirtied it, as well as
- * if it was already dirty before being allocated and the caller
- * doesn't claim to have cleaned it.
- */
- assert(arena_mapbits_dirty_get(chunk, run_ind) ==
- arena_mapbits_dirty_get(chunk, run_ind+run_pages-1));
- if (!cleaned && !decommitted && arena_mapbits_dirty_get(chunk, run_ind)
- != 0)
- dirty = true;
- flag_dirty = dirty ? CHUNK_MAP_DIRTY : 0;
- flag_decommitted = decommitted ? CHUNK_MAP_DECOMMITTED : 0;
-
- /* Mark pages as unallocated in the chunk map. */
- if (dirty || decommitted) {
- size_t flags = flag_dirty | flag_decommitted;
- arena_mapbits_unallocated_set(chunk, run_ind, size, flags);
- arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1, size,
- flags);
- } else {
- arena_mapbits_unallocated_set(chunk, run_ind, size,
- arena_mapbits_unzeroed_get(chunk, run_ind));
- arena_mapbits_unallocated_set(chunk, run_ind+run_pages-1, size,
- arena_mapbits_unzeroed_get(chunk, run_ind+run_pages-1));
- }
-
- arena_run_coalesce(arena, chunk, &size, &run_ind, &run_pages,
- flag_dirty, flag_decommitted);
+ extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
+ arena_extents_dirty_dalloc(tsdn, arena, &extent_hooks, slab);
+}
- /* Insert into runs_avail, now that coalescing is complete. */
- assert(arena_mapbits_unallocated_size_get(chunk, run_ind) ==
- arena_mapbits_unallocated_size_get(chunk, run_ind+run_pages-1));
- assert(arena_mapbits_dirty_get(chunk, run_ind) ==
- arena_mapbits_dirty_get(chunk, run_ind+run_pages-1));
- assert(arena_mapbits_decommitted_get(chunk, run_ind) ==
- arena_mapbits_decommitted_get(chunk, run_ind+run_pages-1));
- arena_avail_insert(arena, chunk, run_ind, run_pages);
+static void
+arena_bin_slabs_nonfull_insert(bin_t *bin, extent_t *slab) {
+ assert(extent_nfree_get(slab) > 0);
+ extent_heap_insert(&bin->slabs_nonfull, slab);
+}
- if (dirty)
- arena_run_dirty_insert(arena, chunk, run_ind, run_pages);
+static void
+arena_bin_slabs_nonfull_remove(bin_t *bin, extent_t *slab) {
+ extent_heap_remove(&bin->slabs_nonfull, slab);
+}
- /* Deallocate chunk if it is now completely unused. */
- if (size == arena_maxrun) {
- assert(run_ind == map_bias);
- assert(run_pages == (arena_maxrun >> LG_PAGE));
- arena_chunk_dalloc(arena, chunk);
+static extent_t *
+arena_bin_slabs_nonfull_tryget(bin_t *bin) {
+ extent_t *slab = extent_heap_remove_first(&bin->slabs_nonfull);
+ if (slab == NULL) {
+ return NULL;
+ }
+ if (config_stats) {
+ bin->stats.reslabs++;
}
+ return slab;
+}
+static void
+arena_bin_slabs_full_insert(arena_t *arena, bin_t *bin, extent_t *slab) {
+ assert(extent_nfree_get(slab) == 0);
/*
- * It is okay to do dirty page processing here even if the chunk was
- * deallocated above, since in that case it is the spare. Waiting
- * until after possible chunk deallocation to do dirty processing
- * allows for an old spare to be fully deallocated, thus decreasing the
- * chances of spuriously crossing the dirty page purging threshold.
+ * Tracking extents is required by arena_reset, which is not allowed
+ * for auto arenas. Bypass this step to avoid touching the extent
+ * linkage (often results in cache misses) for auto arenas.
*/
- if (dirty)
- arena_maybe_purge(arena);
+ if (arena_is_auto(arena)) {
+ return;
+ }
+ extent_list_append(&bin->slabs_full, slab);
}
static void
-arena_run_dalloc_decommit(arena_t *arena, arena_chunk_t *chunk,
- arena_run_t *run)
-{
- bool committed = arena_run_decommit(arena, chunk, run);
-
- arena_run_dalloc(arena, run, committed, false, !committed);
+arena_bin_slabs_full_remove(arena_t *arena, bin_t *bin, extent_t *slab) {
+ if (arena_is_auto(arena)) {
+ return;
+ }
+ extent_list_remove(&bin->slabs_full, slab);
}
-static void
-arena_run_trim_head(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
- size_t oldsize, size_t newsize)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
- size_t head_npages = (oldsize - newsize) >> LG_PAGE;
- size_t flag_dirty = arena_mapbits_dirty_get(chunk, pageind);
- size_t flag_decommitted = arena_mapbits_decommitted_get(chunk, pageind);
- size_t flag_unzeroed_mask = (flag_dirty | flag_decommitted) == 0 ?
- CHUNK_MAP_UNZEROED : 0;
-
- assert(oldsize > newsize);
-
+void
+arena_reset(tsd_t *tsd, arena_t *arena) {
/*
- * Update the chunk map so that arena_run_dalloc() can treat the
- * leading run as separately allocated. Set the last element of each
- * run first, in case of single-page runs.
+ * Locking in this function is unintuitive. The caller guarantees that
+ * no concurrent operations are happening in this arena, but there are
+ * still reasons that some locking is necessary:
+ *
+ * - Some of the functions in the transitive closure of calls assume
+ * appropriate locks are held, and in some cases these locks are
+ * temporarily dropped to avoid lock order reversal or deadlock due to
+ * reentry.
+ * - mallctl("epoch", ...) may concurrently refresh stats. While
+ * strictly speaking this is a "concurrent operation", disallowing
+ * stats refreshes would impose an inconvenient burden.
*/
- assert(arena_mapbits_large_size_get(chunk, pageind) == oldsize);
- arena_mapbits_large_set(chunk, pageind+head_npages-1, 0, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- pageind+head_npages-1)));
- arena_mapbits_large_set(chunk, pageind, oldsize-newsize, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk, pageind)));
- if (config_debug) {
- UNUSED size_t tail_npages = newsize >> LG_PAGE;
- assert(arena_mapbits_large_size_get(chunk,
- pageind+head_npages+tail_npages-1) == 0);
- assert(arena_mapbits_dirty_get(chunk,
- pageind+head_npages+tail_npages-1) == flag_dirty);
+ /* Large allocations. */
+ malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx);
+
+ for (extent_t *extent = extent_list_first(&arena->large); extent !=
+ NULL; extent = extent_list_first(&arena->large)) {
+ void *ptr = extent_base_get(extent);
+ size_t usize;
+
+ malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx);
+ alloc_ctx_t alloc_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind != NSIZES);
+
+ if (config_stats || (config_prof && opt_prof)) {
+ usize = sz_index2size(alloc_ctx.szind);
+ assert(usize == isalloc(tsd_tsdn(tsd), ptr));
+ }
+ /* Remove large allocation from prof sample set. */
+ if (config_prof && opt_prof) {
+ prof_free(tsd, ptr, usize, &alloc_ctx);
+ }
+ large_dalloc(tsd_tsdn(tsd), extent);
+ malloc_mutex_lock(tsd_tsdn(tsd), &arena->large_mtx);
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &arena->large_mtx);
+
+ /* Bins. */
+ for (unsigned i = 0; i < NBINS; i++) {
+ extent_t *slab;
+ bin_t *bin = &arena->bins[i];
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
+ if (bin->slabcur != NULL) {
+ slab = bin->slabcur;
+ bin->slabcur = NULL;
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
+ arena_slab_dalloc(tsd_tsdn(tsd), arena, slab);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
+ }
+ while ((slab = extent_heap_remove_first(&bin->slabs_nonfull)) !=
+ NULL) {
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
+ arena_slab_dalloc(tsd_tsdn(tsd), arena, slab);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
+ }
+ for (slab = extent_list_first(&bin->slabs_full); slab != NULL;
+ slab = extent_list_first(&bin->slabs_full)) {
+ arena_bin_slabs_full_remove(arena, bin, slab);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
+ arena_slab_dalloc(tsd_tsdn(tsd), arena, slab);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
+ }
+ if (config_stats) {
+ bin->stats.curregs = 0;
+ bin->stats.curslabs = 0;
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
}
- arena_mapbits_large_set(chunk, pageind+head_npages, newsize,
- flag_dirty | (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- pageind+head_npages)));
- arena_run_dalloc(arena, run, false, false, (flag_decommitted != 0));
+ atomic_store_zu(&arena->nactive, 0, ATOMIC_RELAXED);
}
static void
-arena_run_trim_tail(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
- size_t oldsize, size_t newsize, bool dirty)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
- size_t pageind = arena_miscelm_to_pageind(miscelm);
- size_t head_npages = newsize >> LG_PAGE;
- size_t flag_dirty = arena_mapbits_dirty_get(chunk, pageind);
- size_t flag_decommitted = arena_mapbits_decommitted_get(chunk, pageind);
- size_t flag_unzeroed_mask = (flag_dirty | flag_decommitted) == 0 ?
- CHUNK_MAP_UNZEROED : 0;
- arena_chunk_map_misc_t *tail_miscelm;
- arena_run_t *tail_run;
-
- assert(oldsize > newsize);
-
+arena_destroy_retained(tsdn_t *tsdn, arena_t *arena) {
/*
- * Update the chunk map so that arena_run_dalloc() can treat the
- * trailing run as separately allocated. Set the last element of each
- * run first, in case of single-page runs.
+ * Iterate over the retained extents and destroy them. This gives the
+ * extent allocator underlying the extent hooks an opportunity to unmap
+ * all retained memory without having to keep its own metadata
+ * structures. In practice, virtual memory for dss-allocated extents is
+ * leaked here, so best practice is to avoid dss for arenas to be
+ * destroyed, or provide custom extent hooks that track retained
+ * dss-based extents for later reuse.
*/
- assert(arena_mapbits_large_size_get(chunk, pageind) == oldsize);
- arena_mapbits_large_set(chunk, pageind+head_npages-1, 0, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- pageind+head_npages-1)));
- arena_mapbits_large_set(chunk, pageind, newsize, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk, pageind)));
-
- if (config_debug) {
- UNUSED size_t tail_npages = (oldsize - newsize) >> LG_PAGE;
- assert(arena_mapbits_large_size_get(chunk,
- pageind+head_npages+tail_npages-1) == 0);
- assert(arena_mapbits_dirty_get(chunk,
- pageind+head_npages+tail_npages-1) == flag_dirty);
+ extent_hooks_t *extent_hooks = extent_hooks_get(arena);
+ extent_t *extent;
+ while ((extent = extents_evict(tsdn, arena, &extent_hooks,
+ &arena->extents_retained, 0)) != NULL) {
+ extent_destroy_wrapper(tsdn, arena, &extent_hooks, extent);
}
- arena_mapbits_large_set(chunk, pageind+head_npages, oldsize-newsize,
- flag_dirty | (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- pageind+head_npages)));
-
- tail_miscelm = arena_miscelm_get(chunk, pageind + head_npages);
- tail_run = &tail_miscelm->run;
- arena_run_dalloc(arena, tail_run, dirty, false, (flag_decommitted !=
- 0));
}
-static arena_run_t *
-arena_bin_runs_first(arena_bin_t *bin)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_tree_first(&bin->runs);
- if (miscelm != NULL)
- return (&miscelm->run);
+void
+arena_destroy(tsd_t *tsd, arena_t *arena) {
+ assert(base_ind_get(arena->base) >= narenas_auto);
+ assert(arena_nthreads_get(arena, false) == 0);
+ assert(arena_nthreads_get(arena, true) == 0);
- return (NULL);
-}
+ /*
+ * No allocations have occurred since arena_reset() was called.
+ * Furthermore, the caller (arena_i_destroy_ctl()) purged all cached
+ * extents, so only retained extents may remain.
+ */
+ assert(extents_npages_get(&arena->extents_dirty) == 0);
+ assert(extents_npages_get(&arena->extents_muzzy) == 0);
-static void
-arena_bin_runs_insert(arena_bin_t *bin, arena_run_t *run)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
+ /* Deallocate retained memory. */
+ arena_destroy_retained(tsd_tsdn(tsd), arena);
- assert(arena_run_tree_search(&bin->runs, miscelm) == NULL);
+ /*
+ * Remove the arena pointer from the arenas array. We rely on the fact
+ * that there is no way for the application to get a dirty read from the
+ * arenas array unless there is an inherent race in the application
+ * involving access of an arena being concurrently destroyed. The
+ * application must synchronize knowledge of the arena's validity, so as
+ * long as we use an atomic write to update the arenas array, the
+ * application will get a clean read any time after it synchronizes
+ * knowledge that the arena is no longer valid.
+ */
+ arena_set(base_ind_get(arena->base), NULL);
- arena_run_tree_insert(&bin->runs, miscelm);
+ /*
+ * Destroy the base allocator, which manages all metadata ever mapped by
+ * this arena.
+ */
+ base_delete(tsd_tsdn(tsd), arena->base);
}
-static void
-arena_bin_runs_remove(arena_bin_t *bin, arena_run_t *run)
-{
- arena_chunk_map_misc_t *miscelm = arena_run_to_miscelm(run);
+static extent_t *
+arena_slab_alloc_hard(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, const bin_info_t *bin_info,
+ szind_t szind) {
+ extent_t *slab;
+ bool zero, commit;
+
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
- assert(arena_run_tree_search(&bin->runs, miscelm) != NULL);
+ zero = false;
+ commit = true;
+ slab = extent_alloc_wrapper(tsdn, arena, r_extent_hooks, NULL,
+ bin_info->slab_size, 0, PAGE, true, szind, &zero, &commit);
+
+ if (config_stats && slab != NULL) {
+ arena_stats_mapped_add(tsdn, &arena->stats,
+ bin_info->slab_size);
+ }
- arena_run_tree_remove(&bin->runs, miscelm);
+ return slab;
}
-static arena_run_t *
-arena_bin_nonfull_run_tryget(arena_bin_t *bin)
-{
- arena_run_t *run = arena_bin_runs_first(bin);
- if (run != NULL) {
- arena_bin_runs_remove(bin, run);
- if (config_stats)
- bin->stats.reruns++;
+static extent_t *
+arena_slab_alloc(tsdn_t *tsdn, arena_t *arena, szind_t binind,
+ const bin_info_t *bin_info) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
+ szind_t szind = sz_size2index(bin_info->reg_size);
+ bool zero = false;
+ bool commit = true;
+ extent_t *slab = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_dirty, NULL, bin_info->slab_size, 0, PAGE, true,
+ binind, &zero, &commit);
+ if (slab == NULL) {
+ slab = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_muzzy, NULL, bin_info->slab_size, 0, PAGE,
+ true, binind, &zero, &commit);
+ }
+ if (slab == NULL) {
+ slab = arena_slab_alloc_hard(tsdn, arena, &extent_hooks,
+ bin_info, szind);
+ if (slab == NULL) {
+ return NULL;
+ }
}
- return (run);
+ assert(extent_slab_get(slab));
+
+ /* Initialize slab internals. */
+ arena_slab_data_t *slab_data = extent_slab_data_get(slab);
+ extent_nfree_set(slab, bin_info->nregs);
+ bitmap_init(slab_data->bitmap, &bin_info->bitmap_info, false);
+
+ arena_nactive_add(arena, extent_size_get(slab) >> LG_PAGE);
+
+ return slab;
}
-static arena_run_t *
-arena_bin_nonfull_run_get(arena_t *arena, arena_bin_t *bin)
-{
- arena_run_t *run;
- szind_t binind;
- arena_bin_info_t *bin_info;
+static extent_t *
+arena_bin_nonfull_slab_get(tsdn_t *tsdn, arena_t *arena, bin_t *bin,
+ szind_t binind) {
+ extent_t *slab;
+ const bin_info_t *bin_info;
- /* Look for a usable run. */
- run = arena_bin_nonfull_run_tryget(bin);
- if (run != NULL)
- return (run);
- /* No existing runs have any space available. */
+ /* Look for a usable slab. */
+ slab = arena_bin_slabs_nonfull_tryget(bin);
+ if (slab != NULL) {
+ return slab;
+ }
+ /* No existing slabs have any space available. */
- binind = arena_bin_index(arena, bin);
- bin_info = &arena_bin_info[binind];
+ bin_info = &bin_infos[binind];
- /* Allocate a new run. */
- malloc_mutex_unlock(&bin->lock);
+ /* Allocate a new slab. */
+ malloc_mutex_unlock(tsdn, &bin->lock);
/******************************/
- malloc_mutex_lock(&arena->lock);
- run = arena_run_alloc_small(arena, bin_info->run_size, binind);
- if (run != NULL) {
- /* Initialize run internals. */
- run->binind = binind;
- run->nfree = bin_info->nregs;
- bitmap_init(run->bitmap, &bin_info->bitmap_info);
- }
- malloc_mutex_unlock(&arena->lock);
+ slab = arena_slab_alloc(tsdn, arena, binind, bin_info);
/********************************/
- malloc_mutex_lock(&bin->lock);
- if (run != NULL) {
+ malloc_mutex_lock(tsdn, &bin->lock);
+ if (slab != NULL) {
if (config_stats) {
- bin->stats.nruns++;
- bin->stats.curruns++;
+ bin->stats.nslabs++;
+ bin->stats.curslabs++;
}
- return (run);
+ return slab;
}
/*
- * arena_run_alloc_small() failed, but another thread may have made
+ * arena_slab_alloc() failed, but another thread may have made
* sufficient memory available while this one dropped bin->lock above,
* so search one more time.
*/
- run = arena_bin_nonfull_run_tryget(bin);
- if (run != NULL)
- return (run);
+ slab = arena_bin_slabs_nonfull_tryget(bin);
+ if (slab != NULL) {
+ return slab;
+ }
- return (NULL);
+ return NULL;
}
-/* Re-fill bin->runcur, then call arena_run_reg_alloc(). */
+/* Re-fill bin->slabcur, then call arena_slab_reg_alloc(). */
static void *
-arena_bin_malloc_hard(arena_t *arena, arena_bin_t *bin)
-{
- szind_t binind;
- arena_bin_info_t *bin_info;
- arena_run_t *run;
-
- binind = arena_bin_index(arena, bin);
- bin_info = &arena_bin_info[binind];
- bin->runcur = NULL;
- run = arena_bin_nonfull_run_get(arena, bin);
- if (bin->runcur != NULL && bin->runcur->nfree > 0) {
+arena_bin_malloc_hard(tsdn_t *tsdn, arena_t *arena, bin_t *bin,
+ szind_t binind) {
+ const bin_info_t *bin_info;
+ extent_t *slab;
+
+ bin_info = &bin_infos[binind];
+ if (!arena_is_auto(arena) && bin->slabcur != NULL) {
+ arena_bin_slabs_full_insert(arena, bin, bin->slabcur);
+ bin->slabcur = NULL;
+ }
+ slab = arena_bin_nonfull_slab_get(tsdn, arena, bin, binind);
+ if (bin->slabcur != NULL) {
/*
- * Another thread updated runcur while this one ran without the
- * bin lock in arena_bin_nonfull_run_get().
+ * Another thread updated slabcur while this one ran without the
+ * bin lock in arena_bin_nonfull_slab_get().
*/
- void *ret;
- assert(bin->runcur->nfree > 0);
- ret = arena_run_reg_alloc(bin->runcur, bin_info);
- if (run != NULL) {
- arena_chunk_t *chunk;
-
- /*
- * arena_run_alloc_small() may have allocated run, or
- * it may have pulled run from the bin's run tree.
- * Therefore it is unsafe to make any assumptions about
- * how run has previously been used, and
- * arena_bin_lower_run() must be called, as if a region
- * were just deallocated from the run.
- */
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- if (run->nfree == bin_info->nregs)
- arena_dalloc_bin_run(arena, chunk, run, bin);
- else
- arena_bin_lower_run(arena, chunk, run, bin);
+ if (extent_nfree_get(bin->slabcur) > 0) {
+ void *ret = arena_slab_reg_alloc(bin->slabcur,
+ bin_info);
+ if (slab != NULL) {
+ /*
+ * arena_slab_alloc() may have allocated slab,
+ * or it may have been pulled from
+ * slabs_nonfull. Therefore it is unsafe to
+ * make any assumptions about how slab has
+ * previously been used, and
+ * arena_bin_lower_slab() must be called, as if
+ * a region were just deallocated from the slab.
+ */
+ if (extent_nfree_get(slab) == bin_info->nregs) {
+ arena_dalloc_bin_slab(tsdn, arena, slab,
+ bin);
+ } else {
+ arena_bin_lower_slab(tsdn, arena, slab,
+ bin);
+ }
+ }
+ return ret;
}
- return (ret);
- }
- if (run == NULL)
- return (NULL);
+ arena_bin_slabs_full_insert(arena, bin, bin->slabcur);
+ bin->slabcur = NULL;
+ }
- bin->runcur = run;
+ if (slab == NULL) {
+ return NULL;
+ }
+ bin->slabcur = slab;
- assert(bin->runcur->nfree > 0);
+ assert(extent_nfree_get(bin->slabcur) > 0);
- return (arena_run_reg_alloc(bin->runcur, bin_info));
+ return arena_slab_reg_alloc(slab, bin_info);
}
void
-arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin, szind_t binind,
- uint64_t prof_accumbytes)
-{
+arena_tcache_fill_small(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache,
+ cache_bin_t *tbin, szind_t binind, uint64_t prof_accumbytes) {
unsigned i, nfill;
- arena_bin_t *bin;
+ bin_t *bin;
assert(tbin->ncached == 0);
- if (config_prof && arena_prof_accum(arena, prof_accumbytes))
- prof_idump();
+ if (config_prof && arena_prof_accum(tsdn, arena, prof_accumbytes)) {
+ prof_idump(tsdn);
+ }
bin = &arena->bins[binind];
- malloc_mutex_lock(&bin->lock);
+ malloc_mutex_lock(tsdn, &bin->lock);
for (i = 0, nfill = (tcache_bin_info[binind].ncached_max >>
- tbin->lg_fill_div); i < nfill; i++) {
- arena_run_t *run;
+ tcache->lg_fill_div[binind]); i < nfill; i++) {
+ extent_t *slab;
void *ptr;
- if ((run = bin->runcur) != NULL && run->nfree > 0)
- ptr = arena_run_reg_alloc(run, &arena_bin_info[binind]);
- else
- ptr = arena_bin_malloc_hard(arena, bin);
+ if ((slab = bin->slabcur) != NULL && extent_nfree_get(slab) >
+ 0) {
+ ptr = arena_slab_reg_alloc(slab, &bin_infos[binind]);
+ } else {
+ ptr = arena_bin_malloc_hard(tsdn, arena, bin, binind);
+ }
if (ptr == NULL) {
/*
* OOM. tbin->avail isn't yet filled down to its first
* element, so the successful allocations (if any) must
- * be moved to the base of tbin->avail before bailing
- * out.
+ * be moved just before tbin->avail before bailing out.
*/
if (i > 0) {
- memmove(tbin->avail, &tbin->avail[nfill - i],
+ memmove(tbin->avail - i, tbin->avail - nfill,
i * sizeof(void *));
}
break;
}
if (config_fill && unlikely(opt_junk_alloc)) {
- arena_alloc_junk_small(ptr, &arena_bin_info[binind],
- true);
+ arena_alloc_junk_small(ptr, &bin_infos[binind], true);
}
/* Insert such that low regions get used first. */
- tbin->avail[nfill - 1 - i] = ptr;
+ *(tbin->avail - nfill + i) = ptr;
}
if (config_stats) {
bin->stats.nmalloc += i;
@@ -2033,139 +1294,46 @@ arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin, szind_t binind,
bin->stats.nfills++;
tbin->tstats.nrequests = 0;
}
- malloc_mutex_unlock(&bin->lock);
+ malloc_mutex_unlock(tsdn, &bin->lock);
tbin->ncached = i;
+ arena_decay_tick(tsdn, arena);
}
void
-arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info, bool zero)
-{
-
- if (zero) {
- size_t redzone_size = bin_info->redzone_size;
- memset((void *)((uintptr_t)ptr - redzone_size), 0xa5,
- redzone_size);
- memset((void *)((uintptr_t)ptr + bin_info->reg_size), 0xa5,
- redzone_size);
- } else {
- memset((void *)((uintptr_t)ptr - bin_info->redzone_size), 0xa5,
- bin_info->reg_interval);
+arena_alloc_junk_small(void *ptr, const bin_info_t *bin_info, bool zero) {
+ if (!zero) {
+ memset(ptr, JEMALLOC_ALLOC_JUNK, bin_info->reg_size);
}
}
-#ifdef JEMALLOC_JET
-#undef arena_redzone_corruption
-#define arena_redzone_corruption JEMALLOC_N(arena_redzone_corruption_impl)
-#endif
-static void
-arena_redzone_corruption(void *ptr, size_t usize, bool after,
- size_t offset, uint8_t byte)
-{
-
- malloc_printf("<jemalloc>: Corrupt redzone %zu byte%s %s %p "
- "(size %zu), byte=%#x\n", offset, (offset == 1) ? "" : "s",
- after ? "after" : "before", ptr, usize, byte);
-}
-#ifdef JEMALLOC_JET
-#undef arena_redzone_corruption
-#define arena_redzone_corruption JEMALLOC_N(arena_redzone_corruption)
-arena_redzone_corruption_t *arena_redzone_corruption =
- JEMALLOC_N(arena_redzone_corruption_impl);
-#endif
-
static void
-arena_redzones_validate(void *ptr, arena_bin_info_t *bin_info, bool reset)
-{
- bool error = false;
-
- if (opt_junk_alloc) {
- size_t size = bin_info->reg_size;
- size_t redzone_size = bin_info->redzone_size;
- size_t i;
-
- for (i = 1; i <= redzone_size; i++) {
- uint8_t *byte = (uint8_t *)((uintptr_t)ptr - i);
- if (*byte != 0xa5) {
- error = true;
- arena_redzone_corruption(ptr, size, false, i,
- *byte);
- if (reset)
- *byte = 0xa5;
- }
- }
- for (i = 0; i < redzone_size; i++) {
- uint8_t *byte = (uint8_t *)((uintptr_t)ptr + size + i);
- if (*byte != 0xa5) {
- error = true;
- arena_redzone_corruption(ptr, size, true, i,
- *byte);
- if (reset)
- *byte = 0xa5;
- }
- }
- }
-
- if (opt_abort && error)
- abort();
+arena_dalloc_junk_small_impl(void *ptr, const bin_info_t *bin_info) {
+ memset(ptr, JEMALLOC_FREE_JUNK, bin_info->reg_size);
}
+arena_dalloc_junk_small_t *JET_MUTABLE arena_dalloc_junk_small =
+ arena_dalloc_junk_small_impl;
-#ifdef JEMALLOC_JET
-#undef arena_dalloc_junk_small
-#define arena_dalloc_junk_small JEMALLOC_N(arena_dalloc_junk_small_impl)
-#endif
-void
-arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info)
-{
- size_t redzone_size = bin_info->redzone_size;
-
- arena_redzones_validate(ptr, bin_info, false);
- memset((void *)((uintptr_t)ptr - redzone_size), 0x5a,
- bin_info->reg_interval);
-}
-#ifdef JEMALLOC_JET
-#undef arena_dalloc_junk_small
-#define arena_dalloc_junk_small JEMALLOC_N(arena_dalloc_junk_small)
-arena_dalloc_junk_small_t *arena_dalloc_junk_small =
- JEMALLOC_N(arena_dalloc_junk_small_impl);
-#endif
-
-void
-arena_quarantine_junk_small(void *ptr, size_t usize)
-{
- szind_t binind;
- arena_bin_info_t *bin_info;
- cassert(config_fill);
- assert(opt_junk_free);
- assert(opt_quarantine);
- assert(usize <= SMALL_MAXCLASS);
-
- binind = size2index(usize);
- bin_info = &arena_bin_info[binind];
- arena_redzones_validate(ptr, bin_info, true);
-}
-
-void *
-arena_malloc_small(arena_t *arena, size_t size, bool zero)
-{
+static void *
+arena_malloc_small(tsdn_t *tsdn, arena_t *arena, szind_t binind, bool zero) {
void *ret;
- arena_bin_t *bin;
- arena_run_t *run;
- szind_t binind;
+ bin_t *bin;
+ size_t usize;
+ extent_t *slab;
- binind = size2index(size);
assert(binind < NBINS);
bin = &arena->bins[binind];
- size = index2size(binind);
+ usize = sz_index2size(binind);
- malloc_mutex_lock(&bin->lock);
- if ((run = bin->runcur) != NULL && run->nfree > 0)
- ret = arena_run_reg_alloc(run, &arena_bin_info[binind]);
- else
- ret = arena_bin_malloc_hard(arena, bin);
+ malloc_mutex_lock(tsdn, &bin->lock);
+ if ((slab = bin->slabcur) != NULL && extent_nfree_get(slab) > 0) {
+ ret = arena_slab_reg_alloc(slab, &bin_infos[binind]);
+ } else {
+ ret = arena_bin_malloc_hard(tsdn, arena, bin, binind);
+ }
if (ret == NULL) {
- malloc_mutex_unlock(&bin->lock);
- return (NULL);
+ malloc_mutex_unlock(tsdn, &bin->lock);
+ return NULL;
}
if (config_stats) {
@@ -2173,864 +1341,465 @@ arena_malloc_small(arena_t *arena, size_t size, bool zero)
bin->stats.nrequests++;
bin->stats.curregs++;
}
- malloc_mutex_unlock(&bin->lock);
- if (config_prof && !isthreaded && arena_prof_accum(arena, size))
- prof_idump();
+ malloc_mutex_unlock(tsdn, &bin->lock);
+ if (config_prof && arena_prof_accum(tsdn, arena, usize)) {
+ prof_idump(tsdn);
+ }
if (!zero) {
if (config_fill) {
if (unlikely(opt_junk_alloc)) {
arena_alloc_junk_small(ret,
- &arena_bin_info[binind], false);
- } else if (unlikely(opt_zero))
- memset(ret, 0, size);
+ &bin_infos[binind], false);
+ } else if (unlikely(opt_zero)) {
+ memset(ret, 0, usize);
+ }
}
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
} else {
if (config_fill && unlikely(opt_junk_alloc)) {
- arena_alloc_junk_small(ret, &arena_bin_info[binind],
+ arena_alloc_junk_small(ret, &bin_infos[binind],
true);
}
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
- memset(ret, 0, size);
+ memset(ret, 0, usize);
}
- return (ret);
+ arena_decay_tick(tsdn, arena);
+ return ret;
}
void *
-arena_malloc_large(arena_t *arena, size_t size, bool zero)
-{
- void *ret;
- size_t usize;
- uintptr_t random_offset;
- arena_run_t *run;
- arena_chunk_map_misc_t *miscelm;
- UNUSED bool idump;
-
- /* Large allocation. */
- usize = s2u(size);
- malloc_mutex_lock(&arena->lock);
- if (config_cache_oblivious) {
- uint64_t r;
-
- /*
- * Compute a uniformly distributed offset within the first page
- * that is a multiple of the cacheline size, e.g. [0 .. 63) * 64
- * for 4 KiB pages and 64-byte cachelines.
- */
- prng64(r, LG_PAGE - LG_CACHELINE, arena->offset_state,
- UINT64_C(6364136223846793009),
- UINT64_C(1442695040888963409));
- random_offset = ((uintptr_t)r) << LG_CACHELINE;
- } else
- random_offset = 0;
- run = arena_run_alloc_large(arena, usize + large_pad, zero);
- if (run == NULL) {
- malloc_mutex_unlock(&arena->lock);
- return (NULL);
- }
- miscelm = arena_run_to_miscelm(run);
- ret = (void *)((uintptr_t)arena_miscelm_to_rpages(miscelm) +
- random_offset);
- if (config_stats) {
- szind_t index = size2index(usize) - NBINS;
-
- arena->stats.nmalloc_large++;
- arena->stats.nrequests_large++;
- arena->stats.allocated_large += usize;
- arena->stats.lstats[index].nmalloc++;
- arena->stats.lstats[index].nrequests++;
- arena->stats.lstats[index].curruns++;
- }
- if (config_prof)
- idump = arena_prof_accum_locked(arena, usize);
- malloc_mutex_unlock(&arena->lock);
- if (config_prof && idump)
- prof_idump();
+arena_malloc_hard(tsdn_t *tsdn, arena_t *arena, size_t size, szind_t ind,
+ bool zero) {
+ assert(!tsdn_null(tsdn) || arena != NULL);
- if (!zero) {
- if (config_fill) {
- if (unlikely(opt_junk_alloc))
- memset(ret, 0xa5, usize);
- else if (unlikely(opt_zero))
- memset(ret, 0, usize);
- }
+ if (likely(!tsdn_null(tsdn))) {
+ arena = arena_choose(tsdn_tsd(tsdn), arena);
}
-
- return (ret);
-}
-
-/* Only handles large allocations that require more than page alignment. */
-static void *
-arena_palloc_large(tsd_t *tsd, arena_t *arena, size_t usize, size_t alignment,
- bool zero)
-{
- void *ret;
- size_t alloc_size, leadsize, trailsize;
- arena_run_t *run;
- arena_chunk_t *chunk;
- arena_chunk_map_misc_t *miscelm;
- void *rpages;
-
- assert(usize == PAGE_CEILING(usize));
-
- arena = arena_choose(tsd, arena);
- if (unlikely(arena == NULL))
- return (NULL);
-
- alignment = PAGE_CEILING(alignment);
- alloc_size = usize + large_pad + alignment - PAGE;
-
- malloc_mutex_lock(&arena->lock);
- run = arena_run_alloc_large(arena, alloc_size, false);
- if (run == NULL) {
- malloc_mutex_unlock(&arena->lock);
- return (NULL);
- }
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(run);
- miscelm = arena_run_to_miscelm(run);
- rpages = arena_miscelm_to_rpages(miscelm);
-
- leadsize = ALIGNMENT_CEILING((uintptr_t)rpages, alignment) -
- (uintptr_t)rpages;
- assert(alloc_size >= leadsize + usize);
- trailsize = alloc_size - leadsize - usize - large_pad;
- if (leadsize != 0) {
- arena_chunk_map_misc_t *head_miscelm = miscelm;
- arena_run_t *head_run = run;
-
- miscelm = arena_miscelm_get(chunk,
- arena_miscelm_to_pageind(head_miscelm) + (leadsize >>
- LG_PAGE));
- run = &miscelm->run;
-
- arena_run_trim_head(arena, chunk, head_run, alloc_size,
- alloc_size - leadsize);
- }
- if (trailsize != 0) {
- arena_run_trim_tail(arena, chunk, run, usize + large_pad +
- trailsize, usize + large_pad, false);
- }
- if (arena_run_init_large(arena, run, usize + large_pad, zero)) {
- size_t run_ind =
- arena_miscelm_to_pageind(arena_run_to_miscelm(run));
- bool dirty = (arena_mapbits_dirty_get(chunk, run_ind) != 0);
- bool decommitted = (arena_mapbits_decommitted_get(chunk,
- run_ind) != 0);
-
- assert(decommitted); /* Cause of OOM. */
- arena_run_dalloc(arena, run, dirty, false, decommitted);
- malloc_mutex_unlock(&arena->lock);
- return (NULL);
- }
- ret = arena_miscelm_to_rpages(miscelm);
-
- if (config_stats) {
- szind_t index = size2index(usize) - NBINS;
-
- arena->stats.nmalloc_large++;
- arena->stats.nrequests_large++;
- arena->stats.allocated_large += usize;
- arena->stats.lstats[index].nmalloc++;
- arena->stats.lstats[index].nrequests++;
- arena->stats.lstats[index].curruns++;
+ if (unlikely(arena == NULL)) {
+ return NULL;
}
- malloc_mutex_unlock(&arena->lock);
- if (config_fill && !zero) {
- if (unlikely(opt_junk_alloc))
- memset(ret, 0xa5, usize);
- else if (unlikely(opt_zero))
- memset(ret, 0, usize);
+ if (likely(size <= SMALL_MAXCLASS)) {
+ return arena_malloc_small(tsdn, arena, ind, zero);
}
- return (ret);
+ return large_malloc(tsdn, arena, sz_index2size(ind), zero);
}
void *
-arena_palloc(tsd_t *tsd, arena_t *arena, size_t usize, size_t alignment,
- bool zero, tcache_t *tcache)
-{
+arena_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment,
+ bool zero, tcache_t *tcache) {
void *ret;
if (usize <= SMALL_MAXCLASS && (alignment < PAGE || (alignment == PAGE
&& (usize & PAGE_MASK) == 0))) {
- /* Small; alignment doesn't require special run placement. */
- ret = arena_malloc(tsd, arena, usize, zero, tcache);
- } else if (usize <= large_maxclass && alignment <= PAGE) {
- /*
- * Large; alignment doesn't require special run placement.
- * However, the cached pointer may be at a random offset from
- * the base of the run, so do some bit manipulation to retrieve
- * the base.
- */
- ret = arena_malloc(tsd, arena, usize, zero, tcache);
- if (config_cache_oblivious)
- ret = (void *)((uintptr_t)ret & ~PAGE_MASK);
+ /* Small; alignment doesn't require special slab placement. */
+ ret = arena_malloc(tsdn, arena, usize, sz_size2index(usize),
+ zero, tcache, true);
} else {
- if (likely(usize <= large_maxclass)) {
- ret = arena_palloc_large(tsd, arena, usize, alignment,
- zero);
- } else if (likely(alignment <= chunksize))
- ret = huge_malloc(tsd, arena, usize, zero, tcache);
- else {
- ret = huge_palloc(tsd, arena, usize, alignment, zero,
- tcache);
+ if (likely(alignment <= CACHELINE)) {
+ ret = large_malloc(tsdn, arena, usize, zero);
+ } else {
+ ret = large_palloc(tsdn, arena, usize, alignment, zero);
}
}
- return (ret);
+ return ret;
}
void
-arena_prof_promoted(const void *ptr, size_t size)
-{
- arena_chunk_t *chunk;
- size_t pageind;
- szind_t binind;
-
+arena_prof_promote(tsdn_t *tsdn, const void *ptr, size_t usize) {
cassert(config_prof);
assert(ptr != NULL);
- assert(CHUNK_ADDR2BASE(ptr) != ptr);
- assert(isalloc(ptr, false) == LARGE_MINCLASS);
- assert(isalloc(ptr, true) == LARGE_MINCLASS);
- assert(size <= SMALL_MAXCLASS);
-
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- binind = size2index(size);
- assert(binind < NBINS);
- arena_mapbits_large_binind_set(chunk, pageind, binind);
-
- assert(isalloc(ptr, false) == LARGE_MINCLASS);
- assert(isalloc(ptr, true) == size);
-}
-
-static void
-arena_dissociate_bin_run(arena_chunk_t *chunk, arena_run_t *run,
- arena_bin_t *bin)
-{
-
- /* Dissociate run from bin. */
- if (run == bin->runcur)
- bin->runcur = NULL;
- else {
- szind_t binind = arena_bin_index(extent_node_arena_get(
- &chunk->node), bin);
- arena_bin_info_t *bin_info = &arena_bin_info[binind];
-
- if (bin_info->nregs != 1) {
- /*
- * This block's conditional is necessary because if the
- * run only contains one region, then it never gets
- * inserted into the non-full runs tree.
- */
- arena_bin_runs_remove(bin, run);
- }
- }
-}
+ assert(isalloc(tsdn, ptr) == LARGE_MINCLASS);
+ assert(usize <= SMALL_MAXCLASS);
-static void
-arena_dalloc_bin_run(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
- arena_bin_t *bin)
-{
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
- assert(run != bin->runcur);
- assert(arena_run_tree_search(&bin->runs, arena_run_to_miscelm(run)) ==
- NULL);
+ extent_t *extent = rtree_extent_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true);
+ arena_t *arena = extent_arena_get(extent);
- malloc_mutex_unlock(&bin->lock);
- /******************************/
- malloc_mutex_lock(&arena->lock);
- arena_run_dalloc_decommit(arena, chunk, run);
- malloc_mutex_unlock(&arena->lock);
- /****************************/
- malloc_mutex_lock(&bin->lock);
- if (config_stats)
- bin->stats.curruns--;
-}
+ szind_t szind = sz_size2index(usize);
+ extent_szind_set(extent, szind);
+ rtree_szind_slab_update(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr,
+ szind, false);
-static void
-arena_bin_lower_run(arena_t *arena, arena_chunk_t *chunk, arena_run_t *run,
- arena_bin_t *bin)
-{
+ prof_accum_cancel(tsdn, &arena->prof_accum, usize);
- /*
- * Make sure that if bin->runcur is non-NULL, it refers to the lowest
- * non-full run. It is okay to NULL runcur out rather than proactively
- * keeping it pointing at the lowest non-full run.
- */
- if ((uintptr_t)run < (uintptr_t)bin->runcur) {
- /* Switch runcur. */
- if (bin->runcur->nfree > 0)
- arena_bin_runs_insert(bin, bin->runcur);
- bin->runcur = run;
- if (config_stats)
- bin->stats.reruns++;
- } else
- arena_bin_runs_insert(bin, run);
+ assert(isalloc(tsdn, ptr) == usize);
}
-static void
-arena_dalloc_bin_locked_impl(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- arena_chunk_map_bits_t *bitselm, bool junked)
-{
- size_t pageind, rpages_ind;
- arena_run_t *run;
- arena_bin_t *bin;
- arena_bin_info_t *bin_info;
- szind_t binind;
-
- pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- rpages_ind = pageind - arena_mapbits_small_runind_get(chunk, pageind);
- run = &arena_miscelm_get(chunk, rpages_ind)->run;
- binind = run->binind;
- bin = &arena->bins[binind];
- bin_info = &arena_bin_info[binind];
-
- if (!junked && config_fill && unlikely(opt_junk_free))
- arena_dalloc_junk_small(ptr, bin_info);
-
- arena_run_reg_dalloc(run, ptr);
- if (run->nfree == bin_info->nregs) {
- arena_dissociate_bin_run(chunk, run, bin);
- arena_dalloc_bin_run(arena, chunk, run, bin);
- } else if (run->nfree == 1 && run != bin->runcur)
- arena_bin_lower_run(arena, chunk, run, bin);
-
- if (config_stats) {
- bin->stats.ndalloc++;
- bin->stats.curregs--;
- }
-}
-
-void
-arena_dalloc_bin_junked_locked(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- arena_chunk_map_bits_t *bitselm)
-{
+static size_t
+arena_prof_demote(tsdn_t *tsdn, extent_t *extent, const void *ptr) {
+ cassert(config_prof);
+ assert(ptr != NULL);
- arena_dalloc_bin_locked_impl(arena, chunk, ptr, bitselm, true);
-}
+ extent_szind_set(extent, NBINS);
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ rtree_szind_slab_update(tsdn, &extents_rtree, rtree_ctx, (uintptr_t)ptr,
+ NBINS, false);
-void
-arena_dalloc_bin(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t pageind, arena_chunk_map_bits_t *bitselm)
-{
- arena_run_t *run;
- arena_bin_t *bin;
- size_t rpages_ind;
+ assert(isalloc(tsdn, ptr) == LARGE_MINCLASS);
- rpages_ind = pageind - arena_mapbits_small_runind_get(chunk, pageind);
- run = &arena_miscelm_get(chunk, rpages_ind)->run;
- bin = &arena->bins[run->binind];
- malloc_mutex_lock(&bin->lock);
- arena_dalloc_bin_locked_impl(arena, chunk, ptr, bitselm, false);
- malloc_mutex_unlock(&bin->lock);
+ return LARGE_MINCLASS;
}
void
-arena_dalloc_small(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t pageind)
-{
- arena_chunk_map_bits_t *bitselm;
+arena_dalloc_promoted(tsdn_t *tsdn, void *ptr, tcache_t *tcache,
+ bool slow_path) {
+ cassert(config_prof);
+ assert(opt_prof);
- if (config_debug) {
- /* arena_ptr_small_binind_get() does extra sanity checking. */
- assert(arena_ptr_small_binind_get(ptr, arena_mapbits_get(chunk,
- pageind)) != BININD_INVALID);
+ extent_t *extent = iealloc(tsdn, ptr);
+ size_t usize = arena_prof_demote(tsdn, extent, ptr);
+ if (usize <= tcache_maxclass) {
+ tcache_dalloc_large(tsdn_tsd(tsdn), tcache, ptr,
+ sz_size2index(usize), slow_path);
+ } else {
+ large_dalloc(tsdn, extent);
}
- bitselm = arena_bitselm_get(chunk, pageind);
- arena_dalloc_bin(arena, chunk, ptr, pageind, bitselm);
-}
-
-#ifdef JEMALLOC_JET
-#undef arena_dalloc_junk_large
-#define arena_dalloc_junk_large JEMALLOC_N(arena_dalloc_junk_large_impl)
-#endif
-void
-arena_dalloc_junk_large(void *ptr, size_t usize)
-{
-
- if (config_fill && unlikely(opt_junk_free))
- memset(ptr, 0x5a, usize);
}
-#ifdef JEMALLOC_JET
-#undef arena_dalloc_junk_large
-#define arena_dalloc_junk_large JEMALLOC_N(arena_dalloc_junk_large)
-arena_dalloc_junk_large_t *arena_dalloc_junk_large =
- JEMALLOC_N(arena_dalloc_junk_large_impl);
-#endif
static void
-arena_dalloc_large_locked_impl(arena_t *arena, arena_chunk_t *chunk,
- void *ptr, bool junked)
-{
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- arena_chunk_map_misc_t *miscelm = arena_miscelm_get(chunk, pageind);
- arena_run_t *run = &miscelm->run;
-
- if (config_fill || config_stats) {
- size_t usize = arena_mapbits_large_size_get(chunk, pageind) -
- large_pad;
-
- if (!junked)
- arena_dalloc_junk_large(ptr, usize);
- if (config_stats) {
- szind_t index = size2index(usize) - NBINS;
+arena_dissociate_bin_slab(arena_t *arena, extent_t *slab, bin_t *bin) {
+ /* Dissociate slab from bin. */
+ if (slab == bin->slabcur) {
+ bin->slabcur = NULL;
+ } else {
+ szind_t binind = extent_szind_get(slab);
+ const bin_info_t *bin_info = &bin_infos[binind];
- arena->stats.ndalloc_large++;
- arena->stats.allocated_large -= usize;
- arena->stats.lstats[index].ndalloc++;
- arena->stats.lstats[index].curruns--;
+ /*
+ * The following block's conditional is necessary because if the
+ * slab only contains one region, then it never gets inserted
+ * into the non-full slabs heap.
+ */
+ if (bin_info->nregs == 1) {
+ arena_bin_slabs_full_remove(arena, bin, slab);
+ } else {
+ arena_bin_slabs_nonfull_remove(bin, slab);
}
}
-
- arena_run_dalloc_decommit(arena, chunk, run);
-}
-
-void
-arena_dalloc_large_junked_locked(arena_t *arena, arena_chunk_t *chunk,
- void *ptr)
-{
-
- arena_dalloc_large_locked_impl(arena, chunk, ptr, true);
}
-void
-arena_dalloc_large(arena_t *arena, arena_chunk_t *chunk, void *ptr)
-{
+static void
+arena_dalloc_bin_slab(tsdn_t *tsdn, arena_t *arena, extent_t *slab,
+ bin_t *bin) {
+ assert(slab != bin->slabcur);
- malloc_mutex_lock(&arena->lock);
- arena_dalloc_large_locked_impl(arena, chunk, ptr, false);
- malloc_mutex_unlock(&arena->lock);
+ malloc_mutex_unlock(tsdn, &bin->lock);
+ /******************************/
+ arena_slab_dalloc(tsdn, arena, slab);
+ /****************************/
+ malloc_mutex_lock(tsdn, &bin->lock);
+ if (config_stats) {
+ bin->stats.curslabs--;
+ }
}
static void
-arena_ralloc_large_shrink(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t oldsize, size_t size)
-{
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- arena_chunk_map_misc_t *miscelm = arena_miscelm_get(chunk, pageind);
- arena_run_t *run = &miscelm->run;
-
- assert(size < oldsize);
+arena_bin_lower_slab(UNUSED tsdn_t *tsdn, arena_t *arena, extent_t *slab,
+ bin_t *bin) {
+ assert(extent_nfree_get(slab) > 0);
/*
- * Shrink the run, and make trailing pages available for other
- * allocations.
+ * Make sure that if bin->slabcur is non-NULL, it refers to the
+ * oldest/lowest non-full slab. It is okay to NULL slabcur out rather
+ * than proactively keeping it pointing at the oldest/lowest non-full
+ * slab.
*/
- malloc_mutex_lock(&arena->lock);
- arena_run_trim_tail(arena, chunk, run, oldsize + large_pad, size +
- large_pad, true);
- if (config_stats) {
- szind_t oldindex = size2index(oldsize) - NBINS;
- szind_t index = size2index(size) - NBINS;
-
- arena->stats.ndalloc_large++;
- arena->stats.allocated_large -= oldsize;
- arena->stats.lstats[oldindex].ndalloc++;
- arena->stats.lstats[oldindex].curruns--;
-
- arena->stats.nmalloc_large++;
- arena->stats.nrequests_large++;
- arena->stats.allocated_large += size;
- arena->stats.lstats[index].nmalloc++;
- arena->stats.lstats[index].nrequests++;
- arena->stats.lstats[index].curruns++;
- }
- malloc_mutex_unlock(&arena->lock);
-}
-
-static bool
-arena_ralloc_large_grow(arena_t *arena, arena_chunk_t *chunk, void *ptr,
- size_t oldsize, size_t usize_min, size_t usize_max, bool zero)
-{
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- size_t npages = (oldsize + large_pad) >> LG_PAGE;
- size_t followsize;
-
- assert(oldsize == arena_mapbits_large_size_get(chunk, pageind) -
- large_pad);
-
- /* Try to extend the run. */
- malloc_mutex_lock(&arena->lock);
- if (pageind+npages >= chunk_npages || arena_mapbits_allocated_get(chunk,
- pageind+npages) != 0)
- goto label_fail;
- followsize = arena_mapbits_unallocated_size_get(chunk, pageind+npages);
- if (oldsize + followsize >= usize_min) {
- /*
- * The next run is available and sufficiently large. Split the
- * following run, then merge the first part with the existing
- * allocation.
- */
- arena_run_t *run;
- size_t usize, splitsize, size, flag_dirty, flag_unzeroed_mask;
-
- usize = usize_max;
- while (oldsize + followsize < usize)
- usize = index2size(size2index(usize)-1);
- assert(usize >= usize_min);
- assert(usize >= oldsize);
- splitsize = usize - oldsize;
- if (splitsize == 0)
- goto label_fail;
-
- run = &arena_miscelm_get(chunk, pageind+npages)->run;
- if (arena_run_split_large(arena, run, splitsize, zero))
- goto label_fail;
-
- if (config_cache_oblivious && zero) {
- /*
- * Zero the trailing bytes of the original allocation's
- * last page, since they are in an indeterminate state.
- */
- assert(PAGE_CEILING(oldsize) == oldsize);
- memset((void *)((uintptr_t)ptr + oldsize), 0,
- PAGE_CEILING((uintptr_t)ptr) - (uintptr_t)ptr);
+ if (bin->slabcur != NULL && extent_snad_comp(bin->slabcur, slab) > 0) {
+ /* Switch slabcur. */
+ if (extent_nfree_get(bin->slabcur) > 0) {
+ arena_bin_slabs_nonfull_insert(bin, bin->slabcur);
+ } else {
+ arena_bin_slabs_full_insert(arena, bin, bin->slabcur);
}
-
- size = oldsize + splitsize;
- npages = (size + large_pad) >> LG_PAGE;
-
- /*
- * Mark the extended run as dirty if either portion of the run
- * was dirty before allocation. This is rather pedantic,
- * because there's not actually any sequence of events that
- * could cause the resulting run to be passed to
- * arena_run_dalloc() with the dirty argument set to false
- * (which is when dirty flag consistency would really matter).
- */
- flag_dirty = arena_mapbits_dirty_get(chunk, pageind) |
- arena_mapbits_dirty_get(chunk, pageind+npages-1);
- flag_unzeroed_mask = flag_dirty == 0 ? CHUNK_MAP_UNZEROED : 0;
- arena_mapbits_large_set(chunk, pageind, size + large_pad,
- flag_dirty | (flag_unzeroed_mask &
- arena_mapbits_unzeroed_get(chunk, pageind)));
- arena_mapbits_large_set(chunk, pageind+npages-1, 0, flag_dirty |
- (flag_unzeroed_mask & arena_mapbits_unzeroed_get(chunk,
- pageind+npages-1)));
-
+ bin->slabcur = slab;
if (config_stats) {
- szind_t oldindex = size2index(oldsize) - NBINS;
- szind_t index = size2index(size) - NBINS;
-
- arena->stats.ndalloc_large++;
- arena->stats.allocated_large -= oldsize;
- arena->stats.lstats[oldindex].ndalloc++;
- arena->stats.lstats[oldindex].curruns--;
-
- arena->stats.nmalloc_large++;
- arena->stats.nrequests_large++;
- arena->stats.allocated_large += size;
- arena->stats.lstats[index].nmalloc++;
- arena->stats.lstats[index].nrequests++;
- arena->stats.lstats[index].curruns++;
+ bin->stats.reslabs++;
}
- malloc_mutex_unlock(&arena->lock);
- return (false);
+ } else {
+ arena_bin_slabs_nonfull_insert(bin, slab);
}
-label_fail:
- malloc_mutex_unlock(&arena->lock);
- return (true);
}
-#ifdef JEMALLOC_JET
-#undef arena_ralloc_junk_large
-#define arena_ralloc_junk_large JEMALLOC_N(arena_ralloc_junk_large_impl)
-#endif
static void
-arena_ralloc_junk_large(void *ptr, size_t old_usize, size_t usize)
-{
+arena_dalloc_bin_locked_impl(tsdn_t *tsdn, arena_t *arena, extent_t *slab,
+ void *ptr, bool junked) {
+ arena_slab_data_t *slab_data = extent_slab_data_get(slab);
+ szind_t binind = extent_szind_get(slab);
+ bin_t *bin = &arena->bins[binind];
+ const bin_info_t *bin_info = &bin_infos[binind];
+
+ if (!junked && config_fill && unlikely(opt_junk_free)) {
+ arena_dalloc_junk_small(ptr, bin_info);
+ }
+
+ arena_slab_reg_dalloc(slab, slab_data, ptr);
+ unsigned nfree = extent_nfree_get(slab);
+ if (nfree == bin_info->nregs) {
+ arena_dissociate_bin_slab(arena, slab, bin);
+ arena_dalloc_bin_slab(tsdn, arena, slab, bin);
+ } else if (nfree == 1 && slab != bin->slabcur) {
+ arena_bin_slabs_full_remove(arena, bin, slab);
+ arena_bin_lower_slab(tsdn, arena, slab, bin);
+ }
- if (config_fill && unlikely(opt_junk_free)) {
- memset((void *)((uintptr_t)ptr + usize), 0x5a,
- old_usize - usize);
+ if (config_stats) {
+ bin->stats.ndalloc++;
+ bin->stats.curregs--;
}
}
-#ifdef JEMALLOC_JET
-#undef arena_ralloc_junk_large
-#define arena_ralloc_junk_large JEMALLOC_N(arena_ralloc_junk_large)
-arena_ralloc_junk_large_t *arena_ralloc_junk_large =
- JEMALLOC_N(arena_ralloc_junk_large_impl);
-#endif
-/*
- * Try to resize a large allocation, in order to avoid copying. This will
- * always fail if growing an object, and the following run is already in use.
- */
-static bool
-arena_ralloc_large(void *ptr, size_t oldsize, size_t usize_min,
- size_t usize_max, bool zero)
-{
- arena_chunk_t *chunk;
- arena_t *arena;
+void
+arena_dalloc_bin_junked_locked(tsdn_t *tsdn, arena_t *arena, extent_t *extent,
+ void *ptr) {
+ arena_dalloc_bin_locked_impl(tsdn, arena, extent, ptr, true);
+}
- if (oldsize == usize_max) {
- /* Current size class is compatible and maximal. */
- return (false);
- }
+static void
+arena_dalloc_bin(tsdn_t *tsdn, arena_t *arena, extent_t *extent, void *ptr) {
+ szind_t binind = extent_szind_get(extent);
+ bin_t *bin = &arena->bins[binind];
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- arena = extent_node_arena_get(&chunk->node);
+ malloc_mutex_lock(tsdn, &bin->lock);
+ arena_dalloc_bin_locked_impl(tsdn, arena, extent, ptr, false);
+ malloc_mutex_unlock(tsdn, &bin->lock);
+}
- if (oldsize < usize_max) {
- bool ret = arena_ralloc_large_grow(arena, chunk, ptr, oldsize,
- usize_min, usize_max, zero);
- if (config_fill && !ret && !zero) {
- if (unlikely(opt_junk_alloc)) {
- memset((void *)((uintptr_t)ptr + oldsize), 0xa5,
- isalloc(ptr, config_prof) - oldsize);
- } else if (unlikely(opt_zero)) {
- memset((void *)((uintptr_t)ptr + oldsize), 0,
- isalloc(ptr, config_prof) - oldsize);
- }
- }
- return (ret);
- }
+void
+arena_dalloc_small(tsdn_t *tsdn, void *ptr) {
+ extent_t *extent = iealloc(tsdn, ptr);
+ arena_t *arena = extent_arena_get(extent);
- assert(oldsize > usize_max);
- /* Fill before shrinking in order avoid a race. */
- arena_ralloc_junk_large(ptr, oldsize, usize_max);
- arena_ralloc_large_shrink(arena, chunk, ptr, oldsize, usize_max);
- return (false);
+ arena_dalloc_bin(tsdn, arena, extent, ptr);
+ arena_decay_tick(tsdn, arena);
}
bool
-arena_ralloc_no_move(void *ptr, size_t oldsize, size_t size, size_t extra,
- bool zero)
-{
- size_t usize_min, usize_max;
-
- usize_min = s2u(size);
- usize_max = s2u(size + extra);
- if (likely(oldsize <= large_maxclass && usize_min <= large_maxclass)) {
+arena_ralloc_no_move(tsdn_t *tsdn, void *ptr, size_t oldsize, size_t size,
+ size_t extra, bool zero) {
+ /* Calls with non-zero extra had to clamp extra. */
+ assert(extra == 0 || size + extra <= LARGE_MAXCLASS);
+
+ if (unlikely(size > LARGE_MAXCLASS)) {
+ return true;
+ }
+
+ extent_t *extent = iealloc(tsdn, ptr);
+ size_t usize_min = sz_s2u(size);
+ size_t usize_max = sz_s2u(size + extra);
+ if (likely(oldsize <= SMALL_MAXCLASS && usize_min <= SMALL_MAXCLASS)) {
/*
* Avoid moving the allocation if the size class can be left the
* same.
*/
- if (oldsize <= SMALL_MAXCLASS) {
- assert(arena_bin_info[size2index(oldsize)].reg_size ==
- oldsize);
- if ((usize_max <= SMALL_MAXCLASS &&
- size2index(usize_max) == size2index(oldsize)) ||
- (size <= oldsize && usize_max >= oldsize))
- return (false);
- } else {
- if (usize_max > SMALL_MAXCLASS) {
- if (!arena_ralloc_large(ptr, oldsize, usize_min,
- usize_max, zero))
- return (false);
- }
+ assert(bin_infos[sz_size2index(oldsize)].reg_size ==
+ oldsize);
+ if ((usize_max > SMALL_MAXCLASS || sz_size2index(usize_max) !=
+ sz_size2index(oldsize)) && (size > oldsize || usize_max <
+ oldsize)) {
+ return true;
}
- /* Reallocation would require a move. */
- return (true);
- } else {
- return (huge_ralloc_no_move(ptr, oldsize, usize_min, usize_max,
- zero));
+ arena_decay_tick(tsdn, extent_arena_get(extent));
+ return false;
+ } else if (oldsize >= LARGE_MINCLASS && usize_max >= LARGE_MINCLASS) {
+ return large_ralloc_no_move(tsdn, extent, usize_min, usize_max,
+ zero);
}
+
+ return true;
}
static void *
-arena_ralloc_move_helper(tsd_t *tsd, arena_t *arena, size_t usize,
- size_t alignment, bool zero, tcache_t *tcache)
-{
-
- if (alignment == 0)
- return (arena_malloc(tsd, arena, usize, zero, tcache));
- usize = sa2u(usize, alignment);
- if (usize == 0)
- return (NULL);
- return (ipalloct(tsd, usize, alignment, zero, tcache, arena));
+arena_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize,
+ size_t alignment, bool zero, tcache_t *tcache) {
+ if (alignment == 0) {
+ return arena_malloc(tsdn, arena, usize, sz_size2index(usize),
+ zero, tcache, true);
+ }
+ usize = sz_sa2u(usize, alignment);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
+ return NULL;
+ }
+ return ipalloct(tsdn, usize, alignment, zero, tcache, arena);
}
void *
-arena_ralloc(tsd_t *tsd, arena_t *arena, void *ptr, size_t oldsize, size_t size,
- size_t alignment, bool zero, tcache_t *tcache)
-{
- void *ret;
- size_t usize;
-
- usize = s2u(size);
- if (usize == 0)
- return (NULL);
-
- if (likely(usize <= large_maxclass)) {
- size_t copysize;
+arena_ralloc(tsdn_t *tsdn, arena_t *arena, void *ptr, size_t oldsize,
+ size_t size, size_t alignment, bool zero, tcache_t *tcache) {
+ size_t usize = sz_s2u(size);
+ if (unlikely(usize == 0 || size > LARGE_MAXCLASS)) {
+ return NULL;
+ }
+ if (likely(usize <= SMALL_MAXCLASS)) {
/* Try to avoid moving the allocation. */
- if (!arena_ralloc_no_move(ptr, oldsize, usize, 0, zero))
- return (ptr);
-
- /*
- * size and oldsize are different enough that we need to move
- * the object. In that case, fall back to allocating new space
- * and copying.
- */
- ret = arena_ralloc_move_helper(tsd, arena, usize, alignment,
- zero, tcache);
- if (ret == NULL)
- return (NULL);
+ if (!arena_ralloc_no_move(tsdn, ptr, oldsize, usize, 0, zero)) {
+ return ptr;
+ }
+ }
- /*
- * Junk/zero-filling were already done by
- * ipalloc()/arena_malloc().
- */
+ if (oldsize >= LARGE_MINCLASS && usize >= LARGE_MINCLASS) {
+ return large_ralloc(tsdn, arena, iealloc(tsdn, ptr), usize,
+ alignment, zero, tcache);
+ }
- copysize = (usize < oldsize) ? usize : oldsize;
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, copysize);
- memcpy(ret, ptr, copysize);
- isqalloc(tsd, ptr, oldsize, tcache);
- } else {
- ret = huge_ralloc(tsd, arena, ptr, oldsize, usize, alignment,
- zero, tcache);
+ /*
+ * size and oldsize are different enough that we need to move the
+ * object. In that case, fall back to allocating new space and copying.
+ */
+ void *ret = arena_ralloc_move_helper(tsdn, arena, usize, alignment,
+ zero, tcache);
+ if (ret == NULL) {
+ return NULL;
}
- return (ret);
+
+ /*
+ * Junk/zero-filling were already done by
+ * ipalloc()/arena_malloc().
+ */
+
+ size_t copysize = (usize < oldsize) ? usize : oldsize;
+ memcpy(ret, ptr, copysize);
+ isdalloct(tsdn, ptr, oldsize, tcache, NULL, true);
+ return ret;
}
dss_prec_t
-arena_dss_prec_get(arena_t *arena)
-{
- dss_prec_t ret;
-
- malloc_mutex_lock(&arena->lock);
- ret = arena->dss_prec;
- malloc_mutex_unlock(&arena->lock);
- return (ret);
+arena_dss_prec_get(arena_t *arena) {
+ return (dss_prec_t)atomic_load_u(&arena->dss_prec, ATOMIC_ACQUIRE);
}
bool
-arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec)
-{
-
- if (!have_dss)
+arena_dss_prec_set(arena_t *arena, dss_prec_t dss_prec) {
+ if (!have_dss) {
return (dss_prec != dss_prec_disabled);
- malloc_mutex_lock(&arena->lock);
- arena->dss_prec = dss_prec;
- malloc_mutex_unlock(&arena->lock);
- return (false);
+ }
+ atomic_store_u(&arena->dss_prec, (unsigned)dss_prec, ATOMIC_RELEASE);
+ return false;
}
ssize_t
-arena_lg_dirty_mult_default_get(void)
-{
+arena_dirty_decay_ms_default_get(void) {
+ return atomic_load_zd(&dirty_decay_ms_default, ATOMIC_RELAXED);
+}
- return ((ssize_t)atomic_read_z((size_t *)&lg_dirty_mult_default));
+bool
+arena_dirty_decay_ms_default_set(ssize_t decay_ms) {
+ if (!arena_decay_ms_valid(decay_ms)) {
+ return true;
+ }
+ atomic_store_zd(&dirty_decay_ms_default, decay_ms, ATOMIC_RELAXED);
+ return false;
+}
+
+ssize_t
+arena_muzzy_decay_ms_default_get(void) {
+ return atomic_load_zd(&muzzy_decay_ms_default, ATOMIC_RELAXED);
+}
+
+bool
+arena_muzzy_decay_ms_default_set(ssize_t decay_ms) {
+ if (!arena_decay_ms_valid(decay_ms)) {
+ return true;
+ }
+ atomic_store_zd(&muzzy_decay_ms_default, decay_ms, ATOMIC_RELAXED);
+ return false;
}
bool
-arena_lg_dirty_mult_default_set(ssize_t lg_dirty_mult)
-{
+arena_retain_grow_limit_get_set(tsd_t *tsd, arena_t *arena, size_t *old_limit,
+ size_t *new_limit) {
+ assert(opt_retain);
+
+ pszind_t new_ind JEMALLOC_CC_SILENCE_INIT(0);
+ if (new_limit != NULL) {
+ size_t limit = *new_limit;
+ /* Grow no more than the new limit. */
+ if ((new_ind = sz_psz2ind(limit + 1) - 1) >
+ EXTENT_GROW_MAX_PIND) {
+ return true;
+ }
+ }
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &arena->extent_grow_mtx);
+ if (old_limit != NULL) {
+ *old_limit = sz_pind2sz(arena->retain_grow_limit);
+ }
+ if (new_limit != NULL) {
+ arena->retain_grow_limit = new_ind;
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &arena->extent_grow_mtx);
- if (!arena_lg_dirty_mult_valid(lg_dirty_mult))
- return (true);
- atomic_write_z((size_t *)&lg_dirty_mult_default, (size_t)lg_dirty_mult);
- return (false);
+ return false;
+}
+
+unsigned
+arena_nthreads_get(arena_t *arena, bool internal) {
+ return atomic_load_u(&arena->nthreads[internal], ATOMIC_RELAXED);
}
void
-arena_stats_merge(arena_t *arena, const char **dss, ssize_t *lg_dirty_mult,
- size_t *nactive, size_t *ndirty, arena_stats_t *astats,
- malloc_bin_stats_t *bstats, malloc_large_stats_t *lstats,
- malloc_huge_stats_t *hstats)
-{
- unsigned i;
+arena_nthreads_inc(arena_t *arena, bool internal) {
+ atomic_fetch_add_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED);
+}
- malloc_mutex_lock(&arena->lock);
- *dss = dss_prec_names[arena->dss_prec];
- *lg_dirty_mult = arena->lg_dirty_mult;
- *nactive += arena->nactive;
- *ndirty += arena->ndirty;
-
- astats->mapped += arena->stats.mapped;
- astats->npurge += arena->stats.npurge;
- astats->nmadvise += arena->stats.nmadvise;
- astats->purged += arena->stats.purged;
- astats->metadata_mapped += arena->stats.metadata_mapped;
- astats->metadata_allocated += arena_metadata_allocated_get(arena);
- astats->allocated_large += arena->stats.allocated_large;
- astats->nmalloc_large += arena->stats.nmalloc_large;
- astats->ndalloc_large += arena->stats.ndalloc_large;
- astats->nrequests_large += arena->stats.nrequests_large;
- astats->allocated_huge += arena->stats.allocated_huge;
- astats->nmalloc_huge += arena->stats.nmalloc_huge;
- astats->ndalloc_huge += arena->stats.ndalloc_huge;
-
- for (i = 0; i < nlclasses; i++) {
- lstats[i].nmalloc += arena->stats.lstats[i].nmalloc;
- lstats[i].ndalloc += arena->stats.lstats[i].ndalloc;
- lstats[i].nrequests += arena->stats.lstats[i].nrequests;
- lstats[i].curruns += arena->stats.lstats[i].curruns;
- }
-
- for (i = 0; i < nhclasses; i++) {
- hstats[i].nmalloc += arena->stats.hstats[i].nmalloc;
- hstats[i].ndalloc += arena->stats.hstats[i].ndalloc;
- hstats[i].curhchunks += arena->stats.hstats[i].curhchunks;
- }
- malloc_mutex_unlock(&arena->lock);
+void
+arena_nthreads_dec(arena_t *arena, bool internal) {
+ atomic_fetch_sub_u(&arena->nthreads[internal], 1, ATOMIC_RELAXED);
+}
- for (i = 0; i < NBINS; i++) {
- arena_bin_t *bin = &arena->bins[i];
-
- malloc_mutex_lock(&bin->lock);
- bstats[i].nmalloc += bin->stats.nmalloc;
- bstats[i].ndalloc += bin->stats.ndalloc;
- bstats[i].nrequests += bin->stats.nrequests;
- bstats[i].curregs += bin->stats.curregs;
- if (config_tcache) {
- bstats[i].nfills += bin->stats.nfills;
- bstats[i].nflushes += bin->stats.nflushes;
- }
- bstats[i].nruns += bin->stats.nruns;
- bstats[i].reruns += bin->stats.reruns;
- bstats[i].curruns += bin->stats.curruns;
- malloc_mutex_unlock(&bin->lock);
- }
+size_t
+arena_extent_sn_next(arena_t *arena) {
+ return atomic_fetch_add_zu(&arena->extent_sn_next, 1, ATOMIC_RELAXED);
}
arena_t *
-arena_new(unsigned ind)
-{
+arena_new(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks) {
arena_t *arena;
+ base_t *base;
unsigned i;
- arena_bin_t *bin;
- /*
- * Allocate arena, arena->lstats, and arena->hstats contiguously, mainly
- * because there is no way to clean up if base_alloc() OOMs.
- */
- if (config_stats) {
- arena = (arena_t *)base_alloc(CACHELINE_CEILING(sizeof(arena_t))
- + QUANTUM_CEILING(nlclasses * sizeof(malloc_large_stats_t) +
- nhclasses) * sizeof(malloc_huge_stats_t));
- } else
- arena = (arena_t *)base_alloc(sizeof(arena_t));
- if (arena == NULL)
- return (NULL);
-
- arena->ind = ind;
- arena->nthreads = 0;
- if (malloc_mutex_init(&arena->lock))
- return (NULL);
+ if (ind == 0) {
+ base = b0get();
+ } else {
+ base = base_new(tsdn, ind, extent_hooks);
+ if (base == NULL) {
+ return NULL;
+ }
+ }
+
+ arena = (arena_t *)base_alloc(tsdn, base, sizeof(arena_t), CACHELINE);
+ if (arena == NULL) {
+ goto label_error;
+ }
+
+ atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED);
+ atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED);
+ arena->last_thd = NULL;
if (config_stats) {
- memset(&arena->stats, 0, sizeof(arena_stats_t));
- arena->stats.lstats = (malloc_large_stats_t *)((uintptr_t)arena
- + CACHELINE_CEILING(sizeof(arena_t)));
- memset(arena->stats.lstats, 0, nlclasses *
- sizeof(malloc_large_stats_t));
- arena->stats.hstats = (malloc_huge_stats_t *)((uintptr_t)arena
- + CACHELINE_CEILING(sizeof(arena_t)) +
- QUANTUM_CEILING(nlclasses * sizeof(malloc_large_stats_t)));
- memset(arena->stats.hstats, 0, nhclasses *
- sizeof(malloc_huge_stats_t));
- if (config_tcache)
- ql_new(&arena->tcache_ql);
- }
-
- if (config_prof)
- arena->prof_accumbytes = 0;
+ if (arena_stats_init(tsdn, &arena->stats)) {
+ goto label_error;
+ }
+
+ ql_new(&arena->tcache_ql);
+ ql_new(&arena->cache_bin_array_descriptor_ql);
+ if (malloc_mutex_init(&arena->tcache_ql_mtx, "tcache_ql",
+ WITNESS_RANK_TCACHE_QL, malloc_mutex_rank_exclusive)) {
+ goto label_error;
+ }
+ }
+
+ if (config_prof) {
+ if (prof_accum_init(tsdn, &arena->prof_accum)) {
+ goto label_error;
+ }
+ }
if (config_cache_oblivious) {
/*
@@ -3040,279 +1809,235 @@ arena_new(unsigned ind)
* cost of test repeatability. For debug builds, instead use a
* deterministic seed.
*/
- arena->offset_state = config_debug ? ind :
- (uint64_t)(uintptr_t)arena;
+ atomic_store_zu(&arena->offset_state, config_debug ? ind :
+ (size_t)(uintptr_t)arena, ATOMIC_RELAXED);
}
- arena->dss_prec = chunk_dss_prec_get();
-
- arena->spare = NULL;
-
- arena->lg_dirty_mult = arena_lg_dirty_mult_default_get();
- arena->purging = false;
- arena->nactive = 0;
- arena->ndirty = 0;
-
- arena_avail_tree_new(&arena->runs_avail);
- qr_new(&arena->runs_dirty, rd_link);
- qr_new(&arena->chunks_cache, cc_link);
-
- ql_new(&arena->huge);
- if (malloc_mutex_init(&arena->huge_mtx))
- return (NULL);
+ atomic_store_zu(&arena->extent_sn_next, 0, ATOMIC_RELAXED);
- extent_tree_szad_new(&arena->chunks_szad_cached);
- extent_tree_ad_new(&arena->chunks_ad_cached);
- extent_tree_szad_new(&arena->chunks_szad_retained);
- extent_tree_ad_new(&arena->chunks_ad_retained);
- if (malloc_mutex_init(&arena->chunks_mtx))
- return (NULL);
- ql_new(&arena->node_cache);
- if (malloc_mutex_init(&arena->node_cache_mtx))
- return (NULL);
+ atomic_store_u(&arena->dss_prec, (unsigned)extent_dss_prec_get(),
+ ATOMIC_RELAXED);
- arena->chunk_hooks = chunk_hooks_default;
+ atomic_store_zu(&arena->nactive, 0, ATOMIC_RELAXED);
- /* Initialize bins. */
- for (i = 0; i < NBINS; i++) {
- bin = &arena->bins[i];
- if (malloc_mutex_init(&bin->lock))
- return (NULL);
- bin->runcur = NULL;
- arena_run_tree_new(&bin->runs);
- if (config_stats)
- memset(&bin->stats, 0, sizeof(malloc_bin_stats_t));
+ extent_list_init(&arena->large);
+ if (malloc_mutex_init(&arena->large_mtx, "arena_large",
+ WITNESS_RANK_ARENA_LARGE, malloc_mutex_rank_exclusive)) {
+ goto label_error;
}
- return (arena);
-}
-
-/*
- * Calculate bin_info->run_size such that it meets the following constraints:
- *
- * *) bin_info->run_size <= arena_maxrun
- * *) bin_info->nregs <= RUN_MAXREGS
- *
- * bin_info->nregs and bin_info->reg0_offset are also calculated here, since
- * these settings are all interdependent.
- */
-static void
-bin_info_run_size_calc(arena_bin_info_t *bin_info)
-{
- size_t pad_size;
- size_t try_run_size, perfect_run_size, actual_run_size;
- uint32_t try_nregs, perfect_nregs, actual_nregs;
-
/*
- * Determine redzone size based on minimum alignment and minimum
- * redzone size. Add padding to the end of the run if it is needed to
- * align the regions. The padding allows each redzone to be half the
- * minimum alignment; without the padding, each redzone would have to
- * be twice as large in order to maintain alignment.
+ * Delay coalescing for dirty extents despite the disruptive effect on
+ * memory layout for best-fit extent allocation, since cached extents
+ * are likely to be reused soon after deallocation, and the cost of
+ * merging/splitting extents is non-trivial.
*/
- if (config_fill && unlikely(opt_redzone)) {
- size_t align_min = ZU(1) << (jemalloc_ffs(bin_info->reg_size) -
- 1);
- if (align_min <= REDZONE_MINSIZE) {
- bin_info->redzone_size = REDZONE_MINSIZE;
- pad_size = 0;
- } else {
- bin_info->redzone_size = align_min >> 1;
- pad_size = bin_info->redzone_size;
- }
- } else {
- bin_info->redzone_size = 0;
- pad_size = 0;
+ if (extents_init(tsdn, &arena->extents_dirty, extent_state_dirty,
+ true)) {
+ goto label_error;
}
- bin_info->reg_interval = bin_info->reg_size +
- (bin_info->redzone_size << 1);
-
/*
- * Compute run size under ideal conditions (no redzones, no limit on run
- * size).
+ * Coalesce muzzy extents immediately, because operations on them are in
+ * the critical path much less often than for dirty extents.
*/
- try_run_size = PAGE;
- try_nregs = try_run_size / bin_info->reg_size;
- do {
- perfect_run_size = try_run_size;
- perfect_nregs = try_nregs;
-
- try_run_size += PAGE;
- try_nregs = try_run_size / bin_info->reg_size;
- } while (perfect_run_size != perfect_nregs * bin_info->reg_size);
- assert(perfect_nregs <= RUN_MAXREGS);
-
- actual_run_size = perfect_run_size;
- actual_nregs = (actual_run_size - pad_size) / bin_info->reg_interval;
-
+ if (extents_init(tsdn, &arena->extents_muzzy, extent_state_muzzy,
+ false)) {
+ goto label_error;
+ }
/*
- * Redzones can require enough padding that not even a single region can
- * fit within the number of pages that would normally be dedicated to a
- * run for this size class. Increase the run size until at least one
- * region fits.
+ * Coalesce retained extents immediately, in part because they will
+ * never be evicted (and therefore there's no opportunity for delayed
+ * coalescing), but also because operations on retained extents are not
+ * in the critical path.
*/
- while (actual_nregs == 0) {
- assert(config_fill && unlikely(opt_redzone));
+ if (extents_init(tsdn, &arena->extents_retained, extent_state_retained,
+ false)) {
+ goto label_error;
+ }
- actual_run_size += PAGE;
- actual_nregs = (actual_run_size - pad_size) /
- bin_info->reg_interval;
+ if (arena_decay_init(&arena->decay_dirty,
+ arena_dirty_decay_ms_default_get(), &arena->stats.decay_dirty)) {
+ goto label_error;
+ }
+ if (arena_decay_init(&arena->decay_muzzy,
+ arena_muzzy_decay_ms_default_get(), &arena->stats.decay_muzzy)) {
+ goto label_error;
}
- /*
- * Make sure that the run will fit within an arena chunk.
- */
- while (actual_run_size > arena_maxrun) {
- actual_run_size -= PAGE;
- actual_nregs = (actual_run_size - pad_size) /
- bin_info->reg_interval;
+ arena->extent_grow_next = sz_psz2ind(HUGEPAGE);
+ arena->retain_grow_limit = EXTENT_GROW_MAX_PIND;
+ if (malloc_mutex_init(&arena->extent_grow_mtx, "extent_grow",
+ WITNESS_RANK_EXTENT_GROW, malloc_mutex_rank_exclusive)) {
+ goto label_error;
+ }
+
+ extent_avail_new(&arena->extent_avail);
+ if (malloc_mutex_init(&arena->extent_avail_mtx, "extent_avail",
+ WITNESS_RANK_EXTENT_AVAIL, malloc_mutex_rank_exclusive)) {
+ goto label_error;
+ }
+
+ /* Initialize bins. */
+ for (i = 0; i < NBINS; i++) {
+ bool err = bin_init(&arena->bins[i]);
+ if (err) {
+ goto label_error;
+ }
}
- assert(actual_nregs > 0);
- assert(actual_run_size == s2u(actual_run_size));
- /* Copy final settings. */
- bin_info->run_size = actual_run_size;
- bin_info->nregs = actual_nregs;
- bin_info->reg0_offset = actual_run_size - (actual_nregs *
- bin_info->reg_interval) - pad_size + bin_info->redzone_size;
+ arena->base = base;
+ /* Set arena before creating background threads. */
+ arena_set(ind, arena);
- if (actual_run_size > small_maxrun)
- small_maxrun = actual_run_size;
+ nstime_init(&arena->create_time, 0);
+ nstime_update(&arena->create_time);
- assert(bin_info->reg0_offset - bin_info->redzone_size + (bin_info->nregs
- * bin_info->reg_interval) + pad_size == bin_info->run_size);
+ /* We don't support reentrancy for arena 0 bootstrapping. */
+ if (ind != 0) {
+ /*
+ * If we're here, then arena 0 already exists, so bootstrapping
+ * is done enough that we should have tsd.
+ */
+ assert(!tsdn_null(tsdn));
+ pre_reentrancy(tsdn_tsd(tsdn), arena);
+ if (hooks_arena_new_hook) {
+ hooks_arena_new_hook();
+ }
+ post_reentrancy(tsdn_tsd(tsdn));
+ }
+
+ return arena;
+label_error:
+ if (ind != 0) {
+ base_delete(tsdn, base);
+ }
+ return NULL;
}
-static void
-bin_info_init(void)
-{
- arena_bin_info_t *bin_info;
-
-#define BIN_INFO_INIT_bin_yes(index, size) \
- bin_info = &arena_bin_info[index]; \
- bin_info->reg_size = size; \
- bin_info_run_size_calc(bin_info); \
- bitmap_info_init(&bin_info->bitmap_info, bin_info->nregs);
-#define BIN_INFO_INIT_bin_no(index, size)
-#define SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
- BIN_INFO_INIT_bin_##bin(index, (ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta))
+void
+arena_boot(void) {
+ arena_dirty_decay_ms_default_set(opt_dirty_decay_ms);
+ arena_muzzy_decay_ms_default_set(opt_muzzy_decay_ms);
+#define REGIND_bin_yes(index, reg_size) \
+ div_init(&arena_binind_div_info[(index)], (reg_size));
+#define REGIND_bin_no(index, reg_size)
+#define SC(index, lg_grp, lg_delta, ndelta, psz, bin, pgs, \
+ lg_delta_lookup) \
+ REGIND_bin_##bin(index, (1U<<lg_grp) + (ndelta << lg_delta))
SIZE_CLASSES
-#undef BIN_INFO_INIT_bin_yes
-#undef BIN_INFO_INIT_bin_no
+#undef REGIND_bin_yes
+#undef REGIND_bin_no
#undef SC
}
-static bool
-small_run_size_init(void)
-{
-
- assert(small_maxrun != 0);
-
- small_run_tab = (bool *)base_alloc(sizeof(bool) * (small_maxrun >>
- LG_PAGE));
- if (small_run_tab == NULL)
- return (true);
+void
+arena_prefork0(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_prefork(tsdn, &arena->decay_dirty.mtx);
+ malloc_mutex_prefork(tsdn, &arena->decay_muzzy.mtx);
+}
-#define TAB_INIT_bin_yes(index, size) { \
- arena_bin_info_t *bin_info = &arena_bin_info[index]; \
- small_run_tab[bin_info->run_size >> LG_PAGE] = true; \
+void
+arena_prefork1(tsdn_t *tsdn, arena_t *arena) {
+ if (config_stats) {
+ malloc_mutex_prefork(tsdn, &arena->tcache_ql_mtx);
}
-#define TAB_INIT_bin_no(index, size)
-#define SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
- TAB_INIT_bin_##bin(index, (ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta))
- SIZE_CLASSES
-#undef TAB_INIT_bin_yes
-#undef TAB_INIT_bin_no
-#undef SC
-
- return (false);
}
-bool
-arena_boot(void)
-{
- unsigned i;
+void
+arena_prefork2(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_prefork(tsdn, &arena->extent_grow_mtx);
+}
- arena_lg_dirty_mult_default_set(opt_lg_dirty_mult);
+void
+arena_prefork3(tsdn_t *tsdn, arena_t *arena) {
+ extents_prefork(tsdn, &arena->extents_dirty);
+ extents_prefork(tsdn, &arena->extents_muzzy);
+ extents_prefork(tsdn, &arena->extents_retained);
+}
- /*
- * Compute the header size such that it is large enough to contain the
- * page map. The page map is biased to omit entries for the header
- * itself, so some iteration is necessary to compute the map bias.
- *
- * 1) Compute safe header_size and map_bias values that include enough
- * space for an unbiased page map.
- * 2) Refine map_bias based on (1) to omit the header pages in the page
- * map. The resulting map_bias may be one too small.
- * 3) Refine map_bias based on (2). The result will be >= the result
- * from (2), and will always be correct.
- */
- map_bias = 0;
- for (i = 0; i < 3; i++) {
- size_t header_size = offsetof(arena_chunk_t, map_bits) +
- ((sizeof(arena_chunk_map_bits_t) +
- sizeof(arena_chunk_map_misc_t)) * (chunk_npages-map_bias));
- map_bias = (header_size + PAGE_MASK) >> LG_PAGE;
- }
- assert(map_bias > 0);
-
- map_misc_offset = offsetof(arena_chunk_t, map_bits) +
- sizeof(arena_chunk_map_bits_t) * (chunk_npages-map_bias);
-
- arena_maxrun = chunksize - (map_bias << LG_PAGE);
- assert(arena_maxrun > 0);
- large_maxclass = index2size(size2index(chunksize)-1);
- if (large_maxclass > arena_maxrun) {
- /*
- * For small chunk sizes it's possible for there to be fewer
- * non-header pages available than are necessary to serve the
- * size classes just below chunksize.
- */
- large_maxclass = arena_maxrun;
- }
- assert(large_maxclass > 0);
- nlclasses = size2index(large_maxclass) - size2index(SMALL_MAXCLASS);
- nhclasses = NSIZES - nlclasses - NBINS;
+void
+arena_prefork4(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_prefork(tsdn, &arena->extent_avail_mtx);
+}
- bin_info_init();
- return (small_run_size_init());
+void
+arena_prefork5(tsdn_t *tsdn, arena_t *arena) {
+ base_prefork(tsdn, arena->base);
}
void
-arena_prefork(arena_t *arena)
-{
- unsigned i;
+arena_prefork6(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_prefork(tsdn, &arena->large_mtx);
+}
- malloc_mutex_prefork(&arena->lock);
- malloc_mutex_prefork(&arena->huge_mtx);
- malloc_mutex_prefork(&arena->chunks_mtx);
- malloc_mutex_prefork(&arena->node_cache_mtx);
- for (i = 0; i < NBINS; i++)
- malloc_mutex_prefork(&arena->bins[i].lock);
+void
+arena_prefork7(tsdn_t *tsdn, arena_t *arena) {
+ for (unsigned i = 0; i < NBINS; i++) {
+ bin_prefork(tsdn, &arena->bins[i]);
+ }
}
void
-arena_postfork_parent(arena_t *arena)
-{
+arena_postfork_parent(tsdn_t *tsdn, arena_t *arena) {
unsigned i;
- for (i = 0; i < NBINS; i++)
- malloc_mutex_postfork_parent(&arena->bins[i].lock);
- malloc_mutex_postfork_parent(&arena->node_cache_mtx);
- malloc_mutex_postfork_parent(&arena->chunks_mtx);
- malloc_mutex_postfork_parent(&arena->huge_mtx);
- malloc_mutex_postfork_parent(&arena->lock);
+ for (i = 0; i < NBINS; i++) {
+ bin_postfork_parent(tsdn, &arena->bins[i]);
+ }
+ malloc_mutex_postfork_parent(tsdn, &arena->large_mtx);
+ base_postfork_parent(tsdn, arena->base);
+ malloc_mutex_postfork_parent(tsdn, &arena->extent_avail_mtx);
+ extents_postfork_parent(tsdn, &arena->extents_dirty);
+ extents_postfork_parent(tsdn, &arena->extents_muzzy);
+ extents_postfork_parent(tsdn, &arena->extents_retained);
+ malloc_mutex_postfork_parent(tsdn, &arena->extent_grow_mtx);
+ malloc_mutex_postfork_parent(tsdn, &arena->decay_dirty.mtx);
+ malloc_mutex_postfork_parent(tsdn, &arena->decay_muzzy.mtx);
+ if (config_stats) {
+ malloc_mutex_postfork_parent(tsdn, &arena->tcache_ql_mtx);
+ }
}
void
-arena_postfork_child(arena_t *arena)
-{
+arena_postfork_child(tsdn_t *tsdn, arena_t *arena) {
unsigned i;
- for (i = 0; i < NBINS; i++)
- malloc_mutex_postfork_child(&arena->bins[i].lock);
- malloc_mutex_postfork_child(&arena->node_cache_mtx);
- malloc_mutex_postfork_child(&arena->chunks_mtx);
- malloc_mutex_postfork_child(&arena->huge_mtx);
- malloc_mutex_postfork_child(&arena->lock);
+ atomic_store_u(&arena->nthreads[0], 0, ATOMIC_RELAXED);
+ atomic_store_u(&arena->nthreads[1], 0, ATOMIC_RELAXED);
+ if (tsd_arena_get(tsdn_tsd(tsdn)) == arena) {
+ arena_nthreads_inc(arena, false);
+ }
+ if (tsd_iarena_get(tsdn_tsd(tsdn)) == arena) {
+ arena_nthreads_inc(arena, true);
+ }
+ if (config_stats) {
+ ql_new(&arena->tcache_ql);
+ ql_new(&arena->cache_bin_array_descriptor_ql);
+ tcache_t *tcache = tcache_get(tsdn_tsd(tsdn));
+ if (tcache != NULL && tcache->arena == arena) {
+ ql_elm_new(tcache, link);
+ ql_tail_insert(&arena->tcache_ql, tcache, link);
+ cache_bin_array_descriptor_init(
+ &tcache->cache_bin_array_descriptor,
+ tcache->bins_small, tcache->bins_large);
+ ql_tail_insert(&arena->cache_bin_array_descriptor_ql,
+ &tcache->cache_bin_array_descriptor, link);
+ }
+ }
+
+ for (i = 0; i < NBINS; i++) {
+ bin_postfork_child(tsdn, &arena->bins[i]);
+ }
+ malloc_mutex_postfork_child(tsdn, &arena->large_mtx);
+ base_postfork_child(tsdn, arena->base);
+ malloc_mutex_postfork_child(tsdn, &arena->extent_avail_mtx);
+ extents_postfork_child(tsdn, &arena->extents_dirty);
+ extents_postfork_child(tsdn, &arena->extents_muzzy);
+ extents_postfork_child(tsdn, &arena->extents_retained);
+ malloc_mutex_postfork_child(tsdn, &arena->extent_grow_mtx);
+ malloc_mutex_postfork_child(tsdn, &arena->decay_dirty.mtx);
+ malloc_mutex_postfork_child(tsdn, &arena->decay_muzzy.mtx);
+ if (config_stats) {
+ malloc_mutex_postfork_child(tsdn, &arena->tcache_ql_mtx);
+ }
}
diff --git a/deps/jemalloc/src/atomic.c b/deps/jemalloc/src/atomic.c
deleted file mode 100644
index 77ee31311..000000000
--- a/deps/jemalloc/src/atomic.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define JEMALLOC_ATOMIC_C_
-#include "jemalloc/internal/jemalloc_internal.h"
diff --git a/deps/jemalloc/src/background_thread.c b/deps/jemalloc/src/background_thread.c
new file mode 100644
index 000000000..457669c9e
--- /dev/null
+++ b/deps/jemalloc/src/background_thread.c
@@ -0,0 +1,915 @@
+#define JEMALLOC_BACKGROUND_THREAD_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+
+/******************************************************************************/
+/* Data. */
+
+/* This option should be opt-in only. */
+#define BACKGROUND_THREAD_DEFAULT false
+/* Read-only after initialization. */
+bool opt_background_thread = BACKGROUND_THREAD_DEFAULT;
+size_t opt_max_background_threads = MAX_BACKGROUND_THREAD_LIMIT;
+
+/* Used for thread creation, termination and stats. */
+malloc_mutex_t background_thread_lock;
+/* Indicates global state. Atomic because decay reads this w/o locking. */
+atomic_b_t background_thread_enabled_state;
+size_t n_background_threads;
+size_t max_background_threads;
+/* Thread info per-index. */
+background_thread_info_t *background_thread_info;
+
+/* False if no necessary runtime support. */
+bool can_enable_background_thread;
+
+/******************************************************************************/
+
+#ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER
+#include <dlfcn.h>
+
+static int (*pthread_create_fptr)(pthread_t *__restrict, const pthread_attr_t *,
+ void *(*)(void *), void *__restrict);
+
+static void
+pthread_create_wrapper_init(void) {
+#ifdef JEMALLOC_LAZY_LOCK
+ if (!isthreaded) {
+ isthreaded = true;
+ }
+#endif
+}
+
+int
+pthread_create_wrapper(pthread_t *__restrict thread, const pthread_attr_t *attr,
+ void *(*start_routine)(void *), void *__restrict arg) {
+ pthread_create_wrapper_init();
+
+ return pthread_create_fptr(thread, attr, start_routine, arg);
+}
+#endif /* JEMALLOC_PTHREAD_CREATE_WRAPPER */
+
+#ifndef JEMALLOC_BACKGROUND_THREAD
+#define NOT_REACHED { not_reached(); }
+bool background_thread_create(tsd_t *tsd, unsigned arena_ind) NOT_REACHED
+bool background_threads_enable(tsd_t *tsd) NOT_REACHED
+bool background_threads_disable(tsd_t *tsd) NOT_REACHED
+void background_thread_interval_check(tsdn_t *tsdn, arena_t *arena,
+ arena_decay_t *decay, size_t npages_new) NOT_REACHED
+void background_thread_prefork0(tsdn_t *tsdn) NOT_REACHED
+void background_thread_prefork1(tsdn_t *tsdn) NOT_REACHED
+void background_thread_postfork_parent(tsdn_t *tsdn) NOT_REACHED
+void background_thread_postfork_child(tsdn_t *tsdn) NOT_REACHED
+bool background_thread_stats_read(tsdn_t *tsdn,
+ background_thread_stats_t *stats) NOT_REACHED
+void background_thread_ctl_init(tsdn_t *tsdn) NOT_REACHED
+#undef NOT_REACHED
+#else
+
+static bool background_thread_enabled_at_fork;
+
+static void
+background_thread_info_init(tsdn_t *tsdn, background_thread_info_t *info) {
+ background_thread_wakeup_time_set(tsdn, info, 0);
+ info->npages_to_purge_new = 0;
+ if (config_stats) {
+ info->tot_n_runs = 0;
+ nstime_init(&info->tot_sleep_time, 0);
+ }
+}
+
+static inline bool
+set_current_thread_affinity(UNUSED int cpu) {
+#if defined(JEMALLOC_HAVE_SCHED_SETAFFINITY)
+ cpu_set_t cpuset;
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu, &cpuset);
+ int ret = sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);
+
+ return (ret != 0);
+#else
+ return false;
+#endif
+}
+
+/* Threshold for determining when to wake up the background thread. */
+#define BACKGROUND_THREAD_NPAGES_THRESHOLD UINT64_C(1024)
+#define BILLION UINT64_C(1000000000)
+/* Minimal sleep interval 100 ms. */
+#define BACKGROUND_THREAD_MIN_INTERVAL_NS (BILLION / 10)
+
+static inline size_t
+decay_npurge_after_interval(arena_decay_t *decay, size_t interval) {
+ size_t i;
+ uint64_t sum = 0;
+ for (i = 0; i < interval; i++) {
+ sum += decay->backlog[i] * h_steps[i];
+ }
+ for (; i < SMOOTHSTEP_NSTEPS; i++) {
+ sum += decay->backlog[i] * (h_steps[i] - h_steps[i - interval]);
+ }
+
+ return (size_t)(sum >> SMOOTHSTEP_BFP);
+}
+
+static uint64_t
+arena_decay_compute_purge_interval_impl(tsdn_t *tsdn, arena_decay_t *decay,
+ extents_t *extents) {
+ if (malloc_mutex_trylock(tsdn, &decay->mtx)) {
+ /* Use minimal interval if decay is contended. */
+ return BACKGROUND_THREAD_MIN_INTERVAL_NS;
+ }
+
+ uint64_t interval;
+ ssize_t decay_time = atomic_load_zd(&decay->time_ms, ATOMIC_RELAXED);
+ if (decay_time <= 0) {
+ /* Purging is eagerly done or disabled currently. */
+ interval = BACKGROUND_THREAD_INDEFINITE_SLEEP;
+ goto label_done;
+ }
+
+ uint64_t decay_interval_ns = nstime_ns(&decay->interval);
+ assert(decay_interval_ns > 0);
+ size_t npages = extents_npages_get(extents);
+ if (npages == 0) {
+ unsigned i;
+ for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) {
+ if (decay->backlog[i] > 0) {
+ break;
+ }
+ }
+ if (i == SMOOTHSTEP_NSTEPS) {
+ /* No dirty pages recorded. Sleep indefinitely. */
+ interval = BACKGROUND_THREAD_INDEFINITE_SLEEP;
+ goto label_done;
+ }
+ }
+ if (npages <= BACKGROUND_THREAD_NPAGES_THRESHOLD) {
+ /* Use max interval. */
+ interval = decay_interval_ns * SMOOTHSTEP_NSTEPS;
+ goto label_done;
+ }
+
+ size_t lb = BACKGROUND_THREAD_MIN_INTERVAL_NS / decay_interval_ns;
+ size_t ub = SMOOTHSTEP_NSTEPS;
+ /* Minimal 2 intervals to ensure reaching next epoch deadline. */
+ lb = (lb < 2) ? 2 : lb;
+ if ((decay_interval_ns * ub <= BACKGROUND_THREAD_MIN_INTERVAL_NS) ||
+ (lb + 2 > ub)) {
+ interval = BACKGROUND_THREAD_MIN_INTERVAL_NS;
+ goto label_done;
+ }
+
+ assert(lb + 2 <= ub);
+ size_t npurge_lb, npurge_ub;
+ npurge_lb = decay_npurge_after_interval(decay, lb);
+ if (npurge_lb > BACKGROUND_THREAD_NPAGES_THRESHOLD) {
+ interval = decay_interval_ns * lb;
+ goto label_done;
+ }
+ npurge_ub = decay_npurge_after_interval(decay, ub);
+ if (npurge_ub < BACKGROUND_THREAD_NPAGES_THRESHOLD) {
+ interval = decay_interval_ns * ub;
+ goto label_done;
+ }
+
+ unsigned n_search = 0;
+ size_t target, npurge;
+ while ((npurge_lb + BACKGROUND_THREAD_NPAGES_THRESHOLD < npurge_ub)
+ && (lb + 2 < ub)) {
+ target = (lb + ub) / 2;
+ npurge = decay_npurge_after_interval(decay, target);
+ if (npurge > BACKGROUND_THREAD_NPAGES_THRESHOLD) {
+ ub = target;
+ npurge_ub = npurge;
+ } else {
+ lb = target;
+ npurge_lb = npurge;
+ }
+ assert(n_search++ < lg_floor(SMOOTHSTEP_NSTEPS) + 1);
+ }
+ interval = decay_interval_ns * (ub + lb) / 2;
+label_done:
+ interval = (interval < BACKGROUND_THREAD_MIN_INTERVAL_NS) ?
+ BACKGROUND_THREAD_MIN_INTERVAL_NS : interval;
+ malloc_mutex_unlock(tsdn, &decay->mtx);
+
+ return interval;
+}
+
+/* Compute purge interval for background threads. */
+static uint64_t
+arena_decay_compute_purge_interval(tsdn_t *tsdn, arena_t *arena) {
+ uint64_t i1, i2;
+ i1 = arena_decay_compute_purge_interval_impl(tsdn, &arena->decay_dirty,
+ &arena->extents_dirty);
+ if (i1 == BACKGROUND_THREAD_MIN_INTERVAL_NS) {
+ return i1;
+ }
+ i2 = arena_decay_compute_purge_interval_impl(tsdn, &arena->decay_muzzy,
+ &arena->extents_muzzy);
+
+ return i1 < i2 ? i1 : i2;
+}
+
+static void
+background_thread_sleep(tsdn_t *tsdn, background_thread_info_t *info,
+ uint64_t interval) {
+ if (config_stats) {
+ info->tot_n_runs++;
+ }
+ info->npages_to_purge_new = 0;
+
+ struct timeval tv;
+ /* Specific clock required by timedwait. */
+ gettimeofday(&tv, NULL);
+ nstime_t before_sleep;
+ nstime_init2(&before_sleep, tv.tv_sec, tv.tv_usec * 1000);
+
+ int ret;
+ if (interval == BACKGROUND_THREAD_INDEFINITE_SLEEP) {
+ assert(background_thread_indefinite_sleep(info));
+ ret = pthread_cond_wait(&info->cond, &info->mtx.lock);
+ assert(ret == 0);
+ } else {
+ assert(interval >= BACKGROUND_THREAD_MIN_INTERVAL_NS &&
+ interval <= BACKGROUND_THREAD_INDEFINITE_SLEEP);
+ /* We need malloc clock (can be different from tv). */
+ nstime_t next_wakeup;
+ nstime_init(&next_wakeup, 0);
+ nstime_update(&next_wakeup);
+ nstime_iadd(&next_wakeup, interval);
+ assert(nstime_ns(&next_wakeup) <
+ BACKGROUND_THREAD_INDEFINITE_SLEEP);
+ background_thread_wakeup_time_set(tsdn, info,
+ nstime_ns(&next_wakeup));
+
+ nstime_t ts_wakeup;
+ nstime_copy(&ts_wakeup, &before_sleep);
+ nstime_iadd(&ts_wakeup, interval);
+ struct timespec ts;
+ ts.tv_sec = (size_t)nstime_sec(&ts_wakeup);
+ ts.tv_nsec = (size_t)nstime_nsec(&ts_wakeup);
+
+ assert(!background_thread_indefinite_sleep(info));
+ ret = pthread_cond_timedwait(&info->cond, &info->mtx.lock, &ts);
+ assert(ret == ETIMEDOUT || ret == 0);
+ background_thread_wakeup_time_set(tsdn, info,
+ BACKGROUND_THREAD_INDEFINITE_SLEEP);
+ }
+ if (config_stats) {
+ gettimeofday(&tv, NULL);
+ nstime_t after_sleep;
+ nstime_init2(&after_sleep, tv.tv_sec, tv.tv_usec * 1000);
+ if (nstime_compare(&after_sleep, &before_sleep) > 0) {
+ nstime_subtract(&after_sleep, &before_sleep);
+ nstime_add(&info->tot_sleep_time, &after_sleep);
+ }
+ }
+}
+
+static bool
+background_thread_pause_check(tsdn_t *tsdn, background_thread_info_t *info) {
+ if (unlikely(info->state == background_thread_paused)) {
+ malloc_mutex_unlock(tsdn, &info->mtx);
+ /* Wait on global lock to update status. */
+ malloc_mutex_lock(tsdn, &background_thread_lock);
+ malloc_mutex_unlock(tsdn, &background_thread_lock);
+ malloc_mutex_lock(tsdn, &info->mtx);
+ return true;
+ }
+
+ return false;
+}
+
+static inline void
+background_work_sleep_once(tsdn_t *tsdn, background_thread_info_t *info, unsigned ind) {
+ uint64_t min_interval = BACKGROUND_THREAD_INDEFINITE_SLEEP;
+ unsigned narenas = narenas_total_get();
+
+ for (unsigned i = ind; i < narenas; i += max_background_threads) {
+ arena_t *arena = arena_get(tsdn, i, false);
+ if (!arena) {
+ continue;
+ }
+ arena_decay(tsdn, arena, true, false);
+ if (min_interval == BACKGROUND_THREAD_MIN_INTERVAL_NS) {
+ /* Min interval will be used. */
+ continue;
+ }
+ uint64_t interval = arena_decay_compute_purge_interval(tsdn,
+ arena);
+ assert(interval >= BACKGROUND_THREAD_MIN_INTERVAL_NS);
+ if (min_interval > interval) {
+ min_interval = interval;
+ }
+ }
+ background_thread_sleep(tsdn, info, min_interval);
+}
+
+static bool
+background_threads_disable_single(tsd_t *tsd, background_thread_info_t *info) {
+ if (info == &background_thread_info[0]) {
+ malloc_mutex_assert_owner(tsd_tsdn(tsd),
+ &background_thread_lock);
+ } else {
+ malloc_mutex_assert_not_owner(tsd_tsdn(tsd),
+ &background_thread_lock);
+ }
+
+ pre_reentrancy(tsd, NULL);
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ bool has_thread;
+ assert(info->state != background_thread_paused);
+ if (info->state == background_thread_started) {
+ has_thread = true;
+ info->state = background_thread_stopped;
+ pthread_cond_signal(&info->cond);
+ } else {
+ has_thread = false;
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+
+ if (!has_thread) {
+ post_reentrancy(tsd);
+ return false;
+ }
+ void *ret;
+ if (pthread_join(info->thread, &ret)) {
+ post_reentrancy(tsd);
+ return true;
+ }
+ assert(ret == NULL);
+ n_background_threads--;
+ post_reentrancy(tsd);
+
+ return false;
+}
+
+static void *background_thread_entry(void *ind_arg);
+
+static int
+background_thread_create_signals_masked(pthread_t *thread,
+ const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) {
+ /*
+ * Mask signals during thread creation so that the thread inherits
+ * an empty signal set.
+ */
+ sigset_t set;
+ sigfillset(&set);
+ sigset_t oldset;
+ int mask_err = pthread_sigmask(SIG_SETMASK, &set, &oldset);
+ if (mask_err != 0) {
+ return mask_err;
+ }
+ int create_err = pthread_create_wrapper(thread, attr, start_routine,
+ arg);
+ /*
+ * Restore the signal mask. Failure to restore the signal mask here
+ * changes program behavior.
+ */
+ int restore_err = pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+ if (restore_err != 0) {
+ malloc_printf("<jemalloc>: background thread creation "
+ "failed (%d), and signal mask restoration failed "
+ "(%d)\n", create_err, restore_err);
+ if (opt_abort) {
+ abort();
+ }
+ }
+ return create_err;
+}
+
+static bool
+check_background_thread_creation(tsd_t *tsd, unsigned *n_created,
+ bool *created_threads) {
+ bool ret = false;
+ if (likely(*n_created == n_background_threads)) {
+ return ret;
+ }
+
+ tsdn_t *tsdn = tsd_tsdn(tsd);
+ malloc_mutex_unlock(tsdn, &background_thread_info[0].mtx);
+ for (unsigned i = 1; i < max_background_threads; i++) {
+ if (created_threads[i]) {
+ continue;
+ }
+ background_thread_info_t *info = &background_thread_info[i];
+ malloc_mutex_lock(tsdn, &info->mtx);
+ /*
+ * In case of the background_thread_paused state because of
+ * arena reset, delay the creation.
+ */
+ bool create = (info->state == background_thread_started);
+ malloc_mutex_unlock(tsdn, &info->mtx);
+ if (!create) {
+ continue;
+ }
+
+ pre_reentrancy(tsd, NULL);
+ int err = background_thread_create_signals_masked(&info->thread,
+ NULL, background_thread_entry, (void *)(uintptr_t)i);
+ post_reentrancy(tsd);
+
+ if (err == 0) {
+ (*n_created)++;
+ created_threads[i] = true;
+ } else {
+ malloc_printf("<jemalloc>: background thread "
+ "creation failed (%d)\n", err);
+ if (opt_abort) {
+ abort();
+ }
+ }
+ /* Return to restart the loop since we unlocked. */
+ ret = true;
+ break;
+ }
+ malloc_mutex_lock(tsdn, &background_thread_info[0].mtx);
+
+ return ret;
+}
+
+static void
+background_thread0_work(tsd_t *tsd) {
+ /* Thread0 is also responsible for launching / terminating threads. */
+ VARIABLE_ARRAY(bool, created_threads, max_background_threads);
+ unsigned i;
+ for (i = 1; i < max_background_threads; i++) {
+ created_threads[i] = false;
+ }
+ /* Start working, and create more threads when asked. */
+ unsigned n_created = 1;
+ while (background_thread_info[0].state != background_thread_stopped) {
+ if (background_thread_pause_check(tsd_tsdn(tsd),
+ &background_thread_info[0])) {
+ continue;
+ }
+ if (check_background_thread_creation(tsd, &n_created,
+ (bool *)&created_threads)) {
+ continue;
+ }
+ background_work_sleep_once(tsd_tsdn(tsd),
+ &background_thread_info[0], 0);
+ }
+
+ /*
+ * Shut down other threads at exit. Note that the ctl thread is holding
+ * the global background_thread mutex (and is waiting) for us.
+ */
+ assert(!background_thread_enabled());
+ for (i = 1; i < max_background_threads; i++) {
+ background_thread_info_t *info = &background_thread_info[i];
+ assert(info->state != background_thread_paused);
+ if (created_threads[i]) {
+ background_threads_disable_single(tsd, info);
+ } else {
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ if (info->state != background_thread_stopped) {
+ /* The thread was not created. */
+ assert(info->state ==
+ background_thread_started);
+ n_background_threads--;
+ info->state = background_thread_stopped;
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ }
+ }
+ background_thread_info[0].state = background_thread_stopped;
+ assert(n_background_threads == 1);
+}
+
+static void
+background_work(tsd_t *tsd, unsigned ind) {
+ background_thread_info_t *info = &background_thread_info[ind];
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ background_thread_wakeup_time_set(tsd_tsdn(tsd), info,
+ BACKGROUND_THREAD_INDEFINITE_SLEEP);
+ if (ind == 0) {
+ background_thread0_work(tsd);
+ } else {
+ while (info->state != background_thread_stopped) {
+ if (background_thread_pause_check(tsd_tsdn(tsd),
+ info)) {
+ continue;
+ }
+ background_work_sleep_once(tsd_tsdn(tsd), info, ind);
+ }
+ }
+ assert(info->state == background_thread_stopped);
+ background_thread_wakeup_time_set(tsd_tsdn(tsd), info, 0);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+}
+
+static void *
+background_thread_entry(void *ind_arg) {
+ unsigned thread_ind = (unsigned)(uintptr_t)ind_arg;
+ assert(thread_ind < max_background_threads);
+#ifdef JEMALLOC_HAVE_PTHREAD_SETNAME_NP
+ pthread_setname_np(pthread_self(), "jemalloc_bg_thd");
+#endif
+ if (opt_percpu_arena != percpu_arena_disabled) {
+ set_current_thread_affinity((int)thread_ind);
+ }
+ /*
+ * Start periodic background work. We use internal tsd which avoids
+ * side effects, for example triggering new arena creation (which in
+ * turn triggers another background thread creation).
+ */
+ background_work(tsd_internal_fetch(), thread_ind);
+ assert(pthread_equal(pthread_self(),
+ background_thread_info[thread_ind].thread));
+
+ return NULL;
+}
+
+static void
+background_thread_init(tsd_t *tsd, background_thread_info_t *info) {
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock);
+ info->state = background_thread_started;
+ background_thread_info_init(tsd_tsdn(tsd), info);
+ n_background_threads++;
+}
+
+/* Create a new background thread if needed. */
+bool
+background_thread_create(tsd_t *tsd, unsigned arena_ind) {
+ assert(have_background_thread);
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock);
+
+ /* We create at most NCPUs threads. */
+ size_t thread_ind = arena_ind % max_background_threads;
+ background_thread_info_t *info = &background_thread_info[thread_ind];
+
+ bool need_new_thread;
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ need_new_thread = background_thread_enabled() &&
+ (info->state == background_thread_stopped);
+ if (need_new_thread) {
+ background_thread_init(tsd, info);
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ if (!need_new_thread) {
+ return false;
+ }
+ if (arena_ind != 0) {
+ /* Threads are created asynchronously by Thread 0. */
+ background_thread_info_t *t0 = &background_thread_info[0];
+ malloc_mutex_lock(tsd_tsdn(tsd), &t0->mtx);
+ assert(t0->state == background_thread_started);
+ pthread_cond_signal(&t0->cond);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &t0->mtx);
+
+ return false;
+ }
+
+ pre_reentrancy(tsd, NULL);
+ /*
+ * To avoid complications (besides reentrancy), create internal
+ * background threads with the underlying pthread_create.
+ */
+ int err = background_thread_create_signals_masked(&info->thread, NULL,
+ background_thread_entry, (void *)thread_ind);
+ post_reentrancy(tsd);
+
+ if (err != 0) {
+ malloc_printf("<jemalloc>: arena 0 background thread creation "
+ "failed (%d)\n", err);
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ info->state = background_thread_stopped;
+ n_background_threads--;
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+
+ return true;
+ }
+
+ return false;
+}
+
+bool
+background_threads_enable(tsd_t *tsd) {
+ assert(n_background_threads == 0);
+ assert(background_thread_enabled());
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock);
+
+ VARIABLE_ARRAY(bool, marked, max_background_threads);
+ unsigned i, nmarked;
+ for (i = 0; i < max_background_threads; i++) {
+ marked[i] = false;
+ }
+ nmarked = 0;
+ /* Thread 0 is required and created at the end. */
+ marked[0] = true;
+ /* Mark the threads we need to create for thread 0. */
+ unsigned n = narenas_total_get();
+ for (i = 1; i < n; i++) {
+ if (marked[i % max_background_threads] ||
+ arena_get(tsd_tsdn(tsd), i, false) == NULL) {
+ continue;
+ }
+ background_thread_info_t *info = &background_thread_info[
+ i % max_background_threads];
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ assert(info->state == background_thread_stopped);
+ background_thread_init(tsd, info);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ marked[i % max_background_threads] = true;
+ if (++nmarked == max_background_threads) {
+ break;
+ }
+ }
+
+ return background_thread_create(tsd, 0);
+}
+
+bool
+background_threads_disable(tsd_t *tsd) {
+ assert(!background_thread_enabled());
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &background_thread_lock);
+
+ /* Thread 0 will be responsible for terminating other threads. */
+ if (background_threads_disable_single(tsd,
+ &background_thread_info[0])) {
+ return true;
+ }
+ assert(n_background_threads == 0);
+
+ return false;
+}
+
+/* Check if we need to signal the background thread early. */
+void
+background_thread_interval_check(tsdn_t *tsdn, arena_t *arena,
+ arena_decay_t *decay, size_t npages_new) {
+ background_thread_info_t *info = arena_background_thread_info_get(
+ arena);
+ if (malloc_mutex_trylock(tsdn, &info->mtx)) {
+ /*
+ * Background thread may hold the mutex for a long period of
+ * time. We'd like to avoid the variance on application
+ * threads. So keep this non-blocking, and leave the work to a
+ * future epoch.
+ */
+ return;
+ }
+
+ if (info->state != background_thread_started) {
+ goto label_done;
+ }
+ if (malloc_mutex_trylock(tsdn, &decay->mtx)) {
+ goto label_done;
+ }
+
+ ssize_t decay_time = atomic_load_zd(&decay->time_ms, ATOMIC_RELAXED);
+ if (decay_time <= 0) {
+ /* Purging is eagerly done or disabled currently. */
+ goto label_done_unlock2;
+ }
+ uint64_t decay_interval_ns = nstime_ns(&decay->interval);
+ assert(decay_interval_ns > 0);
+
+ nstime_t diff;
+ nstime_init(&diff, background_thread_wakeup_time_get(info));
+ if (nstime_compare(&diff, &decay->epoch) <= 0) {
+ goto label_done_unlock2;
+ }
+ nstime_subtract(&diff, &decay->epoch);
+ if (nstime_ns(&diff) < BACKGROUND_THREAD_MIN_INTERVAL_NS) {
+ goto label_done_unlock2;
+ }
+
+ if (npages_new > 0) {
+ size_t n_epoch = (size_t)(nstime_ns(&diff) / decay_interval_ns);
+ /*
+ * Compute how many new pages we would need to purge by the next
+ * wakeup, which is used to determine if we should signal the
+ * background thread.
+ */
+ uint64_t npurge_new;
+ if (n_epoch >= SMOOTHSTEP_NSTEPS) {
+ npurge_new = npages_new;
+ } else {
+ uint64_t h_steps_max = h_steps[SMOOTHSTEP_NSTEPS - 1];
+ assert(h_steps_max >=
+ h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]);
+ npurge_new = npages_new * (h_steps_max -
+ h_steps[SMOOTHSTEP_NSTEPS - 1 - n_epoch]);
+ npurge_new >>= SMOOTHSTEP_BFP;
+ }
+ info->npages_to_purge_new += npurge_new;
+ }
+
+ bool should_signal;
+ if (info->npages_to_purge_new > BACKGROUND_THREAD_NPAGES_THRESHOLD) {
+ should_signal = true;
+ } else if (unlikely(background_thread_indefinite_sleep(info)) &&
+ (extents_npages_get(&arena->extents_dirty) > 0 ||
+ extents_npages_get(&arena->extents_muzzy) > 0 ||
+ info->npages_to_purge_new > 0)) {
+ should_signal = true;
+ } else {
+ should_signal = false;
+ }
+
+ if (should_signal) {
+ info->npages_to_purge_new = 0;
+ pthread_cond_signal(&info->cond);
+ }
+label_done_unlock2:
+ malloc_mutex_unlock(tsdn, &decay->mtx);
+label_done:
+ malloc_mutex_unlock(tsdn, &info->mtx);
+}
+
+void
+background_thread_prefork0(tsdn_t *tsdn) {
+ malloc_mutex_prefork(tsdn, &background_thread_lock);
+ background_thread_enabled_at_fork = background_thread_enabled();
+}
+
+void
+background_thread_prefork1(tsdn_t *tsdn) {
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ malloc_mutex_prefork(tsdn, &background_thread_info[i].mtx);
+ }
+}
+
+void
+background_thread_postfork_parent(tsdn_t *tsdn) {
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ malloc_mutex_postfork_parent(tsdn,
+ &background_thread_info[i].mtx);
+ }
+ malloc_mutex_postfork_parent(tsdn, &background_thread_lock);
+}
+
+void
+background_thread_postfork_child(tsdn_t *tsdn) {
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ malloc_mutex_postfork_child(tsdn,
+ &background_thread_info[i].mtx);
+ }
+ malloc_mutex_postfork_child(tsdn, &background_thread_lock);
+ if (!background_thread_enabled_at_fork) {
+ return;
+ }
+
+ /* Clear background_thread state (reset to disabled for child). */
+ malloc_mutex_lock(tsdn, &background_thread_lock);
+ n_background_threads = 0;
+ background_thread_enabled_set(tsdn, false);
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ background_thread_info_t *info = &background_thread_info[i];
+ malloc_mutex_lock(tsdn, &info->mtx);
+ info->state = background_thread_stopped;
+ int ret = pthread_cond_init(&info->cond, NULL);
+ assert(ret == 0);
+ background_thread_info_init(tsdn, info);
+ malloc_mutex_unlock(tsdn, &info->mtx);
+ }
+ malloc_mutex_unlock(tsdn, &background_thread_lock);
+}
+
+bool
+background_thread_stats_read(tsdn_t *tsdn, background_thread_stats_t *stats) {
+ assert(config_stats);
+ malloc_mutex_lock(tsdn, &background_thread_lock);
+ if (!background_thread_enabled()) {
+ malloc_mutex_unlock(tsdn, &background_thread_lock);
+ return true;
+ }
+
+ stats->num_threads = n_background_threads;
+ uint64_t num_runs = 0;
+ nstime_init(&stats->run_interval, 0);
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ background_thread_info_t *info = &background_thread_info[i];
+ if (malloc_mutex_trylock(tsdn, &info->mtx)) {
+ /*
+ * Each background thread run may take a long time;
+ * avoid waiting on the stats if the thread is active.
+ */
+ continue;
+ }
+ if (info->state != background_thread_stopped) {
+ num_runs += info->tot_n_runs;
+ nstime_add(&stats->run_interval, &info->tot_sleep_time);
+ }
+ malloc_mutex_unlock(tsdn, &info->mtx);
+ }
+ stats->num_runs = num_runs;
+ if (num_runs > 0) {
+ nstime_idivide(&stats->run_interval, num_runs);
+ }
+ malloc_mutex_unlock(tsdn, &background_thread_lock);
+
+ return false;
+}
+
+#undef BACKGROUND_THREAD_NPAGES_THRESHOLD
+#undef BILLION
+#undef BACKGROUND_THREAD_MIN_INTERVAL_NS
+
+static bool
+pthread_create_fptr_init(void) {
+ if (pthread_create_fptr != NULL) {
+ return false;
+ }
+ pthread_create_fptr = dlsym(RTLD_NEXT, "pthread_create");
+ if (pthread_create_fptr == NULL) {
+ can_enable_background_thread = false;
+ if (config_lazy_lock || opt_background_thread) {
+ malloc_write("<jemalloc>: Error in dlsym(RTLD_NEXT, "
+ "\"pthread_create\")\n");
+ abort();
+ }
+ } else {
+ can_enable_background_thread = true;
+ }
+
+ return false;
+}
+
+/*
+ * When lazy lock is enabled, we need to make sure setting isthreaded before
+ * taking any background_thread locks. This is called early in ctl (instead of
+ * wait for the pthread_create calls to trigger) because the mutex is required
+ * before creating background threads.
+ */
+void
+background_thread_ctl_init(tsdn_t *tsdn) {
+ malloc_mutex_assert_not_owner(tsdn, &background_thread_lock);
+#ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER
+ pthread_create_fptr_init();
+ pthread_create_wrapper_init();
+#endif
+}
+
+#endif /* defined(JEMALLOC_BACKGROUND_THREAD) */
+
+bool
+background_thread_boot0(void) {
+ if (!have_background_thread && opt_background_thread) {
+ malloc_printf("<jemalloc>: option background_thread currently "
+ "supports pthread only\n");
+ return true;
+ }
+#ifdef JEMALLOC_PTHREAD_CREATE_WRAPPER
+ if ((config_lazy_lock || opt_background_thread) &&
+ pthread_create_fptr_init()) {
+ return true;
+ }
+#endif
+ return false;
+}
+
+bool
+background_thread_boot1(tsdn_t *tsdn) {
+#ifdef JEMALLOC_BACKGROUND_THREAD
+ assert(have_background_thread);
+ assert(narenas_total_get() > 0);
+
+ if (opt_max_background_threads == MAX_BACKGROUND_THREAD_LIMIT &&
+ ncpus < MAX_BACKGROUND_THREAD_LIMIT) {
+ opt_max_background_threads = ncpus;
+ }
+ max_background_threads = opt_max_background_threads;
+
+ background_thread_enabled_set(tsdn, opt_background_thread);
+ if (malloc_mutex_init(&background_thread_lock,
+ "background_thread_global",
+ WITNESS_RANK_BACKGROUND_THREAD_GLOBAL,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+
+ background_thread_info = (background_thread_info_t *)base_alloc(tsdn,
+ b0get(), opt_max_background_threads *
+ sizeof(background_thread_info_t), CACHELINE);
+ if (background_thread_info == NULL) {
+ return true;
+ }
+
+ for (unsigned i = 0; i < max_background_threads; i++) {
+ background_thread_info_t *info = &background_thread_info[i];
+ /* Thread mutex is rank_inclusive because of thread0. */
+ if (malloc_mutex_init(&info->mtx, "background_thread",
+ WITNESS_RANK_BACKGROUND_THREAD,
+ malloc_mutex_address_ordered)) {
+ return true;
+ }
+ if (pthread_cond_init(&info->cond, NULL)) {
+ return true;
+ }
+ malloc_mutex_lock(tsdn, &info->mtx);
+ info->state = background_thread_stopped;
+ background_thread_info_init(tsdn, info);
+ malloc_mutex_unlock(tsdn, &info->mtx);
+ }
+#endif
+
+ return false;
+}
diff --git a/deps/jemalloc/src/base.c b/deps/jemalloc/src/base.c
index 7cdcfed86..b0324b5d7 100644
--- a/deps/jemalloc/src/base.c
+++ b/deps/jemalloc/src/base.c
@@ -1,174 +1,514 @@
-#define JEMALLOC_BASE_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_BASE_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/sz.h"
/******************************************************************************/
/* Data. */
-static malloc_mutex_t base_mtx;
-static extent_tree_t base_avail_szad;
-static extent_node_t *base_nodes;
-static size_t base_allocated;
-static size_t base_resident;
-static size_t base_mapped;
+static base_t *b0;
+
+metadata_thp_mode_t opt_metadata_thp = METADATA_THP_DEFAULT;
+
+const char *metadata_thp_mode_names[] = {
+ "disabled",
+ "auto",
+ "always"
+};
/******************************************************************************/
-/* base_mtx must be held. */
-static extent_node_t *
-base_node_try_alloc(void)
-{
- extent_node_t *node;
+static inline bool
+metadata_thp_madvise(void) {
+ return (metadata_thp_enabled() &&
+ (init_system_thp_mode == thp_mode_default));
+}
+
+static void *
+base_map(tsdn_t *tsdn, extent_hooks_t *extent_hooks, unsigned ind, size_t size) {
+ void *addr;
+ bool zero = true;
+ bool commit = true;
- if (base_nodes == NULL)
- return (NULL);
- node = base_nodes;
- base_nodes = *(extent_node_t **)node;
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(node, sizeof(extent_node_t));
- return (node);
+ /* Use huge page sizes and alignment regardless of opt_metadata_thp. */
+ assert(size == HUGEPAGE_CEILING(size));
+ size_t alignment = HUGEPAGE;
+ if (extent_hooks == &extent_hooks_default) {
+ addr = extent_alloc_mmap(NULL, size, alignment, &zero, &commit);
+ } else {
+ /* No arena context as we are creating new arenas. */
+ tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
+ pre_reentrancy(tsd, NULL);
+ addr = extent_hooks->alloc(extent_hooks, NULL, size, alignment,
+ &zero, &commit, ind);
+ post_reentrancy(tsd);
+ }
+
+ return addr;
}
-/* base_mtx must be held. */
static void
-base_node_dalloc(extent_node_t *node)
-{
+base_unmap(tsdn_t *tsdn, extent_hooks_t *extent_hooks, unsigned ind, void *addr,
+ size_t size) {
+ /*
+ * Cascade through dalloc, decommit, purge_forced, and purge_lazy,
+ * stopping at first success. This cascade is performed for consistency
+ * with the cascade in extent_dalloc_wrapper() because an application's
+ * custom hooks may not support e.g. dalloc. This function is only ever
+ * called as a side effect of arena destruction, so although it might
+ * seem pointless to do anything besides dalloc here, the application
+ * may in fact want the end state of all associated virtual memory to be
+ * in some consistent-but-allocated state.
+ */
+ if (extent_hooks == &extent_hooks_default) {
+ if (!extent_dalloc_mmap(addr, size)) {
+ goto label_done;
+ }
+ if (!pages_decommit(addr, size)) {
+ goto label_done;
+ }
+ if (!pages_purge_forced(addr, size)) {
+ goto label_done;
+ }
+ if (!pages_purge_lazy(addr, size)) {
+ goto label_done;
+ }
+ /* Nothing worked. This should never happen. */
+ not_reached();
+ } else {
+ tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
+ pre_reentrancy(tsd, NULL);
+ if (extent_hooks->dalloc != NULL &&
+ !extent_hooks->dalloc(extent_hooks, addr, size, true,
+ ind)) {
+ goto label_post_reentrancy;
+ }
+ if (extent_hooks->decommit != NULL &&
+ !extent_hooks->decommit(extent_hooks, addr, size, 0, size,
+ ind)) {
+ goto label_post_reentrancy;
+ }
+ if (extent_hooks->purge_forced != NULL &&
+ !extent_hooks->purge_forced(extent_hooks, addr, size, 0,
+ size, ind)) {
+ goto label_post_reentrancy;
+ }
+ if (extent_hooks->purge_lazy != NULL &&
+ !extent_hooks->purge_lazy(extent_hooks, addr, size, 0, size,
+ ind)) {
+ goto label_post_reentrancy;
+ }
+ /* Nothing worked. That's the application's problem. */
+ label_post_reentrancy:
+ post_reentrancy(tsd);
+ }
+label_done:
+ if (metadata_thp_madvise()) {
+ /* Set NOHUGEPAGE after unmap to avoid kernel defrag. */
+ assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 &&
+ (size & HUGEPAGE_MASK) == 0);
+ pages_nohuge(addr, size);
+ }
+}
+
+static void
+base_extent_init(size_t *extent_sn_next, extent_t *extent, void *addr,
+ size_t size) {
+ size_t sn;
+
+ sn = *extent_sn_next;
+ (*extent_sn_next)++;
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(node, sizeof(extent_node_t));
- *(extent_node_t **)node = base_nodes;
- base_nodes = node;
+ extent_binit(extent, addr, size, sn);
}
-/* base_mtx must be held. */
-static extent_node_t *
-base_chunk_alloc(size_t minsize)
-{
- extent_node_t *node;
- size_t csize, nsize;
- void *addr;
+static size_t
+base_get_num_blocks(base_t *base, bool with_new_block) {
+ base_block_t *b = base->blocks;
+ assert(b != NULL);
+
+ size_t n_blocks = with_new_block ? 2 : 1;
+ while (b->next != NULL) {
+ n_blocks++;
+ b = b->next;
+ }
- assert(minsize != 0);
- node = base_node_try_alloc();
- /* Allocate enough space to also carve a node out if necessary. */
- nsize = (node == NULL) ? CACHELINE_CEILING(sizeof(extent_node_t)) : 0;
- csize = CHUNK_CEILING(minsize + nsize);
- addr = chunk_alloc_base(csize);
- if (addr == NULL) {
- if (node != NULL)
- base_node_dalloc(node);
- return (NULL);
- }
- base_mapped += csize;
- if (node == NULL) {
- node = (extent_node_t *)addr;
- addr = (void *)((uintptr_t)addr + nsize);
- csize -= nsize;
+ return n_blocks;
+}
+
+static void
+base_auto_thp_switch(tsdn_t *tsdn, base_t *base) {
+ assert(opt_metadata_thp == metadata_thp_auto);
+ malloc_mutex_assert_owner(tsdn, &base->mtx);
+ if (base->auto_thp_switched) {
+ return;
+ }
+ /* Called when adding a new block. */
+ bool should_switch;
+ if (base_ind_get(base) != 0) {
+ should_switch = (base_get_num_blocks(base, true) ==
+ BASE_AUTO_THP_THRESHOLD);
+ } else {
+ should_switch = (base_get_num_blocks(base, true) ==
+ BASE_AUTO_THP_THRESHOLD_A0);
+ }
+ if (!should_switch) {
+ return;
+ }
+
+ base->auto_thp_switched = true;
+ assert(!config_stats || base->n_thp == 0);
+ /* Make the initial blocks THP lazily. */
+ base_block_t *block = base->blocks;
+ while (block != NULL) {
+ assert((block->size & HUGEPAGE_MASK) == 0);
+ pages_huge(block, block->size);
if (config_stats) {
- base_allocated += nsize;
- base_resident += PAGE_CEILING(nsize);
+ base->n_thp += HUGEPAGE_CEILING(block->size -
+ extent_bsize_get(&block->extent)) >> LG_HUGEPAGE;
+ }
+ block = block->next;
+ assert(block == NULL || (base_ind_get(base) == 0));
+ }
+}
+
+static void *
+base_extent_bump_alloc_helper(extent_t *extent, size_t *gap_size, size_t size,
+ size_t alignment) {
+ void *ret;
+
+ assert(alignment == ALIGNMENT_CEILING(alignment, QUANTUM));
+ assert(size == ALIGNMENT_CEILING(size, alignment));
+
+ *gap_size = ALIGNMENT_CEILING((uintptr_t)extent_addr_get(extent),
+ alignment) - (uintptr_t)extent_addr_get(extent);
+ ret = (void *)((uintptr_t)extent_addr_get(extent) + *gap_size);
+ assert(extent_bsize_get(extent) >= *gap_size + size);
+ extent_binit(extent, (void *)((uintptr_t)extent_addr_get(extent) +
+ *gap_size + size), extent_bsize_get(extent) - *gap_size - size,
+ extent_sn_get(extent));
+ return ret;
+}
+
+static void
+base_extent_bump_alloc_post(base_t *base, extent_t *extent, size_t gap_size,
+ void *addr, size_t size) {
+ if (extent_bsize_get(extent) > 0) {
+ /*
+ * Compute the index for the largest size class that does not
+ * exceed extent's size.
+ */
+ szind_t index_floor =
+ sz_size2index(extent_bsize_get(extent) + 1) - 1;
+ extent_heap_insert(&base->avail[index_floor], extent);
+ }
+
+ if (config_stats) {
+ base->allocated += size;
+ /*
+ * Add one PAGE to base_resident for every page boundary that is
+ * crossed by the new allocation. Adjust n_thp similarly when
+ * metadata_thp is enabled.
+ */
+ base->resident += PAGE_CEILING((uintptr_t)addr + size) -
+ PAGE_CEILING((uintptr_t)addr - gap_size);
+ assert(base->allocated <= base->resident);
+ assert(base->resident <= base->mapped);
+ if (metadata_thp_madvise() && (opt_metadata_thp ==
+ metadata_thp_always || base->auto_thp_switched)) {
+ base->n_thp += (HUGEPAGE_CEILING((uintptr_t)addr + size)
+ - HUGEPAGE_CEILING((uintptr_t)addr - gap_size)) >>
+ LG_HUGEPAGE;
+ assert(base->mapped >= base->n_thp << LG_HUGEPAGE);
+ }
+ }
+}
+
+static void *
+base_extent_bump_alloc(base_t *base, extent_t *extent, size_t size,
+ size_t alignment) {
+ void *ret;
+ size_t gap_size;
+
+ ret = base_extent_bump_alloc_helper(extent, &gap_size, size, alignment);
+ base_extent_bump_alloc_post(base, extent, gap_size, ret, size);
+ return ret;
+}
+
+/*
+ * Allocate a block of virtual memory that is large enough to start with a
+ * base_block_t header, followed by an object of specified size and alignment.
+ * On success a pointer to the initialized base_block_t header is returned.
+ */
+static base_block_t *
+base_block_alloc(tsdn_t *tsdn, base_t *base, extent_hooks_t *extent_hooks,
+ unsigned ind, pszind_t *pind_last, size_t *extent_sn_next, size_t size,
+ size_t alignment) {
+ alignment = ALIGNMENT_CEILING(alignment, QUANTUM);
+ size_t usize = ALIGNMENT_CEILING(size, alignment);
+ size_t header_size = sizeof(base_block_t);
+ size_t gap_size = ALIGNMENT_CEILING(header_size, alignment) -
+ header_size;
+ /*
+ * Create increasingly larger blocks in order to limit the total number
+ * of disjoint virtual memory ranges. Choose the next size in the page
+ * size class series (skipping size classes that are not a multiple of
+ * HUGEPAGE), or a size large enough to satisfy the requested size and
+ * alignment, whichever is larger.
+ */
+ size_t min_block_size = HUGEPAGE_CEILING(sz_psz2u(header_size + gap_size
+ + usize));
+ pszind_t pind_next = (*pind_last + 1 < NPSIZES) ? *pind_last + 1 :
+ *pind_last;
+ size_t next_block_size = HUGEPAGE_CEILING(sz_pind2sz(pind_next));
+ size_t block_size = (min_block_size > next_block_size) ? min_block_size
+ : next_block_size;
+ base_block_t *block = (base_block_t *)base_map(tsdn, extent_hooks, ind,
+ block_size);
+ if (block == NULL) {
+ return NULL;
+ }
+
+ if (metadata_thp_madvise()) {
+ void *addr = (void *)block;
+ assert(((uintptr_t)addr & HUGEPAGE_MASK) == 0 &&
+ (block_size & HUGEPAGE_MASK) == 0);
+ if (opt_metadata_thp == metadata_thp_always) {
+ pages_huge(addr, block_size);
+ } else if (opt_metadata_thp == metadata_thp_auto &&
+ base != NULL) {
+ /* base != NULL indicates this is not a new base. */
+ malloc_mutex_lock(tsdn, &base->mtx);
+ base_auto_thp_switch(tsdn, base);
+ if (base->auto_thp_switched) {
+ pages_huge(addr, block_size);
+ }
+ malloc_mutex_unlock(tsdn, &base->mtx);
}
}
- extent_node_init(node, NULL, addr, csize, true, true);
- return (node);
+
+ *pind_last = sz_psz2ind(block_size);
+ block->size = block_size;
+ block->next = NULL;
+ assert(block_size >= header_size);
+ base_extent_init(extent_sn_next, &block->extent,
+ (void *)((uintptr_t)block + header_size), block_size - header_size);
+ return block;
}
/*
- * base_alloc() guarantees demand-zeroed memory, in order to make multi-page
- * sparse data structures such as radix tree nodes efficient with respect to
- * physical memory usage.
+ * Allocate an extent that is at least as large as specified size, with
+ * specified alignment.
*/
-void *
-base_alloc(size_t size)
-{
- void *ret;
- size_t csize, usize;
- extent_node_t *node;
- extent_node_t key;
+static extent_t *
+base_extent_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) {
+ malloc_mutex_assert_owner(tsdn, &base->mtx);
+ extent_hooks_t *extent_hooks = base_extent_hooks_get(base);
/*
- * Round size up to nearest multiple of the cacheline size, so that
- * there is no chance of false cache line sharing.
+ * Drop mutex during base_block_alloc(), because an extent hook will be
+ * called.
*/
- csize = CACHELINE_CEILING(size);
-
- usize = s2u(csize);
- extent_node_init(&key, NULL, NULL, usize, false, false);
- malloc_mutex_lock(&base_mtx);
- node = extent_tree_szad_nsearch(&base_avail_szad, &key);
- if (node != NULL) {
- /* Use existing space. */
- extent_tree_szad_remove(&base_avail_szad, node);
- } else {
- /* Try to allocate more space. */
- node = base_chunk_alloc(csize);
+ malloc_mutex_unlock(tsdn, &base->mtx);
+ base_block_t *block = base_block_alloc(tsdn, base, extent_hooks,
+ base_ind_get(base), &base->pind_last, &base->extent_sn_next, size,
+ alignment);
+ malloc_mutex_lock(tsdn, &base->mtx);
+ if (block == NULL) {
+ return NULL;
}
- if (node == NULL) {
- ret = NULL;
- goto label_return;
+ block->next = base->blocks;
+ base->blocks = block;
+ if (config_stats) {
+ base->allocated += sizeof(base_block_t);
+ base->resident += PAGE_CEILING(sizeof(base_block_t));
+ base->mapped += block->size;
+ if (metadata_thp_madvise() &&
+ !(opt_metadata_thp == metadata_thp_auto
+ && !base->auto_thp_switched)) {
+ assert(base->n_thp > 0);
+ base->n_thp += HUGEPAGE_CEILING(sizeof(base_block_t)) >>
+ LG_HUGEPAGE;
+ }
+ assert(base->allocated <= base->resident);
+ assert(base->resident <= base->mapped);
+ assert(base->n_thp << LG_HUGEPAGE <= base->mapped);
+ }
+ return &block->extent;
+}
+
+base_t *
+b0get(void) {
+ return b0;
+}
+
+base_t *
+base_new(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks) {
+ pszind_t pind_last = 0;
+ size_t extent_sn_next = 0;
+ base_block_t *block = base_block_alloc(tsdn, NULL, extent_hooks, ind,
+ &pind_last, &extent_sn_next, sizeof(base_t), QUANTUM);
+ if (block == NULL) {
+ return NULL;
}
- ret = extent_node_addr_get(node);
- if (extent_node_size_get(node) > csize) {
- extent_node_addr_set(node, (void *)((uintptr_t)ret + csize));
- extent_node_size_set(node, extent_node_size_get(node) - csize);
- extent_tree_szad_insert(&base_avail_szad, node);
- } else
- base_node_dalloc(node);
+ size_t gap_size;
+ size_t base_alignment = CACHELINE;
+ size_t base_size = ALIGNMENT_CEILING(sizeof(base_t), base_alignment);
+ base_t *base = (base_t *)base_extent_bump_alloc_helper(&block->extent,
+ &gap_size, base_size, base_alignment);
+ base->ind = ind;
+ atomic_store_p(&base->extent_hooks, extent_hooks, ATOMIC_RELAXED);
+ if (malloc_mutex_init(&base->mtx, "base", WITNESS_RANK_BASE,
+ malloc_mutex_rank_exclusive)) {
+ base_unmap(tsdn, extent_hooks, ind, block, block->size);
+ return NULL;
+ }
+ base->pind_last = pind_last;
+ base->extent_sn_next = extent_sn_next;
+ base->blocks = block;
+ base->auto_thp_switched = false;
+ for (szind_t i = 0; i < NSIZES; i++) {
+ extent_heap_new(&base->avail[i]);
+ }
if (config_stats) {
- base_allocated += csize;
- /*
- * Add one PAGE to base_resident for every page boundary that is
- * crossed by the new allocation.
- */
- base_resident += PAGE_CEILING((uintptr_t)ret + csize) -
- PAGE_CEILING((uintptr_t)ret);
+ base->allocated = sizeof(base_block_t);
+ base->resident = PAGE_CEILING(sizeof(base_block_t));
+ base->mapped = block->size;
+ base->n_thp = (opt_metadata_thp == metadata_thp_always) &&
+ metadata_thp_madvise() ? HUGEPAGE_CEILING(sizeof(base_block_t))
+ >> LG_HUGEPAGE : 0;
+ assert(base->allocated <= base->resident);
+ assert(base->resident <= base->mapped);
+ assert(base->n_thp << LG_HUGEPAGE <= base->mapped);
}
- JEMALLOC_VALGRIND_MAKE_MEM_DEFINED(ret, csize);
-label_return:
- malloc_mutex_unlock(&base_mtx);
- return (ret);
+ base_extent_bump_alloc_post(base, &block->extent, gap_size, base,
+ base_size);
+
+ return base;
}
void
-base_stats_get(size_t *allocated, size_t *resident, size_t *mapped)
-{
+base_delete(tsdn_t *tsdn, base_t *base) {
+ extent_hooks_t *extent_hooks = base_extent_hooks_get(base);
+ base_block_t *next = base->blocks;
+ do {
+ base_block_t *block = next;
+ next = block->next;
+ base_unmap(tsdn, extent_hooks, base_ind_get(base), block,
+ block->size);
+ } while (next != NULL);
+}
- malloc_mutex_lock(&base_mtx);
- assert(base_allocated <= base_resident);
- assert(base_resident <= base_mapped);
- *allocated = base_allocated;
- *resident = base_resident;
- *mapped = base_mapped;
- malloc_mutex_unlock(&base_mtx);
+extent_hooks_t *
+base_extent_hooks_get(base_t *base) {
+ return (extent_hooks_t *)atomic_load_p(&base->extent_hooks,
+ ATOMIC_ACQUIRE);
}
-bool
-base_boot(void)
-{
+extent_hooks_t *
+base_extent_hooks_set(base_t *base, extent_hooks_t *extent_hooks) {
+ extent_hooks_t *old_extent_hooks = base_extent_hooks_get(base);
+ atomic_store_p(&base->extent_hooks, extent_hooks, ATOMIC_RELEASE);
+ return old_extent_hooks;
+}
- if (malloc_mutex_init(&base_mtx))
- return (true);
- extent_tree_szad_new(&base_avail_szad);
- base_nodes = NULL;
+static void *
+base_alloc_impl(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment,
+ size_t *esn) {
+ alignment = QUANTUM_CEILING(alignment);
+ size_t usize = ALIGNMENT_CEILING(size, alignment);
+ size_t asize = usize + alignment - QUANTUM;
+
+ extent_t *extent = NULL;
+ malloc_mutex_lock(tsdn, &base->mtx);
+ for (szind_t i = sz_size2index(asize); i < NSIZES; i++) {
+ extent = extent_heap_remove_first(&base->avail[i]);
+ if (extent != NULL) {
+ /* Use existing space. */
+ break;
+ }
+ }
+ if (extent == NULL) {
+ /* Try to allocate more space. */
+ extent = base_extent_alloc(tsdn, base, usize, alignment);
+ }
+ void *ret;
+ if (extent == NULL) {
+ ret = NULL;
+ goto label_return;
+ }
- return (false);
+ ret = base_extent_bump_alloc(base, extent, usize, alignment);
+ if (esn != NULL) {
+ *esn = extent_sn_get(extent);
+ }
+label_return:
+ malloc_mutex_unlock(tsdn, &base->mtx);
+ return ret;
+}
+
+/*
+ * base_alloc() returns zeroed memory, which is always demand-zeroed for the
+ * auto arenas, in order to make multi-page sparse data structures such as radix
+ * tree nodes efficient with respect to physical memory usage. Upon success a
+ * pointer to at least size bytes with specified alignment is returned. Note
+ * that size is rounded up to the nearest multiple of alignment to avoid false
+ * sharing.
+ */
+void *
+base_alloc(tsdn_t *tsdn, base_t *base, size_t size, size_t alignment) {
+ return base_alloc_impl(tsdn, base, size, alignment, NULL);
+}
+
+extent_t *
+base_alloc_extent(tsdn_t *tsdn, base_t *base) {
+ size_t esn;
+ extent_t *extent = base_alloc_impl(tsdn, base, sizeof(extent_t),
+ CACHELINE, &esn);
+ if (extent == NULL) {
+ return NULL;
+ }
+ extent_esn_set(extent, esn);
+ return extent;
}
void
-base_prefork(void)
-{
+base_stats_get(tsdn_t *tsdn, base_t *base, size_t *allocated, size_t *resident,
+ size_t *mapped, size_t *n_thp) {
+ cassert(config_stats);
- malloc_mutex_prefork(&base_mtx);
+ malloc_mutex_lock(tsdn, &base->mtx);
+ assert(base->allocated <= base->resident);
+ assert(base->resident <= base->mapped);
+ *allocated = base->allocated;
+ *resident = base->resident;
+ *mapped = base->mapped;
+ *n_thp = base->n_thp;
+ malloc_mutex_unlock(tsdn, &base->mtx);
}
void
-base_postfork_parent(void)
-{
+base_prefork(tsdn_t *tsdn, base_t *base) {
+ malloc_mutex_prefork(tsdn, &base->mtx);
+}
- malloc_mutex_postfork_parent(&base_mtx);
+void
+base_postfork_parent(tsdn_t *tsdn, base_t *base) {
+ malloc_mutex_postfork_parent(tsdn, &base->mtx);
}
void
-base_postfork_child(void)
-{
+base_postfork_child(tsdn_t *tsdn, base_t *base) {
+ malloc_mutex_postfork_child(tsdn, &base->mtx);
+}
- malloc_mutex_postfork_child(&base_mtx);
+bool
+base_boot(tsdn_t *tsdn) {
+ b0 = base_new(tsdn, 0, (extent_hooks_t *)&extent_hooks_default);
+ return (b0 == NULL);
}
diff --git a/deps/jemalloc/src/bin.c b/deps/jemalloc/src/bin.c
new file mode 100644
index 000000000..0886bc4ea
--- /dev/null
+++ b/deps/jemalloc/src/bin.c
@@ -0,0 +1,50 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/bin.h"
+#include "jemalloc/internal/witness.h"
+
+const bin_info_t bin_infos[NBINS] = {
+#define BIN_INFO_bin_yes(reg_size, slab_size, nregs) \
+ {reg_size, slab_size, nregs, BITMAP_INFO_INITIALIZER(nregs)},
+#define BIN_INFO_bin_no(reg_size, slab_size, nregs)
+#define SC(index, lg_grp, lg_delta, ndelta, psz, bin, pgs, \
+ lg_delta_lookup) \
+ BIN_INFO_bin_##bin((1U<<lg_grp) + (ndelta<<lg_delta), \
+ (pgs << LG_PAGE), (pgs << LG_PAGE) / ((1U<<lg_grp) + \
+ (ndelta<<lg_delta)))
+ SIZE_CLASSES
+#undef BIN_INFO_bin_yes
+#undef BIN_INFO_bin_no
+#undef SC
+};
+
+bool
+bin_init(bin_t *bin) {
+ if (malloc_mutex_init(&bin->lock, "bin", WITNESS_RANK_BIN,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+ bin->slabcur = NULL;
+ extent_heap_new(&bin->slabs_nonfull);
+ extent_list_init(&bin->slabs_full);
+ if (config_stats) {
+ memset(&bin->stats, 0, sizeof(bin_stats_t));
+ }
+ return false;
+}
+
+void
+bin_prefork(tsdn_t *tsdn, bin_t *bin) {
+ malloc_mutex_prefork(tsdn, &bin->lock);
+}
+
+void
+bin_postfork_parent(tsdn_t *tsdn, bin_t *bin) {
+ malloc_mutex_postfork_parent(tsdn, &bin->lock);
+}
+
+void
+bin_postfork_child(tsdn_t *tsdn, bin_t *bin) {
+ malloc_mutex_postfork_child(tsdn, &bin->lock);
+}
diff --git a/deps/jemalloc/src/bitmap.c b/deps/jemalloc/src/bitmap.c
index c733372b4..468b3178e 100644
--- a/deps/jemalloc/src/bitmap.c
+++ b/deps/jemalloc/src/bitmap.c
@@ -1,11 +1,15 @@
-#define JEMALLOC_BITMAP_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_BITMAP_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
/******************************************************************************/
+#ifdef BITMAP_USE_TREE
+
void
-bitmap_info_init(bitmap_info_t *binfo, size_t nbits)
-{
+bitmap_info_init(bitmap_info_t *binfo, size_t nbits) {
unsigned i;
size_t group_count;
@@ -32,47 +36,86 @@ bitmap_info_init(bitmap_info_t *binfo, size_t nbits)
binfo->nbits = nbits;
}
-size_t
-bitmap_info_ngroups(const bitmap_info_t *binfo)
-{
-
- return (binfo->levels[binfo->nlevels].group_offset << LG_SIZEOF_BITMAP);
-}
-
-size_t
-bitmap_size(size_t nbits)
-{
- bitmap_info_t binfo;
-
- bitmap_info_init(&binfo, nbits);
- return (bitmap_info_ngroups(&binfo));
+static size_t
+bitmap_info_ngroups(const bitmap_info_t *binfo) {
+ return binfo->levels[binfo->nlevels].group_offset;
}
void
-bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo)
-{
+bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) {
size_t extra;
unsigned i;
/*
* Bits are actually inverted with regard to the external bitmap
- * interface, so the bitmap starts out with all 1 bits, except for
- * trailing unused bits (if any). Note that each group uses bit 0 to
- * correspond to the first logical bit in the group, so extra bits
- * are the most significant bits of the last group.
+ * interface.
*/
- memset(bitmap, 0xffU, binfo->levels[binfo->nlevels].group_offset <<
- LG_SIZEOF_BITMAP);
+
+ if (fill) {
+ /* The "filled" bitmap starts out with all 0 bits. */
+ memset(bitmap, 0, bitmap_size(binfo));
+ return;
+ }
+
+ /*
+ * The "empty" bitmap starts out with all 1 bits, except for trailing
+ * unused bits (if any). Note that each group uses bit 0 to correspond
+ * to the first logical bit in the group, so extra bits are the most
+ * significant bits of the last group.
+ */
+ memset(bitmap, 0xffU, bitmap_size(binfo));
extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK))
& BITMAP_GROUP_NBITS_MASK;
- if (extra != 0)
+ if (extra != 0) {
bitmap[binfo->levels[1].group_offset - 1] >>= extra;
+ }
for (i = 1; i < binfo->nlevels; i++) {
size_t group_count = binfo->levels[i].group_offset -
binfo->levels[i-1].group_offset;
extra = (BITMAP_GROUP_NBITS - (group_count &
BITMAP_GROUP_NBITS_MASK)) & BITMAP_GROUP_NBITS_MASK;
- if (extra != 0)
+ if (extra != 0) {
bitmap[binfo->levels[i+1].group_offset - 1] >>= extra;
+ }
+ }
+}
+
+#else /* BITMAP_USE_TREE */
+
+void
+bitmap_info_init(bitmap_info_t *binfo, size_t nbits) {
+ assert(nbits > 0);
+ assert(nbits <= (ZU(1) << LG_BITMAP_MAXBITS));
+
+ binfo->ngroups = BITMAP_BITS2GROUPS(nbits);
+ binfo->nbits = nbits;
+}
+
+static size_t
+bitmap_info_ngroups(const bitmap_info_t *binfo) {
+ return binfo->ngroups;
+}
+
+void
+bitmap_init(bitmap_t *bitmap, const bitmap_info_t *binfo, bool fill) {
+ size_t extra;
+
+ if (fill) {
+ memset(bitmap, 0, bitmap_size(binfo));
+ return;
+ }
+
+ memset(bitmap, 0xffU, bitmap_size(binfo));
+ extra = (BITMAP_GROUP_NBITS - (binfo->nbits & BITMAP_GROUP_NBITS_MASK))
+ & BITMAP_GROUP_NBITS_MASK;
+ if (extra != 0) {
+ bitmap[binfo->ngroups - 1] >>= extra;
}
}
+
+#endif /* BITMAP_USE_TREE */
+
+size_t
+bitmap_size(const bitmap_info_t *binfo) {
+ return (bitmap_info_ngroups(binfo) << LG_SIZEOF_BITMAP);
+}
diff --git a/deps/jemalloc/src/chunk.c b/deps/jemalloc/src/chunk.c
deleted file mode 100644
index 6ba1ca7a5..000000000
--- a/deps/jemalloc/src/chunk.c
+++ /dev/null
@@ -1,761 +0,0 @@
-#define JEMALLOC_CHUNK_C_
-#include "jemalloc/internal/jemalloc_internal.h"
-
-/******************************************************************************/
-/* Data. */
-
-const char *opt_dss = DSS_DEFAULT;
-size_t opt_lg_chunk = 0;
-
-/* Used exclusively for gdump triggering. */
-static size_t curchunks;
-static size_t highchunks;
-
-rtree_t chunks_rtree;
-
-/* Various chunk-related settings. */
-size_t chunksize;
-size_t chunksize_mask; /* (chunksize - 1). */
-size_t chunk_npages;
-
-static void *chunk_alloc_default(void *new_addr, size_t size,
- size_t alignment, bool *zero, bool *commit, unsigned arena_ind);
-static bool chunk_dalloc_default(void *chunk, size_t size, bool committed,
- unsigned arena_ind);
-static bool chunk_commit_default(void *chunk, size_t size, size_t offset,
- size_t length, unsigned arena_ind);
-static bool chunk_decommit_default(void *chunk, size_t size, size_t offset,
- size_t length, unsigned arena_ind);
-static bool chunk_purge_default(void *chunk, size_t size, size_t offset,
- size_t length, unsigned arena_ind);
-static bool chunk_split_default(void *chunk, size_t size, size_t size_a,
- size_t size_b, bool committed, unsigned arena_ind);
-static bool chunk_merge_default(void *chunk_a, size_t size_a, void *chunk_b,
- size_t size_b, bool committed, unsigned arena_ind);
-
-const chunk_hooks_t chunk_hooks_default = {
- chunk_alloc_default,
- chunk_dalloc_default,
- chunk_commit_default,
- chunk_decommit_default,
- chunk_purge_default,
- chunk_split_default,
- chunk_merge_default
-};
-
-/******************************************************************************/
-/*
- * Function prototypes for static functions that are referenced prior to
- * definition.
- */
-
-static void chunk_record(arena_t *arena, chunk_hooks_t *chunk_hooks,
- extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, bool cache,
- void *chunk, size_t size, bool zeroed, bool committed);
-
-/******************************************************************************/
-
-static chunk_hooks_t
-chunk_hooks_get_locked(arena_t *arena)
-{
-
- return (arena->chunk_hooks);
-}
-
-chunk_hooks_t
-chunk_hooks_get(arena_t *arena)
-{
- chunk_hooks_t chunk_hooks;
-
- malloc_mutex_lock(&arena->chunks_mtx);
- chunk_hooks = chunk_hooks_get_locked(arena);
- malloc_mutex_unlock(&arena->chunks_mtx);
-
- return (chunk_hooks);
-}
-
-chunk_hooks_t
-chunk_hooks_set(arena_t *arena, const chunk_hooks_t *chunk_hooks)
-{
- chunk_hooks_t old_chunk_hooks;
-
- malloc_mutex_lock(&arena->chunks_mtx);
- old_chunk_hooks = arena->chunk_hooks;
- /*
- * Copy each field atomically so that it is impossible for readers to
- * see partially updated pointers. There are places where readers only
- * need one hook function pointer (therefore no need to copy the
- * entirety of arena->chunk_hooks), and stale reads do not affect
- * correctness, so they perform unlocked reads.
- */
-#define ATOMIC_COPY_HOOK(n) do { \
- union { \
- chunk_##n##_t **n; \
- void **v; \
- } u; \
- u.n = &arena->chunk_hooks.n; \
- atomic_write_p(u.v, chunk_hooks->n); \
-} while (0)
- ATOMIC_COPY_HOOK(alloc);
- ATOMIC_COPY_HOOK(dalloc);
- ATOMIC_COPY_HOOK(commit);
- ATOMIC_COPY_HOOK(decommit);
- ATOMIC_COPY_HOOK(purge);
- ATOMIC_COPY_HOOK(split);
- ATOMIC_COPY_HOOK(merge);
-#undef ATOMIC_COPY_HOOK
- malloc_mutex_unlock(&arena->chunks_mtx);
-
- return (old_chunk_hooks);
-}
-
-static void
-chunk_hooks_assure_initialized_impl(arena_t *arena, chunk_hooks_t *chunk_hooks,
- bool locked)
-{
- static const chunk_hooks_t uninitialized_hooks =
- CHUNK_HOOKS_INITIALIZER;
-
- if (memcmp(chunk_hooks, &uninitialized_hooks, sizeof(chunk_hooks_t)) ==
- 0) {
- *chunk_hooks = locked ? chunk_hooks_get_locked(arena) :
- chunk_hooks_get(arena);
- }
-}
-
-static void
-chunk_hooks_assure_initialized_locked(arena_t *arena,
- chunk_hooks_t *chunk_hooks)
-{
-
- chunk_hooks_assure_initialized_impl(arena, chunk_hooks, true);
-}
-
-static void
-chunk_hooks_assure_initialized(arena_t *arena, chunk_hooks_t *chunk_hooks)
-{
-
- chunk_hooks_assure_initialized_impl(arena, chunk_hooks, false);
-}
-
-bool
-chunk_register(const void *chunk, const extent_node_t *node)
-{
-
- assert(extent_node_addr_get(node) == chunk);
-
- if (rtree_set(&chunks_rtree, (uintptr_t)chunk, node))
- return (true);
- if (config_prof && opt_prof) {
- size_t size = extent_node_size_get(node);
- size_t nadd = (size == 0) ? 1 : size / chunksize;
- size_t cur = atomic_add_z(&curchunks, nadd);
- size_t high = atomic_read_z(&highchunks);
- while (cur > high && atomic_cas_z(&highchunks, high, cur)) {
- /*
- * Don't refresh cur, because it may have decreased
- * since this thread lost the highchunks update race.
- */
- high = atomic_read_z(&highchunks);
- }
- if (cur > high && prof_gdump_get_unlocked())
- prof_gdump();
- }
-
- return (false);
-}
-
-void
-chunk_deregister(const void *chunk, const extent_node_t *node)
-{
- bool err;
-
- err = rtree_set(&chunks_rtree, (uintptr_t)chunk, NULL);
- assert(!err);
- if (config_prof && opt_prof) {
- size_t size = extent_node_size_get(node);
- size_t nsub = (size == 0) ? 1 : size / chunksize;
- assert(atomic_read_z(&curchunks) >= nsub);
- atomic_sub_z(&curchunks, nsub);
- }
-}
-
-/*
- * Do first-best-fit chunk selection, i.e. select the lowest chunk that best
- * fits.
- */
-static extent_node_t *
-chunk_first_best_fit(arena_t *arena, extent_tree_t *chunks_szad,
- extent_tree_t *chunks_ad, size_t size)
-{
- extent_node_t key;
-
- assert(size == CHUNK_CEILING(size));
-
- extent_node_init(&key, arena, NULL, size, false, false);
- return (extent_tree_szad_nsearch(chunks_szad, &key));
-}
-
-static void *
-chunk_recycle(arena_t *arena, chunk_hooks_t *chunk_hooks,
- extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, bool cache,
- void *new_addr, size_t size, size_t alignment, bool *zero, bool *commit,
- bool dalloc_node)
-{
- void *ret;
- extent_node_t *node;
- size_t alloc_size, leadsize, trailsize;
- bool zeroed, committed;
-
- assert(new_addr == NULL || alignment == chunksize);
- /*
- * Cached chunks use the node linkage embedded in their headers, in
- * which case dalloc_node is true, and new_addr is non-NULL because
- * we're operating on a specific chunk.
- */
- assert(dalloc_node || new_addr != NULL);
-
- alloc_size = CHUNK_CEILING(s2u(size + alignment - chunksize));
- /* Beware size_t wrap-around. */
- if (alloc_size < size)
- return (NULL);
- malloc_mutex_lock(&arena->chunks_mtx);
- chunk_hooks_assure_initialized_locked(arena, chunk_hooks);
- if (new_addr != NULL) {
- extent_node_t key;
- extent_node_init(&key, arena, new_addr, alloc_size, false,
- false);
- node = extent_tree_ad_search(chunks_ad, &key);
- } else {
- node = chunk_first_best_fit(arena, chunks_szad, chunks_ad,
- alloc_size);
- }
- if (node == NULL || (new_addr != NULL && extent_node_size_get(node) <
- size)) {
- malloc_mutex_unlock(&arena->chunks_mtx);
- return (NULL);
- }
- leadsize = ALIGNMENT_CEILING((uintptr_t)extent_node_addr_get(node),
- alignment) - (uintptr_t)extent_node_addr_get(node);
- assert(new_addr == NULL || leadsize == 0);
- assert(extent_node_size_get(node) >= leadsize + size);
- trailsize = extent_node_size_get(node) - leadsize - size;
- ret = (void *)((uintptr_t)extent_node_addr_get(node) + leadsize);
- zeroed = extent_node_zeroed_get(node);
- if (zeroed)
- *zero = true;
- committed = extent_node_committed_get(node);
- if (committed)
- *commit = true;
- /* Split the lead. */
- if (leadsize != 0 &&
- chunk_hooks->split(extent_node_addr_get(node),
- extent_node_size_get(node), leadsize, size, false, arena->ind)) {
- malloc_mutex_unlock(&arena->chunks_mtx);
- return (NULL);
- }
- /* Remove node from the tree. */
- extent_tree_szad_remove(chunks_szad, node);
- extent_tree_ad_remove(chunks_ad, node);
- arena_chunk_cache_maybe_remove(arena, node, cache);
- if (leadsize != 0) {
- /* Insert the leading space as a smaller chunk. */
- extent_node_size_set(node, leadsize);
- extent_tree_szad_insert(chunks_szad, node);
- extent_tree_ad_insert(chunks_ad, node);
- arena_chunk_cache_maybe_insert(arena, node, cache);
- node = NULL;
- }
- if (trailsize != 0) {
- /* Split the trail. */
- if (chunk_hooks->split(ret, size + trailsize, size,
- trailsize, false, arena->ind)) {
- if (dalloc_node && node != NULL)
- arena_node_dalloc(arena, node);
- malloc_mutex_unlock(&arena->chunks_mtx);
- chunk_record(arena, chunk_hooks, chunks_szad, chunks_ad,
- cache, ret, size + trailsize, zeroed, committed);
- return (NULL);
- }
- /* Insert the trailing space as a smaller chunk. */
- if (node == NULL) {
- node = arena_node_alloc(arena);
- if (node == NULL) {
- malloc_mutex_unlock(&arena->chunks_mtx);
- chunk_record(arena, chunk_hooks, chunks_szad,
- chunks_ad, cache, ret, size + trailsize,
- zeroed, committed);
- return (NULL);
- }
- }
- extent_node_init(node, arena, (void *)((uintptr_t)(ret) + size),
- trailsize, zeroed, committed);
- extent_tree_szad_insert(chunks_szad, node);
- extent_tree_ad_insert(chunks_ad, node);
- arena_chunk_cache_maybe_insert(arena, node, cache);
- node = NULL;
- }
- if (!committed && chunk_hooks->commit(ret, size, 0, size, arena->ind)) {
- malloc_mutex_unlock(&arena->chunks_mtx);
- chunk_record(arena, chunk_hooks, chunks_szad, chunks_ad, cache,
- ret, size, zeroed, committed);
- return (NULL);
- }
- malloc_mutex_unlock(&arena->chunks_mtx);
-
- assert(dalloc_node || node != NULL);
- if (dalloc_node && node != NULL)
- arena_node_dalloc(arena, node);
- if (*zero) {
- if (!zeroed)
- memset(ret, 0, size);
- else if (config_debug) {
- size_t i;
- size_t *p = (size_t *)(uintptr_t)ret;
-
- JEMALLOC_VALGRIND_MAKE_MEM_DEFINED(ret, size);
- for (i = 0; i < size / sizeof(size_t); i++)
- assert(p[i] == 0);
- }
- }
- return (ret);
-}
-
-/*
- * If the caller specifies (!*zero), it is still possible to receive zeroed
- * memory, in which case *zero is toggled to true. arena_chunk_alloc() takes
- * advantage of this to avoid demanding zeroed chunks, but taking advantage of
- * them if they are returned.
- */
-static void *
-chunk_alloc_core(arena_t *arena, void *new_addr, size_t size, size_t alignment,
- bool *zero, bool *commit, dss_prec_t dss_prec)
-{
- void *ret;
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
-
- assert(size != 0);
- assert((size & chunksize_mask) == 0);
- assert(alignment != 0);
- assert((alignment & chunksize_mask) == 0);
-
- /* Retained. */
- if ((ret = chunk_recycle(arena, &chunk_hooks,
- &arena->chunks_szad_retained, &arena->chunks_ad_retained, false,
- new_addr, size, alignment, zero, commit, true)) != NULL)
- return (ret);
-
- /* "primary" dss. */
- if (have_dss && dss_prec == dss_prec_primary && (ret =
- chunk_alloc_dss(arena, new_addr, size, alignment, zero, commit)) !=
- NULL)
- return (ret);
- /*
- * mmap. Requesting an address is not implemented for
- * chunk_alloc_mmap(), so only call it if (new_addr == NULL).
- */
- if (new_addr == NULL && (ret = chunk_alloc_mmap(size, alignment, zero,
- commit)) != NULL)
- return (ret);
- /* "secondary" dss. */
- if (have_dss && dss_prec == dss_prec_secondary && (ret =
- chunk_alloc_dss(arena, new_addr, size, alignment, zero, commit)) !=
- NULL)
- return (ret);
-
- /* All strategies for allocation failed. */
- return (NULL);
-}
-
-void *
-chunk_alloc_base(size_t size)
-{
- void *ret;
- bool zero, commit;
-
- /*
- * Directly call chunk_alloc_mmap() rather than chunk_alloc_core()
- * because it's critical that chunk_alloc_base() return untouched
- * demand-zeroed virtual memory.
- */
- zero = true;
- commit = true;
- ret = chunk_alloc_mmap(size, chunksize, &zero, &commit);
- if (ret == NULL)
- return (NULL);
- if (config_valgrind)
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
-
- return (ret);
-}
-
-void *
-chunk_alloc_cache(arena_t *arena, chunk_hooks_t *chunk_hooks, void *new_addr,
- size_t size, size_t alignment, bool *zero, bool dalloc_node)
-{
- void *ret;
- bool commit;
-
- assert(size != 0);
- assert((size & chunksize_mask) == 0);
- assert(alignment != 0);
- assert((alignment & chunksize_mask) == 0);
-
- commit = true;
- ret = chunk_recycle(arena, chunk_hooks, &arena->chunks_szad_cached,
- &arena->chunks_ad_cached, true, new_addr, size, alignment, zero,
- &commit, dalloc_node);
- if (ret == NULL)
- return (NULL);
- assert(commit);
- if (config_valgrind)
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
- return (ret);
-}
-
-static arena_t *
-chunk_arena_get(unsigned arena_ind)
-{
- arena_t *arena;
-
- /* Dodge tsd for a0 in order to avoid bootstrapping issues. */
- arena = (arena_ind == 0) ? a0get() : arena_get(tsd_fetch(), arena_ind,
- false, true);
- /*
- * The arena we're allocating on behalf of must have been initialized
- * already.
- */
- assert(arena != NULL);
- return (arena);
-}
-
-static void *
-chunk_alloc_default(void *new_addr, size_t size, size_t alignment, bool *zero,
- bool *commit, unsigned arena_ind)
-{
- void *ret;
- arena_t *arena;
-
- arena = chunk_arena_get(arena_ind);
- ret = chunk_alloc_core(arena, new_addr, size, alignment, zero,
- commit, arena->dss_prec);
- if (ret == NULL)
- return (NULL);
- if (config_valgrind)
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, size);
-
- return (ret);
-}
-
-void *
-chunk_alloc_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks, void *new_addr,
- size_t size, size_t alignment, bool *zero, bool *commit)
-{
- void *ret;
-
- chunk_hooks_assure_initialized(arena, chunk_hooks);
- ret = chunk_hooks->alloc(new_addr, size, alignment, zero, commit,
- arena->ind);
- if (ret == NULL)
- return (NULL);
- if (config_valgrind && chunk_hooks->alloc != chunk_alloc_default)
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(ret, chunksize);
- return (ret);
-}
-
-static void
-chunk_record(arena_t *arena, chunk_hooks_t *chunk_hooks,
- extent_tree_t *chunks_szad, extent_tree_t *chunks_ad, bool cache,
- void *chunk, size_t size, bool zeroed, bool committed)
-{
- bool unzeroed;
- extent_node_t *node, *prev;
- extent_node_t key;
-
- assert(!cache || !zeroed);
- unzeroed = cache || !zeroed;
- JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(chunk, size);
-
- malloc_mutex_lock(&arena->chunks_mtx);
- chunk_hooks_assure_initialized_locked(arena, chunk_hooks);
- extent_node_init(&key, arena, (void *)((uintptr_t)chunk + size), 0,
- false, false);
- node = extent_tree_ad_nsearch(chunks_ad, &key);
- /* Try to coalesce forward. */
- if (node != NULL && extent_node_addr_get(node) ==
- extent_node_addr_get(&key) && extent_node_committed_get(node) ==
- committed && !chunk_hooks->merge(chunk, size,
- extent_node_addr_get(node), extent_node_size_get(node), false,
- arena->ind)) {
- /*
- * Coalesce chunk with the following address range. This does
- * not change the position within chunks_ad, so only
- * remove/insert from/into chunks_szad.
- */
- extent_tree_szad_remove(chunks_szad, node);
- arena_chunk_cache_maybe_remove(arena, node, cache);
- extent_node_addr_set(node, chunk);
- extent_node_size_set(node, size + extent_node_size_get(node));
- extent_node_zeroed_set(node, extent_node_zeroed_get(node) &&
- !unzeroed);
- extent_tree_szad_insert(chunks_szad, node);
- arena_chunk_cache_maybe_insert(arena, node, cache);
- } else {
- /* Coalescing forward failed, so insert a new node. */
- node = arena_node_alloc(arena);
- if (node == NULL) {
- /*
- * Node allocation failed, which is an exceedingly
- * unlikely failure. Leak chunk after making sure its
- * pages have already been purged, so that this is only
- * a virtual memory leak.
- */
- if (cache) {
- chunk_purge_wrapper(arena, chunk_hooks, chunk,
- size, 0, size);
- }
- goto label_return;
- }
- extent_node_init(node, arena, chunk, size, !unzeroed,
- committed);
- extent_tree_ad_insert(chunks_ad, node);
- extent_tree_szad_insert(chunks_szad, node);
- arena_chunk_cache_maybe_insert(arena, node, cache);
- }
-
- /* Try to coalesce backward. */
- prev = extent_tree_ad_prev(chunks_ad, node);
- if (prev != NULL && (void *)((uintptr_t)extent_node_addr_get(prev) +
- extent_node_size_get(prev)) == chunk &&
- extent_node_committed_get(prev) == committed &&
- !chunk_hooks->merge(extent_node_addr_get(prev),
- extent_node_size_get(prev), chunk, size, false, arena->ind)) {
- /*
- * Coalesce chunk with the previous address range. This does
- * not change the position within chunks_ad, so only
- * remove/insert node from/into chunks_szad.
- */
- extent_tree_szad_remove(chunks_szad, prev);
- extent_tree_ad_remove(chunks_ad, prev);
- arena_chunk_cache_maybe_remove(arena, prev, cache);
- extent_tree_szad_remove(chunks_szad, node);
- arena_chunk_cache_maybe_remove(arena, node, cache);
- extent_node_addr_set(node, extent_node_addr_get(prev));
- extent_node_size_set(node, extent_node_size_get(prev) +
- extent_node_size_get(node));
- extent_node_zeroed_set(node, extent_node_zeroed_get(prev) &&
- extent_node_zeroed_get(node));
- extent_tree_szad_insert(chunks_szad, node);
- arena_chunk_cache_maybe_insert(arena, node, cache);
-
- arena_node_dalloc(arena, prev);
- }
-
-label_return:
- malloc_mutex_unlock(&arena->chunks_mtx);
-}
-
-void
-chunk_dalloc_cache(arena_t *arena, chunk_hooks_t *chunk_hooks, void *chunk,
- size_t size, bool committed)
-{
-
- assert(chunk != NULL);
- assert(CHUNK_ADDR2BASE(chunk) == chunk);
- assert(size != 0);
- assert((size & chunksize_mask) == 0);
-
- chunk_record(arena, chunk_hooks, &arena->chunks_szad_cached,
- &arena->chunks_ad_cached, true, chunk, size, false, committed);
- arena_maybe_purge(arena);
-}
-
-void
-chunk_dalloc_arena(arena_t *arena, chunk_hooks_t *chunk_hooks, void *chunk,
- size_t size, bool zeroed, bool committed)
-{
-
- assert(chunk != NULL);
- assert(CHUNK_ADDR2BASE(chunk) == chunk);
- assert(size != 0);
- assert((size & chunksize_mask) == 0);
-
- chunk_hooks_assure_initialized(arena, chunk_hooks);
- /* Try to deallocate. */
- if (!chunk_hooks->dalloc(chunk, size, committed, arena->ind))
- return;
- /* Try to decommit; purge if that fails. */
- if (committed) {
- committed = chunk_hooks->decommit(chunk, size, 0, size,
- arena->ind);
- }
- zeroed = !committed || !chunk_hooks->purge(chunk, size, 0, size,
- arena->ind);
- chunk_record(arena, chunk_hooks, &arena->chunks_szad_retained,
- &arena->chunks_ad_retained, false, chunk, size, zeroed, committed);
-}
-
-static bool
-chunk_dalloc_default(void *chunk, size_t size, bool committed,
- unsigned arena_ind)
-{
-
- if (!have_dss || !chunk_in_dss(chunk))
- return (chunk_dalloc_mmap(chunk, size));
- return (true);
-}
-
-void
-chunk_dalloc_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks, void *chunk,
- size_t size, bool committed)
-{
-
- chunk_hooks_assure_initialized(arena, chunk_hooks);
- chunk_hooks->dalloc(chunk, size, committed, arena->ind);
- if (config_valgrind && chunk_hooks->dalloc != chunk_dalloc_default)
- JEMALLOC_VALGRIND_MAKE_MEM_NOACCESS(chunk, size);
-}
-
-static bool
-chunk_commit_default(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
-
- return (pages_commit((void *)((uintptr_t)chunk + (uintptr_t)offset),
- length));
-}
-
-static bool
-chunk_decommit_default(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
-
- return (pages_decommit((void *)((uintptr_t)chunk + (uintptr_t)offset),
- length));
-}
-
-bool
-chunk_purge_arena(arena_t *arena, void *chunk, size_t offset, size_t length)
-{
-
- assert(chunk != NULL);
- assert(CHUNK_ADDR2BASE(chunk) == chunk);
- assert((offset & PAGE_MASK) == 0);
- assert(length != 0);
- assert((length & PAGE_MASK) == 0);
-
- return (pages_purge((void *)((uintptr_t)chunk + (uintptr_t)offset),
- length));
-}
-
-static bool
-chunk_purge_default(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
-
- return (chunk_purge_arena(chunk_arena_get(arena_ind), chunk, offset,
- length));
-}
-
-bool
-chunk_purge_wrapper(arena_t *arena, chunk_hooks_t *chunk_hooks, void *chunk,
- size_t size, size_t offset, size_t length)
-{
-
- chunk_hooks_assure_initialized(arena, chunk_hooks);
- return (chunk_hooks->purge(chunk, size, offset, length, arena->ind));
-}
-
-static bool
-chunk_split_default(void *chunk, size_t size, size_t size_a, size_t size_b,
- bool committed, unsigned arena_ind)
-{
-
- if (!maps_coalesce)
- return (true);
- return (false);
-}
-
-static bool
-chunk_merge_default(void *chunk_a, size_t size_a, void *chunk_b, size_t size_b,
- bool committed, unsigned arena_ind)
-{
-
- if (!maps_coalesce)
- return (true);
- if (have_dss && chunk_in_dss(chunk_a) != chunk_in_dss(chunk_b))
- return (true);
-
- return (false);
-}
-
-static rtree_node_elm_t *
-chunks_rtree_node_alloc(size_t nelms)
-{
-
- return ((rtree_node_elm_t *)base_alloc(nelms *
- sizeof(rtree_node_elm_t)));
-}
-
-bool
-chunk_boot(void)
-{
-#ifdef _WIN32
- SYSTEM_INFO info;
- GetSystemInfo(&info);
-
- /*
- * Verify actual page size is equal to or an integral multiple of
- * configured page size.
- */
- if (info.dwPageSize & ((1U << LG_PAGE) - 1))
- return (true);
-
- /*
- * Configure chunksize (if not set) to match granularity (usually 64K),
- * so pages_map will always take fast path.
- */
- if (!opt_lg_chunk) {
- opt_lg_chunk = jemalloc_ffs((int)info.dwAllocationGranularity)
- - 1;
- }
-#else
- if (!opt_lg_chunk)
- opt_lg_chunk = LG_CHUNK_DEFAULT;
-#endif
-
- /* Set variables according to the value of opt_lg_chunk. */
- chunksize = (ZU(1) << opt_lg_chunk);
- assert(chunksize >= PAGE);
- chunksize_mask = chunksize - 1;
- chunk_npages = (chunksize >> LG_PAGE);
-
- if (have_dss && chunk_dss_boot())
- return (true);
- if (rtree_new(&chunks_rtree, (ZU(1) << (LG_SIZEOF_PTR+3)) -
- opt_lg_chunk, chunks_rtree_node_alloc, NULL))
- return (true);
-
- return (false);
-}
-
-void
-chunk_prefork(void)
-{
-
- chunk_dss_prefork();
-}
-
-void
-chunk_postfork_parent(void)
-{
-
- chunk_dss_postfork_parent();
-}
-
-void
-chunk_postfork_child(void)
-{
-
- chunk_dss_postfork_child();
-}
diff --git a/deps/jemalloc/src/chunk_dss.c b/deps/jemalloc/src/chunk_dss.c
deleted file mode 100644
index 61fc91696..000000000
--- a/deps/jemalloc/src/chunk_dss.c
+++ /dev/null
@@ -1,214 +0,0 @@
-#define JEMALLOC_CHUNK_DSS_C_
-#include "jemalloc/internal/jemalloc_internal.h"
-/******************************************************************************/
-/* Data. */
-
-const char *dss_prec_names[] = {
- "disabled",
- "primary",
- "secondary",
- "N/A"
-};
-
-/* Current dss precedence default, used when creating new arenas. */
-static dss_prec_t dss_prec_default = DSS_PREC_DEFAULT;
-
-/*
- * Protects sbrk() calls. This avoids malloc races among threads, though it
- * does not protect against races with threads that call sbrk() directly.
- */
-static malloc_mutex_t dss_mtx;
-
-/* Base address of the DSS. */
-static void *dss_base;
-/* Current end of the DSS, or ((void *)-1) if the DSS is exhausted. */
-static void *dss_prev;
-/* Current upper limit on DSS addresses. */
-static void *dss_max;
-
-/******************************************************************************/
-
-static void *
-chunk_dss_sbrk(intptr_t increment)
-{
-
-#ifdef JEMALLOC_DSS
- return (sbrk(increment));
-#else
- not_implemented();
- return (NULL);
-#endif
-}
-
-dss_prec_t
-chunk_dss_prec_get(void)
-{
- dss_prec_t ret;
-
- if (!have_dss)
- return (dss_prec_disabled);
- malloc_mutex_lock(&dss_mtx);
- ret = dss_prec_default;
- malloc_mutex_unlock(&dss_mtx);
- return (ret);
-}
-
-bool
-chunk_dss_prec_set(dss_prec_t dss_prec)
-{
-
- if (!have_dss)
- return (dss_prec != dss_prec_disabled);
- malloc_mutex_lock(&dss_mtx);
- dss_prec_default = dss_prec;
- malloc_mutex_unlock(&dss_mtx);
- return (false);
-}
-
-void *
-chunk_alloc_dss(arena_t *arena, void *new_addr, size_t size, size_t alignment,
- bool *zero, bool *commit)
-{
- cassert(have_dss);
- assert(size > 0 && (size & chunksize_mask) == 0);
- assert(alignment > 0 && (alignment & chunksize_mask) == 0);
-
- /*
- * sbrk() uses a signed increment argument, so take care not to
- * interpret a huge allocation request as a negative increment.
- */
- if ((intptr_t)size < 0)
- return (NULL);
-
- malloc_mutex_lock(&dss_mtx);
- if (dss_prev != (void *)-1) {
-
- /*
- * The loop is necessary to recover from races with other
- * threads that are using the DSS for something other than
- * malloc.
- */
- do {
- void *ret, *cpad, *dss_next;
- size_t gap_size, cpad_size;
- intptr_t incr;
- /* Avoid an unnecessary system call. */
- if (new_addr != NULL && dss_max != new_addr)
- break;
-
- /* Get the current end of the DSS. */
- dss_max = chunk_dss_sbrk(0);
-
- /* Make sure the earlier condition still holds. */
- if (new_addr != NULL && dss_max != new_addr)
- break;
-
- /*
- * Calculate how much padding is necessary to
- * chunk-align the end of the DSS.
- */
- gap_size = (chunksize - CHUNK_ADDR2OFFSET(dss_max)) &
- chunksize_mask;
- /*
- * Compute how much chunk-aligned pad space (if any) is
- * necessary to satisfy alignment. This space can be
- * recycled for later use.
- */
- cpad = (void *)((uintptr_t)dss_max + gap_size);
- ret = (void *)ALIGNMENT_CEILING((uintptr_t)dss_max,
- alignment);
- cpad_size = (uintptr_t)ret - (uintptr_t)cpad;
- dss_next = (void *)((uintptr_t)ret + size);
- if ((uintptr_t)ret < (uintptr_t)dss_max ||
- (uintptr_t)dss_next < (uintptr_t)dss_max) {
- /* Wrap-around. */
- malloc_mutex_unlock(&dss_mtx);
- return (NULL);
- }
- incr = gap_size + cpad_size + size;
- dss_prev = chunk_dss_sbrk(incr);
- if (dss_prev == dss_max) {
- /* Success. */
- dss_max = dss_next;
- malloc_mutex_unlock(&dss_mtx);
- if (cpad_size != 0) {
- chunk_hooks_t chunk_hooks =
- CHUNK_HOOKS_INITIALIZER;
- chunk_dalloc_wrapper(arena,
- &chunk_hooks, cpad, cpad_size,
- true);
- }
- if (*zero) {
- JEMALLOC_VALGRIND_MAKE_MEM_UNDEFINED(
- ret, size);
- memset(ret, 0, size);
- }
- if (!*commit)
- *commit = pages_decommit(ret, size);
- return (ret);
- }
- } while (dss_prev != (void *)-1);
- }
- malloc_mutex_unlock(&dss_mtx);
-
- return (NULL);
-}
-
-bool
-chunk_in_dss(void *chunk)
-{
- bool ret;
-
- cassert(have_dss);
-
- malloc_mutex_lock(&dss_mtx);
- if ((uintptr_t)chunk >= (uintptr_t)dss_base
- && (uintptr_t)chunk < (uintptr_t)dss_max)
- ret = true;
- else
- ret = false;
- malloc_mutex_unlock(&dss_mtx);
-
- return (ret);
-}
-
-bool
-chunk_dss_boot(void)
-{
-
- cassert(have_dss);
-
- if (malloc_mutex_init(&dss_mtx))
- return (true);
- dss_base = chunk_dss_sbrk(0);
- dss_prev = dss_base;
- dss_max = dss_base;
-
- return (false);
-}
-
-void
-chunk_dss_prefork(void)
-{
-
- if (have_dss)
- malloc_mutex_prefork(&dss_mtx);
-}
-
-void
-chunk_dss_postfork_parent(void)
-{
-
- if (have_dss)
- malloc_mutex_postfork_parent(&dss_mtx);
-}
-
-void
-chunk_dss_postfork_child(void)
-{
-
- if (have_dss)
- malloc_mutex_postfork_child(&dss_mtx);
-}
-
-/******************************************************************************/
diff --git a/deps/jemalloc/src/chunk_mmap.c b/deps/jemalloc/src/chunk_mmap.c
deleted file mode 100644
index b9ba74191..000000000
--- a/deps/jemalloc/src/chunk_mmap.c
+++ /dev/null
@@ -1,80 +0,0 @@
-#define JEMALLOC_CHUNK_MMAP_C_
-#include "jemalloc/internal/jemalloc_internal.h"
-
-/******************************************************************************/
-
-static void *
-chunk_alloc_mmap_slow(size_t size, size_t alignment, bool *zero, bool *commit)
-{
- void *ret;
- size_t alloc_size;
-
- alloc_size = size + alignment - PAGE;
- /* Beware size_t wrap-around. */
- if (alloc_size < size)
- return (NULL);
- do {
- void *pages;
- size_t leadsize;
- pages = pages_map(NULL, alloc_size);
- if (pages == NULL)
- return (NULL);
- leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment) -
- (uintptr_t)pages;
- ret = pages_trim(pages, alloc_size, leadsize, size);
- } while (ret == NULL);
-
- assert(ret != NULL);
- *zero = true;
- if (!*commit)
- *commit = pages_decommit(ret, size);
- return (ret);
-}
-
-void *
-chunk_alloc_mmap(size_t size, size_t alignment, bool *zero, bool *commit)
-{
- void *ret;
- size_t offset;
-
- /*
- * Ideally, there would be a way to specify alignment to mmap() (like
- * NetBSD has), but in the absence of such a feature, we have to work
- * hard to efficiently create aligned mappings. The reliable, but
- * slow method is to create a mapping that is over-sized, then trim the
- * excess. However, that always results in one or two calls to
- * pages_unmap().
- *
- * Optimistically try mapping precisely the right amount before falling
- * back to the slow method, with the expectation that the optimistic
- * approach works most of the time.
- */
-
- assert(alignment != 0);
- assert((alignment & chunksize_mask) == 0);
-
- ret = pages_map(NULL, size);
- if (ret == NULL)
- return (NULL);
- offset = ALIGNMENT_ADDR2OFFSET(ret, alignment);
- if (offset != 0) {
- pages_unmap(ret, size);
- return (chunk_alloc_mmap_slow(size, alignment, zero, commit));
- }
-
- assert(ret != NULL);
- *zero = true;
- if (!*commit)
- *commit = pages_decommit(ret, size);
- return (ret);
-}
-
-bool
-chunk_dalloc_mmap(void *chunk, size_t size)
-{
-
- if (config_munmap)
- pages_unmap(chunk, size);
-
- return (!config_munmap);
-}
diff --git a/deps/jemalloc/src/ckh.c b/deps/jemalloc/src/ckh.c
index 53a1c1ef1..e95e0a3ed 100644
--- a/deps/jemalloc/src/ckh.c
+++ b/deps/jemalloc/src/ckh.c
@@ -34,8 +34,18 @@
* respectively.
*
******************************************************************************/
-#define JEMALLOC_CKH_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_CKH_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+
+#include "jemalloc/internal/ckh.h"
+
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/hash.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/prng.h"
+#include "jemalloc/internal/util.h"
/******************************************************************************/
/* Function prototypes for non-inline static functions. */
@@ -49,27 +59,26 @@ static void ckh_shrink(tsd_t *tsd, ckh_t *ckh);
* Search bucket for key and return the cell number if found; SIZE_T_MAX
* otherwise.
*/
-JEMALLOC_INLINE_C size_t
-ckh_bucket_search(ckh_t *ckh, size_t bucket, const void *key)
-{
+static size_t
+ckh_bucket_search(ckh_t *ckh, size_t bucket, const void *key) {
ckhc_t *cell;
unsigned i;
for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) {
cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i];
- if (cell->key != NULL && ckh->keycomp(key, cell->key))
- return ((bucket << LG_CKH_BUCKET_CELLS) + i);
+ if (cell->key != NULL && ckh->keycomp(key, cell->key)) {
+ return (bucket << LG_CKH_BUCKET_CELLS) + i;
+ }
}
- return (SIZE_T_MAX);
+ return SIZE_T_MAX;
}
/*
* Search table for key and return cell number if found; SIZE_T_MAX otherwise.
*/
-JEMALLOC_INLINE_C size_t
-ckh_isearch(ckh_t *ckh, const void *key)
-{
+static size_t
+ckh_isearch(ckh_t *ckh, const void *key) {
size_t hashes[2], bucket, cell;
assert(ckh != NULL);
@@ -79,19 +88,19 @@ ckh_isearch(ckh_t *ckh, const void *key)
/* Search primary bucket. */
bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1);
cell = ckh_bucket_search(ckh, bucket, key);
- if (cell != SIZE_T_MAX)
- return (cell);
+ if (cell != SIZE_T_MAX) {
+ return cell;
+ }
/* Search secondary bucket. */
bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1);
cell = ckh_bucket_search(ckh, bucket, key);
- return (cell);
+ return cell;
}
-JEMALLOC_INLINE_C bool
+static bool
ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key,
- const void *data)
-{
+ const void *data) {
ckhc_t *cell;
unsigned offset, i;
@@ -99,7 +108,8 @@ ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key,
* Cycle through the cells in the bucket, starting at a random position.
* The randomness avoids worst-case search overhead as buckets fill up.
*/
- prng32(offset, LG_CKH_BUCKET_CELLS, ckh->prng_state, CKH_A, CKH_C);
+ offset = (unsigned)prng_lg_range_u64(&ckh->prng_state,
+ LG_CKH_BUCKET_CELLS);
for (i = 0; i < (ZU(1) << LG_CKH_BUCKET_CELLS); i++) {
cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) +
((i + offset) & ((ZU(1) << LG_CKH_BUCKET_CELLS) - 1))];
@@ -107,11 +117,11 @@ ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key,
cell->key = key;
cell->data = data;
ckh->count++;
- return (false);
+ return false;
}
}
- return (true);
+ return true;
}
/*
@@ -120,10 +130,9 @@ ckh_try_bucket_insert(ckh_t *ckh, size_t bucket, const void *key,
* eviction/relocation procedure until either success or detection of an
* eviction/relocation bucket cycle.
*/
-JEMALLOC_INLINE_C bool
+static bool
ckh_evict_reloc_insert(ckh_t *ckh, size_t argbucket, void const **argkey,
- void const **argdata)
-{
+ void const **argdata) {
const void *key, *data, *tkey, *tdata;
ckhc_t *cell;
size_t hashes[2], bucket, tbucket;
@@ -141,7 +150,8 @@ ckh_evict_reloc_insert(ckh_t *ckh, size_t argbucket, void const **argkey,
* were an item for which both hashes indicated the same
* bucket.
*/
- prng32(i, LG_CKH_BUCKET_CELLS, ckh->prng_state, CKH_A, CKH_C);
+ i = (unsigned)prng_lg_range_u64(&ckh->prng_state,
+ LG_CKH_BUCKET_CELLS);
cell = &ckh->tab[(bucket << LG_CKH_BUCKET_CELLS) + i];
assert(cell->key != NULL);
@@ -181,18 +191,18 @@ ckh_evict_reloc_insert(ckh_t *ckh, size_t argbucket, void const **argkey,
if (tbucket == argbucket) {
*argkey = key;
*argdata = data;
- return (true);
+ return true;
}
bucket = tbucket;
- if (!ckh_try_bucket_insert(ckh, bucket, key, data))
- return (false);
+ if (!ckh_try_bucket_insert(ckh, bucket, key, data)) {
+ return false;
+ }
}
}
-JEMALLOC_INLINE_C bool
-ckh_try_insert(ckh_t *ckh, void const**argkey, void const**argdata)
-{
+static bool
+ckh_try_insert(ckh_t *ckh, void const**argkey, void const**argdata) {
size_t hashes[2], bucket;
const void *key = *argkey;
const void *data = *argdata;
@@ -201,27 +211,28 @@ ckh_try_insert(ckh_t *ckh, void const**argkey, void const**argdata)
/* Try to insert in primary bucket. */
bucket = hashes[0] & ((ZU(1) << ckh->lg_curbuckets) - 1);
- if (!ckh_try_bucket_insert(ckh, bucket, key, data))
- return (false);
+ if (!ckh_try_bucket_insert(ckh, bucket, key, data)) {
+ return false;
+ }
/* Try to insert in secondary bucket. */
bucket = hashes[1] & ((ZU(1) << ckh->lg_curbuckets) - 1);
- if (!ckh_try_bucket_insert(ckh, bucket, key, data))
- return (false);
+ if (!ckh_try_bucket_insert(ckh, bucket, key, data)) {
+ return false;
+ }
/*
* Try to find a place for this item via iterative eviction/relocation.
*/
- return (ckh_evict_reloc_insert(ckh, bucket, argkey, argdata));
+ return ckh_evict_reloc_insert(ckh, bucket, argkey, argdata);
}
/*
* Try to rebuild the hash table from scratch by inserting all items from the
* old table into the new.
*/
-JEMALLOC_INLINE_C bool
-ckh_rebuild(ckh_t *ckh, ckhc_t *aTab)
-{
+static bool
+ckh_rebuild(ckh_t *ckh, ckhc_t *aTab) {
size_t count, i, nins;
const void *key, *data;
@@ -233,22 +244,20 @@ ckh_rebuild(ckh_t *ckh, ckhc_t *aTab)
data = aTab[i].data;
if (ckh_try_insert(ckh, &key, &data)) {
ckh->count = count;
- return (true);
+ return true;
}
nins++;
}
}
- return (false);
+ return false;
}
static bool
-ckh_grow(tsd_t *tsd, ckh_t *ckh)
-{
+ckh_grow(tsd_t *tsd, ckh_t *ckh) {
bool ret;
ckhc_t *tab, *ttab;
- size_t lg_curcells;
- unsigned lg_prevbuckets;
+ unsigned lg_prevbuckets, lg_curcells;
#ifdef CKH_COUNT
ckh->ngrows++;
@@ -265,13 +274,13 @@ ckh_grow(tsd_t *tsd, ckh_t *ckh)
size_t usize;
lg_curcells++;
- usize = sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE);
- if (usize == 0) {
+ usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
ret = true;
goto label_return;
}
- tab = (ckhc_t *)ipallocztm(tsd, usize, CACHELINE, true, NULL,
- true, NULL);
+ tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE,
+ true, NULL, true, arena_ichoose(tsd, NULL));
if (tab == NULL) {
ret = true;
goto label_return;
@@ -283,27 +292,26 @@ ckh_grow(tsd_t *tsd, ckh_t *ckh)
ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS;
if (!ckh_rebuild(ckh, tab)) {
- idalloctm(tsd, tab, tcache_get(tsd, false), true);
+ idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true);
break;
}
/* Rebuilding failed, so back out partially rebuilt table. */
- idalloctm(tsd, ckh->tab, tcache_get(tsd, false), true);
+ idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true);
ckh->tab = tab;
ckh->lg_curbuckets = lg_prevbuckets;
}
ret = false;
label_return:
- return (ret);
+ return ret;
}
static void
-ckh_shrink(tsd_t *tsd, ckh_t *ckh)
-{
+ckh_shrink(tsd_t *tsd, ckh_t *ckh) {
ckhc_t *tab, *ttab;
- size_t lg_curcells, usize;
- unsigned lg_prevbuckets;
+ size_t usize;
+ unsigned lg_prevbuckets, lg_curcells;
/*
* It is possible (though unlikely, given well behaved hashes) that the
@@ -311,11 +319,12 @@ ckh_shrink(tsd_t *tsd, ckh_t *ckh)
*/
lg_prevbuckets = ckh->lg_curbuckets;
lg_curcells = ckh->lg_curbuckets + LG_CKH_BUCKET_CELLS - 1;
- usize = sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE);
- if (usize == 0)
+ usize = sz_sa2u(sizeof(ckhc_t) << lg_curcells, CACHELINE);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
return;
- tab = (ckhc_t *)ipallocztm(tsd, usize, CACHELINE, true, NULL, true,
- NULL);
+ }
+ tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true, NULL,
+ true, arena_ichoose(tsd, NULL));
if (tab == NULL) {
/*
* An OOM error isn't worth propagating, since it doesn't
@@ -330,7 +339,7 @@ ckh_shrink(tsd_t *tsd, ckh_t *ckh)
ckh->lg_curbuckets = lg_curcells - LG_CKH_BUCKET_CELLS;
if (!ckh_rebuild(ckh, tab)) {
- idalloctm(tsd, tab, tcache_get(tsd, false), true);
+ idalloctm(tsd_tsdn(tsd), tab, NULL, NULL, true, true);
#ifdef CKH_COUNT
ckh->nshrinks++;
#endif
@@ -338,7 +347,7 @@ ckh_shrink(tsd_t *tsd, ckh_t *ckh)
}
/* Rebuilding failed, so back out partially rebuilt table. */
- idalloctm(tsd, ckh->tab, tcache_get(tsd, false), true);
+ idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true);
ckh->tab = tab;
ckh->lg_curbuckets = lg_prevbuckets;
#ifdef CKH_COUNT
@@ -348,8 +357,7 @@ ckh_shrink(tsd_t *tsd, ckh_t *ckh)
bool
ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash,
- ckh_keycomp_t *keycomp)
-{
+ ckh_keycomp_t *keycomp) {
bool ret;
size_t mincells, usize;
unsigned lg_mincells;
@@ -379,20 +387,21 @@ ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash,
mincells = ((minitems + (3 - (minitems % 3))) / 3) << 2;
for (lg_mincells = LG_CKH_BUCKET_CELLS;
(ZU(1) << lg_mincells) < mincells;
- lg_mincells++)
- ; /* Do nothing. */
+ lg_mincells++) {
+ /* Do nothing. */
+ }
ckh->lg_minbuckets = lg_mincells - LG_CKH_BUCKET_CELLS;
ckh->lg_curbuckets = lg_mincells - LG_CKH_BUCKET_CELLS;
ckh->hash = hash;
ckh->keycomp = keycomp;
- usize = sa2u(sizeof(ckhc_t) << lg_mincells, CACHELINE);
- if (usize == 0) {
+ usize = sz_sa2u(sizeof(ckhc_t) << lg_mincells, CACHELINE);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
ret = true;
goto label_return;
}
- ckh->tab = (ckhc_t *)ipallocztm(tsd, usize, CACHELINE, true, NULL, true,
- NULL);
+ ckh->tab = (ckhc_t *)ipallocztm(tsd_tsdn(tsd), usize, CACHELINE, true,
+ NULL, true, arena_ichoose(tsd, NULL));
if (ckh->tab == NULL) {
ret = true;
goto label_return;
@@ -400,13 +409,11 @@ ckh_new(tsd_t *tsd, ckh_t *ckh, size_t minitems, ckh_hash_t *hash,
ret = false;
label_return:
- return (ret);
+ return ret;
}
void
-ckh_delete(tsd_t *tsd, ckh_t *ckh)
-{
-
+ckh_delete(tsd_t *tsd, ckh_t *ckh) {
assert(ckh != NULL);
#ifdef CKH_VERBOSE
@@ -421,43 +428,42 @@ ckh_delete(tsd_t *tsd, ckh_t *ckh)
(unsigned long long)ckh->nrelocs);
#endif
- idalloctm(tsd, ckh->tab, tcache_get(tsd, false), true);
- if (config_debug)
- memset(ckh, 0x5a, sizeof(ckh_t));
+ idalloctm(tsd_tsdn(tsd), ckh->tab, NULL, NULL, true, true);
+ if (config_debug) {
+ memset(ckh, JEMALLOC_FREE_JUNK, sizeof(ckh_t));
+ }
}
size_t
-ckh_count(ckh_t *ckh)
-{
-
+ckh_count(ckh_t *ckh) {
assert(ckh != NULL);
- return (ckh->count);
+ return ckh->count;
}
bool
-ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data)
-{
+ckh_iter(ckh_t *ckh, size_t *tabind, void **key, void **data) {
size_t i, ncells;
for (i = *tabind, ncells = (ZU(1) << (ckh->lg_curbuckets +
LG_CKH_BUCKET_CELLS)); i < ncells; i++) {
if (ckh->tab[i].key != NULL) {
- if (key != NULL)
+ if (key != NULL) {
*key = (void *)ckh->tab[i].key;
- if (data != NULL)
+ }
+ if (data != NULL) {
*data = (void *)ckh->tab[i].data;
+ }
*tabind = i + 1;
- return (false);
+ return false;
}
}
- return (true);
+ return true;
}
bool
-ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data)
-{
+ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data) {
bool ret;
assert(ckh != NULL);
@@ -476,23 +482,24 @@ ckh_insert(tsd_t *tsd, ckh_t *ckh, const void *key, const void *data)
ret = false;
label_return:
- return (ret);
+ return ret;
}
bool
ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key,
- void **data)
-{
+ void **data) {
size_t cell;
assert(ckh != NULL);
cell = ckh_isearch(ckh, searchkey);
if (cell != SIZE_T_MAX) {
- if (key != NULL)
+ if (key != NULL) {
*key = (void *)ckh->tab[cell].key;
- if (data != NULL)
+ }
+ if (data != NULL) {
*data = (void *)ckh->tab[cell].data;
+ }
ckh->tab[cell].key = NULL;
ckh->tab[cell].data = NULL; /* Not necessary. */
@@ -505,51 +512,47 @@ ckh_remove(tsd_t *tsd, ckh_t *ckh, const void *searchkey, void **key,
ckh_shrink(tsd, ckh);
}
- return (false);
+ return false;
}
- return (true);
+ return true;
}
bool
-ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data)
-{
+ckh_search(ckh_t *ckh, const void *searchkey, void **key, void **data) {
size_t cell;
assert(ckh != NULL);
cell = ckh_isearch(ckh, searchkey);
if (cell != SIZE_T_MAX) {
- if (key != NULL)
+ if (key != NULL) {
*key = (void *)ckh->tab[cell].key;
- if (data != NULL)
+ }
+ if (data != NULL) {
*data = (void *)ckh->tab[cell].data;
- return (false);
+ }
+ return false;
}
- return (true);
+ return true;
}
void
-ckh_string_hash(const void *key, size_t r_hash[2])
-{
-
+ckh_string_hash(const void *key, size_t r_hash[2]) {
hash(key, strlen((const char *)key), 0x94122f33U, r_hash);
}
bool
-ckh_string_keycomp(const void *k1, const void *k2)
-{
-
- assert(k1 != NULL);
- assert(k2 != NULL);
+ckh_string_keycomp(const void *k1, const void *k2) {
+ assert(k1 != NULL);
+ assert(k2 != NULL);
- return (strcmp((char *)k1, (char *)k2) ? false : true);
+ return !strcmp((char *)k1, (char *)k2);
}
void
-ckh_pointer_hash(const void *key, size_t r_hash[2])
-{
+ckh_pointer_hash(const void *key, size_t r_hash[2]) {
union {
const void *v;
size_t i;
@@ -561,8 +564,6 @@ ckh_pointer_hash(const void *key, size_t r_hash[2])
}
bool
-ckh_pointer_keycomp(const void *k1, const void *k2)
-{
-
- return ((k1 == k2) ? true : false);
+ckh_pointer_keycomp(const void *k1, const void *k2) {
+ return (k1 == k2);
}
diff --git a/deps/jemalloc/src/ctl.c b/deps/jemalloc/src/ctl.c
index 3de8e602d..1e713a3d1 100644
--- a/deps/jemalloc/src/ctl.c
+++ b/deps/jemalloc/src/ctl.c
@@ -1,69 +1,63 @@
-#define JEMALLOC_CTL_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_CTL_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/ctl.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/nstime.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/util.h"
/******************************************************************************/
/* Data. */
/*
* ctl_mtx protects the following:
- * - ctl_stats.*
+ * - ctl_stats->*
*/
static malloc_mutex_t ctl_mtx;
static bool ctl_initialized;
-static uint64_t ctl_epoch;
-static ctl_stats_t ctl_stats;
+static ctl_stats_t *ctl_stats;
+static ctl_arenas_t *ctl_arenas;
/******************************************************************************/
/* Helpers for named and indexed nodes. */
-JEMALLOC_INLINE_C const ctl_named_node_t *
-ctl_named_node(const ctl_node_t *node)
-{
-
+static const ctl_named_node_t *
+ctl_named_node(const ctl_node_t *node) {
return ((node->named) ? (const ctl_named_node_t *)node : NULL);
}
-JEMALLOC_INLINE_C const ctl_named_node_t *
-ctl_named_children(const ctl_named_node_t *node, int index)
-{
+static const ctl_named_node_t *
+ctl_named_children(const ctl_named_node_t *node, size_t index) {
const ctl_named_node_t *children = ctl_named_node(node->children);
return (children ? &children[index] : NULL);
}
-JEMALLOC_INLINE_C const ctl_indexed_node_t *
-ctl_indexed_node(const ctl_node_t *node)
-{
-
+static const ctl_indexed_node_t *
+ctl_indexed_node(const ctl_node_t *node) {
return (!node->named ? (const ctl_indexed_node_t *)node : NULL);
}
/******************************************************************************/
/* Function prototypes for non-inline static functions. */
-#define CTL_PROTO(n) \
-static int n##_ctl(const size_t *mib, size_t miblen, void *oldp, \
- size_t *oldlenp, void *newp, size_t newlen);
-
-#define INDEX_PROTO(n) \
-static const ctl_named_node_t *n##_index(const size_t *mib, \
- size_t miblen, size_t i);
-
-static bool ctl_arena_init(ctl_arena_stats_t *astats);
-static void ctl_arena_clear(ctl_arena_stats_t *astats);
-static void ctl_arena_stats_amerge(ctl_arena_stats_t *cstats,
- arena_t *arena);
-static void ctl_arena_stats_smerge(ctl_arena_stats_t *sstats,
- ctl_arena_stats_t *astats);
-static void ctl_arena_refresh(arena_t *arena, unsigned i);
-static bool ctl_grow(void);
-static void ctl_refresh(void);
-static bool ctl_init(void);
-static int ctl_lookup(const char *name, ctl_node_t const **nodesp,
- size_t *mibp, size_t *depthp);
+#define CTL_PROTO(n) \
+static int n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, \
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen);
+
+#define INDEX_PROTO(n) \
+static const ctl_named_node_t *n##_index(tsdn_t *tsdn, \
+ const size_t *mib, size_t miblen, size_t i);
CTL_PROTO(version)
CTL_PROTO(epoch)
+CTL_PROTO(background_thread)
+CTL_PROTO(max_background_threads)
CTL_PROTO(thread_tcache_enabled)
CTL_PROTO(thread_tcache_flush)
CTL_PROTO(thread_prof_name)
@@ -77,29 +71,33 @@ CTL_PROTO(config_cache_oblivious)
CTL_PROTO(config_debug)
CTL_PROTO(config_fill)
CTL_PROTO(config_lazy_lock)
-CTL_PROTO(config_munmap)
+CTL_PROTO(config_malloc_conf)
CTL_PROTO(config_prof)
CTL_PROTO(config_prof_libgcc)
CTL_PROTO(config_prof_libunwind)
CTL_PROTO(config_stats)
-CTL_PROTO(config_tcache)
-CTL_PROTO(config_tls)
CTL_PROTO(config_utrace)
-CTL_PROTO(config_valgrind)
CTL_PROTO(config_xmalloc)
CTL_PROTO(opt_abort)
+CTL_PROTO(opt_abort_conf)
+CTL_PROTO(opt_metadata_thp)
+CTL_PROTO(opt_retain)
CTL_PROTO(opt_dss)
-CTL_PROTO(opt_lg_chunk)
CTL_PROTO(opt_narenas)
-CTL_PROTO(opt_lg_dirty_mult)
+CTL_PROTO(opt_percpu_arena)
+CTL_PROTO(opt_background_thread)
+CTL_PROTO(opt_max_background_threads)
+CTL_PROTO(opt_dirty_decay_ms)
+CTL_PROTO(opt_muzzy_decay_ms)
CTL_PROTO(opt_stats_print)
+CTL_PROTO(opt_stats_print_opts)
CTL_PROTO(opt_junk)
CTL_PROTO(opt_zero)
-CTL_PROTO(opt_quarantine)
-CTL_PROTO(opt_redzone)
CTL_PROTO(opt_utrace)
CTL_PROTO(opt_xmalloc)
CTL_PROTO(opt_tcache)
+CTL_PROTO(opt_thp)
+CTL_PROTO(opt_lg_extent_max_active_fit)
CTL_PROTO(opt_lg_tcache_max)
CTL_PROTO(opt_prof)
CTL_PROTO(opt_prof_prefix)
@@ -114,31 +112,34 @@ CTL_PROTO(opt_prof_accum)
CTL_PROTO(tcache_create)
CTL_PROTO(tcache_flush)
CTL_PROTO(tcache_destroy)
+CTL_PROTO(arena_i_initialized)
+CTL_PROTO(arena_i_decay)
CTL_PROTO(arena_i_purge)
-static void arena_purge(unsigned arena_ind);
+CTL_PROTO(arena_i_reset)
+CTL_PROTO(arena_i_destroy)
CTL_PROTO(arena_i_dss)
-CTL_PROTO(arena_i_lg_dirty_mult)
-CTL_PROTO(arena_i_chunk_hooks)
+CTL_PROTO(arena_i_dirty_decay_ms)
+CTL_PROTO(arena_i_muzzy_decay_ms)
+CTL_PROTO(arena_i_extent_hooks)
+CTL_PROTO(arena_i_retain_grow_limit)
INDEX_PROTO(arena_i)
CTL_PROTO(arenas_bin_i_size)
CTL_PROTO(arenas_bin_i_nregs)
-CTL_PROTO(arenas_bin_i_run_size)
+CTL_PROTO(arenas_bin_i_slab_size)
INDEX_PROTO(arenas_bin_i)
-CTL_PROTO(arenas_lrun_i_size)
-INDEX_PROTO(arenas_lrun_i)
-CTL_PROTO(arenas_hchunk_i_size)
-INDEX_PROTO(arenas_hchunk_i)
+CTL_PROTO(arenas_lextent_i_size)
+INDEX_PROTO(arenas_lextent_i)
CTL_PROTO(arenas_narenas)
-CTL_PROTO(arenas_initialized)
-CTL_PROTO(arenas_lg_dirty_mult)
+CTL_PROTO(arenas_dirty_decay_ms)
+CTL_PROTO(arenas_muzzy_decay_ms)
CTL_PROTO(arenas_quantum)
CTL_PROTO(arenas_page)
CTL_PROTO(arenas_tcache_max)
CTL_PROTO(arenas_nbins)
CTL_PROTO(arenas_nhbins)
-CTL_PROTO(arenas_nlruns)
-CTL_PROTO(arenas_nhchunks)
-CTL_PROTO(arenas_extend)
+CTL_PROTO(arenas_nlextents)
+CTL_PROTO(arenas_create)
+CTL_PROTO(arenas_lookup)
CTL_PROTO(prof_thread_active_init)
CTL_PROTO(prof_active)
CTL_PROTO(prof_dump)
@@ -154,67 +155,94 @@ CTL_PROTO(stats_arenas_i_large_allocated)
CTL_PROTO(stats_arenas_i_large_nmalloc)
CTL_PROTO(stats_arenas_i_large_ndalloc)
CTL_PROTO(stats_arenas_i_large_nrequests)
-CTL_PROTO(stats_arenas_i_huge_allocated)
-CTL_PROTO(stats_arenas_i_huge_nmalloc)
-CTL_PROTO(stats_arenas_i_huge_ndalloc)
-CTL_PROTO(stats_arenas_i_huge_nrequests)
CTL_PROTO(stats_arenas_i_bins_j_nmalloc)
CTL_PROTO(stats_arenas_i_bins_j_ndalloc)
CTL_PROTO(stats_arenas_i_bins_j_nrequests)
CTL_PROTO(stats_arenas_i_bins_j_curregs)
CTL_PROTO(stats_arenas_i_bins_j_nfills)
CTL_PROTO(stats_arenas_i_bins_j_nflushes)
-CTL_PROTO(stats_arenas_i_bins_j_nruns)
-CTL_PROTO(stats_arenas_i_bins_j_nreruns)
-CTL_PROTO(stats_arenas_i_bins_j_curruns)
+CTL_PROTO(stats_arenas_i_bins_j_nslabs)
+CTL_PROTO(stats_arenas_i_bins_j_nreslabs)
+CTL_PROTO(stats_arenas_i_bins_j_curslabs)
INDEX_PROTO(stats_arenas_i_bins_j)
-CTL_PROTO(stats_arenas_i_lruns_j_nmalloc)
-CTL_PROTO(stats_arenas_i_lruns_j_ndalloc)
-CTL_PROTO(stats_arenas_i_lruns_j_nrequests)
-CTL_PROTO(stats_arenas_i_lruns_j_curruns)
-INDEX_PROTO(stats_arenas_i_lruns_j)
-CTL_PROTO(stats_arenas_i_hchunks_j_nmalloc)
-CTL_PROTO(stats_arenas_i_hchunks_j_ndalloc)
-CTL_PROTO(stats_arenas_i_hchunks_j_nrequests)
-CTL_PROTO(stats_arenas_i_hchunks_j_curhchunks)
-INDEX_PROTO(stats_arenas_i_hchunks_j)
+CTL_PROTO(stats_arenas_i_lextents_j_nmalloc)
+CTL_PROTO(stats_arenas_i_lextents_j_ndalloc)
+CTL_PROTO(stats_arenas_i_lextents_j_nrequests)
+CTL_PROTO(stats_arenas_i_lextents_j_curlextents)
+INDEX_PROTO(stats_arenas_i_lextents_j)
CTL_PROTO(stats_arenas_i_nthreads)
+CTL_PROTO(stats_arenas_i_uptime)
CTL_PROTO(stats_arenas_i_dss)
-CTL_PROTO(stats_arenas_i_lg_dirty_mult)
+CTL_PROTO(stats_arenas_i_dirty_decay_ms)
+CTL_PROTO(stats_arenas_i_muzzy_decay_ms)
CTL_PROTO(stats_arenas_i_pactive)
CTL_PROTO(stats_arenas_i_pdirty)
+CTL_PROTO(stats_arenas_i_pmuzzy)
CTL_PROTO(stats_arenas_i_mapped)
-CTL_PROTO(stats_arenas_i_npurge)
-CTL_PROTO(stats_arenas_i_nmadvise)
-CTL_PROTO(stats_arenas_i_purged)
-CTL_PROTO(stats_arenas_i_metadata_mapped)
-CTL_PROTO(stats_arenas_i_metadata_allocated)
+CTL_PROTO(stats_arenas_i_retained)
+CTL_PROTO(stats_arenas_i_dirty_npurge)
+CTL_PROTO(stats_arenas_i_dirty_nmadvise)
+CTL_PROTO(stats_arenas_i_dirty_purged)
+CTL_PROTO(stats_arenas_i_muzzy_npurge)
+CTL_PROTO(stats_arenas_i_muzzy_nmadvise)
+CTL_PROTO(stats_arenas_i_muzzy_purged)
+CTL_PROTO(stats_arenas_i_base)
+CTL_PROTO(stats_arenas_i_internal)
+CTL_PROTO(stats_arenas_i_metadata_thp)
+CTL_PROTO(stats_arenas_i_tcache_bytes)
+CTL_PROTO(stats_arenas_i_resident)
INDEX_PROTO(stats_arenas_i)
-CTL_PROTO(stats_cactive)
CTL_PROTO(stats_allocated)
CTL_PROTO(stats_active)
+CTL_PROTO(stats_background_thread_num_threads)
+CTL_PROTO(stats_background_thread_num_runs)
+CTL_PROTO(stats_background_thread_run_interval)
CTL_PROTO(stats_metadata)
+CTL_PROTO(stats_metadata_thp)
CTL_PROTO(stats_resident)
CTL_PROTO(stats_mapped)
+CTL_PROTO(stats_retained)
+
+#define MUTEX_STATS_CTL_PROTO_GEN(n) \
+CTL_PROTO(stats_##n##_num_ops) \
+CTL_PROTO(stats_##n##_num_wait) \
+CTL_PROTO(stats_##n##_num_spin_acq) \
+CTL_PROTO(stats_##n##_num_owner_switch) \
+CTL_PROTO(stats_##n##_total_wait_time) \
+CTL_PROTO(stats_##n##_max_wait_time) \
+CTL_PROTO(stats_##n##_max_num_thds)
+
+/* Global mutexes. */
+#define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(mutexes_##mtx)
+MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+
+/* Per arena mutexes. */
+#define OP(mtx) MUTEX_STATS_CTL_PROTO_GEN(arenas_i_mutexes_##mtx)
+MUTEX_PROF_ARENA_MUTEXES
+#undef OP
+
+/* Arena bin mutexes. */
+MUTEX_STATS_CTL_PROTO_GEN(arenas_i_bins_j_mutex)
+#undef MUTEX_STATS_CTL_PROTO_GEN
+
+CTL_PROTO(stats_mutexes_reset)
/******************************************************************************/
/* mallctl tree. */
-/* Maximum tree depth. */
-#define CTL_MAX_DEPTH 6
-
-#define NAME(n) {true}, n
-#define CHILD(t, c) \
+#define NAME(n) {true}, n
+#define CHILD(t, c) \
sizeof(c##_node) / sizeof(ctl_##t##_node_t), \
(ctl_node_t *)c##_node, \
NULL
-#define CTL(c) 0, NULL, c##_ctl
+#define CTL(c) 0, NULL, c##_ctl
/*
* Only handles internal indexed nodes, since there are currently no external
* ones.
*/
-#define INDEX(i) {false}, i##_index
+#define INDEX(i) {false}, i##_index
static const ctl_named_node_t thread_tcache_node[] = {
{NAME("enabled"), CTL(thread_tcache_enabled)},
@@ -241,32 +269,36 @@ static const ctl_named_node_t config_node[] = {
{NAME("debug"), CTL(config_debug)},
{NAME("fill"), CTL(config_fill)},
{NAME("lazy_lock"), CTL(config_lazy_lock)},
- {NAME("munmap"), CTL(config_munmap)},
+ {NAME("malloc_conf"), CTL(config_malloc_conf)},
{NAME("prof"), CTL(config_prof)},
{NAME("prof_libgcc"), CTL(config_prof_libgcc)},
{NAME("prof_libunwind"), CTL(config_prof_libunwind)},
{NAME("stats"), CTL(config_stats)},
- {NAME("tcache"), CTL(config_tcache)},
- {NAME("tls"), CTL(config_tls)},
{NAME("utrace"), CTL(config_utrace)},
- {NAME("valgrind"), CTL(config_valgrind)},
{NAME("xmalloc"), CTL(config_xmalloc)}
};
static const ctl_named_node_t opt_node[] = {
{NAME("abort"), CTL(opt_abort)},
+ {NAME("abort_conf"), CTL(opt_abort_conf)},
+ {NAME("metadata_thp"), CTL(opt_metadata_thp)},
+ {NAME("retain"), CTL(opt_retain)},
{NAME("dss"), CTL(opt_dss)},
- {NAME("lg_chunk"), CTL(opt_lg_chunk)},
{NAME("narenas"), CTL(opt_narenas)},
- {NAME("lg_dirty_mult"), CTL(opt_lg_dirty_mult)},
+ {NAME("percpu_arena"), CTL(opt_percpu_arena)},
+ {NAME("background_thread"), CTL(opt_background_thread)},
+ {NAME("max_background_threads"), CTL(opt_max_background_threads)},
+ {NAME("dirty_decay_ms"), CTL(opt_dirty_decay_ms)},
+ {NAME("muzzy_decay_ms"), CTL(opt_muzzy_decay_ms)},
{NAME("stats_print"), CTL(opt_stats_print)},
+ {NAME("stats_print_opts"), CTL(opt_stats_print_opts)},
{NAME("junk"), CTL(opt_junk)},
{NAME("zero"), CTL(opt_zero)},
- {NAME("quarantine"), CTL(opt_quarantine)},
- {NAME("redzone"), CTL(opt_redzone)},
{NAME("utrace"), CTL(opt_utrace)},
{NAME("xmalloc"), CTL(opt_xmalloc)},
{NAME("tcache"), CTL(opt_tcache)},
+ {NAME("thp"), CTL(opt_thp)},
+ {NAME("lg_extent_max_active_fit"), CTL(opt_lg_extent_max_active_fit)},
{NAME("lg_tcache_max"), CTL(opt_lg_tcache_max)},
{NAME("prof"), CTL(opt_prof)},
{NAME("prof_prefix"), CTL(opt_prof_prefix)},
@@ -287,10 +319,16 @@ static const ctl_named_node_t tcache_node[] = {
};
static const ctl_named_node_t arena_i_node[] = {
+ {NAME("initialized"), CTL(arena_i_initialized)},
+ {NAME("decay"), CTL(arena_i_decay)},
{NAME("purge"), CTL(arena_i_purge)},
+ {NAME("reset"), CTL(arena_i_reset)},
+ {NAME("destroy"), CTL(arena_i_destroy)},
{NAME("dss"), CTL(arena_i_dss)},
- {NAME("lg_dirty_mult"), CTL(arena_i_lg_dirty_mult)},
- {NAME("chunk_hooks"), CTL(arena_i_chunk_hooks)}
+ {NAME("dirty_decay_ms"), CTL(arena_i_dirty_decay_ms)},
+ {NAME("muzzy_decay_ms"), CTL(arena_i_muzzy_decay_ms)},
+ {NAME("extent_hooks"), CTL(arena_i_extent_hooks)},
+ {NAME("retain_grow_limit"), CTL(arena_i_retain_grow_limit)}
};
static const ctl_named_node_t super_arena_i_node[] = {
{NAME(""), CHILD(named, arena_i)}
@@ -303,7 +341,7 @@ static const ctl_indexed_node_t arena_node[] = {
static const ctl_named_node_t arenas_bin_i_node[] = {
{NAME("size"), CTL(arenas_bin_i_size)},
{NAME("nregs"), CTL(arenas_bin_i_nregs)},
- {NAME("run_size"), CTL(arenas_bin_i_run_size)}
+ {NAME("slab_size"), CTL(arenas_bin_i_slab_size)}
};
static const ctl_named_node_t super_arenas_bin_i_node[] = {
{NAME(""), CHILD(named, arenas_bin_i)}
@@ -313,43 +351,31 @@ static const ctl_indexed_node_t arenas_bin_node[] = {
{INDEX(arenas_bin_i)}
};
-static const ctl_named_node_t arenas_lrun_i_node[] = {
- {NAME("size"), CTL(arenas_lrun_i_size)}
+static const ctl_named_node_t arenas_lextent_i_node[] = {
+ {NAME("size"), CTL(arenas_lextent_i_size)}
};
-static const ctl_named_node_t super_arenas_lrun_i_node[] = {
- {NAME(""), CHILD(named, arenas_lrun_i)}
+static const ctl_named_node_t super_arenas_lextent_i_node[] = {
+ {NAME(""), CHILD(named, arenas_lextent_i)}
};
-static const ctl_indexed_node_t arenas_lrun_node[] = {
- {INDEX(arenas_lrun_i)}
-};
-
-static const ctl_named_node_t arenas_hchunk_i_node[] = {
- {NAME("size"), CTL(arenas_hchunk_i_size)}
-};
-static const ctl_named_node_t super_arenas_hchunk_i_node[] = {
- {NAME(""), CHILD(named, arenas_hchunk_i)}
-};
-
-static const ctl_indexed_node_t arenas_hchunk_node[] = {
- {INDEX(arenas_hchunk_i)}
+static const ctl_indexed_node_t arenas_lextent_node[] = {
+ {INDEX(arenas_lextent_i)}
};
static const ctl_named_node_t arenas_node[] = {
{NAME("narenas"), CTL(arenas_narenas)},
- {NAME("initialized"), CTL(arenas_initialized)},
- {NAME("lg_dirty_mult"), CTL(arenas_lg_dirty_mult)},
+ {NAME("dirty_decay_ms"), CTL(arenas_dirty_decay_ms)},
+ {NAME("muzzy_decay_ms"), CTL(arenas_muzzy_decay_ms)},
{NAME("quantum"), CTL(arenas_quantum)},
{NAME("page"), CTL(arenas_page)},
{NAME("tcache_max"), CTL(arenas_tcache_max)},
{NAME("nbins"), CTL(arenas_nbins)},
{NAME("nhbins"), CTL(arenas_nhbins)},
{NAME("bin"), CHILD(indexed, arenas_bin)},
- {NAME("nlruns"), CTL(arenas_nlruns)},
- {NAME("lrun"), CHILD(indexed, arenas_lrun)},
- {NAME("nhchunks"), CTL(arenas_nhchunks)},
- {NAME("hchunk"), CHILD(indexed, arenas_hchunk)},
- {NAME("extend"), CTL(arenas_extend)}
+ {NAME("nlextents"), CTL(arenas_nlextents)},
+ {NAME("lextent"), CHILD(indexed, arenas_lextent)},
+ {NAME("create"), CTL(arenas_create)},
+ {NAME("lookup"), CTL(arenas_lookup)}
};
static const ctl_named_node_t prof_node[] = {
@@ -362,11 +388,6 @@ static const ctl_named_node_t prof_node[] = {
{NAME("lg_sample"), CTL(lg_prof_sample)}
};
-static const ctl_named_node_t stats_arenas_i_metadata_node[] = {
- {NAME("mapped"), CTL(stats_arenas_i_metadata_mapped)},
- {NAME("allocated"), CTL(stats_arenas_i_metadata_allocated)}
-};
-
static const ctl_named_node_t stats_arenas_i_small_node[] = {
{NAME("allocated"), CTL(stats_arenas_i_small_allocated)},
{NAME("nmalloc"), CTL(stats_arenas_i_small_nmalloc)},
@@ -381,13 +402,27 @@ static const ctl_named_node_t stats_arenas_i_large_node[] = {
{NAME("nrequests"), CTL(stats_arenas_i_large_nrequests)}
};
-static const ctl_named_node_t stats_arenas_i_huge_node[] = {
- {NAME("allocated"), CTL(stats_arenas_i_huge_allocated)},
- {NAME("nmalloc"), CTL(stats_arenas_i_huge_nmalloc)},
- {NAME("ndalloc"), CTL(stats_arenas_i_huge_ndalloc)},
- {NAME("nrequests"), CTL(stats_arenas_i_huge_nrequests)}
+#define MUTEX_PROF_DATA_NODE(prefix) \
+static const ctl_named_node_t stats_##prefix##_node[] = { \
+ {NAME("num_ops"), \
+ CTL(stats_##prefix##_num_ops)}, \
+ {NAME("num_wait"), \
+ CTL(stats_##prefix##_num_wait)}, \
+ {NAME("num_spin_acq"), \
+ CTL(stats_##prefix##_num_spin_acq)}, \
+ {NAME("num_owner_switch"), \
+ CTL(stats_##prefix##_num_owner_switch)}, \
+ {NAME("total_wait_time"), \
+ CTL(stats_##prefix##_total_wait_time)}, \
+ {NAME("max_wait_time"), \
+ CTL(stats_##prefix##_max_wait_time)}, \
+ {NAME("max_num_thds"), \
+ CTL(stats_##prefix##_max_num_thds)} \
+ /* Note that # of current waiting thread not provided. */ \
};
+MUTEX_PROF_DATA_NODE(arenas_i_bins_j_mutex)
+
static const ctl_named_node_t stats_arenas_i_bins_j_node[] = {
{NAME("nmalloc"), CTL(stats_arenas_i_bins_j_nmalloc)},
{NAME("ndalloc"), CTL(stats_arenas_i_bins_j_ndalloc)},
@@ -395,10 +430,12 @@ static const ctl_named_node_t stats_arenas_i_bins_j_node[] = {
{NAME("curregs"), CTL(stats_arenas_i_bins_j_curregs)},
{NAME("nfills"), CTL(stats_arenas_i_bins_j_nfills)},
{NAME("nflushes"), CTL(stats_arenas_i_bins_j_nflushes)},
- {NAME("nruns"), CTL(stats_arenas_i_bins_j_nruns)},
- {NAME("nreruns"), CTL(stats_arenas_i_bins_j_nreruns)},
- {NAME("curruns"), CTL(stats_arenas_i_bins_j_curruns)}
+ {NAME("nslabs"), CTL(stats_arenas_i_bins_j_nslabs)},
+ {NAME("nreslabs"), CTL(stats_arenas_i_bins_j_nreslabs)},
+ {NAME("curslabs"), CTL(stats_arenas_i_bins_j_curslabs)},
+ {NAME("mutex"), CHILD(named, stats_arenas_i_bins_j_mutex)}
};
+
static const ctl_named_node_t super_stats_arenas_i_bins_j_node[] = {
{NAME(""), CHILD(named, stats_arenas_i_bins_j)}
};
@@ -407,51 +444,57 @@ static const ctl_indexed_node_t stats_arenas_i_bins_node[] = {
{INDEX(stats_arenas_i_bins_j)}
};
-static const ctl_named_node_t stats_arenas_i_lruns_j_node[] = {
- {NAME("nmalloc"), CTL(stats_arenas_i_lruns_j_nmalloc)},
- {NAME("ndalloc"), CTL(stats_arenas_i_lruns_j_ndalloc)},
- {NAME("nrequests"), CTL(stats_arenas_i_lruns_j_nrequests)},
- {NAME("curruns"), CTL(stats_arenas_i_lruns_j_curruns)}
+static const ctl_named_node_t stats_arenas_i_lextents_j_node[] = {
+ {NAME("nmalloc"), CTL(stats_arenas_i_lextents_j_nmalloc)},
+ {NAME("ndalloc"), CTL(stats_arenas_i_lextents_j_ndalloc)},
+ {NAME("nrequests"), CTL(stats_arenas_i_lextents_j_nrequests)},
+ {NAME("curlextents"), CTL(stats_arenas_i_lextents_j_curlextents)}
};
-static const ctl_named_node_t super_stats_arenas_i_lruns_j_node[] = {
- {NAME(""), CHILD(named, stats_arenas_i_lruns_j)}
+static const ctl_named_node_t super_stats_arenas_i_lextents_j_node[] = {
+ {NAME(""), CHILD(named, stats_arenas_i_lextents_j)}
};
-static const ctl_indexed_node_t stats_arenas_i_lruns_node[] = {
- {INDEX(stats_arenas_i_lruns_j)}
+static const ctl_indexed_node_t stats_arenas_i_lextents_node[] = {
+ {INDEX(stats_arenas_i_lextents_j)}
};
-static const ctl_named_node_t stats_arenas_i_hchunks_j_node[] = {
- {NAME("nmalloc"), CTL(stats_arenas_i_hchunks_j_nmalloc)},
- {NAME("ndalloc"), CTL(stats_arenas_i_hchunks_j_ndalloc)},
- {NAME("nrequests"), CTL(stats_arenas_i_hchunks_j_nrequests)},
- {NAME("curhchunks"), CTL(stats_arenas_i_hchunks_j_curhchunks)}
-};
-static const ctl_named_node_t super_stats_arenas_i_hchunks_j_node[] = {
- {NAME(""), CHILD(named, stats_arenas_i_hchunks_j)}
-};
+#define OP(mtx) MUTEX_PROF_DATA_NODE(arenas_i_mutexes_##mtx)
+MUTEX_PROF_ARENA_MUTEXES
+#undef OP
-static const ctl_indexed_node_t stats_arenas_i_hchunks_node[] = {
- {INDEX(stats_arenas_i_hchunks_j)}
+static const ctl_named_node_t stats_arenas_i_mutexes_node[] = {
+#define OP(mtx) {NAME(#mtx), CHILD(named, stats_arenas_i_mutexes_##mtx)},
+MUTEX_PROF_ARENA_MUTEXES
+#undef OP
};
static const ctl_named_node_t stats_arenas_i_node[] = {
{NAME("nthreads"), CTL(stats_arenas_i_nthreads)},
+ {NAME("uptime"), CTL(stats_arenas_i_uptime)},
{NAME("dss"), CTL(stats_arenas_i_dss)},
- {NAME("lg_dirty_mult"), CTL(stats_arenas_i_lg_dirty_mult)},
+ {NAME("dirty_decay_ms"), CTL(stats_arenas_i_dirty_decay_ms)},
+ {NAME("muzzy_decay_ms"), CTL(stats_arenas_i_muzzy_decay_ms)},
{NAME("pactive"), CTL(stats_arenas_i_pactive)},
{NAME("pdirty"), CTL(stats_arenas_i_pdirty)},
+ {NAME("pmuzzy"), CTL(stats_arenas_i_pmuzzy)},
{NAME("mapped"), CTL(stats_arenas_i_mapped)},
- {NAME("npurge"), CTL(stats_arenas_i_npurge)},
- {NAME("nmadvise"), CTL(stats_arenas_i_nmadvise)},
- {NAME("purged"), CTL(stats_arenas_i_purged)},
- {NAME("metadata"), CHILD(named, stats_arenas_i_metadata)},
+ {NAME("retained"), CTL(stats_arenas_i_retained)},
+ {NAME("dirty_npurge"), CTL(stats_arenas_i_dirty_npurge)},
+ {NAME("dirty_nmadvise"), CTL(stats_arenas_i_dirty_nmadvise)},
+ {NAME("dirty_purged"), CTL(stats_arenas_i_dirty_purged)},
+ {NAME("muzzy_npurge"), CTL(stats_arenas_i_muzzy_npurge)},
+ {NAME("muzzy_nmadvise"), CTL(stats_arenas_i_muzzy_nmadvise)},
+ {NAME("muzzy_purged"), CTL(stats_arenas_i_muzzy_purged)},
+ {NAME("base"), CTL(stats_arenas_i_base)},
+ {NAME("internal"), CTL(stats_arenas_i_internal)},
+ {NAME("metadata_thp"), CTL(stats_arenas_i_metadata_thp)},
+ {NAME("tcache_bytes"), CTL(stats_arenas_i_tcache_bytes)},
+ {NAME("resident"), CTL(stats_arenas_i_resident)},
{NAME("small"), CHILD(named, stats_arenas_i_small)},
{NAME("large"), CHILD(named, stats_arenas_i_large)},
- {NAME("huge"), CHILD(named, stats_arenas_i_huge)},
{NAME("bins"), CHILD(indexed, stats_arenas_i_bins)},
- {NAME("lruns"), CHILD(indexed, stats_arenas_i_lruns)},
- {NAME("hchunks"), CHILD(indexed, stats_arenas_i_hchunks)}
+ {NAME("lextents"), CHILD(indexed, stats_arenas_i_lextents)},
+ {NAME("mutexes"), CHILD(named, stats_arenas_i_mutexes)}
};
static const ctl_named_node_t super_stats_arenas_i_node[] = {
{NAME(""), CHILD(named, stats_arenas_i)}
@@ -461,19 +504,43 @@ static const ctl_indexed_node_t stats_arenas_node[] = {
{INDEX(stats_arenas_i)}
};
+static const ctl_named_node_t stats_background_thread_node[] = {
+ {NAME("num_threads"), CTL(stats_background_thread_num_threads)},
+ {NAME("num_runs"), CTL(stats_background_thread_num_runs)},
+ {NAME("run_interval"), CTL(stats_background_thread_run_interval)}
+};
+
+#define OP(mtx) MUTEX_PROF_DATA_NODE(mutexes_##mtx)
+MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+
+static const ctl_named_node_t stats_mutexes_node[] = {
+#define OP(mtx) {NAME(#mtx), CHILD(named, stats_mutexes_##mtx)},
+MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+ {NAME("reset"), CTL(stats_mutexes_reset)}
+};
+#undef MUTEX_PROF_DATA_NODE
+
static const ctl_named_node_t stats_node[] = {
- {NAME("cactive"), CTL(stats_cactive)},
{NAME("allocated"), CTL(stats_allocated)},
{NAME("active"), CTL(stats_active)},
{NAME("metadata"), CTL(stats_metadata)},
+ {NAME("metadata_thp"), CTL(stats_metadata_thp)},
{NAME("resident"), CTL(stats_resident)},
{NAME("mapped"), CTL(stats_mapped)},
+ {NAME("retained"), CTL(stats_retained)},
+ {NAME("background_thread"),
+ CHILD(named, stats_background_thread)},
+ {NAME("mutexes"), CHILD(named, stats_mutexes)},
{NAME("arenas"), CHILD(indexed, stats_arenas)}
};
static const ctl_named_node_t root_node[] = {
{NAME("version"), CTL(version)},
{NAME("epoch"), CTL(epoch)},
+ {NAME("background_thread"), CTL(background_thread)},
+ {NAME("max_background_threads"), CTL(max_background_threads)},
{NAME("thread"), CHILD(named, thread)},
{NAME("config"), CHILD(named, config)},
{NAME("opt"), CHILD(named, opt)},
@@ -494,312 +561,519 @@ static const ctl_named_node_t super_root_node[] = {
/******************************************************************************/
-static bool
-ctl_arena_init(ctl_arena_stats_t *astats)
-{
+/*
+ * Sets *dst + *src non-atomically. This is safe, since everything is
+ * synchronized by the ctl mutex.
+ */
+static void
+ctl_accum_arena_stats_u64(arena_stats_u64_t *dst, arena_stats_u64_t *src) {
+#ifdef JEMALLOC_ATOMIC_U64
+ uint64_t cur_dst = atomic_load_u64(dst, ATOMIC_RELAXED);
+ uint64_t cur_src = atomic_load_u64(src, ATOMIC_RELAXED);
+ atomic_store_u64(dst, cur_dst + cur_src, ATOMIC_RELAXED);
+#else
+ *dst += *src;
+#endif
+}
+
+/* Likewise: with ctl mutex synchronization, reading is simple. */
+static uint64_t
+ctl_arena_stats_read_u64(arena_stats_u64_t *p) {
+#ifdef JEMALLOC_ATOMIC_U64
+ return atomic_load_u64(p, ATOMIC_RELAXED);
+#else
+ return *p;
+#endif
+}
+
+static void
+accum_atomic_zu(atomic_zu_t *dst, atomic_zu_t *src) {
+ size_t cur_dst = atomic_load_zu(dst, ATOMIC_RELAXED);
+ size_t cur_src = atomic_load_zu(src, ATOMIC_RELAXED);
+ atomic_store_zu(dst, cur_dst + cur_src, ATOMIC_RELAXED);
+}
+
+/******************************************************************************/
- if (astats->lstats == NULL) {
- astats->lstats = (malloc_large_stats_t *)a0malloc(nlclasses *
- sizeof(malloc_large_stats_t));
- if (astats->lstats == NULL)
- return (true);
+static unsigned
+arenas_i2a_impl(size_t i, bool compat, bool validate) {
+ unsigned a;
+
+ switch (i) {
+ case MALLCTL_ARENAS_ALL:
+ a = 0;
+ break;
+ case MALLCTL_ARENAS_DESTROYED:
+ a = 1;
+ break;
+ default:
+ if (compat && i == ctl_arenas->narenas) {
+ /*
+ * Provide deprecated backward compatibility for
+ * accessing the merged stats at index narenas rather
+ * than via MALLCTL_ARENAS_ALL. This is scheduled for
+ * removal in 6.0.0.
+ */
+ a = 0;
+ } else if (validate && i >= ctl_arenas->narenas) {
+ a = UINT_MAX;
+ } else {
+ /*
+ * This function should never be called for an index
+ * more than one past the range of indices that have
+ * initialized ctl data.
+ */
+ assert(i < ctl_arenas->narenas || (!validate && i ==
+ ctl_arenas->narenas));
+ a = (unsigned)i + 2;
+ }
+ break;
}
- if (astats->hstats == NULL) {
- astats->hstats = (malloc_huge_stats_t *)a0malloc(nhclasses *
- sizeof(malloc_huge_stats_t));
- if (astats->hstats == NULL)
- return (true);
+ return a;
+}
+
+static unsigned
+arenas_i2a(size_t i) {
+ return arenas_i2a_impl(i, true, false);
+}
+
+static ctl_arena_t *
+arenas_i_impl(tsd_t *tsd, size_t i, bool compat, bool init) {
+ ctl_arena_t *ret;
+
+ assert(!compat || !init);
+
+ ret = ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)];
+ if (init && ret == NULL) {
+ if (config_stats) {
+ struct container_s {
+ ctl_arena_t ctl_arena;
+ ctl_arena_stats_t astats;
+ };
+ struct container_s *cont =
+ (struct container_s *)base_alloc(tsd_tsdn(tsd),
+ b0get(), sizeof(struct container_s), QUANTUM);
+ if (cont == NULL) {
+ return NULL;
+ }
+ ret = &cont->ctl_arena;
+ ret->astats = &cont->astats;
+ } else {
+ ret = (ctl_arena_t *)base_alloc(tsd_tsdn(tsd), b0get(),
+ sizeof(ctl_arena_t), QUANTUM);
+ if (ret == NULL) {
+ return NULL;
+ }
+ }
+ ret->arena_ind = (unsigned)i;
+ ctl_arenas->arenas[arenas_i2a_impl(i, compat, false)] = ret;
}
- return (false);
+ assert(ret == NULL || arenas_i2a(ret->arena_ind) == arenas_i2a(i));
+ return ret;
}
-static void
-ctl_arena_clear(ctl_arena_stats_t *astats)
-{
+static ctl_arena_t *
+arenas_i(size_t i) {
+ ctl_arena_t *ret = arenas_i_impl(tsd_fetch(), i, true, false);
+ assert(ret != NULL);
+ return ret;
+}
- astats->dss = dss_prec_names[dss_prec_limit];
- astats->lg_dirty_mult = -1;
- astats->pactive = 0;
- astats->pdirty = 0;
+static void
+ctl_arena_clear(ctl_arena_t *ctl_arena) {
+ ctl_arena->nthreads = 0;
+ ctl_arena->dss = dss_prec_names[dss_prec_limit];
+ ctl_arena->dirty_decay_ms = -1;
+ ctl_arena->muzzy_decay_ms = -1;
+ ctl_arena->pactive = 0;
+ ctl_arena->pdirty = 0;
+ ctl_arena->pmuzzy = 0;
if (config_stats) {
- memset(&astats->astats, 0, sizeof(arena_stats_t));
- astats->allocated_small = 0;
- astats->nmalloc_small = 0;
- astats->ndalloc_small = 0;
- astats->nrequests_small = 0;
- memset(astats->bstats, 0, NBINS * sizeof(malloc_bin_stats_t));
- memset(astats->lstats, 0, nlclasses *
- sizeof(malloc_large_stats_t));
- memset(astats->hstats, 0, nhclasses *
- sizeof(malloc_huge_stats_t));
+ memset(&ctl_arena->astats->astats, 0, sizeof(arena_stats_t));
+ ctl_arena->astats->allocated_small = 0;
+ ctl_arena->astats->nmalloc_small = 0;
+ ctl_arena->astats->ndalloc_small = 0;
+ ctl_arena->astats->nrequests_small = 0;
+ memset(ctl_arena->astats->bstats, 0, NBINS *
+ sizeof(bin_stats_t));
+ memset(ctl_arena->astats->lstats, 0, (NSIZES - NBINS) *
+ sizeof(arena_stats_large_t));
}
}
static void
-ctl_arena_stats_amerge(ctl_arena_stats_t *cstats, arena_t *arena)
-{
+ctl_arena_stats_amerge(tsdn_t *tsdn, ctl_arena_t *ctl_arena, arena_t *arena) {
unsigned i;
- arena_stats_merge(arena, &cstats->dss, &cstats->lg_dirty_mult,
- &cstats->pactive, &cstats->pdirty, &cstats->astats, cstats->bstats,
- cstats->lstats, cstats->hstats);
-
- for (i = 0; i < NBINS; i++) {
- cstats->allocated_small += cstats->bstats[i].curregs *
- index2size(i);
- cstats->nmalloc_small += cstats->bstats[i].nmalloc;
- cstats->ndalloc_small += cstats->bstats[i].ndalloc;
- cstats->nrequests_small += cstats->bstats[i].nrequests;
+ if (config_stats) {
+ arena_stats_merge(tsdn, arena, &ctl_arena->nthreads,
+ &ctl_arena->dss, &ctl_arena->dirty_decay_ms,
+ &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive,
+ &ctl_arena->pdirty, &ctl_arena->pmuzzy,
+ &ctl_arena->astats->astats, ctl_arena->astats->bstats,
+ ctl_arena->astats->lstats);
+
+ for (i = 0; i < NBINS; i++) {
+ ctl_arena->astats->allocated_small +=
+ ctl_arena->astats->bstats[i].curregs *
+ sz_index2size(i);
+ ctl_arena->astats->nmalloc_small +=
+ ctl_arena->astats->bstats[i].nmalloc;
+ ctl_arena->astats->ndalloc_small +=
+ ctl_arena->astats->bstats[i].ndalloc;
+ ctl_arena->astats->nrequests_small +=
+ ctl_arena->astats->bstats[i].nrequests;
+ }
+ } else {
+ arena_basic_stats_merge(tsdn, arena, &ctl_arena->nthreads,
+ &ctl_arena->dss, &ctl_arena->dirty_decay_ms,
+ &ctl_arena->muzzy_decay_ms, &ctl_arena->pactive,
+ &ctl_arena->pdirty, &ctl_arena->pmuzzy);
}
}
static void
-ctl_arena_stats_smerge(ctl_arena_stats_t *sstats, ctl_arena_stats_t *astats)
-{
+ctl_arena_stats_sdmerge(ctl_arena_t *ctl_sdarena, ctl_arena_t *ctl_arena,
+ bool destroyed) {
unsigned i;
- sstats->pactive += astats->pactive;
- sstats->pdirty += astats->pdirty;
-
- sstats->astats.mapped += astats->astats.mapped;
- sstats->astats.npurge += astats->astats.npurge;
- sstats->astats.nmadvise += astats->astats.nmadvise;
- sstats->astats.purged += astats->astats.purged;
-
- sstats->astats.metadata_mapped += astats->astats.metadata_mapped;
- sstats->astats.metadata_allocated += astats->astats.metadata_allocated;
-
- sstats->allocated_small += astats->allocated_small;
- sstats->nmalloc_small += astats->nmalloc_small;
- sstats->ndalloc_small += astats->ndalloc_small;
- sstats->nrequests_small += astats->nrequests_small;
-
- sstats->astats.allocated_large += astats->astats.allocated_large;
- sstats->astats.nmalloc_large += astats->astats.nmalloc_large;
- sstats->astats.ndalloc_large += astats->astats.ndalloc_large;
- sstats->astats.nrequests_large += astats->astats.nrequests_large;
-
- sstats->astats.allocated_huge += astats->astats.allocated_huge;
- sstats->astats.nmalloc_huge += astats->astats.nmalloc_huge;
- sstats->astats.ndalloc_huge += astats->astats.ndalloc_huge;
-
- for (i = 0; i < NBINS; i++) {
- sstats->bstats[i].nmalloc += astats->bstats[i].nmalloc;
- sstats->bstats[i].ndalloc += astats->bstats[i].ndalloc;
- sstats->bstats[i].nrequests += astats->bstats[i].nrequests;
- sstats->bstats[i].curregs += astats->bstats[i].curregs;
- if (config_tcache) {
- sstats->bstats[i].nfills += astats->bstats[i].nfills;
- sstats->bstats[i].nflushes +=
- astats->bstats[i].nflushes;
- }
- sstats->bstats[i].nruns += astats->bstats[i].nruns;
- sstats->bstats[i].reruns += astats->bstats[i].reruns;
- sstats->bstats[i].curruns += astats->bstats[i].curruns;
+ if (!destroyed) {
+ ctl_sdarena->nthreads += ctl_arena->nthreads;
+ ctl_sdarena->pactive += ctl_arena->pactive;
+ ctl_sdarena->pdirty += ctl_arena->pdirty;
+ ctl_sdarena->pmuzzy += ctl_arena->pmuzzy;
+ } else {
+ assert(ctl_arena->nthreads == 0);
+ assert(ctl_arena->pactive == 0);
+ assert(ctl_arena->pdirty == 0);
+ assert(ctl_arena->pmuzzy == 0);
}
- for (i = 0; i < nlclasses; i++) {
- sstats->lstats[i].nmalloc += astats->lstats[i].nmalloc;
- sstats->lstats[i].ndalloc += astats->lstats[i].ndalloc;
- sstats->lstats[i].nrequests += astats->lstats[i].nrequests;
- sstats->lstats[i].curruns += astats->lstats[i].curruns;
- }
+ if (config_stats) {
+ ctl_arena_stats_t *sdstats = ctl_sdarena->astats;
+ ctl_arena_stats_t *astats = ctl_arena->astats;
+
+ if (!destroyed) {
+ accum_atomic_zu(&sdstats->astats.mapped,
+ &astats->astats.mapped);
+ accum_atomic_zu(&sdstats->astats.retained,
+ &astats->astats.retained);
+ }
+
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_dirty.npurge,
+ &astats->astats.decay_dirty.npurge);
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_dirty.nmadvise,
+ &astats->astats.decay_dirty.nmadvise);
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_dirty.purged,
+ &astats->astats.decay_dirty.purged);
+
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_muzzy.npurge,
+ &astats->astats.decay_muzzy.npurge);
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_muzzy.nmadvise,
+ &astats->astats.decay_muzzy.nmadvise);
+ ctl_accum_arena_stats_u64(&sdstats->astats.decay_muzzy.purged,
+ &astats->astats.decay_muzzy.purged);
+
+#define OP(mtx) malloc_mutex_prof_merge( \
+ &(sdstats->astats.mutex_prof_data[ \
+ arena_prof_mutex_##mtx]), \
+ &(astats->astats.mutex_prof_data[ \
+ arena_prof_mutex_##mtx]));
+MUTEX_PROF_ARENA_MUTEXES
+#undef OP
+ if (!destroyed) {
+ accum_atomic_zu(&sdstats->astats.base,
+ &astats->astats.base);
+ accum_atomic_zu(&sdstats->astats.internal,
+ &astats->astats.internal);
+ accum_atomic_zu(&sdstats->astats.resident,
+ &astats->astats.resident);
+ accum_atomic_zu(&sdstats->astats.metadata_thp,
+ &astats->astats.metadata_thp);
+ } else {
+ assert(atomic_load_zu(
+ &astats->astats.internal, ATOMIC_RELAXED) == 0);
+ }
+
+ if (!destroyed) {
+ sdstats->allocated_small += astats->allocated_small;
+ } else {
+ assert(astats->allocated_small == 0);
+ }
+ sdstats->nmalloc_small += astats->nmalloc_small;
+ sdstats->ndalloc_small += astats->ndalloc_small;
+ sdstats->nrequests_small += astats->nrequests_small;
- for (i = 0; i < nhclasses; i++) {
- sstats->hstats[i].nmalloc += astats->hstats[i].nmalloc;
- sstats->hstats[i].ndalloc += astats->hstats[i].ndalloc;
- sstats->hstats[i].curhchunks += astats->hstats[i].curhchunks;
+ if (!destroyed) {
+ accum_atomic_zu(&sdstats->astats.allocated_large,
+ &astats->astats.allocated_large);
+ } else {
+ assert(atomic_load_zu(&astats->astats.allocated_large,
+ ATOMIC_RELAXED) == 0);
+ }
+ ctl_accum_arena_stats_u64(&sdstats->astats.nmalloc_large,
+ &astats->astats.nmalloc_large);
+ ctl_accum_arena_stats_u64(&sdstats->astats.ndalloc_large,
+ &astats->astats.ndalloc_large);
+ ctl_accum_arena_stats_u64(&sdstats->astats.nrequests_large,
+ &astats->astats.nrequests_large);
+
+ accum_atomic_zu(&sdstats->astats.tcache_bytes,
+ &astats->astats.tcache_bytes);
+
+ if (ctl_arena->arena_ind == 0) {
+ sdstats->astats.uptime = astats->astats.uptime;
+ }
+
+ for (i = 0; i < NBINS; i++) {
+ sdstats->bstats[i].nmalloc += astats->bstats[i].nmalloc;
+ sdstats->bstats[i].ndalloc += astats->bstats[i].ndalloc;
+ sdstats->bstats[i].nrequests +=
+ astats->bstats[i].nrequests;
+ if (!destroyed) {
+ sdstats->bstats[i].curregs +=
+ astats->bstats[i].curregs;
+ } else {
+ assert(astats->bstats[i].curregs == 0);
+ }
+ sdstats->bstats[i].nfills += astats->bstats[i].nfills;
+ sdstats->bstats[i].nflushes +=
+ astats->bstats[i].nflushes;
+ sdstats->bstats[i].nslabs += astats->bstats[i].nslabs;
+ sdstats->bstats[i].reslabs += astats->bstats[i].reslabs;
+ if (!destroyed) {
+ sdstats->bstats[i].curslabs +=
+ astats->bstats[i].curslabs;
+ } else {
+ assert(astats->bstats[i].curslabs == 0);
+ }
+ malloc_mutex_prof_merge(&sdstats->bstats[i].mutex_data,
+ &astats->bstats[i].mutex_data);
+ }
+
+ for (i = 0; i < NSIZES - NBINS; i++) {
+ ctl_accum_arena_stats_u64(&sdstats->lstats[i].nmalloc,
+ &astats->lstats[i].nmalloc);
+ ctl_accum_arena_stats_u64(&sdstats->lstats[i].ndalloc,
+ &astats->lstats[i].ndalloc);
+ ctl_accum_arena_stats_u64(&sdstats->lstats[i].nrequests,
+ &astats->lstats[i].nrequests);
+ if (!destroyed) {
+ sdstats->lstats[i].curlextents +=
+ astats->lstats[i].curlextents;
+ } else {
+ assert(astats->lstats[i].curlextents == 0);
+ }
+ }
}
}
static void
-ctl_arena_refresh(arena_t *arena, unsigned i)
-{
- ctl_arena_stats_t *astats = &ctl_stats.arenas[i];
- ctl_arena_stats_t *sstats = &ctl_stats.arenas[ctl_stats.narenas];
+ctl_arena_refresh(tsdn_t *tsdn, arena_t *arena, ctl_arena_t *ctl_sdarena,
+ unsigned i, bool destroyed) {
+ ctl_arena_t *ctl_arena = arenas_i(i);
+
+ ctl_arena_clear(ctl_arena);
+ ctl_arena_stats_amerge(tsdn, ctl_arena, arena);
+ /* Merge into sum stats as well. */
+ ctl_arena_stats_sdmerge(ctl_sdarena, ctl_arena, destroyed);
+}
- ctl_arena_clear(astats);
+static unsigned
+ctl_arena_init(tsd_t *tsd, extent_hooks_t *extent_hooks) {
+ unsigned arena_ind;
+ ctl_arena_t *ctl_arena;
- sstats->nthreads += astats->nthreads;
- if (config_stats) {
- ctl_arena_stats_amerge(astats, arena);
- /* Merge into sum stats as well. */
- ctl_arena_stats_smerge(sstats, astats);
+ if ((ctl_arena = ql_last(&ctl_arenas->destroyed, destroyed_link)) !=
+ NULL) {
+ ql_remove(&ctl_arenas->destroyed, ctl_arena, destroyed_link);
+ arena_ind = ctl_arena->arena_ind;
} else {
- astats->pactive += arena->nactive;
- astats->pdirty += arena->ndirty;
- /* Merge into sum stats as well. */
- sstats->pactive += arena->nactive;
- sstats->pdirty += arena->ndirty;
+ arena_ind = ctl_arenas->narenas;
}
-}
-static bool
-ctl_grow(void)
-{
- ctl_arena_stats_t *astats;
+ /* Trigger stats allocation. */
+ if (arenas_i_impl(tsd, arena_ind, false, true) == NULL) {
+ return UINT_MAX;
+ }
/* Initialize new arena. */
- if (arena_init(ctl_stats.narenas) == NULL)
- return (true);
-
- /* Allocate extended arena stats. */
- astats = (ctl_arena_stats_t *)a0malloc((ctl_stats.narenas + 2) *
- sizeof(ctl_arena_stats_t));
- if (astats == NULL)
- return (true);
-
- /* Initialize the new astats element. */
- memcpy(astats, ctl_stats.arenas, (ctl_stats.narenas + 1) *
- sizeof(ctl_arena_stats_t));
- memset(&astats[ctl_stats.narenas + 1], 0, sizeof(ctl_arena_stats_t));
- if (ctl_arena_init(&astats[ctl_stats.narenas + 1])) {
- a0dalloc(astats);
- return (true);
- }
- /* Swap merged stats to their new location. */
- {
- ctl_arena_stats_t tstats;
- memcpy(&tstats, &astats[ctl_stats.narenas],
- sizeof(ctl_arena_stats_t));
- memcpy(&astats[ctl_stats.narenas],
- &astats[ctl_stats.narenas + 1], sizeof(ctl_arena_stats_t));
- memcpy(&astats[ctl_stats.narenas + 1], &tstats,
- sizeof(ctl_arena_stats_t));
+ if (arena_init(tsd_tsdn(tsd), arena_ind, extent_hooks) == NULL) {
+ return UINT_MAX;
}
- a0dalloc(ctl_stats.arenas);
- ctl_stats.arenas = astats;
- ctl_stats.narenas++;
- return (false);
+ if (arena_ind == ctl_arenas->narenas) {
+ ctl_arenas->narenas++;
+ }
+
+ return arena_ind;
+}
+
+static void
+ctl_background_thread_stats_read(tsdn_t *tsdn) {
+ background_thread_stats_t *stats = &ctl_stats->background_thread;
+ if (!have_background_thread ||
+ background_thread_stats_read(tsdn, stats)) {
+ memset(stats, 0, sizeof(background_thread_stats_t));
+ nstime_init(&stats->run_interval, 0);
+ }
}
static void
-ctl_refresh(void)
-{
- tsd_t *tsd;
+ctl_refresh(tsdn_t *tsdn) {
unsigned i;
- bool refreshed;
- VARIABLE_ARRAY(arena_t *, tarenas, ctl_stats.narenas);
+ ctl_arena_t *ctl_sarena = arenas_i(MALLCTL_ARENAS_ALL);
+ VARIABLE_ARRAY(arena_t *, tarenas, ctl_arenas->narenas);
/*
* Clear sum stats, since they will be merged into by
* ctl_arena_refresh().
*/
- ctl_stats.arenas[ctl_stats.narenas].nthreads = 0;
- ctl_arena_clear(&ctl_stats.arenas[ctl_stats.narenas]);
-
- tsd = tsd_fetch();
- for (i = 0, refreshed = false; i < ctl_stats.narenas; i++) {
- tarenas[i] = arena_get(tsd, i, false, false);
- if (tarenas[i] == NULL && !refreshed) {
- tarenas[i] = arena_get(tsd, i, false, true);
- refreshed = true;
- }
- }
+ ctl_arena_clear(ctl_sarena);
- for (i = 0; i < ctl_stats.narenas; i++) {
- if (tarenas[i] != NULL)
- ctl_stats.arenas[i].nthreads = arena_nbound(i);
- else
- ctl_stats.arenas[i].nthreads = 0;
+ for (i = 0; i < ctl_arenas->narenas; i++) {
+ tarenas[i] = arena_get(tsdn, i, false);
}
- for (i = 0; i < ctl_stats.narenas; i++) {
+ for (i = 0; i < ctl_arenas->narenas; i++) {
+ ctl_arena_t *ctl_arena = arenas_i(i);
bool initialized = (tarenas[i] != NULL);
- ctl_stats.arenas[i].initialized = initialized;
- if (initialized)
- ctl_arena_refresh(tarenas[i], i);
+ ctl_arena->initialized = initialized;
+ if (initialized) {
+ ctl_arena_refresh(tsdn, tarenas[i], ctl_sarena, i,
+ false);
+ }
}
if (config_stats) {
- size_t base_allocated, base_resident, base_mapped;
- base_stats_get(&base_allocated, &base_resident, &base_mapped);
- ctl_stats.allocated =
- ctl_stats.arenas[ctl_stats.narenas].allocated_small +
- ctl_stats.arenas[ctl_stats.narenas].astats.allocated_large +
- ctl_stats.arenas[ctl_stats.narenas].astats.allocated_huge;
- ctl_stats.active =
- (ctl_stats.arenas[ctl_stats.narenas].pactive << LG_PAGE);
- ctl_stats.metadata = base_allocated +
- ctl_stats.arenas[ctl_stats.narenas].astats.metadata_mapped +
- ctl_stats.arenas[ctl_stats.narenas].astats
- .metadata_allocated;
- ctl_stats.resident = base_resident +
- ctl_stats.arenas[ctl_stats.narenas].astats.metadata_mapped +
- ((ctl_stats.arenas[ctl_stats.narenas].pactive +
- ctl_stats.arenas[ctl_stats.narenas].pdirty) << LG_PAGE);
- ctl_stats.mapped = base_mapped +
- ctl_stats.arenas[ctl_stats.narenas].astats.mapped;
- }
-
- ctl_epoch++;
+ ctl_stats->allocated = ctl_sarena->astats->allocated_small +
+ atomic_load_zu(&ctl_sarena->astats->astats.allocated_large,
+ ATOMIC_RELAXED);
+ ctl_stats->active = (ctl_sarena->pactive << LG_PAGE);
+ ctl_stats->metadata = atomic_load_zu(
+ &ctl_sarena->astats->astats.base, ATOMIC_RELAXED) +
+ atomic_load_zu(&ctl_sarena->astats->astats.internal,
+ ATOMIC_RELAXED);
+ ctl_stats->metadata_thp = atomic_load_zu(
+ &ctl_sarena->astats->astats.metadata_thp, ATOMIC_RELAXED);
+ ctl_stats->resident = atomic_load_zu(
+ &ctl_sarena->astats->astats.resident, ATOMIC_RELAXED);
+ ctl_stats->mapped = atomic_load_zu(
+ &ctl_sarena->astats->astats.mapped, ATOMIC_RELAXED);
+ ctl_stats->retained = atomic_load_zu(
+ &ctl_sarena->astats->astats.retained, ATOMIC_RELAXED);
+
+ ctl_background_thread_stats_read(tsdn);
+
+#define READ_GLOBAL_MUTEX_PROF_DATA(i, mtx) \
+ malloc_mutex_lock(tsdn, &mtx); \
+ malloc_mutex_prof_read(tsdn, &ctl_stats->mutex_prof_data[i], &mtx); \
+ malloc_mutex_unlock(tsdn, &mtx);
+
+ if (config_prof && opt_prof) {
+ READ_GLOBAL_MUTEX_PROF_DATA(global_prof_mutex_prof,
+ bt2gctx_mtx);
+ }
+ if (have_background_thread) {
+ READ_GLOBAL_MUTEX_PROF_DATA(
+ global_prof_mutex_background_thread,
+ background_thread_lock);
+ } else {
+ memset(&ctl_stats->mutex_prof_data[
+ global_prof_mutex_background_thread], 0,
+ sizeof(mutex_prof_data_t));
+ }
+ /* We own ctl mutex already. */
+ malloc_mutex_prof_read(tsdn,
+ &ctl_stats->mutex_prof_data[global_prof_mutex_ctl],
+ &ctl_mtx);
+#undef READ_GLOBAL_MUTEX_PROF_DATA
+ }
+ ctl_arenas->epoch++;
}
static bool
-ctl_init(void)
-{
+ctl_init(tsd_t *tsd) {
bool ret;
+ tsdn_t *tsdn = tsd_tsdn(tsd);
- malloc_mutex_lock(&ctl_mtx);
+ malloc_mutex_lock(tsdn, &ctl_mtx);
if (!ctl_initialized) {
+ ctl_arena_t *ctl_sarena, *ctl_darena;
+ unsigned i;
+
+ /*
+ * Allocate demand-zeroed space for pointers to the full
+ * range of supported arena indices.
+ */
+ if (ctl_arenas == NULL) {
+ ctl_arenas = (ctl_arenas_t *)base_alloc(tsdn,
+ b0get(), sizeof(ctl_arenas_t), QUANTUM);
+ if (ctl_arenas == NULL) {
+ ret = true;
+ goto label_return;
+ }
+ }
+
+ if (config_stats && ctl_stats == NULL) {
+ ctl_stats = (ctl_stats_t *)base_alloc(tsdn, b0get(),
+ sizeof(ctl_stats_t), QUANTUM);
+ if (ctl_stats == NULL) {
+ ret = true;
+ goto label_return;
+ }
+ }
+
/*
- * Allocate space for one extra arena stats element, which
- * contains summed stats across all arenas.
+ * Allocate space for the current full range of arenas
+ * here rather than doing it lazily elsewhere, in order
+ * to limit when OOM-caused errors can occur.
*/
- ctl_stats.narenas = narenas_total_get();
- ctl_stats.arenas = (ctl_arena_stats_t *)a0malloc(
- (ctl_stats.narenas + 1) * sizeof(ctl_arena_stats_t));
- if (ctl_stats.arenas == NULL) {
+ if ((ctl_sarena = arenas_i_impl(tsd, MALLCTL_ARENAS_ALL, false,
+ true)) == NULL) {
ret = true;
goto label_return;
}
- memset(ctl_stats.arenas, 0, (ctl_stats.narenas + 1) *
- sizeof(ctl_arena_stats_t));
+ ctl_sarena->initialized = true;
+ if ((ctl_darena = arenas_i_impl(tsd, MALLCTL_ARENAS_DESTROYED,
+ false, true)) == NULL) {
+ ret = true;
+ goto label_return;
+ }
+ ctl_arena_clear(ctl_darena);
/*
- * Initialize all stats structures, regardless of whether they
- * ever get used. Lazy initialization would allow errors to
- * cause inconsistent state to be viewable by the application.
+ * Don't toggle ctl_darena to initialized until an arena is
+ * actually destroyed, so that arena.<i>.initialized can be used
+ * to query whether the stats are relevant.
*/
- if (config_stats) {
- unsigned i;
- for (i = 0; i <= ctl_stats.narenas; i++) {
- if (ctl_arena_init(&ctl_stats.arenas[i])) {
- unsigned j;
- for (j = 0; j < i; j++) {
- a0dalloc(
- ctl_stats.arenas[j].lstats);
- a0dalloc(
- ctl_stats.arenas[j].hstats);
- }
- a0dalloc(ctl_stats.arenas);
- ctl_stats.arenas = NULL;
- ret = true;
- goto label_return;
- }
+
+ ctl_arenas->narenas = narenas_total_get();
+ for (i = 0; i < ctl_arenas->narenas; i++) {
+ if (arenas_i_impl(tsd, i, false, true) == NULL) {
+ ret = true;
+ goto label_return;
}
}
- ctl_stats.arenas[ctl_stats.narenas].initialized = true;
- ctl_epoch = 0;
- ctl_refresh();
+ ql_new(&ctl_arenas->destroyed);
+ ctl_refresh(tsdn);
+
ctl_initialized = true;
}
ret = false;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+ return ret;
}
static int
-ctl_lookup(const char *name, ctl_node_t const **nodesp, size_t *mibp,
- size_t *depthp)
-{
+ctl_lookup(tsdn_t *tsdn, const char *name, ctl_node_t const **nodesp,
+ size_t *mibp, size_t *depthp) {
int ret;
const char *elm, *tdot, *dot;
size_t elen, i, j;
@@ -827,9 +1101,10 @@ ctl_lookup(const char *name, ctl_node_t const **nodesp, size_t *mibp,
if (strlen(child->name) == elen &&
strncmp(elm, child->name, elen) == 0) {
node = child;
- if (nodesp != NULL)
+ if (nodesp != NULL) {
nodesp[i] =
(const ctl_node_t *)node;
+ }
mibp[i] = j;
break;
}
@@ -850,14 +1125,15 @@ ctl_lookup(const char *name, ctl_node_t const **nodesp, size_t *mibp,
}
inode = ctl_indexed_node(node->children);
- node = inode->index(mibp, *depthp, (size_t)index);
+ node = inode->index(tsdn, mibp, *depthp, (size_t)index);
if (node == NULL) {
ret = ENOENT;
goto label_return;
}
- if (nodesp != NULL)
+ if (nodesp != NULL) {
nodesp[i] = (const ctl_node_t *)node;
+ }
mibp[i] = (size_t)index;
}
@@ -890,33 +1166,33 @@ ctl_lookup(const char *name, ctl_node_t const **nodesp, size_t *mibp,
ret = 0;
label_return:
- return (ret);
+ return ret;
}
int
-ctl_byname(const char *name, void *oldp, size_t *oldlenp, void *newp,
- size_t newlen)
-{
+ctl_byname(tsd_t *tsd, const char *name, void *oldp, size_t *oldlenp,
+ void *newp, size_t newlen) {
int ret;
size_t depth;
ctl_node_t const *nodes[CTL_MAX_DEPTH];
size_t mib[CTL_MAX_DEPTH];
const ctl_named_node_t *node;
- if (!ctl_initialized && ctl_init()) {
+ if (!ctl_initialized && ctl_init(tsd)) {
ret = EAGAIN;
goto label_return;
}
depth = CTL_MAX_DEPTH;
- ret = ctl_lookup(name, nodes, mib, &depth);
- if (ret != 0)
+ ret = ctl_lookup(tsd_tsdn(tsd), name, nodes, mib, &depth);
+ if (ret != 0) {
goto label_return;
+ }
node = ctl_named_node(nodes[depth-1]);
- if (node != NULL && node->ctl)
- ret = node->ctl(mib, depth, oldp, oldlenp, newp, newlen);
- else {
+ if (node != NULL && node->ctl) {
+ ret = node->ctl(tsd, mib, depth, oldp, oldlenp, newp, newlen);
+ } else {
/* The name refers to a partial path through the ctl tree. */
ret = ENOENT;
}
@@ -926,29 +1202,27 @@ label_return:
}
int
-ctl_nametomib(const char *name, size_t *mibp, size_t *miblenp)
-{
+ctl_nametomib(tsd_t *tsd, const char *name, size_t *mibp, size_t *miblenp) {
int ret;
- if (!ctl_initialized && ctl_init()) {
+ if (!ctl_initialized && ctl_init(tsd)) {
ret = EAGAIN;
goto label_return;
}
- ret = ctl_lookup(name, NULL, mibp, miblenp);
+ ret = ctl_lookup(tsd_tsdn(tsd), name, NULL, mibp, miblenp);
label_return:
return(ret);
}
int
-ctl_bymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+ctl_bymib(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
const ctl_named_node_t *node;
size_t i;
- if (!ctl_initialized && ctl_init()) {
+ if (!ctl_initialized && ctl_init(tsd)) {
ret = EAGAIN;
goto label_return;
}
@@ -970,7 +1244,7 @@ ctl_bymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
/* Indexed element. */
inode = ctl_indexed_node(node->children);
- node = inode->index(mib, miblen, mib[i]);
+ node = inode->index(tsd_tsdn(tsd), mib, miblen, mib[i]);
if (node == NULL) {
ret = ENOENT;
goto label_return;
@@ -979,9 +1253,9 @@ ctl_bymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
}
/* Call the ctl function. */
- if (node && node->ctl)
- ret = node->ctl(mib, miblen, oldp, oldlenp, newp, newlen);
- else {
+ if (node && node->ctl) {
+ ret = node->ctl(tsd, mib, miblen, oldp, oldlenp, newp, newlen);
+ } else {
/* Partial MIB. */
ret = ENOENT;
}
@@ -991,56 +1265,50 @@ label_return:
}
bool
-ctl_boot(void)
-{
-
- if (malloc_mutex_init(&ctl_mtx))
- return (true);
+ctl_boot(void) {
+ if (malloc_mutex_init(&ctl_mtx, "ctl", WITNESS_RANK_CTL,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
ctl_initialized = false;
- return (false);
+ return false;
}
void
-ctl_prefork(void)
-{
-
- malloc_mutex_prefork(&ctl_mtx);
+ctl_prefork(tsdn_t *tsdn) {
+ malloc_mutex_prefork(tsdn, &ctl_mtx);
}
void
-ctl_postfork_parent(void)
-{
-
- malloc_mutex_postfork_parent(&ctl_mtx);
+ctl_postfork_parent(tsdn_t *tsdn) {
+ malloc_mutex_postfork_parent(tsdn, &ctl_mtx);
}
void
-ctl_postfork_child(void)
-{
-
- malloc_mutex_postfork_child(&ctl_mtx);
+ctl_postfork_child(tsdn_t *tsdn) {
+ malloc_mutex_postfork_child(tsdn, &ctl_mtx);
}
/******************************************************************************/
/* *_ctl() functions. */
-#define READONLY() do { \
+#define READONLY() do { \
if (newp != NULL || newlen != 0) { \
ret = EPERM; \
goto label_return; \
} \
} while (0)
-#define WRITEONLY() do { \
+#define WRITEONLY() do { \
if (oldp != NULL || oldlenp != NULL) { \
ret = EPERM; \
goto label_return; \
} \
} while (0)
-#define READ_XOR_WRITE() do { \
+#define READ_XOR_WRITE() do { \
if ((oldp != NULL && oldlenp != NULL) && (newp != NULL || \
newlen != 0)) { \
ret = EPERM; \
@@ -1048,7 +1316,7 @@ ctl_postfork_child(void)
} \
} while (0)
-#define READ(v, t) do { \
+#define READ(v, t) do { \
if (oldp != NULL && oldlenp != NULL) { \
if (*oldlenp != sizeof(t)) { \
size_t copylen = (sizeof(t) <= *oldlenp) \
@@ -1061,7 +1329,7 @@ ctl_postfork_child(void)
} \
} while (0)
-#define WRITE(v, t) do { \
+#define WRITE(v, t) do { \
if (newp != NULL) { \
if (newlen != sizeof(t)) { \
ret = EINVAL; \
@@ -1071,101 +1339,109 @@ ctl_postfork_child(void)
} \
} while (0)
+#define MIB_UNSIGNED(v, i) do { \
+ if (mib[i] > UINT_MAX) { \
+ ret = EFAULT; \
+ goto label_return; \
+ } \
+ v = (unsigned)mib[i]; \
+} while (0)
+
/*
* There's a lot of code duplication in the following macros due to limitations
* in how nested cpp macros are expanded.
*/
-#define CTL_RO_CLGEN(c, l, n, v, t) \
+#define CTL_RO_CLGEN(c, l, n, v, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
\
- if (!(c)) \
- return (ENOENT); \
- if (l) \
- malloc_mutex_lock(&ctl_mtx); \
+ if (!(c)) { \
+ return ENOENT; \
+ } \
+ if (l) { \
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \
+ } \
READONLY(); \
oldval = (v); \
READ(oldval, t); \
\
ret = 0; \
label_return: \
- if (l) \
- malloc_mutex_unlock(&ctl_mtx); \
- return (ret); \
+ if (l) { \
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \
+ } \
+ return ret; \
}
-#define CTL_RO_CGEN(c, n, v, t) \
+#define CTL_RO_CGEN(c, n, v, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
\
- if (!(c)) \
- return (ENOENT); \
- malloc_mutex_lock(&ctl_mtx); \
+ if (!(c)) { \
+ return ENOENT; \
+ } \
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \
READONLY(); \
oldval = (v); \
READ(oldval, t); \
\
ret = 0; \
label_return: \
- malloc_mutex_unlock(&ctl_mtx); \
- return (ret); \
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \
+ return ret; \
}
-#define CTL_RO_GEN(n, v, t) \
+#define CTL_RO_GEN(n, v, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
\
- malloc_mutex_lock(&ctl_mtx); \
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx); \
READONLY(); \
oldval = (v); \
READ(oldval, t); \
\
ret = 0; \
label_return: \
- malloc_mutex_unlock(&ctl_mtx); \
- return (ret); \
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx); \
+ return ret; \
}
/*
* ctl_mtx is not acquired, under the assumption that no pertinent data will
* mutate during the call.
*/
-#define CTL_RO_NL_CGEN(c, n, v, t) \
+#define CTL_RO_NL_CGEN(c, n, v, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
\
- if (!(c)) \
- return (ENOENT); \
+ if (!(c)) { \
+ return ENOENT; \
+ } \
READONLY(); \
oldval = (v); \
READ(oldval, t); \
\
ret = 0; \
label_return: \
- return (ret); \
+ return ret; \
}
-#define CTL_RO_NL_GEN(n, v, t) \
+#define CTL_RO_NL_GEN(n, v, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
\
@@ -1175,45 +1451,42 @@ n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
\
ret = 0; \
label_return: \
- return (ret); \
+ return ret; \
}
-#define CTL_TSD_RO_NL_CGEN(c, n, m, t) \
+#define CTL_TSD_RO_NL_CGEN(c, n, m, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
t oldval; \
- tsd_t *tsd; \
\
- if (!(c)) \
- return (ENOENT); \
+ if (!(c)) { \
+ return ENOENT; \
+ } \
READONLY(); \
- tsd = tsd_fetch(); \
oldval = (m(tsd)); \
READ(oldval, t); \
\
ret = 0; \
label_return: \
- return (ret); \
+ return ret; \
}
-#define CTL_RO_BOOL_CONFIG_GEN(n) \
+#define CTL_RO_CONFIG_GEN(n, t) \
static int \
-n##_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp, \
- void *newp, size_t newlen) \
-{ \
+n##_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp, \
+ size_t *oldlenp, void *newp, size_t newlen) { \
int ret; \
- bool oldval; \
+ t oldval; \
\
READONLY(); \
oldval = n; \
- READ(oldval, bool); \
+ READ(oldval, t); \
\
ret = 0; \
label_return: \
- return (ret); \
+ return ret; \
}
/******************************************************************************/
@@ -1221,57 +1494,187 @@ label_return: \
CTL_RO_NL_GEN(version, JEMALLOC_VERSION, const char *)
static int
-epoch_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+epoch_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
UNUSED uint64_t newval;
- malloc_mutex_lock(&ctl_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
WRITE(newval, uint64_t);
- if (newp != NULL)
- ctl_refresh();
- READ(ctl_epoch, uint64_t);
+ if (newp != NULL) {
+ ctl_refresh(tsd_tsdn(tsd));
+ }
+ READ(ctl_arenas->epoch, uint64_t);
+
+ ret = 0;
+label_return:
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
+}
+
+static int
+background_thread_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ bool oldval;
+
+ if (!have_background_thread) {
+ return ENOENT;
+ }
+ background_thread_ctl_init(tsd_tsdn(tsd));
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock);
+ if (newp == NULL) {
+ oldval = background_thread_enabled();
+ READ(oldval, bool);
+ } else {
+ if (newlen != sizeof(bool)) {
+ ret = EINVAL;
+ goto label_return;
+ }
+ oldval = background_thread_enabled();
+ READ(oldval, bool);
+
+ bool newval = *(bool *)newp;
+ if (newval == oldval) {
+ ret = 0;
+ goto label_return;
+ }
+
+ background_thread_enabled_set(tsd_tsdn(tsd), newval);
+ if (newval) {
+ if (!can_enable_background_thread) {
+ malloc_printf("<jemalloc>: Error in dlsym("
+ "RTLD_NEXT, \"pthread_create\"). Cannot "
+ "enable background_thread\n");
+ ret = EFAULT;
+ goto label_return;
+ }
+ if (background_threads_enable(tsd)) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ } else {
+ if (background_threads_disable(tsd)) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ }
+ }
+ ret = 0;
+label_return:
+ malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+
+ return ret;
+}
+
+static int
+max_background_threads_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ size_t oldval;
+
+ if (!have_background_thread) {
+ return ENOENT;
+ }
+ background_thread_ctl_init(tsd_tsdn(tsd));
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock);
+ if (newp == NULL) {
+ oldval = max_background_threads;
+ READ(oldval, size_t);
+ } else {
+ if (newlen != sizeof(size_t)) {
+ ret = EINVAL;
+ goto label_return;
+ }
+ oldval = max_background_threads;
+ READ(oldval, size_t);
+
+ size_t newval = *(size_t *)newp;
+ if (newval == oldval) {
+ ret = 0;
+ goto label_return;
+ }
+ if (newval > opt_max_background_threads) {
+ ret = EINVAL;
+ goto label_return;
+ }
+ if (background_thread_enabled()) {
+ if (!can_enable_background_thread) {
+ malloc_printf("<jemalloc>: Error in dlsym("
+ "RTLD_NEXT, \"pthread_create\"). Cannot "
+ "enable background_thread\n");
+ ret = EFAULT;
+ goto label_return;
+ }
+ background_thread_enabled_set(tsd_tsdn(tsd), false);
+ if (background_threads_disable(tsd)) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ max_background_threads = newval;
+ background_thread_enabled_set(tsd_tsdn(tsd), true);
+ if (background_threads_enable(tsd)) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ } else {
+ max_background_threads = newval;
+ }
+ }
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+
+ return ret;
}
/******************************************************************************/
-CTL_RO_BOOL_CONFIG_GEN(config_cache_oblivious)
-CTL_RO_BOOL_CONFIG_GEN(config_debug)
-CTL_RO_BOOL_CONFIG_GEN(config_fill)
-CTL_RO_BOOL_CONFIG_GEN(config_lazy_lock)
-CTL_RO_BOOL_CONFIG_GEN(config_munmap)
-CTL_RO_BOOL_CONFIG_GEN(config_prof)
-CTL_RO_BOOL_CONFIG_GEN(config_prof_libgcc)
-CTL_RO_BOOL_CONFIG_GEN(config_prof_libunwind)
-CTL_RO_BOOL_CONFIG_GEN(config_stats)
-CTL_RO_BOOL_CONFIG_GEN(config_tcache)
-CTL_RO_BOOL_CONFIG_GEN(config_tls)
-CTL_RO_BOOL_CONFIG_GEN(config_utrace)
-CTL_RO_BOOL_CONFIG_GEN(config_valgrind)
-CTL_RO_BOOL_CONFIG_GEN(config_xmalloc)
+CTL_RO_CONFIG_GEN(config_cache_oblivious, bool)
+CTL_RO_CONFIG_GEN(config_debug, bool)
+CTL_RO_CONFIG_GEN(config_fill, bool)
+CTL_RO_CONFIG_GEN(config_lazy_lock, bool)
+CTL_RO_CONFIG_GEN(config_malloc_conf, const char *)
+CTL_RO_CONFIG_GEN(config_prof, bool)
+CTL_RO_CONFIG_GEN(config_prof_libgcc, bool)
+CTL_RO_CONFIG_GEN(config_prof_libunwind, bool)
+CTL_RO_CONFIG_GEN(config_stats, bool)
+CTL_RO_CONFIG_GEN(config_utrace, bool)
+CTL_RO_CONFIG_GEN(config_xmalloc, bool)
/******************************************************************************/
CTL_RO_NL_GEN(opt_abort, opt_abort, bool)
+CTL_RO_NL_GEN(opt_abort_conf, opt_abort_conf, bool)
+CTL_RO_NL_GEN(opt_metadata_thp, metadata_thp_mode_names[opt_metadata_thp],
+ const char *)
+CTL_RO_NL_GEN(opt_retain, opt_retain, bool)
CTL_RO_NL_GEN(opt_dss, opt_dss, const char *)
-CTL_RO_NL_GEN(opt_lg_chunk, opt_lg_chunk, size_t)
-CTL_RO_NL_GEN(opt_narenas, opt_narenas, size_t)
-CTL_RO_NL_GEN(opt_lg_dirty_mult, opt_lg_dirty_mult, ssize_t)
+CTL_RO_NL_GEN(opt_narenas, opt_narenas, unsigned)
+CTL_RO_NL_GEN(opt_percpu_arena, percpu_arena_mode_names[opt_percpu_arena],
+ const char *)
+CTL_RO_NL_GEN(opt_background_thread, opt_background_thread, bool)
+CTL_RO_NL_GEN(opt_max_background_threads, opt_max_background_threads, size_t)
+CTL_RO_NL_GEN(opt_dirty_decay_ms, opt_dirty_decay_ms, ssize_t)
+CTL_RO_NL_GEN(opt_muzzy_decay_ms, opt_muzzy_decay_ms, ssize_t)
CTL_RO_NL_GEN(opt_stats_print, opt_stats_print, bool)
+CTL_RO_NL_GEN(opt_stats_print_opts, opt_stats_print_opts, const char *)
CTL_RO_NL_CGEN(config_fill, opt_junk, opt_junk, const char *)
-CTL_RO_NL_CGEN(config_fill, opt_quarantine, opt_quarantine, size_t)
-CTL_RO_NL_CGEN(config_fill, opt_redzone, opt_redzone, bool)
CTL_RO_NL_CGEN(config_fill, opt_zero, opt_zero, bool)
CTL_RO_NL_CGEN(config_utrace, opt_utrace, opt_utrace, bool)
CTL_RO_NL_CGEN(config_xmalloc, opt_xmalloc, opt_xmalloc, bool)
-CTL_RO_NL_CGEN(config_tcache, opt_tcache, opt_tcache, bool)
-CTL_RO_NL_CGEN(config_tcache, opt_lg_tcache_max, opt_lg_tcache_max, ssize_t)
+CTL_RO_NL_GEN(opt_tcache, opt_tcache, bool)
+CTL_RO_NL_GEN(opt_thp, thp_mode_names[opt_thp], const char *)
+CTL_RO_NL_GEN(opt_lg_extent_max_active_fit, opt_lg_extent_max_active_fit,
+ size_t)
+CTL_RO_NL_GEN(opt_lg_tcache_max, opt_lg_tcache_max, ssize_t)
CTL_RO_NL_CGEN(config_prof, opt_prof, opt_prof, bool)
CTL_RO_NL_CGEN(config_prof, opt_prof_prefix, opt_prof_prefix, const char *)
CTL_RO_NL_CGEN(config_prof, opt_prof_active, opt_prof_active, bool)
@@ -1287,53 +1690,59 @@ CTL_RO_NL_CGEN(config_prof, opt_prof_leak, opt_prof_leak, bool)
/******************************************************************************/
static int
-thread_arena_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+thread_arena_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- tsd_t *tsd;
arena_t *oldarena;
unsigned newind, oldind;
- tsd = tsd_fetch();
oldarena = arena_choose(tsd, NULL);
- if (oldarena == NULL)
- return (EAGAIN);
-
- malloc_mutex_lock(&ctl_mtx);
- newind = oldind = oldarena->ind;
+ if (oldarena == NULL) {
+ return EAGAIN;
+ }
+ newind = oldind = arena_ind_get(oldarena);
WRITE(newind, unsigned);
READ(oldind, unsigned);
+
if (newind != oldind) {
arena_t *newarena;
- if (newind >= ctl_stats.narenas) {
+ if (newind >= narenas_total_get()) {
/* New arena index is out of range. */
ret = EFAULT;
goto label_return;
}
+ if (have_percpu_arena &&
+ PERCPU_ARENA_ENABLED(opt_percpu_arena)) {
+ if (newind < percpu_arena_ind_limit(opt_percpu_arena)) {
+ /*
+ * If perCPU arena is enabled, thread_arena
+ * control is not allowed for the auto arena
+ * range.
+ */
+ ret = EPERM;
+ goto label_return;
+ }
+ }
+
/* Initialize arena if necessary. */
- newarena = arena_get(tsd, newind, true, true);
+ newarena = arena_get(tsd_tsdn(tsd), newind, true);
if (newarena == NULL) {
ret = EAGAIN;
goto label_return;
}
/* Set new arena/tcache associations. */
arena_migrate(tsd, oldind, newind);
- if (config_tcache) {
- tcache_t *tcache = tsd_tcache_get(tsd);
- if (tcache != NULL) {
- tcache_arena_reassociate(tcache, oldarena,
- newarena);
- }
+ if (tcache_available(tsd)) {
+ tcache_arena_reassociate(tsd_tsdn(tsd),
+ tsd_tcachep_get(tsd), newarena);
}
}
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ return ret;
}
CTL_TSD_RO_NL_CGEN(config_stats, thread_allocated, tsd_thread_allocated_get,
@@ -1346,100 +1755,94 @@ CTL_TSD_RO_NL_CGEN(config_stats, thread_deallocatedp,
tsd_thread_deallocatedp_get, uint64_t *)
static int
-thread_tcache_enabled_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+thread_tcache_enabled_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
int ret;
bool oldval;
- if (!config_tcache)
- return (ENOENT);
-
- oldval = tcache_enabled_get();
+ oldval = tcache_enabled_get(tsd);
if (newp != NULL) {
if (newlen != sizeof(bool)) {
ret = EINVAL;
goto label_return;
}
- tcache_enabled_set(*(bool *)newp);
+ tcache_enabled_set(tsd, *(bool *)newp);
}
READ(oldval, bool);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-thread_tcache_flush_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+thread_tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- if (!config_tcache)
- return (ENOENT);
+ if (!tcache_available(tsd)) {
+ ret = EFAULT;
+ goto label_return;
+ }
READONLY();
WRITEONLY();
- tcache_flush();
+ tcache_flush(tsd);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-thread_prof_name_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+thread_prof_name_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
READ_XOR_WRITE();
if (newp != NULL) {
- tsd_t *tsd;
-
if (newlen != sizeof(const char *)) {
ret = EINVAL;
goto label_return;
}
- tsd = tsd_fetch();
-
if ((ret = prof_thread_name_set(tsd, *(const char **)newp)) !=
- 0)
+ 0) {
goto label_return;
+ }
} else {
- const char *oldname = prof_thread_name_get();
+ const char *oldname = prof_thread_name_get(tsd);
READ(oldname, const char *);
}
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-thread_prof_active_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+thread_prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
bool oldval;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
- oldval = prof_thread_active_get();
+ oldval = prof_thread_active_get(tsd);
if (newp != NULL) {
if (newlen != sizeof(bool)) {
ret = EINVAL;
goto label_return;
}
- if (prof_thread_active_set(*(bool *)newp)) {
+ if (prof_thread_active_set(tsd, *(bool *)newp)) {
ret = EAGAIN;
goto label_return;
}
@@ -1448,25 +1851,17 @@ thread_prof_active_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = 0;
label_return:
- return (ret);
+ return ret;
}
/******************************************************************************/
static int
-tcache_create_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+tcache_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- tsd_t *tsd;
unsigned tcache_ind;
- if (!config_tcache)
- return (ENOENT);
-
- tsd = tsd_fetch();
-
- malloc_mutex_lock(&ctl_mtx);
READONLY();
if (tcaches_create(tsd, &tcache_ind)) {
ret = EFAULT;
@@ -1476,23 +1871,15 @@ tcache_create_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ return ret;
}
static int
-tcache_flush_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+tcache_flush_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- tsd_t *tsd;
unsigned tcache_ind;
- if (!config_tcache)
- return (ENOENT);
-
- tsd = tsd_fetch();
-
WRITEONLY();
tcache_ind = UINT_MAX;
WRITE(tcache_ind, unsigned);
@@ -1504,22 +1891,15 @@ tcache_flush_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-tcache_destroy_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+tcache_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- tsd_t *tsd;
unsigned tcache_ind;
- if (!config_tcache)
- return (ENOENT);
-
- tsd = tsd_fetch();
-
WRITEONLY();
tcache_ind = UINT_MAX;
WRITE(tcache_ind, unsigned);
@@ -1531,71 +1911,239 @@ tcache_destroy_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = 0;
label_return:
- return (ret);
+ return ret;
}
/******************************************************************************/
-/* ctl_mutex must be held during execution of this function. */
+static int
+arena_i_initialized_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ tsdn_t *tsdn = tsd_tsdn(tsd);
+ unsigned arena_ind;
+ bool initialized;
+
+ READONLY();
+ MIB_UNSIGNED(arena_ind, 1);
+
+ malloc_mutex_lock(tsdn, &ctl_mtx);
+ initialized = arenas_i(arena_ind)->initialized;
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+
+ READ(initialized, bool);
+
+ ret = 0;
+label_return:
+ return ret;
+}
+
static void
-arena_purge(unsigned arena_ind)
-{
- tsd_t *tsd;
- unsigned i;
- bool refreshed;
- VARIABLE_ARRAY(arena_t *, tarenas, ctl_stats.narenas);
-
- tsd = tsd_fetch();
- for (i = 0, refreshed = false; i < ctl_stats.narenas; i++) {
- tarenas[i] = arena_get(tsd, i, false, false);
- if (tarenas[i] == NULL && !refreshed) {
- tarenas[i] = arena_get(tsd, i, false, true);
- refreshed = true;
- }
- }
+arena_i_decay(tsdn_t *tsdn, unsigned arena_ind, bool all) {
+ malloc_mutex_lock(tsdn, &ctl_mtx);
+ {
+ unsigned narenas = ctl_arenas->narenas;
- if (arena_ind == ctl_stats.narenas) {
- unsigned i;
- for (i = 0; i < ctl_stats.narenas; i++) {
- if (tarenas[i] != NULL)
- arena_purge_all(tarenas[i]);
+ /*
+ * Access via index narenas is deprecated, and scheduled for
+ * removal in 6.0.0.
+ */
+ if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind == narenas) {
+ unsigned i;
+ VARIABLE_ARRAY(arena_t *, tarenas, narenas);
+
+ for (i = 0; i < narenas; i++) {
+ tarenas[i] = arena_get(tsdn, i, false);
+ }
+
+ /*
+ * No further need to hold ctl_mtx, since narenas and
+ * tarenas contain everything needed below.
+ */
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+
+ for (i = 0; i < narenas; i++) {
+ if (tarenas[i] != NULL) {
+ arena_decay(tsdn, tarenas[i], false,
+ all);
+ }
+ }
+ } else {
+ arena_t *tarena;
+
+ assert(arena_ind < narenas);
+
+ tarena = arena_get(tsdn, arena_ind, false);
+
+ /* No further need to hold ctl_mtx. */
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+
+ if (tarena != NULL) {
+ arena_decay(tsdn, tarena, false, all);
+ }
}
- } else {
- assert(arena_ind < ctl_stats.narenas);
- if (tarenas[arena_ind] != NULL)
- arena_purge_all(tarenas[arena_ind]);
}
}
static int
-arena_i_purge_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+arena_i_decay_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ unsigned arena_ind;
+
+ READONLY();
+ WRITEONLY();
+ MIB_UNSIGNED(arena_ind, 1);
+ arena_i_decay(tsd_tsdn(tsd), arena_ind, false);
+
+ ret = 0;
+label_return:
+ return ret;
+}
+
+static int
+arena_i_purge_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
+ unsigned arena_ind;
READONLY();
WRITEONLY();
- malloc_mutex_lock(&ctl_mtx);
- arena_purge(mib[1]);
- malloc_mutex_unlock(&ctl_mtx);
+ MIB_UNSIGNED(arena_ind, 1);
+ arena_i_decay(tsd_tsdn(tsd), arena_ind, true);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-arena_i_dss_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+arena_i_reset_destroy_helper(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen, unsigned *arena_ind,
+ arena_t **arena) {
+ int ret;
+
+ READONLY();
+ WRITEONLY();
+ MIB_UNSIGNED(*arena_ind, 1);
+
+ *arena = arena_get(tsd_tsdn(tsd), *arena_ind, false);
+ if (*arena == NULL || arena_is_auto(*arena)) {
+ ret = EFAULT;
+ goto label_return;
+ }
+
+ ret = 0;
+label_return:
+ return ret;
+}
+
+static void
+arena_reset_prepare_background_thread(tsd_t *tsd, unsigned arena_ind) {
+ /* Temporarily disable the background thread during arena reset. */
+ if (have_background_thread) {
+ malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock);
+ if (background_thread_enabled()) {
+ unsigned ind = arena_ind % ncpus;
+ background_thread_info_t *info =
+ &background_thread_info[ind];
+ assert(info->state == background_thread_started);
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ info->state = background_thread_paused;
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ }
+ }
+}
+
+static void
+arena_reset_finish_background_thread(tsd_t *tsd, unsigned arena_ind) {
+ if (have_background_thread) {
+ if (background_thread_enabled()) {
+ unsigned ind = arena_ind % ncpus;
+ background_thread_info_t *info =
+ &background_thread_info[ind];
+ assert(info->state == background_thread_paused);
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ info->state = background_thread_started;
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock);
+ }
+}
+
+static int
+arena_i_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ unsigned arena_ind;
+ arena_t *arena;
+
+ ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp,
+ newp, newlen, &arena_ind, &arena);
+ if (ret != 0) {
+ return ret;
+ }
+
+ arena_reset_prepare_background_thread(tsd, arena_ind);
+ arena_reset(tsd, arena);
+ arena_reset_finish_background_thread(tsd, arena_ind);
+
+ return ret;
+}
+
+static int
+arena_i_destroy_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ unsigned arena_ind;
+ arena_t *arena;
+ ctl_arena_t *ctl_darena, *ctl_arena;
+
+ ret = arena_i_reset_destroy_helper(tsd, mib, miblen, oldp, oldlenp,
+ newp, newlen, &arena_ind, &arena);
+ if (ret != 0) {
+ goto label_return;
+ }
+
+ if (arena_nthreads_get(arena, false) != 0 || arena_nthreads_get(arena,
+ true) != 0) {
+ ret = EFAULT;
+ goto label_return;
+ }
+
+ arena_reset_prepare_background_thread(tsd, arena_ind);
+ /* Merge stats after resetting and purging arena. */
+ arena_reset(tsd, arena);
+ arena_decay(tsd_tsdn(tsd), arena, false, true);
+ ctl_darena = arenas_i(MALLCTL_ARENAS_DESTROYED);
+ ctl_darena->initialized = true;
+ ctl_arena_refresh(tsd_tsdn(tsd), arena, ctl_darena, arena_ind, true);
+ /* Destroy arena. */
+ arena_destroy(tsd, arena);
+ ctl_arena = arenas_i(arena_ind);
+ ctl_arena->initialized = false;
+ /* Record arena index for later recycling via arenas.create. */
+ ql_elm_new(ctl_arena, destroyed_link);
+ ql_tail_insert(&ctl_arenas->destroyed, ctl_arena, destroyed_link);
+ arena_reset_finish_background_thread(tsd, arena_ind);
+
+ assert(ret == 0);
+label_return:
+ return ret;
+}
+
+static int
+arena_i_dss_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
const char *dss = NULL;
- unsigned arena_ind = mib[1];
+ unsigned arena_ind;
dss_prec_t dss_prec_old = dss_prec_limit;
dss_prec_t dss_prec = dss_prec_limit;
- malloc_mutex_lock(&ctl_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
WRITE(dss, const char *);
+ MIB_UNSIGNED(arena_ind, 1);
if (dss != NULL) {
int i;
bool match = false;
@@ -1614,21 +2162,26 @@ arena_i_dss_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
}
}
- if (arena_ind < ctl_stats.narenas) {
- arena_t *arena = arena_get(tsd_fetch(), arena_ind, false, true);
- if (arena == NULL || (dss_prec != dss_prec_limit &&
- arena_dss_prec_set(arena, dss_prec))) {
+ /*
+ * Access via index narenas is deprecated, and scheduled for removal in
+ * 6.0.0.
+ */
+ if (arena_ind == MALLCTL_ARENAS_ALL || arena_ind ==
+ ctl_arenas->narenas) {
+ if (dss_prec != dss_prec_limit &&
+ extent_dss_prec_set(dss_prec)) {
ret = EFAULT;
goto label_return;
}
- dss_prec_old = arena_dss_prec_get(arena);
+ dss_prec_old = extent_dss_prec_get();
} else {
- if (dss_prec != dss_prec_limit &&
- chunk_dss_prec_set(dss_prec)) {
+ arena_t *arena = arena_get(tsd_tsdn(tsd), arena_ind, false);
+ if (arena == NULL || (dss_prec != dss_prec_limit &&
+ arena_dss_prec_set(arena, dss_prec))) {
ret = EFAULT;
goto label_return;
}
- dss_prec_old = chunk_dss_prec_get();
+ dss_prec_old = arena_dss_prec_get(arena);
}
dss = dss_prec_names[dss_prec_old];
@@ -1636,26 +2189,27 @@ arena_i_dss_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
}
static int
-arena_i_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+arena_i_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) {
int ret;
- unsigned arena_ind = mib[1];
+ unsigned arena_ind;
arena_t *arena;
- arena = arena_get(tsd_fetch(), arena_ind, false, true);
+ MIB_UNSIGNED(arena_ind, 1);
+ arena = arena_get(tsd_tsdn(tsd), arena_ind, false);
if (arena == NULL) {
ret = EFAULT;
goto label_return;
}
if (oldp != NULL && oldlenp != NULL) {
- size_t oldval = arena_lg_dirty_mult_get(arena);
+ size_t oldval = dirty ? arena_dirty_decay_ms_get(arena) :
+ arena_muzzy_decay_ms_get(arena);
READ(oldval, ssize_t);
}
if (newp != NULL) {
@@ -1663,7 +2217,9 @@ arena_i_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = EINVAL;
goto label_return;
}
- if (arena_lg_dirty_mult_set(arena, *(ssize_t *)newp)) {
+ if (dirty ? arena_dirty_decay_ms_set(tsd_tsdn(tsd), arena,
+ *(ssize_t *)newp) : arena_muzzy_decay_ms_set(tsd_tsdn(tsd),
+ arena, *(ssize_t *)newp)) {
ret = EFAULT;
goto label_return;
}
@@ -1671,29 +2227,67 @@ arena_i_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-arena_i_chunk_hooks_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+arena_i_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp,
+ newlen, true);
+}
+
+static int
+arena_i_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ return arena_i_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp,
+ newlen, false);
+}
+
+static int
+arena_i_extent_hooks_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- unsigned arena_ind = mib[1];
+ unsigned arena_ind;
arena_t *arena;
- malloc_mutex_lock(&ctl_mtx);
- if (arena_ind < narenas_total_get() && (arena =
- arena_get(tsd_fetch(), arena_ind, false, true)) != NULL) {
- if (newp != NULL) {
- chunk_hooks_t old_chunk_hooks, new_chunk_hooks;
- WRITE(new_chunk_hooks, chunk_hooks_t);
- old_chunk_hooks = chunk_hooks_set(arena,
- &new_chunk_hooks);
- READ(old_chunk_hooks, chunk_hooks_t);
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
+ MIB_UNSIGNED(arena_ind, 1);
+ if (arena_ind < narenas_total_get()) {
+ extent_hooks_t *old_extent_hooks;
+ arena = arena_get(tsd_tsdn(tsd), arena_ind, false);
+ if (arena == NULL) {
+ if (arena_ind >= narenas_auto) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ old_extent_hooks =
+ (extent_hooks_t *)&extent_hooks_default;
+ READ(old_extent_hooks, extent_hooks_t *);
+ if (newp != NULL) {
+ /* Initialize a new arena as a side effect. */
+ extent_hooks_t *new_extent_hooks
+ JEMALLOC_CC_SILENCE_INIT(NULL);
+ WRITE(new_extent_hooks, extent_hooks_t *);
+ arena = arena_init(tsd_tsdn(tsd), arena_ind,
+ new_extent_hooks);
+ if (arena == NULL) {
+ ret = EFAULT;
+ goto label_return;
+ }
+ }
} else {
- chunk_hooks_t old_chunk_hooks = chunk_hooks_get(arena);
- READ(old_chunk_hooks, chunk_hooks_t);
+ if (newp != NULL) {
+ extent_hooks_t *new_extent_hooks
+ JEMALLOC_CC_SILENCE_INIT(NULL);
+ WRITE(new_extent_hooks, extent_hooks_t *);
+ old_extent_hooks = extent_hooks_set(tsd, arena,
+ new_extent_hooks);
+ READ(old_extent_hooks, extent_hooks_t *);
+ } else {
+ old_extent_hooks = extent_hooks_get(arena);
+ READ(old_extent_hooks, extent_hooks_t *);
+ }
}
} else {
ret = EFAULT;
@@ -1701,85 +2295,100 @@ arena_i_chunk_hooks_ctl(const size_t *mib, size_t miblen, void *oldp,
}
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
}
-static const ctl_named_node_t *
-arena_i_index(const size_t *mib, size_t miblen, size_t i)
-{
- const ctl_named_node_t * ret;
+static int
+arena_i_retain_grow_limit_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ unsigned arena_ind;
+ arena_t *arena;
- malloc_mutex_lock(&ctl_mtx);
- if (i > ctl_stats.narenas) {
- ret = NULL;
- goto label_return;
+ if (!opt_retain) {
+ /* Only relevant when retain is enabled. */
+ return ENOENT;
+ }
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
+ MIB_UNSIGNED(arena_ind, 1);
+ if (arena_ind < narenas_total_get() && (arena =
+ arena_get(tsd_tsdn(tsd), arena_ind, false)) != NULL) {
+ size_t old_limit, new_limit;
+ if (newp != NULL) {
+ WRITE(new_limit, size_t);
+ }
+ bool err = arena_retain_grow_limit_get_set(tsd, arena,
+ &old_limit, newp != NULL ? &new_limit : NULL);
+ if (!err) {
+ READ(old_limit, size_t);
+ ret = 0;
+ } else {
+ ret = EFAULT;
+ }
+ } else {
+ ret = EFAULT;
+ }
+label_return:
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
+}
+
+static const ctl_named_node_t *
+arena_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) {
+ const ctl_named_node_t *ret;
+
+ malloc_mutex_lock(tsdn, &ctl_mtx);
+ switch (i) {
+ case MALLCTL_ARENAS_ALL:
+ case MALLCTL_ARENAS_DESTROYED:
+ break;
+ default:
+ if (i > ctl_arenas->narenas) {
+ ret = NULL;
+ goto label_return;
+ }
+ break;
}
ret = super_arena_i_node;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+ return ret;
}
/******************************************************************************/
static int
-arenas_narenas_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+arenas_narenas_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
unsigned narenas;
- malloc_mutex_lock(&ctl_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
READONLY();
if (*oldlenp != sizeof(unsigned)) {
ret = EINVAL;
goto label_return;
}
- narenas = ctl_stats.narenas;
+ narenas = ctl_arenas->narenas;
READ(narenas, unsigned);
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
-}
-
-static int
-arenas_initialized_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
- int ret;
- unsigned nread, i;
-
- malloc_mutex_lock(&ctl_mtx);
- READONLY();
- if (*oldlenp != ctl_stats.narenas * sizeof(bool)) {
- ret = EINVAL;
- nread = (*oldlenp < ctl_stats.narenas * sizeof(bool))
- ? (*oldlenp / sizeof(bool)) : ctl_stats.narenas;
- } else {
- ret = 0;
- nread = ctl_stats.narenas;
- }
-
- for (i = 0; i < nread; i++)
- ((bool *)oldp)[i] = ctl_stats.arenas[i].initialized;
-
-label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
}
static int
-arenas_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+arenas_decay_ms_ctl_impl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen, bool dirty) {
int ret;
if (oldp != NULL && oldlenp != NULL) {
- size_t oldval = arena_lg_dirty_mult_default_get();
+ size_t oldval = (dirty ? arena_dirty_decay_ms_default_get() :
+ arena_muzzy_decay_ms_default_get());
READ(oldval, ssize_t);
}
if (newp != NULL) {
@@ -1787,7 +2396,8 @@ arenas_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = EINVAL;
goto label_return;
}
- if (arena_lg_dirty_mult_default_set(*(ssize_t *)newp)) {
+ if (dirty ? arena_dirty_decay_ms_default_set(*(ssize_t *)newp)
+ : arena_muzzy_decay_ms_default_set(*(ssize_t *)newp)) {
ret = EFAULT;
goto label_return;
}
@@ -1795,193 +2405,229 @@ arenas_lg_dirty_mult_ctl(const size_t *mib, size_t miblen, void *oldp,
ret = 0;
label_return:
- return (ret);
+ return ret;
+}
+
+static int
+arenas_dirty_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp,
+ newlen, true);
+}
+
+static int
+arenas_muzzy_decay_ms_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ return arenas_decay_ms_ctl_impl(tsd, mib, miblen, oldp, oldlenp, newp,
+ newlen, false);
}
CTL_RO_NL_GEN(arenas_quantum, QUANTUM, size_t)
CTL_RO_NL_GEN(arenas_page, PAGE, size_t)
-CTL_RO_NL_CGEN(config_tcache, arenas_tcache_max, tcache_maxclass, size_t)
+CTL_RO_NL_GEN(arenas_tcache_max, tcache_maxclass, size_t)
CTL_RO_NL_GEN(arenas_nbins, NBINS, unsigned)
-CTL_RO_NL_CGEN(config_tcache, arenas_nhbins, nhbins, unsigned)
-CTL_RO_NL_GEN(arenas_bin_i_size, arena_bin_info[mib[2]].reg_size, size_t)
-CTL_RO_NL_GEN(arenas_bin_i_nregs, arena_bin_info[mib[2]].nregs, uint32_t)
-CTL_RO_NL_GEN(arenas_bin_i_run_size, arena_bin_info[mib[2]].run_size, size_t)
+CTL_RO_NL_GEN(arenas_nhbins, nhbins, unsigned)
+CTL_RO_NL_GEN(arenas_bin_i_size, bin_infos[mib[2]].reg_size, size_t)
+CTL_RO_NL_GEN(arenas_bin_i_nregs, bin_infos[mib[2]].nregs, uint32_t)
+CTL_RO_NL_GEN(arenas_bin_i_slab_size, bin_infos[mib[2]].slab_size, size_t)
static const ctl_named_node_t *
-arenas_bin_i_index(const size_t *mib, size_t miblen, size_t i)
-{
-
- if (i > NBINS)
- return (NULL);
- return (super_arenas_bin_i_node);
+arenas_bin_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) {
+ if (i > NBINS) {
+ return NULL;
+ }
+ return super_arenas_bin_i_node;
}
-CTL_RO_NL_GEN(arenas_nlruns, nlclasses, unsigned)
-CTL_RO_NL_GEN(arenas_lrun_i_size, index2size(NBINS+mib[2]), size_t)
+CTL_RO_NL_GEN(arenas_nlextents, NSIZES - NBINS, unsigned)
+CTL_RO_NL_GEN(arenas_lextent_i_size, sz_index2size(NBINS+(szind_t)mib[2]),
+ size_t)
static const ctl_named_node_t *
-arenas_lrun_i_index(const size_t *mib, size_t miblen, size_t i)
-{
-
- if (i > nlclasses)
- return (NULL);
- return (super_arenas_lrun_i_node);
+arenas_lextent_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen,
+ size_t i) {
+ if (i > NSIZES - NBINS) {
+ return NULL;
+ }
+ return super_arenas_lextent_i_node;
}
-CTL_RO_NL_GEN(arenas_nhchunks, nhclasses, unsigned)
-CTL_RO_NL_GEN(arenas_hchunk_i_size, index2size(NBINS+nlclasses+mib[2]), size_t)
-static const ctl_named_node_t *
-arenas_hchunk_i_index(const size_t *mib, size_t miblen, size_t i)
-{
+static int
+arenas_create_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
+ int ret;
+ extent_hooks_t *extent_hooks;
+ unsigned arena_ind;
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
- if (i > nhclasses)
- return (NULL);
- return (super_arenas_hchunk_i_node);
+ extent_hooks = (extent_hooks_t *)&extent_hooks_default;
+ WRITE(extent_hooks, extent_hooks_t *);
+ if ((arena_ind = ctl_arena_init(tsd, extent_hooks)) == UINT_MAX) {
+ ret = EAGAIN;
+ goto label_return;
+ }
+ READ(arena_ind, unsigned);
+
+ ret = 0;
+label_return:
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
}
static int
-arenas_extend_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+arenas_lookup_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
- unsigned narenas;
+ unsigned arena_ind;
+ void *ptr;
+ extent_t *extent;
+ arena_t *arena;
- malloc_mutex_lock(&ctl_mtx);
- READONLY();
- if (ctl_grow()) {
- ret = EAGAIN;
+ ptr = NULL;
+ ret = EINVAL;
+ malloc_mutex_lock(tsd_tsdn(tsd), &ctl_mtx);
+ WRITE(ptr, void *);
+ extent = iealloc(tsd_tsdn(tsd), ptr);
+ if (extent == NULL)
+ goto label_return;
+
+ arena = extent_arena_get(extent);
+ if (arena == NULL)
goto label_return;
- }
- narenas = ctl_stats.narenas - 1;
- READ(narenas, unsigned);
+
+ arena_ind = arena_ind_get(arena);
+ READ(arena_ind, unsigned);
ret = 0;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &ctl_mtx);
+ return ret;
}
/******************************************************************************/
static int
-prof_thread_active_init_ctl(const size_t *mib, size_t miblen, void *oldp,
- size_t *oldlenp, void *newp, size_t newlen)
-{
+prof_thread_active_init_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
int ret;
bool oldval;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
if (newp != NULL) {
if (newlen != sizeof(bool)) {
ret = EINVAL;
goto label_return;
}
- oldval = prof_thread_active_init_set(*(bool *)newp);
- } else
- oldval = prof_thread_active_init_get();
+ oldval = prof_thread_active_init_set(tsd_tsdn(tsd),
+ *(bool *)newp);
+ } else {
+ oldval = prof_thread_active_init_get(tsd_tsdn(tsd));
+ }
READ(oldval, bool);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-prof_active_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+prof_active_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
bool oldval;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
if (newp != NULL) {
if (newlen != sizeof(bool)) {
ret = EINVAL;
goto label_return;
}
- oldval = prof_active_set(*(bool *)newp);
- } else
- oldval = prof_active_get();
+ oldval = prof_active_set(tsd_tsdn(tsd), *(bool *)newp);
+ } else {
+ oldval = prof_active_get(tsd_tsdn(tsd));
+ }
READ(oldval, bool);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-prof_dump_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+prof_dump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
const char *filename = NULL;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
WRITEONLY();
WRITE(filename, const char *);
- if (prof_mdump(filename)) {
+ if (prof_mdump(tsd, filename)) {
ret = EFAULT;
goto label_return;
}
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-prof_gdump_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+prof_gdump_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
bool oldval;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
if (newp != NULL) {
if (newlen != sizeof(bool)) {
ret = EINVAL;
goto label_return;
}
- oldval = prof_gdump_set(*(bool *)newp);
- } else
- oldval = prof_gdump_get();
+ oldval = prof_gdump_set(tsd_tsdn(tsd), *(bool *)newp);
+ } else {
+ oldval = prof_gdump_get(tsd_tsdn(tsd));
+ }
READ(oldval, bool);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
static int
-prof_reset_ctl(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+prof_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen, void *oldp,
+ size_t *oldlenp, void *newp, size_t newlen) {
int ret;
size_t lg_sample = lg_prof_sample;
- tsd_t *tsd;
- if (!config_prof)
- return (ENOENT);
+ if (!config_prof) {
+ return ENOENT;
+ }
WRITEONLY();
WRITE(lg_sample, size_t);
- if (lg_sample >= (sizeof(uint64_t) << 3))
+ if (lg_sample >= (sizeof(uint64_t) << 3)) {
lg_sample = (sizeof(uint64_t) << 3) - 1;
-
- tsd = tsd_fetch();
+ }
prof_reset(tsd, lg_sample);
ret = 0;
label_return:
- return (ret);
+ return ret;
}
CTL_RO_NL_CGEN(config_prof, prof_interval, prof_interval, uint64_t)
@@ -1989,135 +2635,249 @@ CTL_RO_NL_CGEN(config_prof, lg_prof_sample, lg_prof_sample, size_t)
/******************************************************************************/
-CTL_RO_CGEN(config_stats, stats_cactive, &stats_cactive, size_t *)
-CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats.allocated, size_t)
-CTL_RO_CGEN(config_stats, stats_active, ctl_stats.active, size_t)
-CTL_RO_CGEN(config_stats, stats_metadata, ctl_stats.metadata, size_t)
-CTL_RO_CGEN(config_stats, stats_resident, ctl_stats.resident, size_t)
-CTL_RO_CGEN(config_stats, stats_mapped, ctl_stats.mapped, size_t)
-
-CTL_RO_GEN(stats_arenas_i_dss, ctl_stats.arenas[mib[2]].dss, const char *)
-CTL_RO_GEN(stats_arenas_i_lg_dirty_mult, ctl_stats.arenas[mib[2]].lg_dirty_mult,
+CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats->allocated, size_t)
+CTL_RO_CGEN(config_stats, stats_active, ctl_stats->active, size_t)
+CTL_RO_CGEN(config_stats, stats_metadata, ctl_stats->metadata, size_t)
+CTL_RO_CGEN(config_stats, stats_metadata_thp, ctl_stats->metadata_thp, size_t)
+CTL_RO_CGEN(config_stats, stats_resident, ctl_stats->resident, size_t)
+CTL_RO_CGEN(config_stats, stats_mapped, ctl_stats->mapped, size_t)
+CTL_RO_CGEN(config_stats, stats_retained, ctl_stats->retained, size_t)
+
+CTL_RO_CGEN(config_stats, stats_background_thread_num_threads,
+ ctl_stats->background_thread.num_threads, size_t)
+CTL_RO_CGEN(config_stats, stats_background_thread_num_runs,
+ ctl_stats->background_thread.num_runs, uint64_t)
+CTL_RO_CGEN(config_stats, stats_background_thread_run_interval,
+ nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t)
+
+CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *)
+CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms,
+ ssize_t)
+CTL_RO_GEN(stats_arenas_i_muzzy_decay_ms, arenas_i(mib[2])->muzzy_decay_ms,
ssize_t)
-CTL_RO_GEN(stats_arenas_i_nthreads, ctl_stats.arenas[mib[2]].nthreads, unsigned)
-CTL_RO_GEN(stats_arenas_i_pactive, ctl_stats.arenas[mib[2]].pactive, size_t)
-CTL_RO_GEN(stats_arenas_i_pdirty, ctl_stats.arenas[mib[2]].pdirty, size_t)
+CTL_RO_GEN(stats_arenas_i_nthreads, arenas_i(mib[2])->nthreads, unsigned)
+CTL_RO_GEN(stats_arenas_i_uptime,
+ nstime_ns(&arenas_i(mib[2])->astats->astats.uptime), uint64_t)
+CTL_RO_GEN(stats_arenas_i_pactive, arenas_i(mib[2])->pactive, size_t)
+CTL_RO_GEN(stats_arenas_i_pdirty, arenas_i(mib[2])->pdirty, size_t)
+CTL_RO_GEN(stats_arenas_i_pmuzzy, arenas_i(mib[2])->pmuzzy, size_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_mapped,
- ctl_stats.arenas[mib[2]].astats.mapped, size_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_npurge,
- ctl_stats.arenas[mib[2]].astats.npurge, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_nmadvise,
- ctl_stats.arenas[mib[2]].astats.nmadvise, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_purged,
- ctl_stats.arenas[mib[2]].astats.purged, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_metadata_mapped,
- ctl_stats.arenas[mib[2]].astats.metadata_mapped, size_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_metadata_allocated,
- ctl_stats.arenas[mib[2]].astats.metadata_allocated, size_t)
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.mapped, ATOMIC_RELAXED),
+ size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_retained,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.retained, ATOMIC_RELAXED),
+ size_t)
+
+CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_npurge,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_dirty.npurge), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_nmadvise,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_dirty.nmadvise), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_dirty_purged,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_dirty.purged), uint64_t)
+
+CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_npurge,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_muzzy.npurge), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_nmadvise,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_muzzy.nmadvise), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_muzzy_purged,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.decay_muzzy.purged), uint64_t)
+
+CTL_RO_CGEN(config_stats, stats_arenas_i_base,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.base, ATOMIC_RELAXED),
+ size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_internal,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.internal, ATOMIC_RELAXED),
+ size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_metadata_thp,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.metadata_thp,
+ ATOMIC_RELAXED), size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_tcache_bytes,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.tcache_bytes,
+ ATOMIC_RELAXED), size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_resident,
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.resident, ATOMIC_RELAXED),
+ size_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_small_allocated,
- ctl_stats.arenas[mib[2]].allocated_small, size_t)
+ arenas_i(mib[2])->astats->allocated_small, size_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_small_nmalloc,
- ctl_stats.arenas[mib[2]].nmalloc_small, uint64_t)
+ arenas_i(mib[2])->astats->nmalloc_small, uint64_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_small_ndalloc,
- ctl_stats.arenas[mib[2]].ndalloc_small, uint64_t)
+ arenas_i(mib[2])->astats->ndalloc_small, uint64_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_small_nrequests,
- ctl_stats.arenas[mib[2]].nrequests_small, uint64_t)
+ arenas_i(mib[2])->astats->nrequests_small, uint64_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_large_allocated,
- ctl_stats.arenas[mib[2]].astats.allocated_large, size_t)
+ atomic_load_zu(&arenas_i(mib[2])->astats->astats.allocated_large,
+ ATOMIC_RELAXED), size_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_large_nmalloc,
- ctl_stats.arenas[mib[2]].astats.nmalloc_large, uint64_t)
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.nmalloc_large), uint64_t)
CTL_RO_CGEN(config_stats, stats_arenas_i_large_ndalloc,
- ctl_stats.arenas[mib[2]].astats.ndalloc_large, uint64_t)
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.ndalloc_large), uint64_t)
+/*
+ * Note: "nmalloc" here instead of "nrequests" in the read. This is intentional.
+ */
CTL_RO_CGEN(config_stats, stats_arenas_i_large_nrequests,
- ctl_stats.arenas[mib[2]].astats.nrequests_large, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_huge_allocated,
- ctl_stats.arenas[mib[2]].astats.allocated_huge, size_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_huge_nmalloc,
- ctl_stats.arenas[mib[2]].astats.nmalloc_huge, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_huge_ndalloc,
- ctl_stats.arenas[mib[2]].astats.ndalloc_huge, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_huge_nrequests,
- ctl_stats.arenas[mib[2]].astats.nmalloc_huge, uint64_t) /* Intentional. */
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->astats.nmalloc_large), uint64_t) /* Intentional. */
+
+/* Lock profiling related APIs below. */
+#define RO_MUTEX_CTL_GEN(n, l) \
+CTL_RO_CGEN(config_stats, stats_##n##_num_ops, \
+ l.n_lock_ops, uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_num_wait, \
+ l.n_wait_times, uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_num_spin_acq, \
+ l.n_spin_acquired, uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_num_owner_switch, \
+ l.n_owner_switches, uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_total_wait_time, \
+ nstime_ns(&l.tot_wait_time), uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_max_wait_time, \
+ nstime_ns(&l.max_wait_time), uint64_t) \
+CTL_RO_CGEN(config_stats, stats_##n##_max_num_thds, \
+ l.max_n_thds, uint32_t)
+
+/* Global mutexes. */
+#define OP(mtx) \
+ RO_MUTEX_CTL_GEN(mutexes_##mtx, \
+ ctl_stats->mutex_prof_data[global_prof_mutex_##mtx])
+MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+
+/* Per arena mutexes */
+#define OP(mtx) RO_MUTEX_CTL_GEN(arenas_i_mutexes_##mtx, \
+ arenas_i(mib[2])->astats->astats.mutex_prof_data[arena_prof_mutex_##mtx])
+MUTEX_PROF_ARENA_MUTEXES
+#undef OP
+
+/* tcache bin mutex */
+RO_MUTEX_CTL_GEN(arenas_i_bins_j_mutex,
+ arenas_i(mib[2])->astats->bstats[mib[4]].mutex_data)
+#undef RO_MUTEX_CTL_GEN
+
+/* Resets all mutex stats, including global, arena and bin mutexes. */
+static int
+stats_mutexes_reset_ctl(tsd_t *tsd, const size_t *mib, size_t miblen,
+ void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
+ if (!config_stats) {
+ return ENOENT;
+ }
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nmalloc,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].nmalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_ndalloc,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].ndalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nrequests,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].nrequests, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curregs,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].curregs, size_t)
-CTL_RO_CGEN(config_stats && config_tcache, stats_arenas_i_bins_j_nfills,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].nfills, uint64_t)
-CTL_RO_CGEN(config_stats && config_tcache, stats_arenas_i_bins_j_nflushes,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].nflushes, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nruns,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].nruns, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nreruns,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].reruns, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curruns,
- ctl_stats.arenas[mib[2]].bstats[mib[4]].curruns, size_t)
+ tsdn_t *tsdn = tsd_tsdn(tsd);
-static const ctl_named_node_t *
-stats_arenas_i_bins_j_index(const size_t *mib, size_t miblen, size_t j)
-{
+#define MUTEX_PROF_RESET(mtx) \
+ malloc_mutex_lock(tsdn, &mtx); \
+ malloc_mutex_prof_data_reset(tsdn, &mtx); \
+ malloc_mutex_unlock(tsdn, &mtx);
- if (j > NBINS)
- return (NULL);
- return (super_stats_arenas_i_bins_j_node);
-}
+ /* Global mutexes: ctl and prof. */
+ MUTEX_PROF_RESET(ctl_mtx);
+ if (have_background_thread) {
+ MUTEX_PROF_RESET(background_thread_lock);
+ }
+ if (config_prof && opt_prof) {
+ MUTEX_PROF_RESET(bt2gctx_mtx);
+ }
-CTL_RO_CGEN(config_stats, stats_arenas_i_lruns_j_nmalloc,
- ctl_stats.arenas[mib[2]].lstats[mib[4]].nmalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_lruns_j_ndalloc,
- ctl_stats.arenas[mib[2]].lstats[mib[4]].ndalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_lruns_j_nrequests,
- ctl_stats.arenas[mib[2]].lstats[mib[4]].nrequests, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_lruns_j_curruns,
- ctl_stats.arenas[mib[2]].lstats[mib[4]].curruns, size_t)
-static const ctl_named_node_t *
-stats_arenas_i_lruns_j_index(const size_t *mib, size_t miblen, size_t j)
-{
+ /* Per arena mutexes. */
+ unsigned n = narenas_total_get();
- if (j > nlclasses)
- return (NULL);
- return (super_stats_arenas_i_lruns_j_node);
+ for (unsigned i = 0; i < n; i++) {
+ arena_t *arena = arena_get(tsdn, i, false);
+ if (!arena) {
+ continue;
+ }
+ MUTEX_PROF_RESET(arena->large_mtx);
+ MUTEX_PROF_RESET(arena->extent_avail_mtx);
+ MUTEX_PROF_RESET(arena->extents_dirty.mtx);
+ MUTEX_PROF_RESET(arena->extents_muzzy.mtx);
+ MUTEX_PROF_RESET(arena->extents_retained.mtx);
+ MUTEX_PROF_RESET(arena->decay_dirty.mtx);
+ MUTEX_PROF_RESET(arena->decay_muzzy.mtx);
+ MUTEX_PROF_RESET(arena->tcache_ql_mtx);
+ MUTEX_PROF_RESET(arena->base->mtx);
+
+ for (szind_t i = 0; i < NBINS; i++) {
+ bin_t *bin = &arena->bins[i];
+ MUTEX_PROF_RESET(bin->lock);
+ }
+ }
+#undef MUTEX_PROF_RESET
+ return 0;
}
-CTL_RO_CGEN(config_stats, stats_arenas_i_hchunks_j_nmalloc,
- ctl_stats.arenas[mib[2]].hstats[mib[4]].nmalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_hchunks_j_ndalloc,
- ctl_stats.arenas[mib[2]].hstats[mib[4]].ndalloc, uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_hchunks_j_nrequests,
- ctl_stats.arenas[mib[2]].hstats[mib[4]].nmalloc, /* Intentional. */
- uint64_t)
-CTL_RO_CGEN(config_stats, stats_arenas_i_hchunks_j_curhchunks,
- ctl_stats.arenas[mib[2]].hstats[mib[4]].curhchunks, size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nmalloc,
+ arenas_i(mib[2])->astats->bstats[mib[4]].nmalloc, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_ndalloc,
+ arenas_i(mib[2])->astats->bstats[mib[4]].ndalloc, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nrequests,
+ arenas_i(mib[2])->astats->bstats[mib[4]].nrequests, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curregs,
+ arenas_i(mib[2])->astats->bstats[mib[4]].curregs, size_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nfills,
+ arenas_i(mib[2])->astats->bstats[mib[4]].nfills, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nflushes,
+ arenas_i(mib[2])->astats->bstats[mib[4]].nflushes, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nslabs,
+ arenas_i(mib[2])->astats->bstats[mib[4]].nslabs, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_nreslabs,
+ arenas_i(mib[2])->astats->bstats[mib[4]].reslabs, uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_bins_j_curslabs,
+ arenas_i(mib[2])->astats->bstats[mib[4]].curslabs, size_t)
static const ctl_named_node_t *
-stats_arenas_i_hchunks_j_index(const size_t *mib, size_t miblen, size_t j)
-{
+stats_arenas_i_bins_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen,
+ size_t j) {
+ if (j > NBINS) {
+ return NULL;
+ }
+ return super_stats_arenas_i_bins_j_node;
+}
- if (j > nhclasses)
- return (NULL);
- return (super_stats_arenas_i_hchunks_j_node);
+CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nmalloc,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->lstats[mib[4]].nmalloc), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_ndalloc,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->lstats[mib[4]].ndalloc), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_nrequests,
+ ctl_arena_stats_read_u64(
+ &arenas_i(mib[2])->astats->lstats[mib[4]].nrequests), uint64_t)
+CTL_RO_CGEN(config_stats, stats_arenas_i_lextents_j_curlextents,
+ arenas_i(mib[2])->astats->lstats[mib[4]].curlextents, size_t)
+
+static const ctl_named_node_t *
+stats_arenas_i_lextents_j_index(tsdn_t *tsdn, const size_t *mib, size_t miblen,
+ size_t j) {
+ if (j > NSIZES - NBINS) {
+ return NULL;
+ }
+ return super_stats_arenas_i_lextents_j_node;
}
static const ctl_named_node_t *
-stats_arenas_i_index(const size_t *mib, size_t miblen, size_t i)
-{
- const ctl_named_node_t * ret;
+stats_arenas_i_index(tsdn_t *tsdn, const size_t *mib, size_t miblen, size_t i) {
+ const ctl_named_node_t *ret;
+ size_t a;
- malloc_mutex_lock(&ctl_mtx);
- if (i > ctl_stats.narenas || !ctl_stats.arenas[i].initialized) {
+ malloc_mutex_lock(tsdn, &ctl_mtx);
+ a = arenas_i2a_impl(i, true, true);
+ if (a == UINT_MAX || !ctl_arenas->arenas[a]->initialized) {
ret = NULL;
goto label_return;
}
ret = super_stats_arenas_i_node;
label_return:
- malloc_mutex_unlock(&ctl_mtx);
- return (ret);
+ malloc_mutex_unlock(tsdn, &ctl_mtx);
+ return ret;
}
diff --git a/deps/jemalloc/src/div.c b/deps/jemalloc/src/div.c
new file mode 100644
index 000000000..808892a13
--- /dev/null
+++ b/deps/jemalloc/src/div.c
@@ -0,0 +1,55 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+
+#include "jemalloc/internal/div.h"
+
+#include "jemalloc/internal/assert.h"
+
+/*
+ * Suppose we have n = q * d, all integers. We know n and d, and want q = n / d.
+ *
+ * For any k, we have (here, all division is exact; not C-style rounding):
+ * floor(ceil(2^k / d) * n / 2^k) = floor((2^k + r) / d * n / 2^k), where
+ * r = (-2^k) mod d.
+ *
+ * Expanding this out:
+ * ... = floor(2^k / d * n / 2^k + r / d * n / 2^k)
+ * = floor(n / d + (r / d) * (n / 2^k)).
+ *
+ * The fractional part of n / d is 0 (because of the assumption that d divides n
+ * exactly), so we have:
+ * ... = n / d + floor((r / d) * (n / 2^k))
+ *
+ * So that our initial expression is equal to the quantity we seek, so long as
+ * (r / d) * (n / 2^k) < 1.
+ *
+ * r is a remainder mod d, so r < d and r / d < 1 always. We can make
+ * n / 2 ^ k < 1 by setting k = 32. This gets us a value of magic that works.
+ */
+
+void
+div_init(div_info_t *div_info, size_t d) {
+ /* Nonsensical. */
+ assert(d != 0);
+ /*
+ * This would make the value of magic too high to fit into a uint32_t
+ * (we would want magic = 2^32 exactly). This would mess with code gen
+ * on 32-bit machines.
+ */
+ assert(d != 1);
+
+ uint64_t two_to_k = ((uint64_t)1 << 32);
+ uint32_t magic = (uint32_t)(two_to_k / d);
+
+ /*
+ * We want magic = ceil(2^k / d), but C gives us floor. We have to
+ * increment it unless the result was exact (i.e. unless d is a power of
+ * two).
+ */
+ if (two_to_k % d != 0) {
+ magic++;
+ }
+ div_info->magic = magic;
+#ifdef JEMALLOC_DEBUG
+ div_info->d = d;
+#endif
+}
diff --git a/deps/jemalloc/src/extent.c b/deps/jemalloc/src/extent.c
index 13f94411c..09d6d7718 100644
--- a/deps/jemalloc/src/extent.c
+++ b/deps/jemalloc/src/extent.c
@@ -1,53 +1,2177 @@
-#define JEMALLOC_EXTENT_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_EXTENT_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/ph.h"
+#include "jemalloc/internal/rtree.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_pool.h"
+
+/******************************************************************************/
+/* Data. */
+
+rtree_t extents_rtree;
+/* Keyed by the address of the extent_t being protected. */
+mutex_pool_t extent_mutex_pool;
+
+size_t opt_lg_extent_max_active_fit = LG_EXTENT_MAX_ACTIVE_FIT_DEFAULT;
+
+static const bitmap_info_t extents_bitmap_info =
+ BITMAP_INFO_INITIALIZER(NPSIZES+1);
+
+static void *extent_alloc_default(extent_hooks_t *extent_hooks, void *new_addr,
+ size_t size, size_t alignment, bool *zero, bool *commit,
+ unsigned arena_ind);
+static bool extent_dalloc_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, bool committed, unsigned arena_ind);
+static void extent_destroy_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, bool committed, unsigned arena_ind);
+static bool extent_commit_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind);
+static bool extent_commit_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained);
+static bool extent_decommit_default(extent_hooks_t *extent_hooks,
+ void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind);
+#ifdef PAGES_CAN_PURGE_LAZY
+static bool extent_purge_lazy_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind);
+#endif
+static bool extent_purge_lazy_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained);
+#ifdef PAGES_CAN_PURGE_FORCED
+static bool extent_purge_forced_default(extent_hooks_t *extent_hooks,
+ void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind);
+#endif
+static bool extent_purge_forced_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained);
+#ifdef JEMALLOC_MAPS_COALESCE
+static bool extent_split_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t size_a, size_t size_b, bool committed,
+ unsigned arena_ind);
+#endif
+static extent_t *extent_split_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
+ szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b,
+ bool growing_retained);
+#ifdef JEMALLOC_MAPS_COALESCE
+static bool extent_merge_default(extent_hooks_t *extent_hooks, void *addr_a,
+ size_t size_a, void *addr_b, size_t size_b, bool committed,
+ unsigned arena_ind);
+#endif
+static bool extent_merge_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b,
+ bool growing_retained);
+
+const extent_hooks_t extent_hooks_default = {
+ extent_alloc_default,
+ extent_dalloc_default,
+ extent_destroy_default,
+ extent_commit_default,
+ extent_decommit_default
+#ifdef PAGES_CAN_PURGE_LAZY
+ ,
+ extent_purge_lazy_default
+#else
+ ,
+ NULL
+#endif
+#ifdef PAGES_CAN_PURGE_FORCED
+ ,
+ extent_purge_forced_default
+#else
+ ,
+ NULL
+#endif
+#ifdef JEMALLOC_MAPS_COALESCE
+ ,
+ extent_split_default,
+ extent_merge_default
+#endif
+};
+
+/* Used exclusively for gdump triggering. */
+static atomic_zu_t curpages;
+static atomic_zu_t highpages;
+
+/******************************************************************************/
+/*
+ * Function prototypes for static functions that are referenced prior to
+ * definition.
+ */
+
+static void extent_deregister(tsdn_t *tsdn, extent_t *extent);
+static extent_t *extent_recycle(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, void *new_addr,
+ size_t usize, size_t pad, size_t alignment, bool slab, szind_t szind,
+ bool *zero, bool *commit, bool growing_retained);
+static extent_t *extent_try_coalesce(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
+ extent_t *extent, bool *coalesced, bool growing_retained);
+static void extent_record(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extents_t *extents, extent_t *extent,
+ bool growing_retained);
/******************************************************************************/
-JEMALLOC_INLINE_C size_t
-extent_quantize(size_t size)
-{
+ph_gen(UNUSED, extent_avail_, extent_tree_t, extent_t, ph_link,
+ extent_esnead_comp)
+
+typedef enum {
+ lock_result_success,
+ lock_result_failure,
+ lock_result_no_extent
+} lock_result_t;
+
+static lock_result_t
+extent_rtree_leaf_elm_try_lock(tsdn_t *tsdn, rtree_leaf_elm_t *elm,
+ extent_t **result) {
+ extent_t *extent1 = rtree_leaf_elm_extent_read(tsdn, &extents_rtree,
+ elm, true);
+
+ if (extent1 == NULL) {
+ return lock_result_no_extent;
+ }
+ /*
+ * It's possible that the extent changed out from under us, and with it
+ * the leaf->extent mapping. We have to recheck while holding the lock.
+ */
+ extent_lock(tsdn, extent1);
+ extent_t *extent2 = rtree_leaf_elm_extent_read(tsdn,
+ &extents_rtree, elm, true);
+
+ if (extent1 == extent2) {
+ *result = extent1;
+ return lock_result_success;
+ } else {
+ extent_unlock(tsdn, extent1);
+ return lock_result_failure;
+ }
+}
+
+/*
+ * Returns a pool-locked extent_t * if there's one associated with the given
+ * address, and NULL otherwise.
+ */
+static extent_t *
+extent_lock_from_addr(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx, void *addr) {
+ extent_t *ret = NULL;
+ rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, &extents_rtree,
+ rtree_ctx, (uintptr_t)addr, false, false);
+ if (elm == NULL) {
+ return NULL;
+ }
+ lock_result_t lock_result;
+ do {
+ lock_result = extent_rtree_leaf_elm_try_lock(tsdn, elm, &ret);
+ } while (lock_result == lock_result_failure);
+ return ret;
+}
+
+extent_t *
+extent_alloc(tsdn_t *tsdn, arena_t *arena) {
+ malloc_mutex_lock(tsdn, &arena->extent_avail_mtx);
+ extent_t *extent = extent_avail_first(&arena->extent_avail);
+ if (extent == NULL) {
+ malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
+ return base_alloc_extent(tsdn, arena->base);
+ }
+ extent_avail_remove(&arena->extent_avail, extent);
+ malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
+ return extent;
+}
+
+void
+extent_dalloc(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
+ malloc_mutex_lock(tsdn, &arena->extent_avail_mtx);
+ extent_avail_insert(&arena->extent_avail, extent);
+ malloc_mutex_unlock(tsdn, &arena->extent_avail_mtx);
+}
+
+extent_hooks_t *
+extent_hooks_get(arena_t *arena) {
+ return base_extent_hooks_get(arena->base);
+}
+
+extent_hooks_t *
+extent_hooks_set(tsd_t *tsd, arena_t *arena, extent_hooks_t *extent_hooks) {
+ background_thread_info_t *info;
+ if (have_background_thread) {
+ info = arena_background_thread_info_get(arena);
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ }
+ extent_hooks_t *ret = base_extent_hooks_set(arena->base, extent_hooks);
+ if (have_background_thread) {
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ }
+
+ return ret;
+}
+
+static void
+extent_hooks_assure_initialized(arena_t *arena,
+ extent_hooks_t **r_extent_hooks) {
+ if (*r_extent_hooks == EXTENT_HOOKS_INITIALIZER) {
+ *r_extent_hooks = extent_hooks_get(arena);
+ }
+}
+
+#ifndef JEMALLOC_JET
+static
+#endif
+size_t
+extent_size_quantize_floor(size_t size) {
+ size_t ret;
+ pszind_t pind;
+
+ assert(size > 0);
+ assert((size & PAGE_MASK) == 0);
+
+ pind = sz_psz2ind(size - sz_large_pad + 1);
+ if (pind == 0) {
+ /*
+ * Avoid underflow. This short-circuit would also do the right
+ * thing for all sizes in the range for which there are
+ * PAGE-spaced size classes, but it's simplest to just handle
+ * the one case that would cause erroneous results.
+ */
+ return size;
+ }
+ ret = sz_pind2sz(pind - 1) + sz_large_pad;
+ assert(ret <= size);
+ return ret;
+}
+
+#ifndef JEMALLOC_JET
+static
+#endif
+size_t
+extent_size_quantize_ceil(size_t size) {
+ size_t ret;
+
+ assert(size > 0);
+ assert(size - sz_large_pad <= LARGE_MAXCLASS);
+ assert((size & PAGE_MASK) == 0);
+
+ ret = extent_size_quantize_floor(size);
+ if (ret < size) {
+ /*
+ * Skip a quantization that may have an adequately large extent,
+ * because under-sized extents may be mixed in. This only
+ * happens when an unusual size is requested, i.e. for aligned
+ * allocation, and is just one of several places where linear
+ * search would potentially find sufficiently aligned available
+ * memory somewhere lower.
+ */
+ ret = sz_pind2sz(sz_psz2ind(ret - sz_large_pad + 1)) +
+ sz_large_pad;
+ }
+ return ret;
+}
+
+/* Generate pairing heap functions. */
+ph_gen(, extent_heap_, extent_heap_t, extent_t, ph_link, extent_snad_comp)
+
+bool
+extents_init(tsdn_t *tsdn, extents_t *extents, extent_state_t state,
+ bool delay_coalesce) {
+ if (malloc_mutex_init(&extents->mtx, "extents", WITNESS_RANK_EXTENTS,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+ for (unsigned i = 0; i < NPSIZES+1; i++) {
+ extent_heap_new(&extents->heaps[i]);
+ }
+ bitmap_init(extents->bitmap, &extents_bitmap_info, true);
+ extent_list_init(&extents->lru);
+ atomic_store_zu(&extents->npages, 0, ATOMIC_RELAXED);
+ extents->state = state;
+ extents->delay_coalesce = delay_coalesce;
+ return false;
+}
+
+extent_state_t
+extents_state_get(const extents_t *extents) {
+ return extents->state;
+}
+
+size_t
+extents_npages_get(extents_t *extents) {
+ return atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
+}
+
+static void
+extents_insert_locked(tsdn_t *tsdn, extents_t *extents, extent_t *extent) {
+ malloc_mutex_assert_owner(tsdn, &extents->mtx);
+ assert(extent_state_get(extent) == extents->state);
+
+ size_t size = extent_size_get(extent);
+ size_t psz = extent_size_quantize_floor(size);
+ pszind_t pind = sz_psz2ind(psz);
+ if (extent_heap_empty(&extents->heaps[pind])) {
+ bitmap_unset(extents->bitmap, &extents_bitmap_info,
+ (size_t)pind);
+ }
+ extent_heap_insert(&extents->heaps[pind], extent);
+ extent_list_append(&extents->lru, extent);
+ size_t npages = size >> LG_PAGE;
+ /*
+ * All modifications to npages hold the mutex (as asserted above), so we
+ * don't need an atomic fetch-add; we can get by with a load followed by
+ * a store.
+ */
+ size_t cur_extents_npages =
+ atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
+ atomic_store_zu(&extents->npages, cur_extents_npages + npages,
+ ATOMIC_RELAXED);
+}
+
+static void
+extents_remove_locked(tsdn_t *tsdn, extents_t *extents, extent_t *extent) {
+ malloc_mutex_assert_owner(tsdn, &extents->mtx);
+ assert(extent_state_get(extent) == extents->state);
+
+ size_t size = extent_size_get(extent);
+ size_t psz = extent_size_quantize_floor(size);
+ pszind_t pind = sz_psz2ind(psz);
+ extent_heap_remove(&extents->heaps[pind], extent);
+ if (extent_heap_empty(&extents->heaps[pind])) {
+ bitmap_set(extents->bitmap, &extents_bitmap_info,
+ (size_t)pind);
+ }
+ extent_list_remove(&extents->lru, extent);
+ size_t npages = size >> LG_PAGE;
+ /*
+ * As in extents_insert_locked, we hold extents->mtx and so don't need
+ * atomic operations for updating extents->npages.
+ */
+ size_t cur_extents_npages =
+ atomic_load_zu(&extents->npages, ATOMIC_RELAXED);
+ assert(cur_extents_npages >= npages);
+ atomic_store_zu(&extents->npages,
+ cur_extents_npages - (size >> LG_PAGE), ATOMIC_RELAXED);
+}
+
+/*
+ * Find an extent with size [min_size, max_size) to satisfy the alignment
+ * requirement. For each size, try only the first extent in the heap.
+ */
+static extent_t *
+extents_fit_alignment(extents_t *extents, size_t min_size, size_t max_size,
+ size_t alignment) {
+ pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(min_size));
+ pszind_t pind_max = sz_psz2ind(extent_size_quantize_ceil(max_size));
+
+ for (pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap,
+ &extents_bitmap_info, (size_t)pind); i < pind_max; i =
+ (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
+ (size_t)i+1)) {
+ assert(i < NPSIZES);
+ assert(!extent_heap_empty(&extents->heaps[i]));
+ extent_t *extent = extent_heap_first(&extents->heaps[i]);
+ uintptr_t base = (uintptr_t)extent_base_get(extent);
+ size_t candidate_size = extent_size_get(extent);
+ assert(candidate_size >= min_size);
+
+ uintptr_t next_align = ALIGNMENT_CEILING((uintptr_t)base,
+ PAGE_CEILING(alignment));
+ if (base > next_align || base + candidate_size <= next_align) {
+ /* Overflow or not crossing the next alignment. */
+ continue;
+ }
+
+ size_t leadsize = next_align - base;
+ if (candidate_size - leadsize >= min_size) {
+ return extent;
+ }
+ }
+
+ return NULL;
+}
+
+/* Do any-best-fit extent selection, i.e. select any extent that best fits. */
+static extent_t *
+extents_best_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ size_t size) {
+ pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(size));
+ pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
+ (size_t)pind);
+ if (i < NPSIZES+1) {
+ /*
+ * In order to reduce fragmentation, avoid reusing and splitting
+ * large extents for much smaller sizes.
+ */
+ if ((sz_pind2sz(i) >> opt_lg_extent_max_active_fit) > size) {
+ return NULL;
+ }
+ assert(!extent_heap_empty(&extents->heaps[i]));
+ extent_t *extent = extent_heap_first(&extents->heaps[i]);
+ assert(extent_size_get(extent) >= size);
+ return extent;
+ }
+
+ return NULL;
+}
+
+/*
+ * Do first-fit extent selection, i.e. select the oldest/lowest extent that is
+ * large enough.
+ */
+static extent_t *
+extents_first_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ size_t size) {
+ extent_t *ret = NULL;
+
+ pszind_t pind = sz_psz2ind(extent_size_quantize_ceil(size));
+ for (pszind_t i = (pszind_t)bitmap_ffu(extents->bitmap,
+ &extents_bitmap_info, (size_t)pind); i < NPSIZES+1; i =
+ (pszind_t)bitmap_ffu(extents->bitmap, &extents_bitmap_info,
+ (size_t)i+1)) {
+ assert(!extent_heap_empty(&extents->heaps[i]));
+ extent_t *extent = extent_heap_first(&extents->heaps[i]);
+ assert(extent_size_get(extent) >= size);
+ if (ret == NULL || extent_snad_comp(extent, ret) < 0) {
+ ret = extent;
+ }
+ if (i == NPSIZES) {
+ break;
+ }
+ assert(i < NPSIZES);
+ }
+
+ return ret;
+}
+
+/*
+ * Do {best,first}-fit extent selection, where the selection policy choice is
+ * based on extents->delay_coalesce. Best-fit selection requires less
+ * searching, but its layout policy is less stable and may cause higher virtual
+ * memory fragmentation as a side effect.
+ */
+static extent_t *
+extents_fit_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ size_t esize, size_t alignment) {
+ malloc_mutex_assert_owner(tsdn, &extents->mtx);
+
+ size_t max_size = esize + PAGE_CEILING(alignment) - PAGE;
+ /* Beware size_t wrap-around. */
+ if (max_size < esize) {
+ return NULL;
+ }
+
+ extent_t *extent = extents->delay_coalesce ?
+ extents_best_fit_locked(tsdn, arena, extents, max_size) :
+ extents_first_fit_locked(tsdn, arena, extents, max_size);
+
+ if (alignment > PAGE && extent == NULL) {
+ /*
+ * max_size guarantees the alignment requirement but is rather
+ * pessimistic. Next we try to satisfy the aligned allocation
+ * with sizes in [esize, max_size).
+ */
+ extent = extents_fit_alignment(extents, esize, max_size,
+ alignment);
+ }
+
+ return extent;
+}
+
+static bool
+extent_try_delayed_coalesce(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
+ extent_t *extent) {
+ extent_state_set(extent, extent_state_active);
+ bool coalesced;
+ extent = extent_try_coalesce(tsdn, arena, r_extent_hooks, rtree_ctx,
+ extents, extent, &coalesced, false);
+ extent_state_set(extent, extents_state_get(extents));
+
+ if (!coalesced) {
+ return true;
+ }
+ extents_insert_locked(tsdn, extents, extent);
+ return false;
+}
+
+extent_t *
+extents_alloc(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
+ assert(size + pad != 0);
+ assert(alignment != 0);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extent_t *extent = extent_recycle(tsdn, arena, r_extent_hooks, extents,
+ new_addr, size, pad, alignment, slab, szind, zero, commit, false);
+ assert(extent == NULL || extent_dumpable_get(extent));
+ return extent;
+}
+
+void
+extents_dalloc(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, extent_t *extent) {
+ assert(extent_base_get(extent) != NULL);
+ assert(extent_size_get(extent) != 0);
+ assert(extent_dumpable_get(extent));
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extent_addr_set(extent, extent_base_get(extent));
+ extent_zeroed_set(extent, false);
+
+ extent_record(tsdn, arena, r_extent_hooks, extents, extent, false);
+}
+
+extent_t *
+extents_evict(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, size_t npages_min) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ malloc_mutex_lock(tsdn, &extents->mtx);
+
+ /*
+ * Get the LRU coalesced extent, if any. If coalescing was delayed,
+ * the loop will iterate until the LRU extent is fully coalesced.
+ */
+ extent_t *extent;
+ while (true) {
+ /* Get the LRU extent, if any. */
+ extent = extent_list_first(&extents->lru);
+ if (extent == NULL) {
+ goto label_return;
+ }
+ /* Check the eviction limit. */
+ size_t extents_npages = atomic_load_zu(&extents->npages,
+ ATOMIC_RELAXED);
+ if (extents_npages <= npages_min) {
+ extent = NULL;
+ goto label_return;
+ }
+ extents_remove_locked(tsdn, extents, extent);
+ if (!extents->delay_coalesce) {
+ break;
+ }
+ /* Try to coalesce. */
+ if (extent_try_delayed_coalesce(tsdn, arena, r_extent_hooks,
+ rtree_ctx, extents, extent)) {
+ break;
+ }
+ /*
+ * The LRU extent was just coalesced and the result placed in
+ * the LRU at its neighbor's position. Start over.
+ */
+ }
+
+ /*
+ * Either mark the extent active or deregister it to protect against
+ * concurrent operations.
+ */
+ switch (extents_state_get(extents)) {
+ case extent_state_active:
+ not_reached();
+ case extent_state_dirty:
+ case extent_state_muzzy:
+ extent_state_set(extent, extent_state_active);
+ break;
+ case extent_state_retained:
+ extent_deregister(tsdn, extent);
+ break;
+ default:
+ not_reached();
+ }
+
+label_return:
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+ return extent;
+}
+
+static void
+extents_leak(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, extent_t *extent, bool growing_retained) {
+ /*
+ * Leak extent after making sure its pages have already been purged, so
+ * that this is only a virtual memory leak.
+ */
+ if (extents_state_get(extents) == extent_state_dirty) {
+ if (extent_purge_lazy_impl(tsdn, arena, r_extent_hooks,
+ extent, 0, extent_size_get(extent), growing_retained)) {
+ extent_purge_forced_impl(tsdn, arena, r_extent_hooks,
+ extent, 0, extent_size_get(extent),
+ growing_retained);
+ }
+ }
+ extent_dalloc(tsdn, arena, extent);
+}
+
+void
+extents_prefork(tsdn_t *tsdn, extents_t *extents) {
+ malloc_mutex_prefork(tsdn, &extents->mtx);
+}
+
+void
+extents_postfork_parent(tsdn_t *tsdn, extents_t *extents) {
+ malloc_mutex_postfork_parent(tsdn, &extents->mtx);
+}
+
+void
+extents_postfork_child(tsdn_t *tsdn, extents_t *extents) {
+ malloc_mutex_postfork_child(tsdn, &extents->mtx);
+}
+
+static void
+extent_deactivate_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ extent_t *extent) {
+ assert(extent_arena_get(extent) == arena);
+ assert(extent_state_get(extent) == extent_state_active);
+
+ extent_state_set(extent, extents_state_get(extents));
+ extents_insert_locked(tsdn, extents, extent);
+}
+
+static void
+extent_deactivate(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ extent_t *extent) {
+ malloc_mutex_lock(tsdn, &extents->mtx);
+ extent_deactivate_locked(tsdn, arena, extents, extent);
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+}
+
+static void
+extent_activate_locked(tsdn_t *tsdn, arena_t *arena, extents_t *extents,
+ extent_t *extent) {
+ assert(extent_arena_get(extent) == arena);
+ assert(extent_state_get(extent) == extents_state_get(extents));
+
+ extents_remove_locked(tsdn, extents, extent);
+ extent_state_set(extent, extent_state_active);
+}
+
+static bool
+extent_rtree_leaf_elms_lookup(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx,
+ const extent_t *extent, bool dependent, bool init_missing,
+ rtree_leaf_elm_t **r_elm_a, rtree_leaf_elm_t **r_elm_b) {
+ *r_elm_a = rtree_leaf_elm_lookup(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_base_get(extent), dependent, init_missing);
+ if (!dependent && *r_elm_a == NULL) {
+ return true;
+ }
+ assert(*r_elm_a != NULL);
+
+ *r_elm_b = rtree_leaf_elm_lookup(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_last_get(extent), dependent, init_missing);
+ if (!dependent && *r_elm_b == NULL) {
+ return true;
+ }
+ assert(*r_elm_b != NULL);
+
+ return false;
+}
+
+static void
+extent_rtree_write_acquired(tsdn_t *tsdn, rtree_leaf_elm_t *elm_a,
+ rtree_leaf_elm_t *elm_b, extent_t *extent, szind_t szind, bool slab) {
+ rtree_leaf_elm_write(tsdn, &extents_rtree, elm_a, extent, szind, slab);
+ if (elm_b != NULL) {
+ rtree_leaf_elm_write(tsdn, &extents_rtree, elm_b, extent, szind,
+ slab);
+ }
+}
+
+static void
+extent_interior_register(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx, extent_t *extent,
+ szind_t szind) {
+ assert(extent_slab_get(extent));
+
+ /* Register interior. */
+ for (size_t i = 1; i < (extent_size_get(extent) >> LG_PAGE) - 1; i++) {
+ rtree_write(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_base_get(extent) + (uintptr_t)(i <<
+ LG_PAGE), extent, szind, true);
+ }
+}
+
+static void
+extent_gdump_add(tsdn_t *tsdn, const extent_t *extent) {
+ cassert(config_prof);
+ /* prof_gdump() requirement. */
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ if (opt_prof && extent_state_get(extent) == extent_state_active) {
+ size_t nadd = extent_size_get(extent) >> LG_PAGE;
+ size_t cur = atomic_fetch_add_zu(&curpages, nadd,
+ ATOMIC_RELAXED) + nadd;
+ size_t high = atomic_load_zu(&highpages, ATOMIC_RELAXED);
+ while (cur > high && !atomic_compare_exchange_weak_zu(
+ &highpages, &high, cur, ATOMIC_RELAXED, ATOMIC_RELAXED)) {
+ /*
+ * Don't refresh cur, because it may have decreased
+ * since this thread lost the highpages update race.
+ * Note that high is updated in case of CAS failure.
+ */
+ }
+ if (cur > high && prof_gdump_get_unlocked()) {
+ prof_gdump(tsdn);
+ }
+ }
+}
+
+static void
+extent_gdump_sub(tsdn_t *tsdn, const extent_t *extent) {
+ cassert(config_prof);
+
+ if (opt_prof && extent_state_get(extent) == extent_state_active) {
+ size_t nsub = extent_size_get(extent) >> LG_PAGE;
+ assert(atomic_load_zu(&curpages, ATOMIC_RELAXED) >= nsub);
+ atomic_fetch_sub_zu(&curpages, nsub, ATOMIC_RELAXED);
+ }
+}
+
+static bool
+extent_register_impl(tsdn_t *tsdn, extent_t *extent, bool gdump_add) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ rtree_leaf_elm_t *elm_a, *elm_b;
+
+ /*
+ * We need to hold the lock to protect against a concurrent coalesce
+ * operation that sees us in a partial state.
+ */
+ extent_lock(tsdn, extent);
+
+ if (extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, extent, false, true,
+ &elm_a, &elm_b)) {
+ return true;
+ }
+
+ szind_t szind = extent_szind_get_maybe_invalid(extent);
+ bool slab = extent_slab_get(extent);
+ extent_rtree_write_acquired(tsdn, elm_a, elm_b, extent, szind, slab);
+ if (slab) {
+ extent_interior_register(tsdn, rtree_ctx, extent, szind);
+ }
+
+ extent_unlock(tsdn, extent);
+
+ if (config_prof && gdump_add) {
+ extent_gdump_add(tsdn, extent);
+ }
+
+ return false;
+}
+
+static bool
+extent_register(tsdn_t *tsdn, extent_t *extent) {
+ return extent_register_impl(tsdn, extent, true);
+}
+
+static bool
+extent_register_no_gdump_add(tsdn_t *tsdn, extent_t *extent) {
+ return extent_register_impl(tsdn, extent, false);
+}
+
+static void
+extent_reregister(tsdn_t *tsdn, extent_t *extent) {
+ bool err = extent_register(tsdn, extent);
+ assert(!err);
+}
+
+/*
+ * Removes all pointers to the given extent from the global rtree indices for
+ * its interior. This is relevant for slab extents, for which we need to do
+ * metadata lookups at places other than the head of the extent. We deregister
+ * on the interior, then, when an extent moves from being an active slab to an
+ * inactive state.
+ */
+static void
+extent_interior_deregister(tsdn_t *tsdn, rtree_ctx_t *rtree_ctx,
+ extent_t *extent) {
+ size_t i;
+
+ assert(extent_slab_get(extent));
+
+ for (i = 1; i < (extent_size_get(extent) >> LG_PAGE) - 1; i++) {
+ rtree_clear(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_base_get(extent) + (uintptr_t)(i <<
+ LG_PAGE));
+ }
+}
+
+/*
+ * Removes all pointers to the given extent from the global rtree.
+ */
+static void
+extent_deregister_impl(tsdn_t *tsdn, extent_t *extent, bool gdump) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ rtree_leaf_elm_t *elm_a, *elm_b;
+ extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, extent, true, false,
+ &elm_a, &elm_b);
+
+ extent_lock(tsdn, extent);
+
+ extent_rtree_write_acquired(tsdn, elm_a, elm_b, NULL, NSIZES, false);
+ if (extent_slab_get(extent)) {
+ extent_interior_deregister(tsdn, rtree_ctx, extent);
+ extent_slab_set(extent, false);
+ }
+
+ extent_unlock(tsdn, extent);
+
+ if (config_prof && gdump) {
+ extent_gdump_sub(tsdn, extent);
+ }
+}
+
+static void
+extent_deregister(tsdn_t *tsdn, extent_t *extent) {
+ extent_deregister_impl(tsdn, extent, true);
+}
+
+static void
+extent_deregister_no_gdump_sub(tsdn_t *tsdn, extent_t *extent) {
+ extent_deregister_impl(tsdn, extent, false);
+}
+
+/*
+ * Tries to find and remove an extent from extents that can be used for the
+ * given allocation request.
+ */
+static extent_t *
+extent_recycle_extract(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
+ void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
+ bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+ assert(alignment > 0);
+ if (config_debug && new_addr != NULL) {
+ /*
+ * Non-NULL new_addr has two use cases:
+ *
+ * 1) Recycle a known-extant extent, e.g. during purging.
+ * 2) Perform in-place expanding reallocation.
+ *
+ * Regardless of use case, new_addr must either refer to a
+ * non-existing extent, or to the base of an extant extent,
+ * since only active slabs support interior lookups (which of
+ * course cannot be recycled).
+ */
+ assert(PAGE_ADDR2BASE(new_addr) == new_addr);
+ assert(pad == 0);
+ assert(alignment <= PAGE);
+ }
+
+ size_t esize = size + pad;
+ malloc_mutex_lock(tsdn, &extents->mtx);
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+ extent_t *extent;
+ if (new_addr != NULL) {
+ extent = extent_lock_from_addr(tsdn, rtree_ctx, new_addr);
+ if (extent != NULL) {
+ /*
+ * We might null-out extent to report an error, but we
+ * still need to unlock the associated mutex after.
+ */
+ extent_t *unlock_extent = extent;
+ assert(extent_base_get(extent) == new_addr);
+ if (extent_arena_get(extent) != arena ||
+ extent_size_get(extent) < esize ||
+ extent_state_get(extent) !=
+ extents_state_get(extents)) {
+ extent = NULL;
+ }
+ extent_unlock(tsdn, unlock_extent);
+ }
+ } else {
+ extent = extents_fit_locked(tsdn, arena, extents, esize,
+ alignment);
+ }
+ if (extent == NULL) {
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+ return NULL;
+ }
+
+ extent_activate_locked(tsdn, arena, extents, extent);
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+
+ return extent;
+}
+
+/*
+ * Given an allocation request and an extent guaranteed to be able to satisfy
+ * it, this splits off lead and trail extents, leaving extent pointing to an
+ * extent satisfying the allocation.
+ * This function doesn't put lead or trail into any extents_t; it's the caller's
+ * job to ensure that they can be reused.
+ */
+typedef enum {
+ /*
+ * Split successfully. lead, extent, and trail, are modified to extents
+ * describing the ranges before, in, and after the given allocation.
+ */
+ extent_split_interior_ok,
+ /*
+ * The extent can't satisfy the given allocation request. None of the
+ * input extent_t *s are touched.
+ */
+ extent_split_interior_cant_alloc,
+ /*
+ * In a potentially invalid state. Must leak (if *to_leak is non-NULL),
+ * and salvage what's still salvageable (if *to_salvage is non-NULL).
+ * None of lead, extent, or trail are valid.
+ */
+ extent_split_interior_error
+} extent_split_interior_result_t;
+
+static extent_split_interior_result_t
+extent_split_interior(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx,
+ /* The result of splitting, in case of success. */
+ extent_t **extent, extent_t **lead, extent_t **trail,
+ /* The mess to clean up, in case of error. */
+ extent_t **to_leak, extent_t **to_salvage,
+ void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
+ szind_t szind, bool growing_retained) {
+ size_t esize = size + pad;
+ size_t leadsize = ALIGNMENT_CEILING((uintptr_t)extent_base_get(*extent),
+ PAGE_CEILING(alignment)) - (uintptr_t)extent_base_get(*extent);
+ assert(new_addr == NULL || leadsize == 0);
+ if (extent_size_get(*extent) < leadsize + esize) {
+ return extent_split_interior_cant_alloc;
+ }
+ size_t trailsize = extent_size_get(*extent) - leadsize - esize;
+
+ *lead = NULL;
+ *trail = NULL;
+ *to_leak = NULL;
+ *to_salvage = NULL;
+
+ /* Split the lead. */
+ if (leadsize != 0) {
+ *lead = *extent;
+ *extent = extent_split_impl(tsdn, arena, r_extent_hooks,
+ *lead, leadsize, NSIZES, false, esize + trailsize, szind,
+ slab, growing_retained);
+ if (*extent == NULL) {
+ *to_leak = *lead;
+ *lead = NULL;
+ return extent_split_interior_error;
+ }
+ }
+
+ /* Split the trail. */
+ if (trailsize != 0) {
+ *trail = extent_split_impl(tsdn, arena, r_extent_hooks, *extent,
+ esize, szind, slab, trailsize, NSIZES, false,
+ growing_retained);
+ if (*trail == NULL) {
+ *to_leak = *extent;
+ *to_salvage = *lead;
+ *lead = NULL;
+ *extent = NULL;
+ return extent_split_interior_error;
+ }
+ }
+
+ if (leadsize == 0 && trailsize == 0) {
+ /*
+ * Splitting causes szind to be set as a side effect, but no
+ * splitting occurred.
+ */
+ extent_szind_set(*extent, szind);
+ if (szind != NSIZES) {
+ rtree_szind_slab_update(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_addr_get(*extent), szind, slab);
+ if (slab && extent_size_get(*extent) > PAGE) {
+ rtree_szind_slab_update(tsdn, &extents_rtree,
+ rtree_ctx,
+ (uintptr_t)extent_past_get(*extent) -
+ (uintptr_t)PAGE, szind, slab);
+ }
+ }
+ }
+
+ return extent_split_interior_ok;
+}
+
+/*
+ * This fulfills the indicated allocation request out of the given extent (which
+ * the caller should have ensured was big enough). If there's any unused space
+ * before or after the resulting allocation, that space is given its own extent
+ * and put back into extents.
+ */
+static extent_t *
+extent_recycle_split(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
+ void *new_addr, size_t size, size_t pad, size_t alignment, bool slab,
+ szind_t szind, extent_t *extent, bool growing_retained) {
+ extent_t *lead;
+ extent_t *trail;
+ extent_t *to_leak;
+ extent_t *to_salvage;
+
+ extent_split_interior_result_t result = extent_split_interior(
+ tsdn, arena, r_extent_hooks, rtree_ctx, &extent, &lead, &trail,
+ &to_leak, &to_salvage, new_addr, size, pad, alignment, slab, szind,
+ growing_retained);
+
+ if (result == extent_split_interior_ok) {
+ if (lead != NULL) {
+ extent_deactivate(tsdn, arena, extents, lead);
+ }
+ if (trail != NULL) {
+ extent_deactivate(tsdn, arena, extents, trail);
+ }
+ return extent;
+ } else {
+ /*
+ * We should have picked an extent that was large enough to
+ * fulfill our allocation request.
+ */
+ assert(result == extent_split_interior_error);
+ if (to_salvage != NULL) {
+ extent_deregister(tsdn, to_salvage);
+ }
+ if (to_leak != NULL) {
+ void *leak = extent_base_get(to_leak);
+ extent_deregister_no_gdump_sub(tsdn, to_leak);
+ extents_leak(tsdn, arena, r_extent_hooks, extents,
+ to_leak, growing_retained);
+ assert(extent_lock_from_addr(tsdn, rtree_ctx, leak)
+ == NULL);
+ }
+ return NULL;
+ }
+ unreachable();
+}
+
+/*
+ * Tries to satisfy the given allocation request by reusing one of the extents
+ * in the given extents_t.
+ */
+static extent_t *
+extent_recycle(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit,
+ bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+ assert(new_addr == NULL || !slab);
+ assert(pad == 0 || !slab);
+ assert(!*zero || !slab);
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ extent_t *extent = extent_recycle_extract(tsdn, arena, r_extent_hooks,
+ rtree_ctx, extents, new_addr, size, pad, alignment, slab,
+ growing_retained);
+ if (extent == NULL) {
+ return NULL;
+ }
+
+ extent = extent_recycle_split(tsdn, arena, r_extent_hooks, rtree_ctx,
+ extents, new_addr, size, pad, alignment, slab, szind, extent,
+ growing_retained);
+ if (extent == NULL) {
+ return NULL;
+ }
+
+ if (*commit && !extent_committed_get(extent)) {
+ if (extent_commit_impl(tsdn, arena, r_extent_hooks, extent,
+ 0, extent_size_get(extent), growing_retained)) {
+ extent_record(tsdn, arena, r_extent_hooks, extents,
+ extent, growing_retained);
+ return NULL;
+ }
+ extent_zeroed_set(extent, true);
+ }
+
+ if (extent_committed_get(extent)) {
+ *commit = true;
+ }
+ if (extent_zeroed_get(extent)) {
+ *zero = true;
+ }
+
+ if (pad != 0) {
+ extent_addr_randomize(tsdn, extent, alignment);
+ }
+ assert(extent_state_get(extent) == extent_state_active);
+ if (slab) {
+ extent_slab_set(extent, slab);
+ extent_interior_register(tsdn, rtree_ctx, extent, szind);
+ }
+
+ if (*zero) {
+ void *addr = extent_base_get(extent);
+ size_t size = extent_size_get(extent);
+ if (!extent_zeroed_get(extent)) {
+ if (pages_purge_forced(addr, size)) {
+ memset(addr, 0, size);
+ }
+ } else if (config_debug) {
+ size_t *p = (size_t *)(uintptr_t)addr;
+ for (size_t i = 0; i < size / sizeof(size_t); i++) {
+ assert(p[i] == 0);
+ }
+ }
+ }
+ return extent;
+}
+
+/*
+ * If the caller specifies (!*zero), it is still possible to receive zeroed
+ * memory, in which case *zero is toggled to true. arena_extent_alloc() takes
+ * advantage of this to avoid demanding zeroed extents, but taking advantage of
+ * them if they are returned.
+ */
+static void *
+extent_alloc_core(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size,
+ size_t alignment, bool *zero, bool *commit, dss_prec_t dss_prec) {
+ void *ret;
+
+ assert(size != 0);
+ assert(alignment != 0);
+
+ /* "primary" dss. */
+ if (have_dss && dss_prec == dss_prec_primary && (ret =
+ extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
+ commit)) != NULL) {
+ return ret;
+ }
+ /* mmap. */
+ if ((ret = extent_alloc_mmap(new_addr, size, alignment, zero, commit))
+ != NULL) {
+ return ret;
+ }
+ /* "secondary" dss. */
+ if (have_dss && dss_prec == dss_prec_secondary && (ret =
+ extent_alloc_dss(tsdn, arena, new_addr, size, alignment, zero,
+ commit)) != NULL) {
+ return ret;
+ }
+
+ /* All strategies for allocation failed. */
+ return NULL;
+}
+
+static void *
+extent_alloc_default_impl(tsdn_t *tsdn, arena_t *arena, void *new_addr,
+ size_t size, size_t alignment, bool *zero, bool *commit) {
+ void *ret = extent_alloc_core(tsdn, arena, new_addr, size, alignment, zero,
+ commit, (dss_prec_t)atomic_load_u(&arena->dss_prec,
+ ATOMIC_RELAXED));
+ if (have_madvise_huge && ret) {
+ pages_set_thp_state(ret, size);
+ }
+ return ret;
+}
+
+static void *
+extent_alloc_default(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
+ size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
+ tsdn_t *tsdn;
+ arena_t *arena;
+
+ tsdn = tsdn_fetch();
+ arena = arena_get(tsdn, arena_ind, false);
+ /*
+ * The arena we're allocating on behalf of must have been initialized
+ * already.
+ */
+ assert(arena != NULL);
+
+ return extent_alloc_default_impl(tsdn, arena, new_addr, size,
+ alignment, zero, commit);
+}
+
+static void
+extent_hook_pre_reentrancy(tsdn_t *tsdn, arena_t *arena) {
+ tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
+ if (arena == arena_get(tsd_tsdn(tsd), 0, false)) {
+ /*
+ * The only legitimate case of customized extent hooks for a0 is
+ * hooks with no allocation activities. One such example is to
+ * place metadata on pre-allocated resources such as huge pages.
+ * In that case, rely on reentrancy_level checks to catch
+ * infinite recursions.
+ */
+ pre_reentrancy(tsd, NULL);
+ } else {
+ pre_reentrancy(tsd, arena);
+ }
+}
+
+static void
+extent_hook_post_reentrancy(tsdn_t *tsdn) {
+ tsd_t *tsd = tsdn_null(tsdn) ? tsd_fetch() : tsdn_tsd(tsdn);
+ post_reentrancy(tsd);
+}
+
+/*
+ * If virtual memory is retained, create increasingly larger extents from which
+ * to split requested extents in order to limit the total number of disjoint
+ * virtual memory ranges retained by each arena.
+ */
+static extent_t *
+extent_grow_retained(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, size_t size, size_t pad, size_t alignment,
+ bool slab, szind_t szind, bool *zero, bool *commit) {
+ malloc_mutex_assert_owner(tsdn, &arena->extent_grow_mtx);
+ assert(pad == 0 || !slab);
+ assert(!*zero || !slab);
+
+ size_t esize = size + pad;
+ size_t alloc_size_min = esize + PAGE_CEILING(alignment) - PAGE;
+ /* Beware size_t wrap-around. */
+ if (alloc_size_min < esize) {
+ goto label_err;
+ }
+ /*
+ * Find the next extent size in the series that would be large enough to
+ * satisfy this request.
+ */
+ pszind_t egn_skip = 0;
+ size_t alloc_size = sz_pind2sz(arena->extent_grow_next + egn_skip);
+ while (alloc_size < alloc_size_min) {
+ egn_skip++;
+ if (arena->extent_grow_next + egn_skip == NPSIZES) {
+ /* Outside legal range. */
+ goto label_err;
+ }
+ assert(arena->extent_grow_next + egn_skip < NPSIZES);
+ alloc_size = sz_pind2sz(arena->extent_grow_next + egn_skip);
+ }
+
+ extent_t *extent = extent_alloc(tsdn, arena);
+ if (extent == NULL) {
+ goto label_err;
+ }
+ bool zeroed = false;
+ bool committed = false;
+
+ void *ptr;
+ if (*r_extent_hooks == &extent_hooks_default) {
+ ptr = extent_alloc_default_impl(tsdn, arena, NULL,
+ alloc_size, PAGE, &zeroed, &committed);
+ } else {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ ptr = (*r_extent_hooks)->alloc(*r_extent_hooks, NULL,
+ alloc_size, PAGE, &zeroed, &committed,
+ arena_ind_get(arena));
+ extent_hook_post_reentrancy(tsdn);
+ }
+
+ extent_init(extent, arena, ptr, alloc_size, false, NSIZES,
+ arena_extent_sn_next(arena), extent_state_active, zeroed,
+ committed, true);
+ if (ptr == NULL) {
+ extent_dalloc(tsdn, arena, extent);
+ goto label_err;
+ }
+
+ if (extent_register_no_gdump_add(tsdn, extent)) {
+ extents_leak(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, extent, true);
+ goto label_err;
+ }
+
+ if (extent_zeroed_get(extent) && extent_committed_get(extent)) {
+ *zero = true;
+ }
+ if (extent_committed_get(extent)) {
+ *commit = true;
+ }
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ extent_t *lead;
+ extent_t *trail;
+ extent_t *to_leak;
+ extent_t *to_salvage;
+ extent_split_interior_result_t result = extent_split_interior(
+ tsdn, arena, r_extent_hooks, rtree_ctx, &extent, &lead, &trail,
+ &to_leak, &to_salvage, NULL, size, pad, alignment, slab, szind,
+ true);
+
+ if (result == extent_split_interior_ok) {
+ if (lead != NULL) {
+ extent_record(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, lead, true);
+ }
+ if (trail != NULL) {
+ extent_record(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, trail, true);
+ }
+ } else {
+ /*
+ * We should have allocated a sufficiently large extent; the
+ * cant_alloc case should not occur.
+ */
+ assert(result == extent_split_interior_error);
+ if (to_salvage != NULL) {
+ if (config_prof) {
+ extent_gdump_add(tsdn, to_salvage);
+ }
+ extent_record(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, to_salvage, true);
+ }
+ if (to_leak != NULL) {
+ extent_deregister_no_gdump_sub(tsdn, to_leak);
+ extents_leak(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, to_leak, true);
+ }
+ goto label_err;
+ }
+
+ if (*commit && !extent_committed_get(extent)) {
+ if (extent_commit_impl(tsdn, arena, r_extent_hooks, extent, 0,
+ extent_size_get(extent), true)) {
+ extent_record(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, extent, true);
+ goto label_err;
+ }
+ extent_zeroed_set(extent, true);
+ }
+
+ /*
+ * Increment extent_grow_next if doing so wouldn't exceed the allowed
+ * range.
+ */
+ if (arena->extent_grow_next + egn_skip + 1 <=
+ arena->retain_grow_limit) {
+ arena->extent_grow_next += egn_skip + 1;
+ } else {
+ arena->extent_grow_next = arena->retain_grow_limit;
+ }
+ /* All opportunities for failure are past. */
+ malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
+
+ if (config_prof) {
+ /* Adjust gdump stats now that extent is final size. */
+ extent_gdump_add(tsdn, extent);
+ }
+ if (pad != 0) {
+ extent_addr_randomize(tsdn, extent, alignment);
+ }
+ if (slab) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn,
+ &rtree_ctx_fallback);
+
+ extent_slab_set(extent, true);
+ extent_interior_register(tsdn, rtree_ctx, extent, szind);
+ }
+ if (*zero && !extent_zeroed_get(extent)) {
+ void *addr = extent_base_get(extent);
+ size_t size = extent_size_get(extent);
+ if (pages_purge_forced(addr, size)) {
+ memset(addr, 0, size);
+ }
+ }
+
+ return extent;
+label_err:
+ malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
+ return NULL;
+}
+
+static extent_t *
+extent_alloc_retained(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
+ assert(size != 0);
+ assert(alignment != 0);
+
+ malloc_mutex_lock(tsdn, &arena->extent_grow_mtx);
+
+ extent_t *extent = extent_recycle(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, new_addr, size, pad, alignment, slab,
+ szind, zero, commit, true);
+ if (extent != NULL) {
+ malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
+ if (config_prof) {
+ extent_gdump_add(tsdn, extent);
+ }
+ } else if (opt_retain && new_addr == NULL) {
+ extent = extent_grow_retained(tsdn, arena, r_extent_hooks, size,
+ pad, alignment, slab, szind, zero, commit);
+ /* extent_grow_retained() always releases extent_grow_mtx. */
+ } else {
+ malloc_mutex_unlock(tsdn, &arena->extent_grow_mtx);
+ }
+ malloc_mutex_assert_not_owner(tsdn, &arena->extent_grow_mtx);
+
+ return extent;
+}
+
+static extent_t *
+extent_alloc_wrapper_hard(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
+ size_t esize = size + pad;
+ extent_t *extent = extent_alloc(tsdn, arena);
+ if (extent == NULL) {
+ return NULL;
+ }
+ void *addr;
+ if (*r_extent_hooks == &extent_hooks_default) {
+ /* Call directly to propagate tsdn. */
+ addr = extent_alloc_default_impl(tsdn, arena, new_addr, esize,
+ alignment, zero, commit);
+ } else {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ addr = (*r_extent_hooks)->alloc(*r_extent_hooks, new_addr,
+ esize, alignment, zero, commit, arena_ind_get(arena));
+ extent_hook_post_reentrancy(tsdn);
+ }
+ if (addr == NULL) {
+ extent_dalloc(tsdn, arena, extent);
+ return NULL;
+ }
+ extent_init(extent, arena, addr, esize, slab, szind,
+ arena_extent_sn_next(arena), extent_state_active, *zero, *commit,
+ true);
+ if (pad != 0) {
+ extent_addr_randomize(tsdn, extent, alignment);
+ }
+ if (extent_register(tsdn, extent)) {
+ extents_leak(tsdn, arena, r_extent_hooks,
+ &arena->extents_retained, extent, false);
+ return NULL;
+ }
+
+ return extent;
+}
+
+extent_t *
+extent_alloc_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, void *new_addr, size_t size, size_t pad,
+ size_t alignment, bool slab, szind_t szind, bool *zero, bool *commit) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ extent_t *extent = extent_alloc_retained(tsdn, arena, r_extent_hooks,
+ new_addr, size, pad, alignment, slab, szind, zero, commit);
+ if (extent == NULL) {
+ if (opt_retain && new_addr != NULL) {
+ /*
+ * When retain is enabled and new_addr is set, we do not
+ * attempt extent_alloc_wrapper_hard which does mmap
+ * that is very unlikely to succeed (unless it happens
+ * to be at the end).
+ */
+ return NULL;
+ }
+ extent = extent_alloc_wrapper_hard(tsdn, arena, r_extent_hooks,
+ new_addr, size, pad, alignment, slab, szind, zero, commit);
+ }
+
+ assert(extent == NULL || extent_dumpable_get(extent));
+ return extent;
+}
+
+static bool
+extent_can_coalesce(arena_t *arena, extents_t *extents, const extent_t *inner,
+ const extent_t *outer) {
+ assert(extent_arena_get(inner) == arena);
+ if (extent_arena_get(outer) != arena) {
+ return false;
+ }
+
+ assert(extent_state_get(inner) == extent_state_active);
+ if (extent_state_get(outer) != extents->state) {
+ return false;
+ }
+
+ if (extent_committed_get(inner) != extent_committed_get(outer)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+extent_coalesce(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, extent_t *inner, extent_t *outer, bool forward,
+ bool growing_retained) {
+ assert(extent_can_coalesce(arena, extents, inner, outer));
+
+ extent_activate_locked(tsdn, arena, extents, outer);
+
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+ bool err = extent_merge_impl(tsdn, arena, r_extent_hooks,
+ forward ? inner : outer, forward ? outer : inner, growing_retained);
+ malloc_mutex_lock(tsdn, &extents->mtx);
+
+ if (err) {
+ extent_deactivate_locked(tsdn, arena, extents, outer);
+ }
+
+ return err;
+}
+
+static extent_t *
+extent_try_coalesce(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, rtree_ctx_t *rtree_ctx, extents_t *extents,
+ extent_t *extent, bool *coalesced, bool growing_retained) {
/*
- * Round down to the nearest chunk size that can actually be requested
- * during normal huge allocation.
+ * Continue attempting to coalesce until failure, to protect against
+ * races with other threads that are thwarted by this one.
*/
- return (index2size(size2index(size + 1) - 1));
+ bool again;
+ do {
+ again = false;
+
+ /* Try to coalesce forward. */
+ extent_t *next = extent_lock_from_addr(tsdn, rtree_ctx,
+ extent_past_get(extent));
+ if (next != NULL) {
+ /*
+ * extents->mtx only protects against races for
+ * like-state extents, so call extent_can_coalesce()
+ * before releasing next's pool lock.
+ */
+ bool can_coalesce = extent_can_coalesce(arena, extents,
+ extent, next);
+
+ extent_unlock(tsdn, next);
+
+ if (can_coalesce && !extent_coalesce(tsdn, arena,
+ r_extent_hooks, extents, extent, next, true,
+ growing_retained)) {
+ if (extents->delay_coalesce) {
+ /* Do minimal coalescing. */
+ *coalesced = true;
+ return extent;
+ }
+ again = true;
+ }
+ }
+
+ /* Try to coalesce backward. */
+ extent_t *prev = extent_lock_from_addr(tsdn, rtree_ctx,
+ extent_before_get(extent));
+ if (prev != NULL) {
+ bool can_coalesce = extent_can_coalesce(arena, extents,
+ extent, prev);
+ extent_unlock(tsdn, prev);
+
+ if (can_coalesce && !extent_coalesce(tsdn, arena,
+ r_extent_hooks, extents, extent, prev, false,
+ growing_retained)) {
+ extent = prev;
+ if (extents->delay_coalesce) {
+ /* Do minimal coalescing. */
+ *coalesced = true;
+ return extent;
+ }
+ again = true;
+ }
+ }
+ } while (again);
+
+ if (extents->delay_coalesce) {
+ *coalesced = false;
+ }
+ return extent;
+}
+
+/*
+ * Does the metadata management portions of putting an unused extent into the
+ * given extents_t (coalesces, deregisters slab interiors, the heap operations).
+ */
+static void
+extent_record(tsdn_t *tsdn, arena_t *arena, extent_hooks_t **r_extent_hooks,
+ extents_t *extents, extent_t *extent, bool growing_retained) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ assert((extents_state_get(extents) != extent_state_dirty &&
+ extents_state_get(extents) != extent_state_muzzy) ||
+ !extent_zeroed_get(extent));
+
+ malloc_mutex_lock(tsdn, &extents->mtx);
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ extent_szind_set(extent, NSIZES);
+ if (extent_slab_get(extent)) {
+ extent_interior_deregister(tsdn, rtree_ctx, extent);
+ extent_slab_set(extent, false);
+ }
+
+ assert(rtree_extent_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_base_get(extent), true) == extent);
+
+ if (!extents->delay_coalesce) {
+ extent = extent_try_coalesce(tsdn, arena, r_extent_hooks,
+ rtree_ctx, extents, extent, NULL, growing_retained);
+ } else if (extent_size_get(extent) >= LARGE_MINCLASS) {
+ /* Always coalesce large extents eagerly. */
+ bool coalesced;
+ size_t prev_size;
+ do {
+ prev_size = extent_size_get(extent);
+ assert(extent_state_get(extent) == extent_state_active);
+ extent = extent_try_coalesce(tsdn, arena,
+ r_extent_hooks, rtree_ctx, extents, extent,
+ &coalesced, growing_retained);
+ } while (coalesced &&
+ extent_size_get(extent) >= prev_size + LARGE_MINCLASS);
+ }
+ extent_deactivate_locked(tsdn, arena, extents, extent);
+
+ malloc_mutex_unlock(tsdn, &extents->mtx);
+}
+
+void
+extent_dalloc_gap(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
+ extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
+
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ if (extent_register(tsdn, extent)) {
+ extents_leak(tsdn, arena, &extent_hooks,
+ &arena->extents_retained, extent, false);
+ return;
+ }
+ extent_dalloc_wrapper(tsdn, arena, &extent_hooks, extent);
+}
+
+static bool
+extent_dalloc_default_impl(void *addr, size_t size) {
+ if (!have_dss || !extent_in_dss(addr)) {
+ return extent_dalloc_mmap(addr, size);
+ }
+ return true;
+}
+
+static bool
+extent_dalloc_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ bool committed, unsigned arena_ind) {
+ return extent_dalloc_default_impl(addr, size);
+}
+
+static bool
+extent_dalloc_wrapper_try(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent) {
+ bool err;
+
+ assert(extent_base_get(extent) != NULL);
+ assert(extent_size_get(extent) != 0);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extent_addr_set(extent, extent_base_get(extent));
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+ /* Try to deallocate. */
+ if (*r_extent_hooks == &extent_hooks_default) {
+ /* Call directly to propagate tsdn. */
+ err = extent_dalloc_default_impl(extent_base_get(extent),
+ extent_size_get(extent));
+ } else {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ err = ((*r_extent_hooks)->dalloc == NULL ||
+ (*r_extent_hooks)->dalloc(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent),
+ extent_committed_get(extent), arena_ind_get(arena)));
+ extent_hook_post_reentrancy(tsdn);
+ }
+
+ if (!err) {
+ extent_dalloc(tsdn, arena, extent);
+ }
+
+ return err;
}
-JEMALLOC_INLINE_C int
-extent_szad_comp(extent_node_t *a, extent_node_t *b)
-{
- int ret;
- size_t a_qsize = extent_quantize(extent_node_size_get(a));
- size_t b_qsize = extent_quantize(extent_node_size_get(b));
+void
+extent_dalloc_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent) {
+ assert(extent_dumpable_get(extent));
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
/*
- * Compare based on quantized size rather than size, in order to sort
- * equally useful extents only by address.
+ * Deregister first to avoid a race with other allocating threads, and
+ * reregister if deallocation fails.
*/
- ret = (a_qsize > b_qsize) - (a_qsize < b_qsize);
- if (ret == 0) {
- uintptr_t a_addr = (uintptr_t)extent_node_addr_get(a);
- uintptr_t b_addr = (uintptr_t)extent_node_addr_get(b);
+ extent_deregister(tsdn, extent);
+ if (!extent_dalloc_wrapper_try(tsdn, arena, r_extent_hooks, extent)) {
+ return;
+ }
+
+ extent_reregister(tsdn, extent);
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ /* Try to decommit; purge if that fails. */
+ bool zeroed;
+ if (!extent_committed_get(extent)) {
+ zeroed = true;
+ } else if (!extent_decommit_wrapper(tsdn, arena, r_extent_hooks, extent,
+ 0, extent_size_get(extent))) {
+ zeroed = true;
+ } else if ((*r_extent_hooks)->purge_forced != NULL &&
+ !(*r_extent_hooks)->purge_forced(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent), 0,
+ extent_size_get(extent), arena_ind_get(arena))) {
+ zeroed = true;
+ } else if (extent_state_get(extent) == extent_state_muzzy ||
+ ((*r_extent_hooks)->purge_lazy != NULL &&
+ !(*r_extent_hooks)->purge_lazy(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent), 0,
+ extent_size_get(extent), arena_ind_get(arena)))) {
+ zeroed = false;
+ } else {
+ zeroed = false;
+ }
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+ extent_zeroed_set(extent, zeroed);
+
+ if (config_prof) {
+ extent_gdump_sub(tsdn, extent);
+ }
+
+ extent_record(tsdn, arena, r_extent_hooks, &arena->extents_retained,
+ extent, false);
+}
+
+static void
+extent_destroy_default_impl(void *addr, size_t size) {
+ if (!have_dss || !extent_in_dss(addr)) {
+ pages_unmap(addr, size);
+ }
+}
+
+static void
+extent_destroy_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ bool committed, unsigned arena_ind) {
+ extent_destroy_default_impl(addr, size);
+}
+
+void
+extent_destroy_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent) {
+ assert(extent_base_get(extent) != NULL);
+ assert(extent_size_get(extent) != 0);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ /* Deregister first to avoid a race with other allocating threads. */
+ extent_deregister(tsdn, extent);
+
+ extent_addr_set(extent, extent_base_get(extent));
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+ /* Try to destroy; silently fail otherwise. */
+ if (*r_extent_hooks == &extent_hooks_default) {
+ /* Call directly to propagate tsdn. */
+ extent_destroy_default_impl(extent_base_get(extent),
+ extent_size_get(extent));
+ } else if ((*r_extent_hooks)->destroy != NULL) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ (*r_extent_hooks)->destroy(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent),
+ extent_committed_get(extent), arena_ind_get(arena));
+ extent_hook_post_reentrancy(tsdn);
+ }
+
+ extent_dalloc(tsdn, arena, extent);
+}
+
+static bool
+extent_commit_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ return pages_commit((void *)((uintptr_t)addr + (uintptr_t)offset),
+ length);
+}
+
+static bool
+extent_commit_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ bool err = ((*r_extent_hooks)->commit == NULL ||
+ (*r_extent_hooks)->commit(*r_extent_hooks, extent_base_get(extent),
+ extent_size_get(extent), offset, length, arena_ind_get(arena)));
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+ extent_committed_set(extent, extent_committed_get(extent) || !err);
+ return err;
+}
+
+bool
+extent_commit_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length) {
+ return extent_commit_impl(tsdn, arena, r_extent_hooks, extent, offset,
+ length, false);
+}
+
+static bool
+extent_decommit_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ return pages_decommit((void *)((uintptr_t)addr + (uintptr_t)offset),
+ length);
+}
+
+bool
+extent_decommit_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ bool err = ((*r_extent_hooks)->decommit == NULL ||
+ (*r_extent_hooks)->decommit(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent), offset, length,
+ arena_ind_get(arena)));
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+ extent_committed_set(extent, extent_committed_get(extent) && err);
+ return err;
+}
+
+#ifdef PAGES_CAN_PURGE_LAZY
+static bool
+extent_purge_lazy_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ assert(addr != NULL);
+ assert((offset & PAGE_MASK) == 0);
+ assert(length != 0);
+ assert((length & PAGE_MASK) == 0);
+
+ return pages_purge_lazy((void *)((uintptr_t)addr + (uintptr_t)offset),
+ length);
+}
+#endif
+
+static bool
+extent_purge_lazy_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ if ((*r_extent_hooks)->purge_lazy == NULL) {
+ return true;
+ }
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ bool err = (*r_extent_hooks)->purge_lazy(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent), offset, length,
+ arena_ind_get(arena));
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+
+ return err;
+}
+
+bool
+extent_purge_lazy_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length) {
+ return extent_purge_lazy_impl(tsdn, arena, r_extent_hooks, extent,
+ offset, length, false);
+}
+
+#ifdef PAGES_CAN_PURGE_FORCED
+static bool
+extent_purge_forced_default(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind) {
+ assert(addr != NULL);
+ assert((offset & PAGE_MASK) == 0);
+ assert(length != 0);
+ assert((length & PAGE_MASK) == 0);
+
+ return pages_purge_forced((void *)((uintptr_t)addr +
+ (uintptr_t)offset), length);
+}
+#endif
+
+static bool
+extent_purge_forced_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length, bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ if ((*r_extent_hooks)->purge_forced == NULL) {
+ return true;
+ }
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ bool err = (*r_extent_hooks)->purge_forced(*r_extent_hooks,
+ extent_base_get(extent), extent_size_get(extent), offset, length,
+ arena_ind_get(arena));
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+ return err;
+}
+
+bool
+extent_purge_forced_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t offset,
+ size_t length) {
+ return extent_purge_forced_impl(tsdn, arena, r_extent_hooks, extent,
+ offset, length, false);
+}
+
+#ifdef JEMALLOC_MAPS_COALESCE
+static bool
+extent_split_default(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t size_a, size_t size_b, bool committed, unsigned arena_ind) {
+ return !maps_coalesce;
+}
+#endif
+
+/*
+ * Accepts the extent to split, and the characteristics of each side of the
+ * split. The 'a' parameters go with the 'lead' of the resulting pair of
+ * extents (the lower addressed portion of the split), and the 'b' parameters go
+ * with the trail (the higher addressed portion). This makes 'extent' the lead,
+ * and returns the trail (except in case of error).
+ */
+static extent_t *
+extent_split_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
+ szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b,
+ bool growing_retained) {
+ assert(extent_size_get(extent) == size_a + size_b);
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ if ((*r_extent_hooks)->split == NULL) {
+ return NULL;
+ }
+
+ extent_t *trail = extent_alloc(tsdn, arena);
+ if (trail == NULL) {
+ goto label_error_a;
+ }
+
+ extent_init(trail, arena, (void *)((uintptr_t)extent_base_get(extent) +
+ size_a), size_b, slab_b, szind_b, extent_sn_get(extent),
+ extent_state_get(extent), extent_zeroed_get(extent),
+ extent_committed_get(extent), extent_dumpable_get(extent));
+
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ rtree_leaf_elm_t *lead_elm_a, *lead_elm_b;
+ {
+ extent_t lead;
+
+ extent_init(&lead, arena, extent_addr_get(extent), size_a,
+ slab_a, szind_a, extent_sn_get(extent),
+ extent_state_get(extent), extent_zeroed_get(extent),
+ extent_committed_get(extent), extent_dumpable_get(extent));
- ret = (a_addr > b_addr) - (a_addr < b_addr);
+ extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, &lead, false,
+ true, &lead_elm_a, &lead_elm_b);
}
+ rtree_leaf_elm_t *trail_elm_a, *trail_elm_b;
+ extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, trail, false, true,
+ &trail_elm_a, &trail_elm_b);
- return (ret);
+ if (lead_elm_a == NULL || lead_elm_b == NULL || trail_elm_a == NULL
+ || trail_elm_b == NULL) {
+ goto label_error_b;
+ }
+
+ extent_lock2(tsdn, extent, trail);
+
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ }
+ bool err = (*r_extent_hooks)->split(*r_extent_hooks, extent_base_get(extent),
+ size_a + size_b, size_a, size_b, extent_committed_get(extent),
+ arena_ind_get(arena));
+ if (*r_extent_hooks != &extent_hooks_default) {
+ extent_hook_post_reentrancy(tsdn);
+ }
+ if (err) {
+ goto label_error_c;
+ }
+
+ extent_size_set(extent, size_a);
+ extent_szind_set(extent, szind_a);
+
+ extent_rtree_write_acquired(tsdn, lead_elm_a, lead_elm_b, extent,
+ szind_a, slab_a);
+ extent_rtree_write_acquired(tsdn, trail_elm_a, trail_elm_b, trail,
+ szind_b, slab_b);
+
+ extent_unlock2(tsdn, extent, trail);
+
+ return trail;
+label_error_c:
+ extent_unlock2(tsdn, extent, trail);
+label_error_b:
+ extent_dalloc(tsdn, arena, trail);
+label_error_a:
+ return NULL;
}
-/* Generate red-black tree functions. */
-rb_gen(, extent_tree_szad_, extent_tree_t, extent_node_t, szad_link,
- extent_szad_comp)
+extent_t *
+extent_split_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *extent, size_t size_a,
+ szind_t szind_a, bool slab_a, size_t size_b, szind_t szind_b, bool slab_b) {
+ return extent_split_impl(tsdn, arena, r_extent_hooks, extent, size_a,
+ szind_a, slab_a, size_b, szind_b, slab_b, false);
+}
-JEMALLOC_INLINE_C int
-extent_ad_comp(extent_node_t *a, extent_node_t *b)
-{
- uintptr_t a_addr = (uintptr_t)extent_node_addr_get(a);
- uintptr_t b_addr = (uintptr_t)extent_node_addr_get(b);
+static bool
+extent_merge_default_impl(void *addr_a, void *addr_b) {
+ if (!maps_coalesce) {
+ return true;
+ }
+ if (have_dss && !extent_dss_mergeable(addr_a, addr_b)) {
+ return true;
+ }
+
+ return false;
+}
- return ((a_addr > b_addr) - (a_addr < b_addr));
+#ifdef JEMALLOC_MAPS_COALESCE
+static bool
+extent_merge_default(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
+ void *addr_b, size_t size_b, bool committed, unsigned arena_ind) {
+ return extent_merge_default_impl(addr_a, addr_b);
}
+#endif
-/* Generate red-black tree functions. */
-rb_gen(, extent_tree_ad_, extent_tree_t, extent_node_t, ad_link, extent_ad_comp)
+static bool
+extent_merge_impl(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b,
+ bool growing_retained) {
+ witness_assert_depth_to_rank(tsdn_witness_tsdp_get(tsdn),
+ WITNESS_RANK_CORE, growing_retained ? 1 : 0);
+
+ extent_hooks_assure_initialized(arena, r_extent_hooks);
+
+ if ((*r_extent_hooks)->merge == NULL) {
+ return true;
+ }
+
+ bool err;
+ if (*r_extent_hooks == &extent_hooks_default) {
+ /* Call directly to propagate tsdn. */
+ err = extent_merge_default_impl(extent_base_get(a),
+ extent_base_get(b));
+ } else {
+ extent_hook_pre_reentrancy(tsdn, arena);
+ err = (*r_extent_hooks)->merge(*r_extent_hooks,
+ extent_base_get(a), extent_size_get(a), extent_base_get(b),
+ extent_size_get(b), extent_committed_get(a),
+ arena_ind_get(arena));
+ extent_hook_post_reentrancy(tsdn);
+ }
+
+ if (err) {
+ return true;
+ }
+
+ /*
+ * The rtree writes must happen while all the relevant elements are
+ * owned, so the following code uses decomposed helper functions rather
+ * than extent_{,de}register() to do things in the right order.
+ */
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ rtree_leaf_elm_t *a_elm_a, *a_elm_b, *b_elm_a, *b_elm_b;
+ extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, a, true, false, &a_elm_a,
+ &a_elm_b);
+ extent_rtree_leaf_elms_lookup(tsdn, rtree_ctx, b, true, false, &b_elm_a,
+ &b_elm_b);
+
+ extent_lock2(tsdn, a, b);
+
+ if (a_elm_b != NULL) {
+ rtree_leaf_elm_write(tsdn, &extents_rtree, a_elm_b, NULL,
+ NSIZES, false);
+ }
+ if (b_elm_b != NULL) {
+ rtree_leaf_elm_write(tsdn, &extents_rtree, b_elm_a, NULL,
+ NSIZES, false);
+ } else {
+ b_elm_b = b_elm_a;
+ }
+
+ extent_size_set(a, extent_size_get(a) + extent_size_get(b));
+ extent_szind_set(a, NSIZES);
+ extent_sn_set(a, (extent_sn_get(a) < extent_sn_get(b)) ?
+ extent_sn_get(a) : extent_sn_get(b));
+ extent_zeroed_set(a, extent_zeroed_get(a) && extent_zeroed_get(b));
+
+ extent_rtree_write_acquired(tsdn, a_elm_a, b_elm_b, a, NSIZES, false);
+
+ extent_unlock2(tsdn, a, b);
+
+ extent_dalloc(tsdn, extent_arena_get(b), b);
+
+ return false;
+}
+
+bool
+extent_merge_wrapper(tsdn_t *tsdn, arena_t *arena,
+ extent_hooks_t **r_extent_hooks, extent_t *a, extent_t *b) {
+ return extent_merge_impl(tsdn, arena, r_extent_hooks, a, b, false);
+}
+
+bool
+extent_boot(void) {
+ if (rtree_new(&extents_rtree, true)) {
+ return true;
+ }
+
+ if (mutex_pool_init(&extent_mutex_pool, "extent_mutex_pool",
+ WITNESS_RANK_EXTENT_POOL)) {
+ return true;
+ }
+
+ if (have_dss) {
+ extent_dss_boot();
+ }
+
+ return false;
+}
diff --git a/deps/jemalloc/src/extent_dss.c b/deps/jemalloc/src/extent_dss.c
new file mode 100644
index 000000000..2b1ea9caf
--- /dev/null
+++ b/deps/jemalloc/src/extent_dss.c
@@ -0,0 +1,270 @@
+#define JEMALLOC_EXTENT_DSS_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/spin.h"
+
+/******************************************************************************/
+/* Data. */
+
+const char *opt_dss = DSS_DEFAULT;
+
+const char *dss_prec_names[] = {
+ "disabled",
+ "primary",
+ "secondary",
+ "N/A"
+};
+
+/*
+ * Current dss precedence default, used when creating new arenas. NB: This is
+ * stored as unsigned rather than dss_prec_t because in principle there's no
+ * guarantee that sizeof(dss_prec_t) is the same as sizeof(unsigned), and we use
+ * atomic operations to synchronize the setting.
+ */
+static atomic_u_t dss_prec_default = ATOMIC_INIT(
+ (unsigned)DSS_PREC_DEFAULT);
+
+/* Base address of the DSS. */
+static void *dss_base;
+/* Atomic boolean indicating whether a thread is currently extending DSS. */
+static atomic_b_t dss_extending;
+/* Atomic boolean indicating whether the DSS is exhausted. */
+static atomic_b_t dss_exhausted;
+/* Atomic current upper limit on DSS addresses. */
+static atomic_p_t dss_max;
+
+/******************************************************************************/
+
+static void *
+extent_dss_sbrk(intptr_t increment) {
+#ifdef JEMALLOC_DSS
+ return sbrk(increment);
+#else
+ not_implemented();
+ return NULL;
+#endif
+}
+
+dss_prec_t
+extent_dss_prec_get(void) {
+ dss_prec_t ret;
+
+ if (!have_dss) {
+ return dss_prec_disabled;
+ }
+ ret = (dss_prec_t)atomic_load_u(&dss_prec_default, ATOMIC_ACQUIRE);
+ return ret;
+}
+
+bool
+extent_dss_prec_set(dss_prec_t dss_prec) {
+ if (!have_dss) {
+ return (dss_prec != dss_prec_disabled);
+ }
+ atomic_store_u(&dss_prec_default, (unsigned)dss_prec, ATOMIC_RELEASE);
+ return false;
+}
+
+static void
+extent_dss_extending_start(void) {
+ spin_t spinner = SPIN_INITIALIZER;
+ while (true) {
+ bool expected = false;
+ if (atomic_compare_exchange_weak_b(&dss_extending, &expected,
+ true, ATOMIC_ACQ_REL, ATOMIC_RELAXED)) {
+ break;
+ }
+ spin_adaptive(&spinner);
+ }
+}
+
+static void
+extent_dss_extending_finish(void) {
+ assert(atomic_load_b(&dss_extending, ATOMIC_RELAXED));
+
+ atomic_store_b(&dss_extending, false, ATOMIC_RELEASE);
+}
+
+static void *
+extent_dss_max_update(void *new_addr) {
+ /*
+ * Get the current end of the DSS as max_cur and assure that dss_max is
+ * up to date.
+ */
+ void *max_cur = extent_dss_sbrk(0);
+ if (max_cur == (void *)-1) {
+ return NULL;
+ }
+ atomic_store_p(&dss_max, max_cur, ATOMIC_RELEASE);
+ /* Fixed new_addr can only be supported if it is at the edge of DSS. */
+ if (new_addr != NULL && max_cur != new_addr) {
+ return NULL;
+ }
+ return max_cur;
+}
+
+void *
+extent_alloc_dss(tsdn_t *tsdn, arena_t *arena, void *new_addr, size_t size,
+ size_t alignment, bool *zero, bool *commit) {
+ extent_t *gap;
+
+ cassert(have_dss);
+ assert(size > 0);
+ assert(alignment > 0);
+
+ /*
+ * sbrk() uses a signed increment argument, so take care not to
+ * interpret a large allocation request as a negative increment.
+ */
+ if ((intptr_t)size < 0) {
+ return NULL;
+ }
+
+ gap = extent_alloc(tsdn, arena);
+ if (gap == NULL) {
+ return NULL;
+ }
+
+ extent_dss_extending_start();
+ if (!atomic_load_b(&dss_exhausted, ATOMIC_ACQUIRE)) {
+ /*
+ * The loop is necessary to recover from races with other
+ * threads that are using the DSS for something other than
+ * malloc.
+ */
+ while (true) {
+ void *max_cur = extent_dss_max_update(new_addr);
+ if (max_cur == NULL) {
+ goto label_oom;
+ }
+
+ /*
+ * Compute how much page-aligned gap space (if any) is
+ * necessary to satisfy alignment. This space can be
+ * recycled for later use.
+ */
+ void *gap_addr_page = (void *)(PAGE_CEILING(
+ (uintptr_t)max_cur));
+ void *ret = (void *)ALIGNMENT_CEILING(
+ (uintptr_t)gap_addr_page, alignment);
+ size_t gap_size_page = (uintptr_t)ret -
+ (uintptr_t)gap_addr_page;
+ if (gap_size_page != 0) {
+ extent_init(gap, arena, gap_addr_page,
+ gap_size_page, false, NSIZES,
+ arena_extent_sn_next(arena),
+ extent_state_active, false, true, true);
+ }
+ /*
+ * Compute the address just past the end of the desired
+ * allocation space.
+ */
+ void *dss_next = (void *)((uintptr_t)ret + size);
+ if ((uintptr_t)ret < (uintptr_t)max_cur ||
+ (uintptr_t)dss_next < (uintptr_t)max_cur) {
+ goto label_oom; /* Wrap-around. */
+ }
+ /* Compute the increment, including subpage bytes. */
+ void *gap_addr_subpage = max_cur;
+ size_t gap_size_subpage = (uintptr_t)ret -
+ (uintptr_t)gap_addr_subpage;
+ intptr_t incr = gap_size_subpage + size;
+
+ assert((uintptr_t)max_cur + incr == (uintptr_t)ret +
+ size);
+
+ /* Try to allocate. */
+ void *dss_prev = extent_dss_sbrk(incr);
+ if (dss_prev == max_cur) {
+ /* Success. */
+ atomic_store_p(&dss_max, dss_next,
+ ATOMIC_RELEASE);
+ extent_dss_extending_finish();
+
+ if (gap_size_page != 0) {
+ extent_dalloc_gap(tsdn, arena, gap);
+ } else {
+ extent_dalloc(tsdn, arena, gap);
+ }
+ if (!*commit) {
+ *commit = pages_decommit(ret, size);
+ }
+ if (*zero && *commit) {
+ extent_hooks_t *extent_hooks =
+ EXTENT_HOOKS_INITIALIZER;
+ extent_t extent;
+
+ extent_init(&extent, arena, ret, size,
+ size, false, NSIZES,
+ extent_state_active, false, true,
+ true);
+ if (extent_purge_forced_wrapper(tsdn,
+ arena, &extent_hooks, &extent, 0,
+ size)) {
+ memset(ret, 0, size);
+ }
+ }
+ return ret;
+ }
+ /*
+ * Failure, whether due to OOM or a race with a raw
+ * sbrk() call from outside the allocator.
+ */
+ if (dss_prev == (void *)-1) {
+ /* OOM. */
+ atomic_store_b(&dss_exhausted, true,
+ ATOMIC_RELEASE);
+ goto label_oom;
+ }
+ }
+ }
+label_oom:
+ extent_dss_extending_finish();
+ extent_dalloc(tsdn, arena, gap);
+ return NULL;
+}
+
+static bool
+extent_in_dss_helper(void *addr, void *max) {
+ return ((uintptr_t)addr >= (uintptr_t)dss_base && (uintptr_t)addr <
+ (uintptr_t)max);
+}
+
+bool
+extent_in_dss(void *addr) {
+ cassert(have_dss);
+
+ return extent_in_dss_helper(addr, atomic_load_p(&dss_max,
+ ATOMIC_ACQUIRE));
+}
+
+bool
+extent_dss_mergeable(void *addr_a, void *addr_b) {
+ void *max;
+
+ cassert(have_dss);
+
+ if ((uintptr_t)addr_a < (uintptr_t)dss_base && (uintptr_t)addr_b <
+ (uintptr_t)dss_base) {
+ return true;
+ }
+
+ max = atomic_load_p(&dss_max, ATOMIC_ACQUIRE);
+ return (extent_in_dss_helper(addr_a, max) ==
+ extent_in_dss_helper(addr_b, max));
+}
+
+void
+extent_dss_boot(void) {
+ cassert(have_dss);
+
+ dss_base = extent_dss_sbrk(0);
+ atomic_store_b(&dss_extending, false, ATOMIC_RELAXED);
+ atomic_store_b(&dss_exhausted, dss_base == (void *)-1, ATOMIC_RELAXED);
+ atomic_store_p(&dss_max, dss_base, ATOMIC_RELAXED);
+}
+
+/******************************************************************************/
diff --git a/deps/jemalloc/src/extent_mmap.c b/deps/jemalloc/src/extent_mmap.c
new file mode 100644
index 000000000..8d607dc80
--- /dev/null
+++ b/deps/jemalloc/src/extent_mmap.c
@@ -0,0 +1,42 @@
+#define JEMALLOC_EXTENT_MMAP_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/extent_mmap.h"
+
+/******************************************************************************/
+/* Data. */
+
+bool opt_retain =
+#ifdef JEMALLOC_RETAIN
+ true
+#else
+ false
+#endif
+ ;
+
+/******************************************************************************/
+
+void *
+extent_alloc_mmap(void *new_addr, size_t size, size_t alignment, bool *zero,
+ bool *commit) {
+ void *ret = pages_map(new_addr, size, ALIGNMENT_CEILING(alignment,
+ PAGE), commit);
+ if (ret == NULL) {
+ return NULL;
+ }
+ assert(ret != NULL);
+ if (*commit) {
+ *zero = true;
+ }
+ return ret;
+}
+
+bool
+extent_dalloc_mmap(void *addr, size_t size) {
+ if (!opt_retain) {
+ pages_unmap(addr, size);
+ }
+ return opt_retain;
+}
diff --git a/deps/jemalloc/src/hash.c b/deps/jemalloc/src/hash.c
index cfa4da027..7b2bdc2bd 100644
--- a/deps/jemalloc/src/hash.c
+++ b/deps/jemalloc/src/hash.c
@@ -1,2 +1,3 @@
-#define JEMALLOC_HASH_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_HASH_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
diff --git a/deps/jemalloc/src/hooks.c b/deps/jemalloc/src/hooks.c
new file mode 100644
index 000000000..6266ecd47
--- /dev/null
+++ b/deps/jemalloc/src/hooks.c
@@ -0,0 +1,12 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+
+/*
+ * The hooks are a little bit screwy -- they're not genuinely exported in the
+ * sense that we want them available to end-users, but we do want them visible
+ * from outside the generated library, so that we can use them in test code.
+ */
+JEMALLOC_EXPORT
+void (*hooks_arena_new_hook)() = NULL;
+
+JEMALLOC_EXPORT
+void (*hooks_libc_hook)() = NULL;
diff --git a/deps/jemalloc/src/huge.c b/deps/jemalloc/src/huge.c
deleted file mode 100644
index 1e9a66512..000000000
--- a/deps/jemalloc/src/huge.c
+++ /dev/null
@@ -1,435 +0,0 @@
-#define JEMALLOC_HUGE_C_
-#include "jemalloc/internal/jemalloc_internal.h"
-
-/******************************************************************************/
-
-static extent_node_t *
-huge_node_get(const void *ptr)
-{
- extent_node_t *node;
-
- node = chunk_lookup(ptr, true);
- assert(!extent_node_achunk_get(node));
-
- return (node);
-}
-
-static bool
-huge_node_set(const void *ptr, extent_node_t *node)
-{
-
- assert(extent_node_addr_get(node) == ptr);
- assert(!extent_node_achunk_get(node));
- return (chunk_register(ptr, node));
-}
-
-static void
-huge_node_unset(const void *ptr, const extent_node_t *node)
-{
-
- chunk_deregister(ptr, node);
-}
-
-void *
-huge_malloc(tsd_t *tsd, arena_t *arena, size_t size, bool zero,
- tcache_t *tcache)
-{
- size_t usize;
-
- usize = s2u(size);
- if (usize == 0) {
- /* size_t overflow. */
- return (NULL);
- }
-
- return (huge_palloc(tsd, arena, usize, chunksize, zero, tcache));
-}
-
-void *
-huge_palloc(tsd_t *tsd, arena_t *arena, size_t size, size_t alignment,
- bool zero, tcache_t *tcache)
-{
- void *ret;
- size_t usize;
- extent_node_t *node;
- bool is_zeroed;
-
- /* Allocate one or more contiguous chunks for this request. */
-
- usize = sa2u(size, alignment);
- if (unlikely(usize == 0))
- return (NULL);
- assert(usize >= chunksize);
-
- /* Allocate an extent node with which to track the chunk. */
- node = ipallocztm(tsd, CACHELINE_CEILING(sizeof(extent_node_t)),
- CACHELINE, false, tcache, true, arena);
- if (node == NULL)
- return (NULL);
-
- /*
- * Copy zero into is_zeroed and pass the copy to chunk_alloc(), so that
- * it is possible to make correct junk/zero fill decisions below.
- */
- is_zeroed = zero;
- arena = arena_choose(tsd, arena);
- if (unlikely(arena == NULL) || (ret = arena_chunk_alloc_huge(arena,
- size, alignment, &is_zeroed)) == NULL) {
- idalloctm(tsd, node, tcache, true);
- return (NULL);
- }
-
- extent_node_init(node, arena, ret, size, is_zeroed, true);
-
- if (huge_node_set(ret, node)) {
- arena_chunk_dalloc_huge(arena, ret, size);
- idalloctm(tsd, node, tcache, true);
- return (NULL);
- }
-
- /* Insert node into huge. */
- malloc_mutex_lock(&arena->huge_mtx);
- ql_elm_new(node, ql_link);
- ql_tail_insert(&arena->huge, node, ql_link);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- if (zero || (config_fill && unlikely(opt_zero))) {
- if (!is_zeroed)
- memset(ret, 0, size);
- } else if (config_fill && unlikely(opt_junk_alloc))
- memset(ret, 0xa5, size);
-
- return (ret);
-}
-
-#ifdef JEMALLOC_JET
-#undef huge_dalloc_junk
-#define huge_dalloc_junk JEMALLOC_N(huge_dalloc_junk_impl)
-#endif
-static void
-huge_dalloc_junk(void *ptr, size_t usize)
-{
-
- if (config_fill && have_dss && unlikely(opt_junk_free)) {
- /*
- * Only bother junk filling if the chunk isn't about to be
- * unmapped.
- */
- if (!config_munmap || (have_dss && chunk_in_dss(ptr)))
- memset(ptr, 0x5a, usize);
- }
-}
-#ifdef JEMALLOC_JET
-#undef huge_dalloc_junk
-#define huge_dalloc_junk JEMALLOC_N(huge_dalloc_junk)
-huge_dalloc_junk_t *huge_dalloc_junk = JEMALLOC_N(huge_dalloc_junk_impl);
-#endif
-
-static void
-huge_ralloc_no_move_similar(void *ptr, size_t oldsize, size_t usize_min,
- size_t usize_max, bool zero)
-{
- size_t usize, usize_next;
- extent_node_t *node;
- arena_t *arena;
- chunk_hooks_t chunk_hooks = CHUNK_HOOKS_INITIALIZER;
- bool pre_zeroed, post_zeroed;
-
- /* Increase usize to incorporate extra. */
- for (usize = usize_min; usize < usize_max && (usize_next = s2u(usize+1))
- <= oldsize; usize = usize_next)
- ; /* Do nothing. */
-
- if (oldsize == usize)
- return;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- pre_zeroed = extent_node_zeroed_get(node);
-
- /* Fill if necessary (shrinking). */
- if (oldsize > usize) {
- size_t sdiff = oldsize - usize;
- if (config_fill && unlikely(opt_junk_free)) {
- memset((void *)((uintptr_t)ptr + usize), 0x5a, sdiff);
- post_zeroed = false;
- } else {
- post_zeroed = !chunk_purge_wrapper(arena, &chunk_hooks,
- ptr, CHUNK_CEILING(oldsize), usize, sdiff);
- }
- } else
- post_zeroed = pre_zeroed;
-
- malloc_mutex_lock(&arena->huge_mtx);
- /* Update the size of the huge allocation. */
- assert(extent_node_size_get(node) != usize);
- extent_node_size_set(node, usize);
- /* Update zeroed. */
- extent_node_zeroed_set(node, post_zeroed);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- arena_chunk_ralloc_huge_similar(arena, ptr, oldsize, usize);
-
- /* Fill if necessary (growing). */
- if (oldsize < usize) {
- if (zero || (config_fill && unlikely(opt_zero))) {
- if (!pre_zeroed) {
- memset((void *)((uintptr_t)ptr + oldsize), 0,
- usize - oldsize);
- }
- } else if (config_fill && unlikely(opt_junk_alloc)) {
- memset((void *)((uintptr_t)ptr + oldsize), 0xa5, usize -
- oldsize);
- }
- }
-}
-
-static bool
-huge_ralloc_no_move_shrink(void *ptr, size_t oldsize, size_t usize)
-{
- extent_node_t *node;
- arena_t *arena;
- chunk_hooks_t chunk_hooks;
- size_t cdiff;
- bool pre_zeroed, post_zeroed;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- pre_zeroed = extent_node_zeroed_get(node);
- chunk_hooks = chunk_hooks_get(arena);
-
- assert(oldsize > usize);
-
- /* Split excess chunks. */
- cdiff = CHUNK_CEILING(oldsize) - CHUNK_CEILING(usize);
- if (cdiff != 0 && chunk_hooks.split(ptr, CHUNK_CEILING(oldsize),
- CHUNK_CEILING(usize), cdiff, true, arena->ind))
- return (true);
-
- if (oldsize > usize) {
- size_t sdiff = oldsize - usize;
- if (config_fill && unlikely(opt_junk_free)) {
- huge_dalloc_junk((void *)((uintptr_t)ptr + usize),
- sdiff);
- post_zeroed = false;
- } else {
- post_zeroed = !chunk_purge_wrapper(arena, &chunk_hooks,
- CHUNK_ADDR2BASE((uintptr_t)ptr + usize),
- CHUNK_CEILING(oldsize),
- CHUNK_ADDR2OFFSET((uintptr_t)ptr + usize), sdiff);
- }
- } else
- post_zeroed = pre_zeroed;
-
- malloc_mutex_lock(&arena->huge_mtx);
- /* Update the size of the huge allocation. */
- extent_node_size_set(node, usize);
- /* Update zeroed. */
- extent_node_zeroed_set(node, post_zeroed);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- /* Zap the excess chunks. */
- arena_chunk_ralloc_huge_shrink(arena, ptr, oldsize, usize);
-
- return (false);
-}
-
-static bool
-huge_ralloc_no_move_expand(void *ptr, size_t oldsize, size_t usize, bool zero) {
- extent_node_t *node;
- arena_t *arena;
- bool is_zeroed_subchunk, is_zeroed_chunk;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- malloc_mutex_lock(&arena->huge_mtx);
- is_zeroed_subchunk = extent_node_zeroed_get(node);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- /*
- * Copy zero into is_zeroed_chunk and pass the copy to chunk_alloc(), so
- * that it is possible to make correct junk/zero fill decisions below.
- */
- is_zeroed_chunk = zero;
-
- if (arena_chunk_ralloc_huge_expand(arena, ptr, oldsize, usize,
- &is_zeroed_chunk))
- return (true);
-
- malloc_mutex_lock(&arena->huge_mtx);
- /* Update the size of the huge allocation. */
- extent_node_size_set(node, usize);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- if (zero || (config_fill && unlikely(opt_zero))) {
- if (!is_zeroed_subchunk) {
- memset((void *)((uintptr_t)ptr + oldsize), 0,
- CHUNK_CEILING(oldsize) - oldsize);
- }
- if (!is_zeroed_chunk) {
- memset((void *)((uintptr_t)ptr +
- CHUNK_CEILING(oldsize)), 0, usize -
- CHUNK_CEILING(oldsize));
- }
- } else if (config_fill && unlikely(opt_junk_alloc)) {
- memset((void *)((uintptr_t)ptr + oldsize), 0xa5, usize -
- oldsize);
- }
-
- return (false);
-}
-
-bool
-huge_ralloc_no_move(void *ptr, size_t oldsize, size_t usize_min,
- size_t usize_max, bool zero)
-{
-
- assert(s2u(oldsize) == oldsize);
-
- /* Both allocations must be huge to avoid a move. */
- if (oldsize < chunksize || usize_max < chunksize)
- return (true);
-
- if (CHUNK_CEILING(usize_max) > CHUNK_CEILING(oldsize)) {
- /* Attempt to expand the allocation in-place. */
- if (!huge_ralloc_no_move_expand(ptr, oldsize, usize_max, zero))
- return (false);
- /* Try again, this time with usize_min. */
- if (usize_min < usize_max && CHUNK_CEILING(usize_min) >
- CHUNK_CEILING(oldsize) && huge_ralloc_no_move_expand(ptr,
- oldsize, usize_min, zero))
- return (false);
- }
-
- /*
- * Avoid moving the allocation if the existing chunk size accommodates
- * the new size.
- */
- if (CHUNK_CEILING(oldsize) >= CHUNK_CEILING(usize_min)
- && CHUNK_CEILING(oldsize) <= CHUNK_CEILING(usize_max)) {
- huge_ralloc_no_move_similar(ptr, oldsize, usize_min, usize_max,
- zero);
- return (false);
- }
-
- /* Attempt to shrink the allocation in-place. */
- if (CHUNK_CEILING(oldsize) > CHUNK_CEILING(usize_max))
- return (huge_ralloc_no_move_shrink(ptr, oldsize, usize_max));
- return (true);
-}
-
-static void *
-huge_ralloc_move_helper(tsd_t *tsd, arena_t *arena, size_t usize,
- size_t alignment, bool zero, tcache_t *tcache)
-{
-
- if (alignment <= chunksize)
- return (huge_malloc(tsd, arena, usize, zero, tcache));
- return (huge_palloc(tsd, arena, usize, alignment, zero, tcache));
-}
-
-void *
-huge_ralloc(tsd_t *tsd, arena_t *arena, void *ptr, size_t oldsize, size_t usize,
- size_t alignment, bool zero, tcache_t *tcache)
-{
- void *ret;
- size_t copysize;
-
- /* Try to avoid moving the allocation. */
- if (!huge_ralloc_no_move(ptr, oldsize, usize, usize, zero))
- return (ptr);
-
- /*
- * usize and oldsize are different enough that we need to use a
- * different size class. In that case, fall back to allocating new
- * space and copying.
- */
- ret = huge_ralloc_move_helper(tsd, arena, usize, alignment, zero,
- tcache);
- if (ret == NULL)
- return (NULL);
-
- copysize = (usize < oldsize) ? usize : oldsize;
- memcpy(ret, ptr, copysize);
- isqalloc(tsd, ptr, oldsize, tcache);
- return (ret);
-}
-
-void
-huge_dalloc(tsd_t *tsd, void *ptr, tcache_t *tcache)
-{
- extent_node_t *node;
- arena_t *arena;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- huge_node_unset(ptr, node);
- malloc_mutex_lock(&arena->huge_mtx);
- ql_remove(&arena->huge, node, ql_link);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- huge_dalloc_junk(extent_node_addr_get(node),
- extent_node_size_get(node));
- arena_chunk_dalloc_huge(extent_node_arena_get(node),
- extent_node_addr_get(node), extent_node_size_get(node));
- idalloctm(tsd, node, tcache, true);
-}
-
-arena_t *
-huge_aalloc(const void *ptr)
-{
-
- return (extent_node_arena_get(huge_node_get(ptr)));
-}
-
-size_t
-huge_salloc(const void *ptr)
-{
- size_t size;
- extent_node_t *node;
- arena_t *arena;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- malloc_mutex_lock(&arena->huge_mtx);
- size = extent_node_size_get(node);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- return (size);
-}
-
-prof_tctx_t *
-huge_prof_tctx_get(const void *ptr)
-{
- prof_tctx_t *tctx;
- extent_node_t *node;
- arena_t *arena;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- malloc_mutex_lock(&arena->huge_mtx);
- tctx = extent_node_prof_tctx_get(node);
- malloc_mutex_unlock(&arena->huge_mtx);
-
- return (tctx);
-}
-
-void
-huge_prof_tctx_set(const void *ptr, prof_tctx_t *tctx)
-{
- extent_node_t *node;
- arena_t *arena;
-
- node = huge_node_get(ptr);
- arena = extent_node_arena_get(node);
- malloc_mutex_lock(&arena->huge_mtx);
- extent_node_prof_tctx_set(node, tctx);
- malloc_mutex_unlock(&arena->huge_mtx);
-}
-
-void
-huge_prof_tctx_reset(const void *ptr)
-{
-
- huge_prof_tctx_set(ptr, (prof_tctx_t *)(uintptr_t)1U);
-}
diff --git a/deps/jemalloc/src/jemalloc.c b/deps/jemalloc/src/jemalloc.c
index fe77c2475..5b936cb48 100644
--- a/deps/jemalloc/src/jemalloc.c
+++ b/deps/jemalloc/src/jemalloc.c
@@ -1,11 +1,32 @@
-#define JEMALLOC_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/atomic.h"
+#include "jemalloc/internal/ctl.h"
+#include "jemalloc/internal/extent_dss.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/jemalloc_internal_types.h"
+#include "jemalloc/internal/log.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree.h"
+#include "jemalloc/internal/size_classes.h"
+#include "jemalloc/internal/spin.h"
+#include "jemalloc/internal/sz.h"
+#include "jemalloc/internal/ticker.h"
+#include "jemalloc/internal/util.h"
/******************************************************************************/
/* Data. */
/* Runtime configuration options. */
-const char *je_malloc_conf JEMALLOC_ATTR(weak);
+const char *je_malloc_conf
+#ifndef _WIN32
+ JEMALLOC_ATTR(weak)
+#endif
+ ;
bool opt_abort =
#ifdef JEMALLOC_DEBUG
true
@@ -13,6 +34,13 @@ bool opt_abort =
false
#endif
;
+bool opt_abort_conf =
+#ifdef JEMALLOC_DEBUG
+ true
+#else
+ false
+#endif
+ ;
const char *opt_junk =
#if (defined(JEMALLOC_DEBUG) && defined(JEMALLOC_FILL))
"true"
@@ -35,20 +63,15 @@ bool opt_junk_free =
#endif
;
-size_t opt_quarantine = ZU(0);
-bool opt_redzone = false;
bool opt_utrace = false;
bool opt_xmalloc = false;
bool opt_zero = false;
-size_t opt_narenas = 0;
-
-/* Initialized to true if the process is running inside Valgrind. */
-bool in_valgrind;
+unsigned opt_narenas = 0;
unsigned ncpus;
-/* Protects arenas initialization (arenas, narenas_total). */
-static malloc_mutex_t arenas_lock;
+/* Protects arenas initialization. */
+malloc_mutex_t arenas_lock;
/*
* Arenas that are used to service external requests. Not all elements of the
* arenas array are necessarily used; arenas are created lazily as needed.
@@ -56,11 +79,14 @@ static malloc_mutex_t arenas_lock;
* arenas[0..narenas_auto) are used for automatic multiplexing of threads and
* arenas. arenas[narenas_auto..narenas_total) are only used if the application
* takes some action to create them and allocate from them.
+ *
+ * Points to an arena_t.
*/
-static arena_t **arenas;
-static unsigned narenas_total;
+JEMALLOC_ALIGNED(CACHELINE)
+atomic_p_t arenas[MALLOCX_ARENA_LIMIT];
+static atomic_u_t narenas_total; /* Use narenas_total_*(). */
static arena_t *a0; /* arenas[0]; read-only after initialization. */
-static unsigned narenas_auto; /* Read-only after initialization. */
+unsigned narenas_auto; /* Read-only after initialization. */
typedef enum {
malloc_init_uninitialized = 3,
@@ -70,95 +96,18 @@ typedef enum {
} malloc_init_t;
static malloc_init_t malloc_init_state = malloc_init_uninitialized;
-JEMALLOC_ALIGNED(CACHELINE)
-const size_t index2size_tab[NSIZES] = {
-#define SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
- ((ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta)),
- SIZE_CLASSES
-#undef SC
-};
+/* False should be the common case. Set to true to trigger initialization. */
+bool malloc_slow = true;
-JEMALLOC_ALIGNED(CACHELINE)
-const uint8_t size2index_tab[] = {
-#if LG_TINY_MIN == 0
-#warning "Dangerous LG_TINY_MIN"
-#define S2B_0(i) i,
-#elif LG_TINY_MIN == 1
-#warning "Dangerous LG_TINY_MIN"
-#define S2B_1(i) i,
-#elif LG_TINY_MIN == 2
-#warning "Dangerous LG_TINY_MIN"
-#define S2B_2(i) i,
-#elif LG_TINY_MIN == 3
-#define S2B_3(i) i,
-#elif LG_TINY_MIN == 4
-#define S2B_4(i) i,
-#elif LG_TINY_MIN == 5
-#define S2B_5(i) i,
-#elif LG_TINY_MIN == 6
-#define S2B_6(i) i,
-#elif LG_TINY_MIN == 7
-#define S2B_7(i) i,
-#elif LG_TINY_MIN == 8
-#define S2B_8(i) i,
-#elif LG_TINY_MIN == 9
-#define S2B_9(i) i,
-#elif LG_TINY_MIN == 10
-#define S2B_10(i) i,
-#elif LG_TINY_MIN == 11
-#define S2B_11(i) i,
-#else
-#error "Unsupported LG_TINY_MIN"
-#endif
-#if LG_TINY_MIN < 1
-#define S2B_1(i) S2B_0(i) S2B_0(i)
-#endif
-#if LG_TINY_MIN < 2
-#define S2B_2(i) S2B_1(i) S2B_1(i)
-#endif
-#if LG_TINY_MIN < 3
-#define S2B_3(i) S2B_2(i) S2B_2(i)
-#endif
-#if LG_TINY_MIN < 4
-#define S2B_4(i) S2B_3(i) S2B_3(i)
-#endif
-#if LG_TINY_MIN < 5
-#define S2B_5(i) S2B_4(i) S2B_4(i)
-#endif
-#if LG_TINY_MIN < 6
-#define S2B_6(i) S2B_5(i) S2B_5(i)
-#endif
-#if LG_TINY_MIN < 7
-#define S2B_7(i) S2B_6(i) S2B_6(i)
-#endif
-#if LG_TINY_MIN < 8
-#define S2B_8(i) S2B_7(i) S2B_7(i)
-#endif
-#if LG_TINY_MIN < 9
-#define S2B_9(i) S2B_8(i) S2B_8(i)
-#endif
-#if LG_TINY_MIN < 10
-#define S2B_10(i) S2B_9(i) S2B_9(i)
-#endif
-#if LG_TINY_MIN < 11
-#define S2B_11(i) S2B_10(i) S2B_10(i)
-#endif
-#define S2B_no(i)
-#define SC(index, lg_grp, lg_delta, ndelta, bin, lg_delta_lookup) \
- S2B_##lg_delta_lookup(index)
- SIZE_CLASSES
-#undef S2B_3
-#undef S2B_4
-#undef S2B_5
-#undef S2B_6
-#undef S2B_7
-#undef S2B_8
-#undef S2B_9
-#undef S2B_10
-#undef S2B_11
-#undef S2B_no
-#undef SC
+/* When malloc_slow is true, set the corresponding bits for sanity check. */
+enum {
+ flag_opt_junk_alloc = (1U),
+ flag_opt_junk_free = (1U << 1),
+ flag_opt_zero = (1U << 2),
+ flag_opt_utrace = (1U << 3),
+ flag_opt_xmalloc = (1U << 4)
};
+static uint8_t malloc_slow_flags;
#ifdef JEMALLOC_THREADED_INIT
/* Used to let the initializing thread recursively allocate. */
@@ -183,19 +132,21 @@ static bool init_lock_initialized = false;
JEMALLOC_ATTR(constructor)
static void WINAPI
-_init_init_lock(void)
-{
-
- /* If another constructor in the same binary is using mallctl to
- * e.g. setup chunk hooks, it may end up running before this one,
- * and malloc_init_hard will crash trying to lock the uninitialized
- * lock. So we force an initialization of the lock in
- * malloc_init_hard as well. We don't try to care about atomicity
- * of the accessed to the init_lock_initialized boolean, since it
- * really only matters early in the process creation, before any
- * separate thread normally starts doing anything. */
- if (!init_lock_initialized)
- malloc_mutex_init(&init_lock);
+_init_init_lock(void) {
+ /*
+ * If another constructor in the same binary is using mallctl to e.g.
+ * set up extent hooks, it may end up running before this one, and
+ * malloc_init_hard will crash trying to lock the uninitialized lock. So
+ * we force an initialization of the lock in malloc_init_hard as well.
+ * We don't try to care about atomicity of the accessed to the
+ * init_lock_initialized boolean, since it really only matters early in
+ * the process creation, before any separate thread normally starts
+ * doing anything.
+ */
+ if (!init_lock_initialized) {
+ malloc_mutex_init(&init_lock, "init", WITNESS_RANK_INIT,
+ malloc_mutex_rank_exclusive);
+ }
init_lock_initialized = true;
}
@@ -231,6 +182,9 @@ typedef struct {
# define UTRACE(a, b, c)
#endif
+/* Whether encountered any invalid config options. */
+static bool had_conf_error = false;
+
/******************************************************************************/
/*
* Function prototypes for static functions that are referenced prior to
@@ -245,91 +199,54 @@ static bool malloc_init_hard(void);
* Begin miscellaneous support functions.
*/
-JEMALLOC_ALWAYS_INLINE_C bool
-malloc_initialized(void)
-{
-
+bool
+malloc_initialized(void) {
return (malloc_init_state == malloc_init_initialized);
}
-JEMALLOC_ALWAYS_INLINE_C void
-malloc_thread_init(void)
-{
-
- /*
- * TSD initialization can't be safely done as a side effect of
- * deallocation, because it is possible for a thread to do nothing but
- * deallocate its TLS data via free(), in which case writing to TLS
- * would cause write-after-free memory corruption. The quarantine
- * facility *only* gets used as a side effect of deallocation, so make
- * a best effort attempt at initializing its TSD by hooking all
- * allocation events.
- */
- if (config_fill && unlikely(opt_quarantine))
- quarantine_alloc_hook();
-}
-
-JEMALLOC_ALWAYS_INLINE_C bool
-malloc_init_a0(void)
-{
-
- if (unlikely(malloc_init_state == malloc_init_uninitialized))
- return (malloc_init_hard_a0());
- return (false);
+JEMALLOC_ALWAYS_INLINE bool
+malloc_init_a0(void) {
+ if (unlikely(malloc_init_state == malloc_init_uninitialized)) {
+ return malloc_init_hard_a0();
+ }
+ return false;
}
-JEMALLOC_ALWAYS_INLINE_C bool
-malloc_init(void)
-{
-
- if (unlikely(!malloc_initialized()) && malloc_init_hard())
- return (true);
- malloc_thread_init();
-
- return (false);
+JEMALLOC_ALWAYS_INLINE bool
+malloc_init(void) {
+ if (unlikely(!malloc_initialized()) && malloc_init_hard()) {
+ return true;
+ }
+ return false;
}
/*
- * The a0*() functions are used instead of i[mcd]alloc() in situations that
+ * The a0*() functions are used instead of i{d,}alloc() in situations that
* cannot tolerate TLS variable access.
*/
-arena_t *
-a0get(void)
-{
-
- assert(a0 != NULL);
- return (a0);
-}
-
static void *
-a0ialloc(size_t size, bool zero, bool is_metadata)
-{
-
- if (unlikely(malloc_init_a0()))
- return (NULL);
+a0ialloc(size_t size, bool zero, bool is_internal) {
+ if (unlikely(malloc_init_a0())) {
+ return NULL;
+ }
- return (iallocztm(NULL, size, zero, false, is_metadata, a0get()));
+ return iallocztm(TSDN_NULL, size, sz_size2index(size), zero, NULL,
+ is_internal, arena_get(TSDN_NULL, 0, true), true);
}
static void
-a0idalloc(void *ptr, bool is_metadata)
-{
-
- idalloctm(NULL, ptr, false, is_metadata);
+a0idalloc(void *ptr, bool is_internal) {
+ idalloctm(TSDN_NULL, ptr, NULL, NULL, is_internal, true);
}
void *
-a0malloc(size_t size)
-{
-
- return (a0ialloc(size, false, true));
+a0malloc(size_t size) {
+ return a0ialloc(size, false, true);
}
void
-a0dalloc(void *ptr)
-{
-
+a0dalloc(void *ptr) {
a0idalloc(ptr, true);
}
@@ -340,18 +257,16 @@ a0dalloc(void *ptr)
*/
void *
-bootstrap_malloc(size_t size)
-{
-
- if (unlikely(size == 0))
+bootstrap_malloc(size_t size) {
+ if (unlikely(size == 0)) {
size = 1;
+ }
- return (a0ialloc(size, false, false));
+ return a0ialloc(size, false, false);
}
void *
-bootstrap_calloc(size_t num, size_t size)
-{
+bootstrap_calloc(size_t num, size_t size) {
size_t num_size;
num_size = num * size;
@@ -360,237 +275,261 @@ bootstrap_calloc(size_t num, size_t size)
num_size = 1;
}
- return (a0ialloc(num_size, true, false));
+ return a0ialloc(num_size, true, false);
}
void
-bootstrap_free(void *ptr)
-{
-
- if (unlikely(ptr == NULL))
+bootstrap_free(void *ptr) {
+ if (unlikely(ptr == NULL)) {
return;
+ }
a0idalloc(ptr, false);
}
+void
+arena_set(unsigned ind, arena_t *arena) {
+ atomic_store_p(&arenas[ind], arena, ATOMIC_RELEASE);
+}
+
+static void
+narenas_total_set(unsigned narenas) {
+ atomic_store_u(&narenas_total, narenas, ATOMIC_RELEASE);
+}
+
+static void
+narenas_total_inc(void) {
+ atomic_fetch_add_u(&narenas_total, 1, ATOMIC_RELEASE);
+}
+
+unsigned
+narenas_total_get(void) {
+ return atomic_load_u(&narenas_total, ATOMIC_ACQUIRE);
+}
+
/* Create a new arena and insert it into the arenas array at index ind. */
static arena_t *
-arena_init_locked(unsigned ind)
-{
+arena_init_locked(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks) {
arena_t *arena;
- /* Expand arenas if necessary. */
- assert(ind <= narenas_total);
- if (ind > MALLOCX_ARENA_MAX)
- return (NULL);
- if (ind == narenas_total) {
- unsigned narenas_new = narenas_total + 1;
- arena_t **arenas_new =
- (arena_t **)a0malloc(CACHELINE_CEILING(narenas_new *
- sizeof(arena_t *)));
- if (arenas_new == NULL)
- return (NULL);
- memcpy(arenas_new, arenas, narenas_total * sizeof(arena_t *));
- arenas_new[ind] = NULL;
- /*
- * Deallocate only if arenas came from a0malloc() (not
- * base_alloc()).
- */
- if (narenas_total != narenas_auto)
- a0dalloc(arenas);
- arenas = arenas_new;
- narenas_total = narenas_new;
+ assert(ind <= narenas_total_get());
+ if (ind >= MALLOCX_ARENA_LIMIT) {
+ return NULL;
+ }
+ if (ind == narenas_total_get()) {
+ narenas_total_inc();
}
/*
* Another thread may have already initialized arenas[ind] if it's an
* auto arena.
*/
- arena = arenas[ind];
+ arena = arena_get(tsdn, ind, false);
if (arena != NULL) {
assert(ind < narenas_auto);
- return (arena);
+ return arena;
}
/* Actually initialize the arena. */
- arena = arenas[ind] = arena_new(ind);
- return (arena);
-}
-
-arena_t *
-arena_init(unsigned ind)
-{
- arena_t *arena;
+ arena = arena_new(tsdn, ind, extent_hooks);
- malloc_mutex_lock(&arenas_lock);
- arena = arena_init_locked(ind);
- malloc_mutex_unlock(&arenas_lock);
- return (arena);
+ return arena;
}
-unsigned
-narenas_total_get(void)
-{
- unsigned narenas;
-
- malloc_mutex_lock(&arenas_lock);
- narenas = narenas_total;
- malloc_mutex_unlock(&arenas_lock);
-
- return (narenas);
+static void
+arena_new_create_background_thread(tsdn_t *tsdn, unsigned ind) {
+ if (ind == 0) {
+ return;
+ }
+ if (have_background_thread) {
+ bool err;
+ malloc_mutex_lock(tsdn, &background_thread_lock);
+ err = background_thread_create(tsdn_tsd(tsdn), ind);
+ malloc_mutex_unlock(tsdn, &background_thread_lock);
+ if (err) {
+ malloc_printf("<jemalloc>: error in background thread "
+ "creation for arena %u. Abort.\n", ind);
+ abort();
+ }
+ }
}
-static void
-arena_bind_locked(tsd_t *tsd, unsigned ind)
-{
+arena_t *
+arena_init(tsdn_t *tsdn, unsigned ind, extent_hooks_t *extent_hooks) {
arena_t *arena;
- arena = arenas[ind];
- arena->nthreads++;
+ malloc_mutex_lock(tsdn, &arenas_lock);
+ arena = arena_init_locked(tsdn, ind, extent_hooks);
+ malloc_mutex_unlock(tsdn, &arenas_lock);
- if (tsd_nominal(tsd))
- tsd_arena_set(tsd, arena);
+ arena_new_create_background_thread(tsdn, ind);
+
+ return arena;
}
static void
-arena_bind(tsd_t *tsd, unsigned ind)
-{
+arena_bind(tsd_t *tsd, unsigned ind, bool internal) {
+ arena_t *arena = arena_get(tsd_tsdn(tsd), ind, false);
+ arena_nthreads_inc(arena, internal);
- malloc_mutex_lock(&arenas_lock);
- arena_bind_locked(tsd, ind);
- malloc_mutex_unlock(&arenas_lock);
+ if (internal) {
+ tsd_iarena_set(tsd, arena);
+ } else {
+ tsd_arena_set(tsd, arena);
+ }
}
void
-arena_migrate(tsd_t *tsd, unsigned oldind, unsigned newind)
-{
+arena_migrate(tsd_t *tsd, unsigned oldind, unsigned newind) {
arena_t *oldarena, *newarena;
- malloc_mutex_lock(&arenas_lock);
- oldarena = arenas[oldind];
- newarena = arenas[newind];
- oldarena->nthreads--;
- newarena->nthreads++;
- malloc_mutex_unlock(&arenas_lock);
+ oldarena = arena_get(tsd_tsdn(tsd), oldind, false);
+ newarena = arena_get(tsd_tsdn(tsd), newind, false);
+ arena_nthreads_dec(oldarena, false);
+ arena_nthreads_inc(newarena, false);
tsd_arena_set(tsd, newarena);
}
-unsigned
-arena_nbound(unsigned ind)
-{
- unsigned nthreads;
-
- malloc_mutex_lock(&arenas_lock);
- nthreads = arenas[ind]->nthreads;
- malloc_mutex_unlock(&arenas_lock);
- return (nthreads);
-}
-
static void
-arena_unbind(tsd_t *tsd, unsigned ind)
-{
+arena_unbind(tsd_t *tsd, unsigned ind, bool internal) {
arena_t *arena;
- malloc_mutex_lock(&arenas_lock);
- arena = arenas[ind];
- arena->nthreads--;
- malloc_mutex_unlock(&arenas_lock);
- tsd_arena_set(tsd, NULL);
+ arena = arena_get(tsd_tsdn(tsd), ind, false);
+ arena_nthreads_dec(arena, internal);
+
+ if (internal) {
+ tsd_iarena_set(tsd, NULL);
+ } else {
+ tsd_arena_set(tsd, NULL);
+ }
}
-arena_t *
-arena_get_hard(tsd_t *tsd, unsigned ind, bool init_if_missing)
-{
- arena_t *arena;
- arena_t **arenas_cache = tsd_arenas_cache_get(tsd);
- unsigned narenas_cache = tsd_narenas_cache_get(tsd);
+arena_tdata_t *
+arena_tdata_get_hard(tsd_t *tsd, unsigned ind) {
+ arena_tdata_t *tdata, *arenas_tdata_old;
+ arena_tdata_t *arenas_tdata = tsd_arenas_tdata_get(tsd);
+ unsigned narenas_tdata_old, i;
+ unsigned narenas_tdata = tsd_narenas_tdata_get(tsd);
unsigned narenas_actual = narenas_total_get();
- /* Deallocate old cache if it's too small. */
- if (arenas_cache != NULL && narenas_cache < narenas_actual) {
- a0dalloc(arenas_cache);
- arenas_cache = NULL;
- narenas_cache = 0;
- tsd_arenas_cache_set(tsd, arenas_cache);
- tsd_narenas_cache_set(tsd, narenas_cache);
- }
-
- /* Allocate cache if it's missing. */
- if (arenas_cache == NULL) {
- bool *arenas_cache_bypassp = tsd_arenas_cache_bypassp_get(tsd);
- assert(ind < narenas_actual || !init_if_missing);
- narenas_cache = (ind < narenas_actual) ? narenas_actual : ind+1;
-
- if (tsd_nominal(tsd) && !*arenas_cache_bypassp) {
- *arenas_cache_bypassp = true;
- arenas_cache = (arena_t **)a0malloc(sizeof(arena_t *) *
- narenas_cache);
- *arenas_cache_bypassp = false;
+ /*
+ * Dissociate old tdata array (and set up for deallocation upon return)
+ * if it's too small.
+ */
+ if (arenas_tdata != NULL && narenas_tdata < narenas_actual) {
+ arenas_tdata_old = arenas_tdata;
+ narenas_tdata_old = narenas_tdata;
+ arenas_tdata = NULL;
+ narenas_tdata = 0;
+ tsd_arenas_tdata_set(tsd, arenas_tdata);
+ tsd_narenas_tdata_set(tsd, narenas_tdata);
+ } else {
+ arenas_tdata_old = NULL;
+ narenas_tdata_old = 0;
+ }
+
+ /* Allocate tdata array if it's missing. */
+ if (arenas_tdata == NULL) {
+ bool *arenas_tdata_bypassp = tsd_arenas_tdata_bypassp_get(tsd);
+ narenas_tdata = (ind < narenas_actual) ? narenas_actual : ind+1;
+
+ if (tsd_nominal(tsd) && !*arenas_tdata_bypassp) {
+ *arenas_tdata_bypassp = true;
+ arenas_tdata = (arena_tdata_t *)a0malloc(
+ sizeof(arena_tdata_t) * narenas_tdata);
+ *arenas_tdata_bypassp = false;
}
- if (arenas_cache == NULL) {
- /*
- * This function must always tell the truth, even if
- * it's slow, so don't let OOM, thread cleanup (note
- * tsd_nominal check), nor recursive allocation
- * avoidance (note arenas_cache_bypass check) get in the
- * way.
- */
- if (ind >= narenas_actual)
- return (NULL);
- malloc_mutex_lock(&arenas_lock);
- arena = arenas[ind];
- malloc_mutex_unlock(&arenas_lock);
- return (arena);
+ if (arenas_tdata == NULL) {
+ tdata = NULL;
+ goto label_return;
}
- assert(tsd_nominal(tsd) && !*arenas_cache_bypassp);
- tsd_arenas_cache_set(tsd, arenas_cache);
- tsd_narenas_cache_set(tsd, narenas_cache);
+ assert(tsd_nominal(tsd) && !*arenas_tdata_bypassp);
+ tsd_arenas_tdata_set(tsd, arenas_tdata);
+ tsd_narenas_tdata_set(tsd, narenas_tdata);
}
/*
- * Copy to cache. It's possible that the actual number of arenas has
- * increased since narenas_total_get() was called above, but that causes
- * no correctness issues unless two threads concurrently execute the
- * arenas.extend mallctl, which we trust mallctl synchronization to
+ * Copy to tdata array. It's possible that the actual number of arenas
+ * has increased since narenas_total_get() was called above, but that
+ * causes no correctness issues unless two threads concurrently execute
+ * the arenas.create mallctl, which we trust mallctl synchronization to
* prevent.
*/
- malloc_mutex_lock(&arenas_lock);
- memcpy(arenas_cache, arenas, sizeof(arena_t *) * narenas_actual);
- malloc_mutex_unlock(&arenas_lock);
- if (narenas_cache > narenas_actual) {
- memset(&arenas_cache[narenas_actual], 0, sizeof(arena_t *) *
- (narenas_cache - narenas_actual));
+
+ /* Copy/initialize tickers. */
+ for (i = 0; i < narenas_actual; i++) {
+ if (i < narenas_tdata_old) {
+ ticker_copy(&arenas_tdata[i].decay_ticker,
+ &arenas_tdata_old[i].decay_ticker);
+ } else {
+ ticker_init(&arenas_tdata[i].decay_ticker,
+ DECAY_NTICKS_PER_UPDATE);
+ }
+ }
+ if (narenas_tdata > narenas_actual) {
+ memset(&arenas_tdata[narenas_actual], 0, sizeof(arena_tdata_t)
+ * (narenas_tdata - narenas_actual));
}
- /* Read the refreshed cache, and init the arena if necessary. */
- arena = arenas_cache[ind];
- if (init_if_missing && arena == NULL)
- arena = arenas_cache[ind] = arena_init(ind);
- return (arena);
+ /* Read the refreshed tdata array. */
+ tdata = &arenas_tdata[ind];
+label_return:
+ if (arenas_tdata_old != NULL) {
+ a0dalloc(arenas_tdata_old);
+ }
+ return tdata;
}
/* Slow path, called only by arena_choose(). */
arena_t *
-arena_choose_hard(tsd_t *tsd)
-{
- arena_t *ret;
+arena_choose_hard(tsd_t *tsd, bool internal) {
+ arena_t *ret JEMALLOC_CC_SILENCE_INIT(NULL);
+
+ if (have_percpu_arena && PERCPU_ARENA_ENABLED(opt_percpu_arena)) {
+ unsigned choose = percpu_arena_choose();
+ ret = arena_get(tsd_tsdn(tsd), choose, true);
+ assert(ret != NULL);
+ arena_bind(tsd, arena_ind_get(ret), false);
+ arena_bind(tsd, arena_ind_get(ret), true);
+
+ return ret;
+ }
if (narenas_auto > 1) {
- unsigned i, choose, first_null;
+ unsigned i, j, choose[2], first_null;
+ bool is_new_arena[2];
+
+ /*
+ * Determine binding for both non-internal and internal
+ * allocation.
+ *
+ * choose[0]: For application allocation.
+ * choose[1]: For internal metadata allocation.
+ */
+
+ for (j = 0; j < 2; j++) {
+ choose[j] = 0;
+ is_new_arena[j] = false;
+ }
- choose = 0;
first_null = narenas_auto;
- malloc_mutex_lock(&arenas_lock);
- assert(a0get() != NULL);
+ malloc_mutex_lock(tsd_tsdn(tsd), &arenas_lock);
+ assert(arena_get(tsd_tsdn(tsd), 0, false) != NULL);
for (i = 1; i < narenas_auto; i++) {
- if (arenas[i] != NULL) {
+ if (arena_get(tsd_tsdn(tsd), i, false) != NULL) {
/*
* Choose the first arena that has the lowest
* number of threads assigned to it.
*/
- if (arenas[i]->nthreads <
- arenas[choose]->nthreads)
- choose = i;
+ for (j = 0; j < 2; j++) {
+ if (arena_nthreads_get(arena_get(
+ tsd_tsdn(tsd), i, false), !!j) <
+ arena_nthreads_get(arena_get(
+ tsd_tsdn(tsd), choose[j], false),
+ !!j)) {
+ choose[j] = i;
+ }
+ }
} else if (first_null == narenas_auto) {
/*
* Record the index of the first uninitialized
@@ -605,89 +544,99 @@ arena_choose_hard(tsd_t *tsd)
}
}
- if (arenas[choose]->nthreads == 0
- || first_null == narenas_auto) {
- /*
- * Use an unloaded arena, or the least loaded arena if
- * all arenas are already initialized.
- */
- ret = arenas[choose];
- } else {
- /* Initialize a new arena. */
- choose = first_null;
- ret = arena_init_locked(choose);
- if (ret == NULL) {
- malloc_mutex_unlock(&arenas_lock);
- return (NULL);
+ for (j = 0; j < 2; j++) {
+ if (arena_nthreads_get(arena_get(tsd_tsdn(tsd),
+ choose[j], false), !!j) == 0 || first_null ==
+ narenas_auto) {
+ /*
+ * Use an unloaded arena, or the least loaded
+ * arena if all arenas are already initialized.
+ */
+ if (!!j == internal) {
+ ret = arena_get(tsd_tsdn(tsd),
+ choose[j], false);
+ }
+ } else {
+ arena_t *arena;
+
+ /* Initialize a new arena. */
+ choose[j] = first_null;
+ arena = arena_init_locked(tsd_tsdn(tsd),
+ choose[j],
+ (extent_hooks_t *)&extent_hooks_default);
+ if (arena == NULL) {
+ malloc_mutex_unlock(tsd_tsdn(tsd),
+ &arenas_lock);
+ return NULL;
+ }
+ is_new_arena[j] = true;
+ if (!!j == internal) {
+ ret = arena;
+ }
}
+ arena_bind(tsd, choose[j], !!j);
}
- arena_bind_locked(tsd, choose);
- malloc_mutex_unlock(&arenas_lock);
- } else {
- ret = a0get();
- arena_bind(tsd, 0);
- }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &arenas_lock);
- return (ret);
-}
+ for (j = 0; j < 2; j++) {
+ if (is_new_arena[j]) {
+ assert(choose[j] > 0);
+ arena_new_create_background_thread(
+ tsd_tsdn(tsd), choose[j]);
+ }
+ }
-void
-thread_allocated_cleanup(tsd_t *tsd)
-{
+ } else {
+ ret = arena_get(tsd_tsdn(tsd), 0, false);
+ arena_bind(tsd, 0, false);
+ arena_bind(tsd, 0, true);
+ }
- /* Do nothing. */
+ return ret;
}
void
-thread_deallocated_cleanup(tsd_t *tsd)
-{
+iarena_cleanup(tsd_t *tsd) {
+ arena_t *iarena;
- /* Do nothing. */
+ iarena = tsd_iarena_get(tsd);
+ if (iarena != NULL) {
+ arena_unbind(tsd, arena_ind_get(iarena), true);
+ }
}
void
-arena_cleanup(tsd_t *tsd)
-{
+arena_cleanup(tsd_t *tsd) {
arena_t *arena;
arena = tsd_arena_get(tsd);
- if (arena != NULL)
- arena_unbind(tsd, arena->ind);
-}
-
-void
-arenas_cache_cleanup(tsd_t *tsd)
-{
- arena_t **arenas_cache;
-
- arenas_cache = tsd_arenas_cache_get(tsd);
- if (arenas_cache != NULL) {
- tsd_arenas_cache_set(tsd, NULL);
- a0dalloc(arenas_cache);
+ if (arena != NULL) {
+ arena_unbind(tsd, arena_ind_get(arena), false);
}
}
void
-narenas_cache_cleanup(tsd_t *tsd)
-{
+arenas_tdata_cleanup(tsd_t *tsd) {
+ arena_tdata_t *arenas_tdata;
- /* Do nothing. */
-}
-
-void
-arenas_cache_bypass_cleanup(tsd_t *tsd)
-{
+ /* Prevent tsd->arenas_tdata from being (re)created. */
+ *tsd_arenas_tdata_bypassp_get(tsd) = true;
- /* Do nothing. */
+ arenas_tdata = tsd_arenas_tdata_get(tsd);
+ if (arenas_tdata != NULL) {
+ tsd_arenas_tdata_set(tsd, NULL);
+ a0dalloc(arenas_tdata);
+ }
}
static void
-stats_print_atexit(void)
-{
-
- if (config_tcache && config_stats) {
+stats_print_atexit(void) {
+ if (config_stats) {
+ tsdn_t *tsdn;
unsigned narenas, i;
+ tsdn = tsdn_fetch();
+
/*
* Merge stats from extant threads. This is racy, since
* individual threads do not lock when recording tcache stats
@@ -696,25 +645,45 @@ stats_print_atexit(void)
* continue to allocate.
*/
for (i = 0, narenas = narenas_total_get(); i < narenas; i++) {
- arena_t *arena = arenas[i];
+ arena_t *arena = arena_get(tsdn, i, false);
if (arena != NULL) {
tcache_t *tcache;
- /*
- * tcache_stats_merge() locks bins, so if any
- * code is introduced that acquires both arena
- * and bin locks in the opposite order,
- * deadlocks may result.
- */
- malloc_mutex_lock(&arena->lock);
+ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
ql_foreach(tcache, &arena->tcache_ql, link) {
- tcache_stats_merge(tcache, arena);
+ tcache_stats_merge(tsdn, tcache, arena);
}
- malloc_mutex_unlock(&arena->lock);
+ malloc_mutex_unlock(tsdn,
+ &arena->tcache_ql_mtx);
}
}
}
- je_malloc_stats_print(NULL, NULL, NULL);
+ je_malloc_stats_print(NULL, NULL, opt_stats_print_opts);
+}
+
+/*
+ * Ensure that we don't hold any locks upon entry to or exit from allocator
+ * code (in a "broad" sense that doesn't count a reentrant allocation as an
+ * entrance or exit).
+ */
+JEMALLOC_ALWAYS_INLINE void
+check_entry_exit_locking(tsdn_t *tsdn) {
+ if (!config_debug) {
+ return;
+ }
+ if (tsdn_null(tsdn)) {
+ return;
+ }
+ tsd_t *tsd = tsdn_tsd(tsdn);
+ /*
+ * It's possible we hold locks at entry/exit if we're in a nested
+ * allocation.
+ */
+ int8_t reentrancy_level = tsd_reentrancy_level_get(tsd);
+ if (reentrancy_level != 0) {
+ return;
+ }
+ witness_assert_lockless(tsdn_witness_tsdp_get(tsdn));
}
/*
@@ -725,38 +694,76 @@ stats_print_atexit(void)
* Begin initialization functions.
*/
-#ifndef JEMALLOC_HAVE_SECURE_GETENV
static char *
-secure_getenv(const char *name)
-{
-
+jemalloc_secure_getenv(const char *name) {
+#ifdef JEMALLOC_HAVE_SECURE_GETENV
+ return secure_getenv(name);
+#else
# ifdef JEMALLOC_HAVE_ISSETUGID
- if (issetugid() != 0)
- return (NULL);
+ if (issetugid() != 0) {
+ return NULL;
+ }
# endif
- return (getenv(name));
-}
+ return getenv(name);
#endif
+}
static unsigned
-malloc_ncpus(void)
-{
+malloc_ncpus(void) {
long result;
#ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);
result = si.dwNumberOfProcessors;
+#elif defined(JEMALLOC_GLIBC_MALLOC_HOOK) && defined(CPU_COUNT)
+ /*
+ * glibc >= 2.6 has the CPU_COUNT macro.
+ *
+ * glibc's sysconf() uses isspace(). glibc allocates for the first time
+ * *before* setting up the isspace tables. Therefore we need a
+ * different method to get the number of CPUs.
+ */
+ {
+ cpu_set_t set;
+
+ pthread_getaffinity_np(pthread_self(), sizeof(set), &set);
+ result = CPU_COUNT(&set);
+ }
#else
result = sysconf(_SC_NPROCESSORS_ONLN);
#endif
return ((result == -1) ? 1 : (unsigned)result);
}
+static void
+init_opt_stats_print_opts(const char *v, size_t vlen) {
+ size_t opts_len = strlen(opt_stats_print_opts);
+ assert(opts_len <= stats_print_tot_num_options);
+
+ for (size_t i = 0; i < vlen; i++) {
+ switch (v[i]) {
+#define OPTION(o, v, d, s) case o: break;
+ STATS_PRINT_OPTIONS
+#undef OPTION
+ default: continue;
+ }
+
+ if (strchr(opt_stats_print_opts, v[i]) != NULL) {
+ /* Ignore repeated. */
+ continue;
+ }
+
+ opt_stats_print_opts[opts_len++] = v[i];
+ opt_stats_print_opts[opts_len] = '\0';
+ assert(opts_len <= stats_print_tot_num_options);
+ }
+ assert(opts_len == strlen(opt_stats_print_opts));
+}
+
static bool
malloc_conf_next(char const **opts_p, char const **k_p, size_t *klen_p,
- char const **v_p, size_t *vlen_p)
-{
+ char const **v_p, size_t *vlen_p) {
bool accept;
const char *opts = *opts_p;
@@ -790,10 +797,10 @@ malloc_conf_next(char const **opts_p, char const **k_p, size_t *klen_p,
malloc_write("<jemalloc>: Conf string ends "
"with key\n");
}
- return (true);
+ return true;
default:
malloc_write("<jemalloc>: Malformed conf string\n");
- return (true);
+ return true;
}
}
@@ -826,48 +833,55 @@ malloc_conf_next(char const **opts_p, char const **k_p, size_t *klen_p,
}
*opts_p = opts;
- return (false);
+ return false;
}
static void
-malloc_conf_error(const char *msg, const char *k, size_t klen, const char *v,
- size_t vlen)
-{
+malloc_abort_invalid_conf(void) {
+ assert(opt_abort_conf);
+ malloc_printf("<jemalloc>: Abort (abort_conf:true) on invalid conf "
+ "value (see above).\n");
+ abort();
+}
+static void
+malloc_conf_error(const char *msg, const char *k, size_t klen, const char *v,
+ size_t vlen) {
malloc_printf("<jemalloc>: %s: %.*s:%.*s\n", msg, (int)klen, k,
(int)vlen, v);
+ /* If abort_conf is set, error out after processing all options. */
+ had_conf_error = true;
}
static void
-malloc_conf_init(void)
-{
+malloc_slow_flag_init(void) {
+ /*
+ * Combine the runtime options into malloc_slow for fast path. Called
+ * after processing all the options.
+ */
+ malloc_slow_flags |= (opt_junk_alloc ? flag_opt_junk_alloc : 0)
+ | (opt_junk_free ? flag_opt_junk_free : 0)
+ | (opt_zero ? flag_opt_zero : 0)
+ | (opt_utrace ? flag_opt_utrace : 0)
+ | (opt_xmalloc ? flag_opt_xmalloc : 0);
+
+ malloc_slow = (malloc_slow_flags != 0);
+}
+
+static void
+malloc_conf_init(void) {
unsigned i;
char buf[PATH_MAX + 1];
const char *opts, *k, *v;
size_t klen, vlen;
- /*
- * Automatically configure valgrind before processing options. The
- * valgrind option remains in jemalloc 3.x for compatibility reasons.
- */
- if (config_valgrind) {
- in_valgrind = (RUNNING_ON_VALGRIND != 0) ? true : false;
- if (config_fill && unlikely(in_valgrind)) {
- opt_junk = "false";
- opt_junk_alloc = false;
- opt_junk_free = false;
- assert(!opt_zero);
- opt_quarantine = JEMALLOC_VALGRIND_QUARANTINE_DEFAULT;
- opt_redzone = true;
- }
- if (config_tcache && unlikely(in_valgrind))
- opt_tcache = false;
- }
-
- for (i = 0; i < 3; i++) {
+ for (i = 0; i < 4; i++) {
/* Get runtime configuration. */
switch (i) {
case 0:
+ opts = config_malloc_conf;
+ break;
+ case 1:
if (je_malloc_conf != NULL) {
/*
* Use options that were compiled into the
@@ -880,8 +894,8 @@ malloc_conf_init(void)
opts = buf;
}
break;
- case 1: {
- int linklen = 0;
+ case 2: {
+ ssize_t linklen = 0;
#ifndef _WIN32
int saved_errno = errno;
const char *linkname =
@@ -907,7 +921,7 @@ malloc_conf_init(void)
buf[linklen] = '\0';
opts = buf;
break;
- } case 2: {
+ } case 3: {
const char *envname =
#ifdef JEMALLOC_PREFIX
JEMALLOC_CPREFIX"MALLOC_CONF"
@@ -916,7 +930,7 @@ malloc_conf_init(void)
#endif
;
- if ((opts = secure_getenv(envname)) != NULL) {
+ if ((opts = jemalloc_secure_getenv(envname)) != NULL) {
/*
* Do nothing; opts is already initialized to
* the value of the MALLOC_CONF environment
@@ -936,25 +950,28 @@ malloc_conf_init(void)
while (*opts != '\0' && !malloc_conf_next(&opts, &k, &klen, &v,
&vlen)) {
-#define CONF_MATCH(n) \
+#define CONF_MATCH(n) \
(sizeof(n)-1 == klen && strncmp(n, k, klen) == 0)
-#define CONF_MATCH_VALUE(n) \
+#define CONF_MATCH_VALUE(n) \
(sizeof(n)-1 == vlen && strncmp(n, v, vlen) == 0)
-#define CONF_HANDLE_BOOL(o, n, cont) \
+#define CONF_HANDLE_BOOL(o, n) \
if (CONF_MATCH(n)) { \
- if (CONF_MATCH_VALUE("true")) \
+ if (CONF_MATCH_VALUE("true")) { \
o = true; \
- else if (CONF_MATCH_VALUE("false")) \
+ } else if (CONF_MATCH_VALUE("false")) { \
o = false; \
- else { \
+ } else { \
malloc_conf_error( \
"Invalid conf value", \
k, klen, v, vlen); \
} \
- if (cont) \
- continue; \
+ continue; \
}
-#define CONF_HANDLE_SIZE_T(o, n, min, max, clip) \
+#define CONF_MIN_no(um, min) false
+#define CONF_MIN_yes(um, min) ((um) < (min))
+#define CONF_MAX_no(um, max) false
+#define CONF_MAX_yes(um, max) ((um) > (max))
+#define CONF_HANDLE_T_U(t, o, n, min, max, check_min, check_max, clip) \
if (CONF_MATCH(n)) { \
uintmax_t um; \
char *end; \
@@ -967,25 +984,39 @@ malloc_conf_init(void)
"Invalid conf value", \
k, klen, v, vlen); \
} else if (clip) { \
- if ((min) != 0 && um < (min)) \
- o = (min); \
- else if (um > (max)) \
- o = (max); \
- else \
- o = um; \
+ if (CONF_MIN_##check_min(um, \
+ (t)(min))) { \
+ o = (t)(min); \
+ } else if ( \
+ CONF_MAX_##check_max(um, \
+ (t)(max))) { \
+ o = (t)(max); \
+ } else { \
+ o = (t)um; \
+ } \
} else { \
- if (((min) != 0 && um < (min)) \
- || um > (max)) { \
+ if (CONF_MIN_##check_min(um, \
+ (t)(min)) || \
+ CONF_MAX_##check_max(um, \
+ (t)(max))) { \
malloc_conf_error( \
"Out-of-range " \
"conf value", \
k, klen, v, vlen); \
- } else \
- o = um; \
+ } else { \
+ o = (t)um; \
+ } \
} \
continue; \
}
-#define CONF_HANDLE_SSIZE_T(o, n, min, max) \
+#define CONF_HANDLE_UNSIGNED(o, n, min, max, check_min, check_max, \
+ clip) \
+ CONF_HANDLE_T_U(unsigned, o, n, min, max, \
+ check_min, check_max, clip)
+#define CONF_HANDLE_SIZE_T(o, n, min, max, check_min, check_max, clip) \
+ CONF_HANDLE_T_U(size_t, o, n, min, max, \
+ check_min, check_max, clip)
+#define CONF_HANDLE_SSIZE_T(o, n, min, max) \
if (CONF_MATCH(n)) { \
long l; \
char *end; \
@@ -1002,11 +1033,12 @@ malloc_conf_init(void)
malloc_conf_error( \
"Out-of-range conf value", \
k, klen, v, vlen); \
- } else \
+ } else { \
o = l; \
+ } \
continue; \
}
-#define CONF_HANDLE_CHAR_P(o, n, d) \
+#define CONF_HANDLE_CHAR_P(o, n, d) \
if (CONF_MATCH(n)) { \
size_t cpylen = (vlen <= \
sizeof(o)-1) ? vlen : \
@@ -1016,25 +1048,33 @@ malloc_conf_init(void)
continue; \
}
- CONF_HANDLE_BOOL(opt_abort, "abort", true)
- /*
- * Chunks always require at least one header page,
- * as many as 2^(LG_SIZE_CLASS_GROUP+1) data pages, and
- * possibly an additional page in the presence of
- * redzones. In order to simplify options processing,
- * use a conservative bound that accommodates all these
- * constraints.
- */
- CONF_HANDLE_SIZE_T(opt_lg_chunk, "lg_chunk", LG_PAGE +
- LG_SIZE_CLASS_GROUP + (config_fill ? 2 : 1),
- (sizeof(size_t) << 3) - 1, true)
+ CONF_HANDLE_BOOL(opt_abort, "abort")
+ CONF_HANDLE_BOOL(opt_abort_conf, "abort_conf")
+ if (strncmp("metadata_thp", k, klen) == 0) {
+ int i;
+ bool match = false;
+ for (i = 0; i < metadata_thp_mode_limit; i++) {
+ if (strncmp(metadata_thp_mode_names[i],
+ v, vlen) == 0) {
+ opt_metadata_thp = i;
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ malloc_conf_error("Invalid conf value",
+ k, klen, v, vlen);
+ }
+ continue;
+ }
+ CONF_HANDLE_BOOL(opt_retain, "retain")
if (strncmp("dss", k, klen) == 0) {
int i;
bool match = false;
for (i = 0; i < dss_prec_limit; i++) {
if (strncmp(dss_prec_names[i], v, vlen)
== 0) {
- if (chunk_dss_prec_set(i)) {
+ if (extent_dss_prec_set(i)) {
malloc_conf_error(
"Error setting dss",
k, klen, v, vlen);
@@ -1052,11 +1092,21 @@ malloc_conf_init(void)
}
continue;
}
- CONF_HANDLE_SIZE_T(opt_narenas, "narenas", 1,
- SIZE_T_MAX, false)
- CONF_HANDLE_SSIZE_T(opt_lg_dirty_mult, "lg_dirty_mult",
- -1, (sizeof(size_t) << 3) - 1)
- CONF_HANDLE_BOOL(opt_stats_print, "stats_print", true)
+ CONF_HANDLE_UNSIGNED(opt_narenas, "narenas", 1,
+ UINT_MAX, yes, no, false)
+ CONF_HANDLE_SSIZE_T(opt_dirty_decay_ms,
+ "dirty_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) <
+ QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) :
+ SSIZE_MAX);
+ CONF_HANDLE_SSIZE_T(opt_muzzy_decay_ms,
+ "muzzy_decay_ms", -1, NSTIME_SEC_MAX * KQU(1000) <
+ QU(SSIZE_MAX) ? NSTIME_SEC_MAX * KQU(1000) :
+ SSIZE_MAX);
+ CONF_HANDLE_BOOL(opt_stats_print, "stats_print")
+ if (CONF_MATCH("stats_print_opts")) {
+ init_opt_stats_print_opts(v, vlen);
+ continue;
+ }
if (config_fill) {
if (CONF_MATCH("junk")) {
if (CONF_MATCH_VALUE("true")) {
@@ -1082,74 +1132,121 @@ malloc_conf_init(void)
}
continue;
}
- CONF_HANDLE_SIZE_T(opt_quarantine, "quarantine",
- 0, SIZE_T_MAX, false)
- CONF_HANDLE_BOOL(opt_redzone, "redzone", true)
- CONF_HANDLE_BOOL(opt_zero, "zero", true)
+ CONF_HANDLE_BOOL(opt_zero, "zero")
}
if (config_utrace) {
- CONF_HANDLE_BOOL(opt_utrace, "utrace", true)
+ CONF_HANDLE_BOOL(opt_utrace, "utrace")
}
if (config_xmalloc) {
- CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc", true)
+ CONF_HANDLE_BOOL(opt_xmalloc, "xmalloc")
}
- if (config_tcache) {
- CONF_HANDLE_BOOL(opt_tcache, "tcache",
- !config_valgrind || !in_valgrind)
- if (CONF_MATCH("tcache")) {
- assert(config_valgrind && in_valgrind);
- if (opt_tcache) {
- opt_tcache = false;
- malloc_conf_error(
- "tcache cannot be enabled "
- "while running inside Valgrind",
- k, klen, v, vlen);
+ CONF_HANDLE_BOOL(opt_tcache, "tcache")
+ CONF_HANDLE_SIZE_T(opt_lg_extent_max_active_fit,
+ "lg_extent_max_active_fit", 0,
+ (sizeof(size_t) << 3), yes, yes, false)
+ CONF_HANDLE_SSIZE_T(opt_lg_tcache_max, "lg_tcache_max",
+ -1, (sizeof(size_t) << 3) - 1)
+ if (strncmp("percpu_arena", k, klen) == 0) {
+ bool match = false;
+ for (int i = percpu_arena_mode_names_base; i <
+ percpu_arena_mode_names_limit; i++) {
+ if (strncmp(percpu_arena_mode_names[i],
+ v, vlen) == 0) {
+ if (!have_percpu_arena) {
+ malloc_conf_error(
+ "No getcpu support",
+ k, klen, v, vlen);
+ }
+ opt_percpu_arena = i;
+ match = true;
+ break;
}
- continue;
}
- CONF_HANDLE_SSIZE_T(opt_lg_tcache_max,
- "lg_tcache_max", -1,
- (sizeof(size_t) << 3) - 1)
+ if (!match) {
+ malloc_conf_error("Invalid conf value",
+ k, klen, v, vlen);
+ }
+ continue;
}
+ CONF_HANDLE_BOOL(opt_background_thread,
+ "background_thread");
+ CONF_HANDLE_SIZE_T(opt_max_background_threads,
+ "max_background_threads", 1,
+ opt_max_background_threads, yes, yes,
+ true);
if (config_prof) {
- CONF_HANDLE_BOOL(opt_prof, "prof", true)
+ CONF_HANDLE_BOOL(opt_prof, "prof")
CONF_HANDLE_CHAR_P(opt_prof_prefix,
"prof_prefix", "jeprof")
- CONF_HANDLE_BOOL(opt_prof_active, "prof_active",
- true)
+ CONF_HANDLE_BOOL(opt_prof_active, "prof_active")
CONF_HANDLE_BOOL(opt_prof_thread_active_init,
- "prof_thread_active_init", true)
+ "prof_thread_active_init")
CONF_HANDLE_SIZE_T(opt_lg_prof_sample,
- "lg_prof_sample", 0,
- (sizeof(uint64_t) << 3) - 1, true)
- CONF_HANDLE_BOOL(opt_prof_accum, "prof_accum",
- true)
+ "lg_prof_sample", 0, (sizeof(uint64_t) << 3)
+ - 1, no, yes, true)
+ CONF_HANDLE_BOOL(opt_prof_accum, "prof_accum")
CONF_HANDLE_SSIZE_T(opt_lg_prof_interval,
"lg_prof_interval", -1,
(sizeof(uint64_t) << 3) - 1)
- CONF_HANDLE_BOOL(opt_prof_gdump, "prof_gdump",
- true)
- CONF_HANDLE_BOOL(opt_prof_final, "prof_final",
- true)
- CONF_HANDLE_BOOL(opt_prof_leak, "prof_leak",
- true)
+ CONF_HANDLE_BOOL(opt_prof_gdump, "prof_gdump")
+ CONF_HANDLE_BOOL(opt_prof_final, "prof_final")
+ CONF_HANDLE_BOOL(opt_prof_leak, "prof_leak")
+ }
+ if (config_log) {
+ if (CONF_MATCH("log")) {
+ size_t cpylen = (
+ vlen <= sizeof(log_var_names) ?
+ vlen : sizeof(log_var_names) - 1);
+ strncpy(log_var_names, v, cpylen);
+ log_var_names[cpylen] = '\0';
+ continue;
+ }
+ }
+ if (CONF_MATCH("thp")) {
+ bool match = false;
+ for (int i = 0; i < thp_mode_names_limit; i++) {
+ if (strncmp(thp_mode_names[i],v, vlen)
+ == 0) {
+ if (!have_madvise_huge) {
+ malloc_conf_error(
+ "No THP support",
+ k, klen, v, vlen);
+ }
+ opt_thp = i;
+ match = true;
+ break;
+ }
+ }
+ if (!match) {
+ malloc_conf_error("Invalid conf value",
+ k, klen, v, vlen);
+ }
+ continue;
}
malloc_conf_error("Invalid conf pair", k, klen, v,
vlen);
#undef CONF_MATCH
+#undef CONF_MATCH_VALUE
#undef CONF_HANDLE_BOOL
+#undef CONF_MIN_no
+#undef CONF_MIN_yes
+#undef CONF_MAX_no
+#undef CONF_MAX_yes
+#undef CONF_HANDLE_T_U
+#undef CONF_HANDLE_UNSIGNED
#undef CONF_HANDLE_SIZE_T
#undef CONF_HANDLE_SSIZE_T
#undef CONF_HANDLE_CHAR_P
}
+ if (opt_abort_conf && had_conf_error) {
+ malloc_abort_invalid_conf();
+ }
}
+ atomic_store_b(&log_init_done, true, ATOMIC_RELEASE);
}
-/* init_lock must be held. */
static bool
-malloc_init_hard_needed(void)
-{
-
+malloc_init_hard_needed(void) {
if (malloc_initialized() || (IS_INITIALIZER && malloc_init_state ==
malloc_init_recursible)) {
/*
@@ -1157,193 +1254,322 @@ malloc_init_hard_needed(void)
* acquired init_lock, or this thread is the initializing
* thread, and it is recursively allocating.
*/
- return (false);
+ return false;
}
#ifdef JEMALLOC_THREADED_INIT
if (malloc_initializer != NO_INITIALIZER && !IS_INITIALIZER) {
/* Busy-wait until the initializing thread completes. */
+ spin_t spinner = SPIN_INITIALIZER;
do {
- malloc_mutex_unlock(&init_lock);
- CPU_SPINWAIT;
- malloc_mutex_lock(&init_lock);
+ malloc_mutex_unlock(TSDN_NULL, &init_lock);
+ spin_adaptive(&spinner);
+ malloc_mutex_lock(TSDN_NULL, &init_lock);
} while (!malloc_initialized());
- return (false);
+ return false;
}
#endif
- return (true);
+ return true;
}
-/* init_lock must be held. */
static bool
-malloc_init_hard_a0_locked(void)
-{
-
+malloc_init_hard_a0_locked() {
malloc_initializer = INITIALIZER;
- if (config_prof)
+ if (config_prof) {
prof_boot0();
+ }
malloc_conf_init();
if (opt_stats_print) {
/* Print statistics at exit. */
if (atexit(stats_print_atexit) != 0) {
malloc_write("<jemalloc>: Error in atexit()\n");
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
}
}
- if (base_boot())
- return (true);
- if (chunk_boot())
- return (true);
- if (ctl_boot())
- return (true);
- if (config_prof)
+ if (pages_boot()) {
+ return true;
+ }
+ if (base_boot(TSDN_NULL)) {
+ return true;
+ }
+ if (extent_boot()) {
+ return true;
+ }
+ if (ctl_boot()) {
+ return true;
+ }
+ if (config_prof) {
prof_boot1();
- if (arena_boot())
- return (true);
- if (config_tcache && tcache_boot())
- return (true);
- if (malloc_mutex_init(&arenas_lock))
- return (true);
+ }
+ arena_boot();
+ if (tcache_boot(TSDN_NULL)) {
+ return true;
+ }
+ if (malloc_mutex_init(&arenas_lock, "arenas", WITNESS_RANK_ARENAS,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
/*
* Create enough scaffolding to allow recursive allocation in
* malloc_ncpus().
*/
- narenas_total = narenas_auto = 1;
- arenas = &a0;
+ narenas_auto = 1;
memset(arenas, 0, sizeof(arena_t *) * narenas_auto);
/*
* Initialize one arena here. The rest are lazily created in
* arena_choose_hard().
*/
- if (arena_init(0) == NULL)
- return (true);
+ if (arena_init(TSDN_NULL, 0, (extent_hooks_t *)&extent_hooks_default)
+ == NULL) {
+ return true;
+ }
+ a0 = arena_get(TSDN_NULL, 0, false);
malloc_init_state = malloc_init_a0_initialized;
- return (false);
+
+ return false;
}
static bool
-malloc_init_hard_a0(void)
-{
+malloc_init_hard_a0(void) {
bool ret;
- malloc_mutex_lock(&init_lock);
+ malloc_mutex_lock(TSDN_NULL, &init_lock);
ret = malloc_init_hard_a0_locked();
- malloc_mutex_unlock(&init_lock);
- return (ret);
+ malloc_mutex_unlock(TSDN_NULL, &init_lock);
+ return ret;
}
-/*
- * Initialize data structures which may trigger recursive allocation.
- *
- * init_lock must be held.
- */
-static void
-malloc_init_hard_recursible(void)
-{
-
+/* Initialize data structures which may trigger recursive allocation. */
+static bool
+malloc_init_hard_recursible(void) {
malloc_init_state = malloc_init_recursible;
- malloc_mutex_unlock(&init_lock);
ncpus = malloc_ncpus();
-#if (!defined(JEMALLOC_MUTEX_INIT_CB) && !defined(JEMALLOC_ZONE) \
- && !defined(_WIN32) && !defined(__native_client__))
- /* LinuxThreads's pthread_atfork() allocates. */
+#if (defined(JEMALLOC_HAVE_PTHREAD_ATFORK) && !defined(JEMALLOC_MUTEX_INIT_CB) \
+ && !defined(JEMALLOC_ZONE) && !defined(_WIN32) && \
+ !defined(__native_client__))
+ /* LinuxThreads' pthread_atfork() allocates. */
if (pthread_atfork(jemalloc_prefork, jemalloc_postfork_parent,
jemalloc_postfork_child) != 0) {
malloc_write("<jemalloc>: Error in pthread_atfork()\n");
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
+ return true;
}
#endif
- malloc_mutex_lock(&init_lock);
+
+ if (background_thread_boot0()) {
+ return true;
+ }
+
+ return false;
}
-/* init_lock must be held. */
-static bool
-malloc_init_hard_finish(void)
-{
+static unsigned
+malloc_narenas_default(void) {
+ assert(ncpus > 0);
+ /*
+ * For SMP systems, create more than one arena per CPU by
+ * default.
+ */
+ if (ncpus > 1) {
+ return ncpus << 2;
+ } else {
+ return 1;
+ }
+}
+
+static percpu_arena_mode_t
+percpu_arena_as_initialized(percpu_arena_mode_t mode) {
+ assert(!malloc_initialized());
+ assert(mode <= percpu_arena_disabled);
- if (mutex_boot())
- return (true);
+ if (mode != percpu_arena_disabled) {
+ mode += percpu_arena_mode_enabled_base;
+ }
+
+ return mode;
+}
+static bool
+malloc_init_narenas(void) {
+ assert(ncpus > 0);
+
+ if (opt_percpu_arena != percpu_arena_disabled) {
+ if (!have_percpu_arena || malloc_getcpu() < 0) {
+ opt_percpu_arena = percpu_arena_disabled;
+ malloc_printf("<jemalloc>: perCPU arena getcpu() not "
+ "available. Setting narenas to %u.\n", opt_narenas ?
+ opt_narenas : malloc_narenas_default());
+ if (opt_abort) {
+ abort();
+ }
+ } else {
+ if (ncpus >= MALLOCX_ARENA_LIMIT) {
+ malloc_printf("<jemalloc>: narenas w/ percpu"
+ "arena beyond limit (%d)\n", ncpus);
+ if (opt_abort) {
+ abort();
+ }
+ return true;
+ }
+ /* NB: opt_percpu_arena isn't fully initialized yet. */
+ if (percpu_arena_as_initialized(opt_percpu_arena) ==
+ per_phycpu_arena && ncpus % 2 != 0) {
+ malloc_printf("<jemalloc>: invalid "
+ "configuration -- per physical CPU arena "
+ "with odd number (%u) of CPUs (no hyper "
+ "threading?).\n", ncpus);
+ if (opt_abort)
+ abort();
+ }
+ unsigned n = percpu_arena_ind_limit(
+ percpu_arena_as_initialized(opt_percpu_arena));
+ if (opt_narenas < n) {
+ /*
+ * If narenas is specified with percpu_arena
+ * enabled, actual narenas is set as the greater
+ * of the two. percpu_arena_choose will be free
+ * to use any of the arenas based on CPU
+ * id. This is conservative (at a small cost)
+ * but ensures correctness.
+ *
+ * If for some reason the ncpus determined at
+ * boot is not the actual number (e.g. because
+ * of affinity setting from numactl), reserving
+ * narenas this way provides a workaround for
+ * percpu_arena.
+ */
+ opt_narenas = n;
+ }
+ }
+ }
if (opt_narenas == 0) {
- /*
- * For SMP systems, create more than one arena per CPU by
- * default.
- */
- if (ncpus > 1)
- opt_narenas = ncpus << 2;
- else
- opt_narenas = 1;
+ opt_narenas = malloc_narenas_default();
}
+ assert(opt_narenas > 0);
+
narenas_auto = opt_narenas;
/*
- * Make sure that the arenas array can be allocated. In practice, this
- * limit is enough to allow the allocator to function, but the ctl
- * machinery will fail to allocate memory at far lower limits.
+ * Limit the number of arenas to the indexing range of MALLOCX_ARENA().
*/
- if (narenas_auto > chunksize / sizeof(arena_t *)) {
- narenas_auto = chunksize / sizeof(arena_t *);
+ if (narenas_auto >= MALLOCX_ARENA_LIMIT) {
+ narenas_auto = MALLOCX_ARENA_LIMIT - 1;
malloc_printf("<jemalloc>: Reducing narenas to limit (%d)\n",
narenas_auto);
}
- narenas_total = narenas_auto;
+ narenas_total_set(narenas_auto);
- /* Allocate and initialize arenas. */
- arenas = (arena_t **)base_alloc(sizeof(arena_t *) * narenas_total);
- if (arenas == NULL)
- return (true);
- /*
- * Zero the array. In practice, this should always be pre-zeroed,
- * since it was just mmap()ed, but let's be sure.
- */
- memset(arenas, 0, sizeof(arena_t *) * narenas_total);
- /* Copy the pointer to the one arena that was already initialized. */
- arenas[0] = a0;
+ return false;
+}
+
+static void
+malloc_init_percpu(void) {
+ opt_percpu_arena = percpu_arena_as_initialized(opt_percpu_arena);
+}
+
+static bool
+malloc_init_hard_finish(void) {
+ if (malloc_mutex_boot()) {
+ return true;
+ }
malloc_init_state = malloc_init_initialized;
- return (false);
+ malloc_slow_flag_init();
+
+ return false;
+}
+
+static void
+malloc_init_hard_cleanup(tsdn_t *tsdn, bool reentrancy_set) {
+ malloc_mutex_assert_owner(tsdn, &init_lock);
+ malloc_mutex_unlock(tsdn, &init_lock);
+ if (reentrancy_set) {
+ assert(!tsdn_null(tsdn));
+ tsd_t *tsd = tsdn_tsd(tsdn);
+ assert(tsd_reentrancy_level_get(tsd) > 0);
+ post_reentrancy(tsd);
+ }
}
static bool
-malloc_init_hard(void)
-{
+malloc_init_hard(void) {
+ tsd_t *tsd;
#if defined(_WIN32) && _WIN32_WINNT < 0x0600
_init_init_lock();
#endif
- malloc_mutex_lock(&init_lock);
+ malloc_mutex_lock(TSDN_NULL, &init_lock);
+
+#define UNLOCK_RETURN(tsdn, ret, reentrancy) \
+ malloc_init_hard_cleanup(tsdn, reentrancy); \
+ return ret;
+
if (!malloc_init_hard_needed()) {
- malloc_mutex_unlock(&init_lock);
- return (false);
+ UNLOCK_RETURN(TSDN_NULL, false, false)
}
if (malloc_init_state != malloc_init_a0_initialized &&
malloc_init_hard_a0_locked()) {
- malloc_mutex_unlock(&init_lock);
- return (true);
+ UNLOCK_RETURN(TSDN_NULL, true, false)
}
- if (malloc_tsd_boot0()) {
- malloc_mutex_unlock(&init_lock);
- return (true);
+
+ malloc_mutex_unlock(TSDN_NULL, &init_lock);
+ /* Recursive allocation relies on functional tsd. */
+ tsd = malloc_tsd_boot0();
+ if (tsd == NULL) {
+ return true;
}
- if (config_prof && prof_boot2()) {
- malloc_mutex_unlock(&init_lock);
- return (true);
+ if (malloc_init_hard_recursible()) {
+ return true;
}
- malloc_init_hard_recursible();
+ malloc_mutex_lock(tsd_tsdn(tsd), &init_lock);
+ /* Set reentrancy level to 1 during init. */
+ pre_reentrancy(tsd, NULL);
+ /* Initialize narenas before prof_boot2 (for allocation). */
+ if (malloc_init_narenas() || background_thread_boot1(tsd_tsdn(tsd))) {
+ UNLOCK_RETURN(tsd_tsdn(tsd), true, true)
+ }
+ if (config_prof && prof_boot2(tsd)) {
+ UNLOCK_RETURN(tsd_tsdn(tsd), true, true)
+ }
+
+ malloc_init_percpu();
if (malloc_init_hard_finish()) {
- malloc_mutex_unlock(&init_lock);
- return (true);
+ UNLOCK_RETURN(tsd_tsdn(tsd), true, true)
}
+ post_reentrancy(tsd);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &init_lock);
- malloc_mutex_unlock(&init_lock);
+ witness_assert_lockless(witness_tsd_tsdn(
+ tsd_witness_tsdp_get_unsafe(tsd)));
malloc_tsd_boot1();
- return (false);
+ /* Update TSD after tsd_boot1. */
+ tsd = tsd_fetch();
+ if (opt_background_thread) {
+ assert(have_background_thread);
+ /*
+ * Need to finish init & unlock first before creating background
+ * threads (pthread_create depends on malloc). ctl_init (which
+ * sets isthreaded) needs to be called without holding any lock.
+ */
+ background_thread_ctl_init(tsd_tsdn(tsd));
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &background_thread_lock);
+ bool err = background_thread_create(tsd, 0);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &background_thread_lock);
+ if (err) {
+ return true;
+ }
+ }
+#undef UNLOCK_RETURN
+ return false;
}
/*
@@ -1351,463 +1577,772 @@ malloc_init_hard(void)
*/
/******************************************************************************/
/*
- * Begin malloc(3)-compatible functions.
+ * Begin allocation-path internal functions and data structures.
*/
-static void *
-imalloc_prof_sample(tsd_t *tsd, size_t usize, prof_tctx_t *tctx)
-{
- void *p;
+/*
+ * Settings determined by the documented behavior of the allocation functions.
+ */
+typedef struct static_opts_s static_opts_t;
+struct static_opts_s {
+ /* Whether or not allocation size may overflow. */
+ bool may_overflow;
+ /* Whether or not allocations of size 0 should be treated as size 1. */
+ bool bump_empty_alloc;
+ /*
+ * Whether to assert that allocations are not of size 0 (after any
+ * bumping).
+ */
+ bool assert_nonempty_alloc;
- if (tctx == NULL)
- return (NULL);
- if (usize <= SMALL_MAXCLASS) {
- p = imalloc(tsd, LARGE_MINCLASS);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
- } else
- p = imalloc(tsd, usize);
+ /*
+ * Whether or not to modify the 'result' argument to malloc in case of
+ * error.
+ */
+ bool null_out_result_on_error;
+ /* Whether to set errno when we encounter an error condition. */
+ bool set_errno_on_error;
- return (p);
-}
+ /*
+ * The minimum valid alignment for functions requesting aligned storage.
+ */
+ size_t min_alignment;
-JEMALLOC_ALWAYS_INLINE_C void *
-imalloc_prof(tsd_t *tsd, size_t usize)
-{
- void *p;
- prof_tctx_t *tctx;
+ /* The error string to use if we oom. */
+ const char *oom_string;
+ /* The error string to use if the passed-in alignment is invalid. */
+ const char *invalid_alignment_string;
- tctx = prof_alloc_prep(tsd, usize, prof_active_get_unlocked(), true);
- if (unlikely((uintptr_t)tctx != (uintptr_t)1U))
- p = imalloc_prof_sample(tsd, usize, tctx);
- else
- p = imalloc(tsd, usize);
- if (unlikely(p == NULL)) {
- prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
- }
- prof_malloc(p, usize, tctx);
+ /*
+ * False if we're configured to skip some time-consuming operations.
+ *
+ * This isn't really a malloc "behavior", but it acts as a useful
+ * summary of several other static (or at least, static after program
+ * initialization) options.
+ */
+ bool slow;
+};
- return (p);
+JEMALLOC_ALWAYS_INLINE void
+static_opts_init(static_opts_t *static_opts) {
+ static_opts->may_overflow = false;
+ static_opts->bump_empty_alloc = false;
+ static_opts->assert_nonempty_alloc = false;
+ static_opts->null_out_result_on_error = false;
+ static_opts->set_errno_on_error = false;
+ static_opts->min_alignment = 0;
+ static_opts->oom_string = "";
+ static_opts->invalid_alignment_string = "";
+ static_opts->slow = false;
}
-JEMALLOC_ALWAYS_INLINE_C void *
-imalloc_body(size_t size, tsd_t **tsd, size_t *usize)
-{
+/*
+ * These correspond to the macros in jemalloc/jemalloc_macros.h. Broadly, we
+ * should have one constant here per magic value there. Note however that the
+ * representations need not be related.
+ */
+#define TCACHE_IND_NONE ((unsigned)-1)
+#define TCACHE_IND_AUTOMATIC ((unsigned)-2)
+#define ARENA_IND_AUTOMATIC ((unsigned)-1)
+
+typedef struct dynamic_opts_s dynamic_opts_t;
+struct dynamic_opts_s {
+ void **result;
+ size_t num_items;
+ size_t item_size;
+ size_t alignment;
+ bool zero;
+ unsigned tcache_ind;
+ unsigned arena_ind;
+};
+
+JEMALLOC_ALWAYS_INLINE void
+dynamic_opts_init(dynamic_opts_t *dynamic_opts) {
+ dynamic_opts->result = NULL;
+ dynamic_opts->num_items = 0;
+ dynamic_opts->item_size = 0;
+ dynamic_opts->alignment = 0;
+ dynamic_opts->zero = false;
+ dynamic_opts->tcache_ind = TCACHE_IND_AUTOMATIC;
+ dynamic_opts->arena_ind = ARENA_IND_AUTOMATIC;
+}
+
+/* ind is ignored if dopts->alignment > 0. */
+JEMALLOC_ALWAYS_INLINE void *
+imalloc_no_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd,
+ size_t size, size_t usize, szind_t ind) {
+ tcache_t *tcache;
+ arena_t *arena;
- if (unlikely(malloc_init()))
- return (NULL);
- *tsd = tsd_fetch();
+ /* Fill in the tcache. */
+ if (dopts->tcache_ind == TCACHE_IND_AUTOMATIC) {
+ if (likely(!sopts->slow)) {
+ /* Getting tcache ptr unconditionally. */
+ tcache = tsd_tcachep_get(tsd);
+ assert(tcache == tcache_get(tsd));
+ } else {
+ tcache = tcache_get(tsd);
+ }
+ } else if (dopts->tcache_ind == TCACHE_IND_NONE) {
+ tcache = NULL;
+ } else {
+ tcache = tcaches_get(tsd, dopts->tcache_ind);
+ }
- if (config_prof && opt_prof) {
- *usize = s2u(size);
- if (unlikely(*usize == 0))
- return (NULL);
- return (imalloc_prof(*tsd, *usize));
+ /* Fill in the arena. */
+ if (dopts->arena_ind == ARENA_IND_AUTOMATIC) {
+ /*
+ * In case of automatic arena management, we defer arena
+ * computation until as late as we can, hoping to fill the
+ * allocation out of the tcache.
+ */
+ arena = NULL;
+ } else {
+ arena = arena_get(tsd_tsdn(tsd), dopts->arena_ind, true);
}
- if (config_stats || (config_valgrind && unlikely(in_valgrind)))
- *usize = s2u(size);
- return (imalloc(*tsd, size));
+ if (unlikely(dopts->alignment != 0)) {
+ return ipalloct(tsd_tsdn(tsd), usize, dopts->alignment,
+ dopts->zero, tcache, arena);
+ }
+
+ return iallocztm(tsd_tsdn(tsd), size, ind, dopts->zero, tcache, false,
+ arena, sopts->slow);
}
-JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
-void JEMALLOC_NOTHROW *
-JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1)
-je_malloc(size_t size)
-{
+JEMALLOC_ALWAYS_INLINE void *
+imalloc_sample(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd,
+ size_t usize, szind_t ind) {
void *ret;
- tsd_t *tsd;
- size_t usize JEMALLOC_CC_SILENCE_INIT(0);
- if (size == 0)
- size = 1;
+ /*
+ * For small allocations, sampling bumps the usize. If so, we allocate
+ * from the ind_large bucket.
+ */
+ szind_t ind_large;
+ size_t bumped_usize = usize;
- ret = imalloc_body(size, &tsd, &usize);
- if (unlikely(ret == NULL)) {
- if (config_xmalloc && unlikely(opt_xmalloc)) {
- malloc_write("<jemalloc>: Error in malloc(): "
- "out of memory\n");
- abort();
+ if (usize <= SMALL_MAXCLASS) {
+ assert(((dopts->alignment == 0) ? sz_s2u(LARGE_MINCLASS) :
+ sz_sa2u(LARGE_MINCLASS, dopts->alignment))
+ == LARGE_MINCLASS);
+ ind_large = sz_size2index(LARGE_MINCLASS);
+ bumped_usize = sz_s2u(LARGE_MINCLASS);
+ ret = imalloc_no_sample(sopts, dopts, tsd, bumped_usize,
+ bumped_usize, ind_large);
+ if (unlikely(ret == NULL)) {
+ return NULL;
}
- set_errno(ENOMEM);
- }
- if (config_stats && likely(ret != NULL)) {
- assert(usize == isalloc(ret, config_prof));
- *tsd_thread_allocatedp_get(tsd) += usize;
+ arena_prof_promote(tsd_tsdn(tsd), ret, usize);
+ } else {
+ ret = imalloc_no_sample(sopts, dopts, tsd, usize, usize, ind);
}
- UTRACE(0, size, ret);
- JEMALLOC_VALGRIND_MALLOC(ret != NULL, ret, usize, false);
- return (ret);
+
+ return ret;
}
-static void *
-imemalign_prof_sample(tsd_t *tsd, size_t alignment, size_t usize,
- prof_tctx_t *tctx)
-{
- void *p;
+/*
+ * Returns true if the allocation will overflow, and false otherwise. Sets
+ * *size to the product either way.
+ */
+JEMALLOC_ALWAYS_INLINE bool
+compute_size_with_overflow(bool may_overflow, dynamic_opts_t *dopts,
+ size_t *size) {
+ /*
+ * This function is just num_items * item_size, except that we may have
+ * to check for overflow.
+ */
- if (tctx == NULL)
- return (NULL);
- if (usize <= SMALL_MAXCLASS) {
- assert(sa2u(LARGE_MINCLASS, alignment) == LARGE_MINCLASS);
- p = ipalloc(tsd, LARGE_MINCLASS, alignment, false);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
- } else
- p = ipalloc(tsd, usize, alignment, false);
+ if (!may_overflow) {
+ assert(dopts->num_items == 1);
+ *size = dopts->item_size;
+ return false;
+ }
- return (p);
-}
+ /* A size_t with its high-half bits all set to 1. */
+ static const size_t high_bits = SIZE_T_MAX << (sizeof(size_t) * 8 / 2);
-JEMALLOC_ALWAYS_INLINE_C void *
-imemalign_prof(tsd_t *tsd, size_t alignment, size_t usize)
-{
- void *p;
- prof_tctx_t *tctx;
+ *size = dopts->item_size * dopts->num_items;
- tctx = prof_alloc_prep(tsd, usize, prof_active_get_unlocked(), true);
- if (unlikely((uintptr_t)tctx != (uintptr_t)1U))
- p = imemalign_prof_sample(tsd, alignment, usize, tctx);
- else
- p = ipalloc(tsd, usize, alignment, false);
- if (unlikely(p == NULL)) {
- prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
+ if (unlikely(*size == 0)) {
+ return (dopts->num_items != 0 && dopts->item_size != 0);
}
- prof_malloc(p, usize, tctx);
- return (p);
+ /*
+ * We got a non-zero size, but we don't know if we overflowed to get
+ * there. To avoid having to do a divide, we'll be clever and note that
+ * if both A and B can be represented in N/2 bits, then their product
+ * can be represented in N bits (without the possibility of overflow).
+ */
+ if (likely((high_bits & (dopts->num_items | dopts->item_size)) == 0)) {
+ return false;
+ }
+ if (likely(*size / dopts->item_size == dopts->num_items)) {
+ return false;
+ }
+ return true;
}
-JEMALLOC_ATTR(nonnull(1))
-static int
-imemalign(void **memptr, size_t alignment, size_t size, size_t min_alignment)
-{
- int ret;
- tsd_t *tsd;
- size_t usize;
- void *result;
+JEMALLOC_ALWAYS_INLINE int
+imalloc_body(static_opts_t *sopts, dynamic_opts_t *dopts, tsd_t *tsd) {
+ /* Where the actual allocated memory will live. */
+ void *allocation = NULL;
+ /* Filled in by compute_size_with_overflow below. */
+ size_t size = 0;
+ /*
+ * For unaligned allocations, we need only ind. For aligned
+ * allocations, or in case of stats or profiling we need usize.
+ *
+ * These are actually dead stores, in that their values are reset before
+ * any branch on their value is taken. Sometimes though, it's
+ * convenient to pass them as arguments before this point. To avoid
+ * undefined behavior then, we initialize them with dummy stores.
+ */
+ szind_t ind = 0;
+ size_t usize = 0;
- assert(min_alignment != 0);
+ /* Reentrancy is only checked on slow path. */
+ int8_t reentrancy_level;
- if (unlikely(malloc_init())) {
- result = NULL;
+ /* Compute the amount of memory the user wants. */
+ if (unlikely(compute_size_with_overflow(sopts->may_overflow, dopts,
+ &size))) {
goto label_oom;
}
- tsd = tsd_fetch();
- if (size == 0)
- size = 1;
- /* Make sure that alignment is a large enough power of 2. */
- if (unlikely(((alignment - 1) & alignment) != 0
- || (alignment < min_alignment))) {
- if (config_xmalloc && unlikely(opt_xmalloc)) {
- malloc_write("<jemalloc>: Error allocating "
- "aligned memory: invalid alignment\n");
- abort();
+ /* Validate the user input. */
+ if (sopts->bump_empty_alloc) {
+ if (unlikely(size == 0)) {
+ size = 1;
}
- result = NULL;
- ret = EINVAL;
- goto label_return;
}
- usize = sa2u(size, alignment);
- if (unlikely(usize == 0)) {
- result = NULL;
- goto label_oom;
+ if (sopts->assert_nonempty_alloc) {
+ assert (size != 0);
}
- if (config_prof && opt_prof)
- result = imemalign_prof(tsd, alignment, usize);
- else
- result = ipalloc(tsd, usize, alignment, false);
- if (unlikely(result == NULL))
- goto label_oom;
- assert(((uintptr_t)result & (alignment - 1)) == ZU(0));
+ if (unlikely(dopts->alignment < sopts->min_alignment
+ || (dopts->alignment & (dopts->alignment - 1)) != 0)) {
+ goto label_invalid_alignment;
+ }
- *memptr = result;
- ret = 0;
-label_return:
- if (config_stats && likely(result != NULL)) {
- assert(usize == isalloc(result, config_prof));
+ /* This is the beginning of the "core" algorithm. */
+
+ if (dopts->alignment == 0) {
+ ind = sz_size2index(size);
+ if (unlikely(ind >= NSIZES)) {
+ goto label_oom;
+ }
+ if (config_stats || (config_prof && opt_prof)) {
+ usize = sz_index2size(ind);
+ assert(usize > 0 && usize <= LARGE_MAXCLASS);
+ }
+ } else {
+ usize = sz_sa2u(size, dopts->alignment);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
+ goto label_oom;
+ }
+ }
+
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ /*
+ * If we need to handle reentrancy, we can do it out of a
+ * known-initialized arena (i.e. arena 0).
+ */
+ reentrancy_level = tsd_reentrancy_level_get(tsd);
+ if (sopts->slow && unlikely(reentrancy_level > 0)) {
+ /*
+ * We should never specify particular arenas or tcaches from
+ * within our internal allocations.
+ */
+ assert(dopts->tcache_ind == TCACHE_IND_AUTOMATIC ||
+ dopts->tcache_ind == TCACHE_IND_NONE);
+ assert(dopts->arena_ind == ARENA_IND_AUTOMATIC);
+ dopts->tcache_ind = TCACHE_IND_NONE;
+ /* We know that arena 0 has already been initialized. */
+ dopts->arena_ind = 0;
+ }
+
+ /* If profiling is on, get our profiling context. */
+ if (config_prof && opt_prof) {
+ /*
+ * Note that if we're going down this path, usize must have been
+ * initialized in the previous if statement.
+ */
+ prof_tctx_t *tctx = prof_alloc_prep(
+ tsd, usize, prof_active_get_unlocked(), true);
+
+ alloc_ctx_t alloc_ctx;
+ if (likely((uintptr_t)tctx == (uintptr_t)1U)) {
+ alloc_ctx.slab = (usize <= SMALL_MAXCLASS);
+ allocation = imalloc_no_sample(
+ sopts, dopts, tsd, usize, usize, ind);
+ } else if ((uintptr_t)tctx > (uintptr_t)1U) {
+ /*
+ * Note that ind might still be 0 here. This is fine;
+ * imalloc_sample ignores ind if dopts->alignment > 0.
+ */
+ allocation = imalloc_sample(
+ sopts, dopts, tsd, usize, ind);
+ alloc_ctx.slab = false;
+ } else {
+ allocation = NULL;
+ }
+
+ if (unlikely(allocation == NULL)) {
+ prof_alloc_rollback(tsd, tctx, true);
+ goto label_oom;
+ }
+ prof_malloc(tsd_tsdn(tsd), allocation, usize, &alloc_ctx, tctx);
+ } else {
+ /*
+ * If dopts->alignment > 0, then ind is still 0, but usize was
+ * computed in the previous if statement. Down the positive
+ * alignment path, imalloc_no_sample ignores ind and size
+ * (relying only on usize).
+ */
+ allocation = imalloc_no_sample(sopts, dopts, tsd, size, usize,
+ ind);
+ if (unlikely(allocation == NULL)) {
+ goto label_oom;
+ }
+ }
+
+ /*
+ * Allocation has been done at this point. We still have some
+ * post-allocation work to do though.
+ */
+ assert(dopts->alignment == 0
+ || ((uintptr_t)allocation & (dopts->alignment - 1)) == ZU(0));
+
+ if (config_stats) {
+ assert(usize == isalloc(tsd_tsdn(tsd), allocation));
*tsd_thread_allocatedp_get(tsd) += usize;
}
- UTRACE(0, size, result);
- return (ret);
+
+ if (sopts->slow) {
+ UTRACE(0, size, allocation);
+ }
+
+ /* Success! */
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ *dopts->result = allocation;
+ return 0;
+
label_oom:
- assert(result == NULL);
+ if (unlikely(sopts->slow) && config_xmalloc && unlikely(opt_xmalloc)) {
+ malloc_write(sopts->oom_string);
+ abort();
+ }
+
+ if (sopts->slow) {
+ UTRACE(NULL, size, NULL);
+ }
+
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ if (sopts->set_errno_on_error) {
+ set_errno(ENOMEM);
+ }
+
+ if (sopts->null_out_result_on_error) {
+ *dopts->result = NULL;
+ }
+
+ return ENOMEM;
+
+ /*
+ * This label is only jumped to by one goto; we move it out of line
+ * anyways to avoid obscuring the non-error paths, and for symmetry with
+ * the oom case.
+ */
+label_invalid_alignment:
if (config_xmalloc && unlikely(opt_xmalloc)) {
- malloc_write("<jemalloc>: Error allocating aligned memory: "
- "out of memory\n");
+ malloc_write(sopts->invalid_alignment_string);
abort();
}
- ret = ENOMEM;
- goto label_return;
+
+ if (sopts->set_errno_on_error) {
+ set_errno(EINVAL);
+ }
+
+ if (sopts->slow) {
+ UTRACE(NULL, size, NULL);
+ }
+
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ if (sopts->null_out_result_on_error) {
+ *dopts->result = NULL;
+ }
+
+ return EINVAL;
+}
+
+/* Returns the errno-style error code of the allocation. */
+JEMALLOC_ALWAYS_INLINE int
+imalloc(static_opts_t *sopts, dynamic_opts_t *dopts) {
+ if (unlikely(!malloc_initialized()) && unlikely(malloc_init())) {
+ if (config_xmalloc && unlikely(opt_xmalloc)) {
+ malloc_write(sopts->oom_string);
+ abort();
+ }
+ UTRACE(NULL, dopts->num_items * dopts->item_size, NULL);
+ set_errno(ENOMEM);
+ *dopts->result = NULL;
+
+ return ENOMEM;
+ }
+
+ /* We always need the tsd. Let's grab it right away. */
+ tsd_t *tsd = tsd_fetch();
+ assert(tsd);
+ if (likely(tsd_fast(tsd))) {
+ /* Fast and common path. */
+ tsd_assert_fast(tsd);
+ sopts->slow = false;
+ return imalloc_body(sopts, dopts, tsd);
+ } else {
+ sopts->slow = true;
+ return imalloc_body(sopts, dopts, tsd);
+ }
+}
+/******************************************************************************/
+/*
+ * Begin malloc(3)-compatible functions.
+ */
+
+JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
+void JEMALLOC_NOTHROW *
+JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1)
+je_malloc(size_t size) {
+ void *ret;
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
+
+ LOG("core.malloc.entry", "size: %zu", size);
+
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
+
+ sopts.bump_empty_alloc = true;
+ sopts.null_out_result_on_error = true;
+ sopts.set_errno_on_error = true;
+ sopts.oom_string = "<jemalloc>: Error in malloc(): out of memory\n";
+
+ dopts.result = &ret;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+
+ imalloc(&sopts, &dopts);
+
+ LOG("core.malloc.exit", "result: %p", ret);
+
+ return ret;
}
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
JEMALLOC_ATTR(nonnull(1))
-je_posix_memalign(void **memptr, size_t alignment, size_t size)
-{
- int ret = imemalign(memptr, alignment, size, sizeof(void *));
- JEMALLOC_VALGRIND_MALLOC(ret == 0, *memptr, isalloc(*memptr,
- config_prof), false);
- return (ret);
+je_posix_memalign(void **memptr, size_t alignment, size_t size) {
+ int ret;
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
+
+ LOG("core.posix_memalign.entry", "mem ptr: %p, alignment: %zu, "
+ "size: %zu", memptr, alignment, size);
+
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
+
+ sopts.bump_empty_alloc = true;
+ sopts.min_alignment = sizeof(void *);
+ sopts.oom_string =
+ "<jemalloc>: Error allocating aligned memory: out of memory\n";
+ sopts.invalid_alignment_string =
+ "<jemalloc>: Error allocating aligned memory: invalid alignment\n";
+
+ dopts.result = memptr;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+ dopts.alignment = alignment;
+
+ ret = imalloc(&sopts, &dopts);
+
+ LOG("core.posix_memalign.exit", "result: %d, alloc ptr: %p", ret,
+ *memptr);
+
+ return ret;
}
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(2)
-je_aligned_alloc(size_t alignment, size_t size)
-{
+je_aligned_alloc(size_t alignment, size_t size) {
void *ret;
- int err;
- if (unlikely((err = imemalign(&ret, alignment, size, 1)) != 0)) {
- ret = NULL;
- set_errno(err);
- }
- JEMALLOC_VALGRIND_MALLOC(err == 0, ret, isalloc(ret, config_prof),
- false);
- return (ret);
-}
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
-static void *
-icalloc_prof_sample(tsd_t *tsd, size_t usize, prof_tctx_t *tctx)
-{
- void *p;
+ LOG("core.aligned_alloc.entry", "alignment: %zu, size: %zu\n",
+ alignment, size);
- if (tctx == NULL)
- return (NULL);
- if (usize <= SMALL_MAXCLASS) {
- p = icalloc(tsd, LARGE_MINCLASS);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
- } else
- p = icalloc(tsd, usize);
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
- return (p);
-}
+ sopts.bump_empty_alloc = true;
+ sopts.null_out_result_on_error = true;
+ sopts.set_errno_on_error = true;
+ sopts.min_alignment = 1;
+ sopts.oom_string =
+ "<jemalloc>: Error allocating aligned memory: out of memory\n";
+ sopts.invalid_alignment_string =
+ "<jemalloc>: Error allocating aligned memory: invalid alignment\n";
-JEMALLOC_ALWAYS_INLINE_C void *
-icalloc_prof(tsd_t *tsd, size_t usize)
-{
- void *p;
- prof_tctx_t *tctx;
+ dopts.result = &ret;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+ dopts.alignment = alignment;
- tctx = prof_alloc_prep(tsd, usize, prof_active_get_unlocked(), true);
- if (unlikely((uintptr_t)tctx != (uintptr_t)1U))
- p = icalloc_prof_sample(tsd, usize, tctx);
- else
- p = icalloc(tsd, usize);
- if (unlikely(p == NULL)) {
- prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
- }
- prof_malloc(p, usize, tctx);
+ imalloc(&sopts, &dopts);
+
+ LOG("core.aligned_alloc.exit", "result: %p", ret);
- return (p);
+ return ret;
}
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE2(1, 2)
-je_calloc(size_t num, size_t size)
-{
+je_calloc(size_t num, size_t size) {
void *ret;
- tsd_t *tsd;
- size_t num_size;
- size_t usize JEMALLOC_CC_SILENCE_INIT(0);
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
- if (unlikely(malloc_init())) {
- num_size = 0;
- ret = NULL;
- goto label_return;
- }
- tsd = tsd_fetch();
+ LOG("core.calloc.entry", "num: %zu, size: %zu\n", num, size);
- num_size = num * size;
- if (unlikely(num_size == 0)) {
- if (num == 0 || size == 0)
- num_size = 1;
- else {
- ret = NULL;
- goto label_return;
- }
- /*
- * Try to avoid division here. We know that it isn't possible to
- * overflow during multiplication if neither operand uses any of the
- * most significant half of the bits in a size_t.
- */
- } else if (unlikely(((num | size) & (SIZE_T_MAX << (sizeof(size_t) <<
- 2))) && (num_size / size != num))) {
- /* size_t overflow. */
- ret = NULL;
- goto label_return;
- }
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
- if (config_prof && opt_prof) {
- usize = s2u(num_size);
- if (unlikely(usize == 0)) {
- ret = NULL;
- goto label_return;
- }
- ret = icalloc_prof(tsd, usize);
- } else {
- if (config_stats || (config_valgrind && unlikely(in_valgrind)))
- usize = s2u(num_size);
- ret = icalloc(tsd, num_size);
- }
+ sopts.may_overflow = true;
+ sopts.bump_empty_alloc = true;
+ sopts.null_out_result_on_error = true;
+ sopts.set_errno_on_error = true;
+ sopts.oom_string = "<jemalloc>: Error in calloc(): out of memory\n";
-label_return:
- if (unlikely(ret == NULL)) {
- if (config_xmalloc && unlikely(opt_xmalloc)) {
- malloc_write("<jemalloc>: Error in calloc(): out of "
- "memory\n");
- abort();
- }
- set_errno(ENOMEM);
- }
- if (config_stats && likely(ret != NULL)) {
- assert(usize == isalloc(ret, config_prof));
- *tsd_thread_allocatedp_get(tsd) += usize;
- }
- UTRACE(0, num_size, ret);
- JEMALLOC_VALGRIND_MALLOC(ret != NULL, ret, usize, true);
- return (ret);
+ dopts.result = &ret;
+ dopts.num_items = num;
+ dopts.item_size = size;
+ dopts.zero = true;
+
+ imalloc(&sopts, &dopts);
+
+ LOG("core.calloc.exit", "result: %p", ret);
+
+ return ret;
}
static void *
irealloc_prof_sample(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t usize,
- prof_tctx_t *tctx)
-{
+ prof_tctx_t *tctx) {
void *p;
- if (tctx == NULL)
- return (NULL);
+ if (tctx == NULL) {
+ return NULL;
+ }
if (usize <= SMALL_MAXCLASS) {
p = iralloc(tsd, old_ptr, old_usize, LARGE_MINCLASS, 0, false);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
- } else
+ if (p == NULL) {
+ return NULL;
+ }
+ arena_prof_promote(tsd_tsdn(tsd), p, usize);
+ } else {
p = iralloc(tsd, old_ptr, old_usize, usize, 0, false);
+ }
- return (p);
+ return p;
}
-JEMALLOC_ALWAYS_INLINE_C void *
-irealloc_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t usize)
-{
+JEMALLOC_ALWAYS_INLINE void *
+irealloc_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t usize,
+ alloc_ctx_t *alloc_ctx) {
void *p;
bool prof_active;
prof_tctx_t *old_tctx, *tctx;
prof_active = prof_active_get_unlocked();
- old_tctx = prof_tctx_get(old_ptr);
+ old_tctx = prof_tctx_get(tsd_tsdn(tsd), old_ptr, alloc_ctx);
tctx = prof_alloc_prep(tsd, usize, prof_active, true);
- if (unlikely((uintptr_t)tctx != (uintptr_t)1U))
+ if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) {
p = irealloc_prof_sample(tsd, old_ptr, old_usize, usize, tctx);
- else
+ } else {
p = iralloc(tsd, old_ptr, old_usize, usize, 0, false);
+ }
if (unlikely(p == NULL)) {
prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
+ return NULL;
}
prof_realloc(tsd, p, usize, tctx, prof_active, true, old_ptr, old_usize,
old_tctx);
- return (p);
+ return p;
}
-JEMALLOC_INLINE_C void
-ifree(tsd_t *tsd, void *ptr, tcache_t *tcache)
-{
- size_t usize;
- UNUSED size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
+JEMALLOC_ALWAYS_INLINE void
+ifree(tsd_t *tsd, void *ptr, tcache_t *tcache, bool slow_path) {
+ if (!slow_path) {
+ tsd_assert_fast(tsd);
+ }
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ if (tsd_reentrancy_level_get(tsd) != 0) {
+ assert(slow_path);
+ }
assert(ptr != NULL);
assert(malloc_initialized() || IS_INITIALIZER);
+ alloc_ctx_t alloc_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind != NSIZES);
+
+ size_t usize;
if (config_prof && opt_prof) {
- usize = isalloc(ptr, config_prof);
- prof_free(tsd, ptr, usize);
- } else if (config_stats || config_valgrind)
- usize = isalloc(ptr, config_prof);
- if (config_stats)
+ usize = sz_index2size(alloc_ctx.szind);
+ prof_free(tsd, ptr, usize, &alloc_ctx);
+ } else if (config_stats) {
+ usize = sz_index2size(alloc_ctx.szind);
+ }
+ if (config_stats) {
*tsd_thread_deallocatedp_get(tsd) += usize;
- if (config_valgrind && unlikely(in_valgrind))
- rzsize = p2rz(ptr);
- iqalloc(tsd, ptr, tcache);
- JEMALLOC_VALGRIND_FREE(ptr, rzsize);
+ }
+
+ if (likely(!slow_path)) {
+ idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false,
+ false);
+ } else {
+ idalloctm(tsd_tsdn(tsd), ptr, tcache, &alloc_ctx, false,
+ true);
+ }
}
-JEMALLOC_INLINE_C void
-isfree(tsd_t *tsd, void *ptr, size_t usize, tcache_t *tcache)
-{
- UNUSED size_t rzsize JEMALLOC_CC_SILENCE_INIT(0);
+JEMALLOC_ALWAYS_INLINE void
+isfree(tsd_t *tsd, void *ptr, size_t usize, tcache_t *tcache, bool slow_path) {
+ if (!slow_path) {
+ tsd_assert_fast(tsd);
+ }
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ if (tsd_reentrancy_level_get(tsd) != 0) {
+ assert(slow_path);
+ }
assert(ptr != NULL);
assert(malloc_initialized() || IS_INITIALIZER);
- if (config_prof && opt_prof)
- prof_free(tsd, ptr, usize);
- if (config_stats)
+ alloc_ctx_t alloc_ctx, *ctx;
+ if (!config_cache_oblivious && ((uintptr_t)ptr & PAGE_MASK) != 0) {
+ /*
+ * When cache_oblivious is disabled and ptr is not page aligned,
+ * the allocation was not sampled -- usize can be used to
+ * determine szind directly.
+ */
+ alloc_ctx.szind = sz_size2index(usize);
+ alloc_ctx.slab = true;
+ ctx = &alloc_ctx;
+ if (config_debug) {
+ alloc_ctx_t dbg_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree,
+ rtree_ctx, (uintptr_t)ptr, true, &dbg_ctx.szind,
+ &dbg_ctx.slab);
+ assert(dbg_ctx.szind == alloc_ctx.szind);
+ assert(dbg_ctx.slab == alloc_ctx.slab);
+ }
+ } else if (config_prof && opt_prof) {
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind == sz_size2index(usize));
+ ctx = &alloc_ctx;
+ } else {
+ ctx = NULL;
+ }
+
+ if (config_prof && opt_prof) {
+ prof_free(tsd, ptr, usize, ctx);
+ }
+ if (config_stats) {
*tsd_thread_deallocatedp_get(tsd) += usize;
- if (config_valgrind && unlikely(in_valgrind))
- rzsize = p2rz(ptr);
- isqalloc(tsd, ptr, usize, tcache);
- JEMALLOC_VALGRIND_FREE(ptr, rzsize);
+ }
+
+ if (likely(!slow_path)) {
+ isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, ctx, false);
+ } else {
+ isdalloct(tsd_tsdn(tsd), ptr, usize, tcache, ctx, true);
+ }
}
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ALLOC_SIZE(2)
-je_realloc(void *ptr, size_t size)
-{
+je_realloc(void *ptr, size_t size) {
void *ret;
- tsd_t *tsd JEMALLOC_CC_SILENCE_INIT(NULL);
+ tsdn_t *tsdn JEMALLOC_CC_SILENCE_INIT(NULL);
size_t usize JEMALLOC_CC_SILENCE_INIT(0);
size_t old_usize = 0;
- UNUSED size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
+
+ LOG("core.realloc.entry", "ptr: %p, size: %zu\n", ptr, size);
if (unlikely(size == 0)) {
if (ptr != NULL) {
/* realloc(ptr, 0) is equivalent to free(ptr). */
UTRACE(ptr, 0, 0);
- tsd = tsd_fetch();
- ifree(tsd, ptr, tcache_get(tsd, false));
- return (NULL);
+ tcache_t *tcache;
+ tsd_t *tsd = tsd_fetch();
+ if (tsd_reentrancy_level_get(tsd) == 0) {
+ tcache = tcache_get(tsd);
+ } else {
+ tcache = NULL;
+ }
+ ifree(tsd, ptr, tcache, true);
+
+ LOG("core.realloc.exit", "result: %p", NULL);
+ return NULL;
}
size = 1;
}
if (likely(ptr != NULL)) {
assert(malloc_initialized() || IS_INITIALIZER);
- malloc_thread_init();
- tsd = tsd_fetch();
+ tsd_t *tsd = tsd_fetch();
- old_usize = isalloc(ptr, config_prof);
- if (config_valgrind && unlikely(in_valgrind))
- old_rzsize = config_prof ? p2rz(ptr) : u2rz(old_usize);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ alloc_ctx_t alloc_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind != NSIZES);
+ old_usize = sz_index2size(alloc_ctx.szind);
+ assert(old_usize == isalloc(tsd_tsdn(tsd), ptr));
if (config_prof && opt_prof) {
- usize = s2u(size);
- ret = unlikely(usize == 0) ? NULL : irealloc_prof(tsd,
- ptr, old_usize, usize);
+ usize = sz_s2u(size);
+ ret = unlikely(usize == 0 || usize > LARGE_MAXCLASS) ?
+ NULL : irealloc_prof(tsd, ptr, old_usize, usize,
+ &alloc_ctx);
} else {
- if (config_stats || (config_valgrind &&
- unlikely(in_valgrind)))
- usize = s2u(size);
+ if (config_stats) {
+ usize = sz_s2u(size);
+ }
ret = iralloc(tsd, ptr, old_usize, size, 0, false);
}
+ tsdn = tsd_tsdn(tsd);
} else {
/* realloc(NULL, size) is equivalent to malloc(size). */
- ret = imalloc_body(size, &tsd, &usize);
+ void *ret = je_malloc(size);
+ LOG("core.realloc.exit", "result: %p", ret);
+ return ret;
}
if (unlikely(ret == NULL)) {
@@ -1819,25 +2354,54 @@ je_realloc(void *ptr, size_t size)
set_errno(ENOMEM);
}
if (config_stats && likely(ret != NULL)) {
- assert(usize == isalloc(ret, config_prof));
+ tsd_t *tsd;
+
+ assert(usize == isalloc(tsdn, ret));
+ tsd = tsdn_tsd(tsdn);
*tsd_thread_allocatedp_get(tsd) += usize;
*tsd_thread_deallocatedp_get(tsd) += old_usize;
}
UTRACE(ptr, size, ret);
- JEMALLOC_VALGRIND_REALLOC(true, ret, usize, true, ptr, old_usize,
- old_rzsize, true, false);
- return (ret);
+ check_entry_exit_locking(tsdn);
+
+ LOG("core.realloc.exit", "result: %p", ret);
+ return ret;
}
JEMALLOC_EXPORT void JEMALLOC_NOTHROW
-je_free(void *ptr)
-{
+je_free(void *ptr) {
+ LOG("core.free.entry", "ptr: %p", ptr);
UTRACE(ptr, 0, 0);
if (likely(ptr != NULL)) {
- tsd_t *tsd = tsd_fetch();
- ifree(tsd, ptr, tcache_get(tsd, false));
+ /*
+ * We avoid setting up tsd fully (e.g. tcache, arena binding)
+ * based on only free() calls -- other activities trigger the
+ * minimal to full transition. This is because free() may
+ * happen during thread shutdown after tls deallocation: if a
+ * thread never had any malloc activities until then, a
+ * fully-setup tsd won't be destructed properly.
+ */
+ tsd_t *tsd = tsd_fetch_min();
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ tcache_t *tcache;
+ if (likely(tsd_fast(tsd))) {
+ tsd_assert_fast(tsd);
+ /* Unconditionally get tcache ptr on fast path. */
+ tcache = tsd_tcachep_get(tsd);
+ ifree(tsd, ptr, tcache, false);
+ } else {
+ if (likely(tsd_reentrancy_level_get(tsd) == 0)) {
+ tcache = tcache_get(tsd);
+ } else {
+ tcache = NULL;
+ }
+ ifree(tsd, ptr, tcache, true);
+ }
+ check_entry_exit_locking(tsd_tsdn(tsd));
}
+ LOG("core.free.exit", "");
}
/*
@@ -1852,13 +2416,34 @@ je_free(void *ptr)
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ATTR(malloc)
-je_memalign(size_t alignment, size_t size)
-{
- void *ret JEMALLOC_CC_SILENCE_INIT(NULL);
- if (unlikely(imemalign(&ret, alignment, size, 1) != 0))
- ret = NULL;
- JEMALLOC_VALGRIND_MALLOC(ret != NULL, ret, size, false);
- return (ret);
+je_memalign(size_t alignment, size_t size) {
+ void *ret;
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
+
+ LOG("core.memalign.entry", "alignment: %zu, size: %zu\n", alignment,
+ size);
+
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
+
+ sopts.bump_empty_alloc = true;
+ sopts.min_alignment = 1;
+ sopts.oom_string =
+ "<jemalloc>: Error allocating aligned memory: out of memory\n";
+ sopts.invalid_alignment_string =
+ "<jemalloc>: Error allocating aligned memory: invalid alignment\n";
+ sopts.null_out_result_on_error = true;
+
+ dopts.result = &ret;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+ dopts.alignment = alignment;
+
+ imalloc(&sopts, &dopts);
+
+ LOG("core.memalign.exit", "result: %p", ret);
+ return ret;
}
#endif
@@ -1866,25 +2451,38 @@ je_memalign(size_t alignment, size_t size)
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ATTR(malloc)
-je_valloc(size_t size)
-{
- void *ret JEMALLOC_CC_SILENCE_INIT(NULL);
- if (unlikely(imemalign(&ret, PAGE, size, 1) != 0))
- ret = NULL;
- JEMALLOC_VALGRIND_MALLOC(ret != NULL, ret, size, false);
- return (ret);
+je_valloc(size_t size) {
+ void *ret;
+
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
+
+ LOG("core.valloc.entry", "size: %zu\n", size);
+
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
+
+ sopts.bump_empty_alloc = true;
+ sopts.null_out_result_on_error = true;
+ sopts.min_alignment = PAGE;
+ sopts.oom_string =
+ "<jemalloc>: Error allocating aligned memory: out of memory\n";
+ sopts.invalid_alignment_string =
+ "<jemalloc>: Error allocating aligned memory: invalid alignment\n";
+
+ dopts.result = &ret;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+ dopts.alignment = PAGE;
+
+ imalloc(&sopts, &dopts);
+
+ LOG("core.valloc.exit", "result: %p\n", ret);
+ return ret;
}
#endif
-/*
- * is_malloc(je_malloc) is some macro magic to detect if jemalloc_defs.h has
- * #define je_malloc malloc
- */
-#define malloc_is_malloc 1
-#define is_malloc_(a) malloc_is_ ## a
-#define is_malloc(a) is_malloc_(a)
-
-#if ((is_malloc(je_malloc) == 1) && defined(JEMALLOC_GLIBC_MALLOC_HOOK))
+#if defined(JEMALLOC_IS_MALLOC) && defined(JEMALLOC_GLIBC_MALLOC_HOOK)
/*
* glibc provides the RTLD_DEEPBIND flag for dlopen which can make it possible
* to inconsistently reference libc's malloc(3)-compatible functions
@@ -1897,10 +2495,44 @@ je_valloc(size_t size)
JEMALLOC_EXPORT void (*__free_hook)(void *ptr) = je_free;
JEMALLOC_EXPORT void *(*__malloc_hook)(size_t size) = je_malloc;
JEMALLOC_EXPORT void *(*__realloc_hook)(void *ptr, size_t size) = je_realloc;
-# ifdef JEMALLOC_GLIBC_MEMALIGN_HOOK
+# ifdef JEMALLOC_GLIBC_MEMALIGN_HOOK
JEMALLOC_EXPORT void *(*__memalign_hook)(size_t alignment, size_t size) =
je_memalign;
-# endif
+# endif
+
+# ifdef CPU_COUNT
+/*
+ * To enable static linking with glibc, the libc specific malloc interface must
+ * be implemented also, so none of glibc's malloc.o functions are added to the
+ * link.
+ */
+# define ALIAS(je_fn) __attribute__((alias (#je_fn), used))
+/* To force macro expansion of je_ prefix before stringification. */
+# define PREALIAS(je_fn) ALIAS(je_fn)
+# ifdef JEMALLOC_OVERRIDE___LIBC_CALLOC
+void *__libc_calloc(size_t n, size_t size) PREALIAS(je_calloc);
+# endif
+# ifdef JEMALLOC_OVERRIDE___LIBC_FREE
+void __libc_free(void* ptr) PREALIAS(je_free);
+# endif
+# ifdef JEMALLOC_OVERRIDE___LIBC_MALLOC
+void *__libc_malloc(size_t size) PREALIAS(je_malloc);
+# endif
+# ifdef JEMALLOC_OVERRIDE___LIBC_MEMALIGN
+void *__libc_memalign(size_t align, size_t s) PREALIAS(je_memalign);
+# endif
+# ifdef JEMALLOC_OVERRIDE___LIBC_REALLOC
+void *__libc_realloc(void* ptr, size_t size) PREALIAS(je_realloc);
+# endif
+# ifdef JEMALLOC_OVERRIDE___LIBC_VALLOC
+void *__libc_valloc(size_t size) PREALIAS(je_valloc);
+# endif
+# ifdef JEMALLOC_OVERRIDE___POSIX_MEMALIGN
+int __posix_memalign(void** r, size_t a, size_t s) PREALIAS(je_posix_memalign);
+# endif
+# undef PREALIAS
+# undef ALIAS
+# endif
#endif
/*
@@ -1911,225 +2543,99 @@ JEMALLOC_EXPORT void *(*__memalign_hook)(size_t alignment, size_t size) =
* Begin non-standard functions.
*/
-JEMALLOC_ALWAYS_INLINE_C bool
-imallocx_flags_decode_hard(tsd_t *tsd, size_t size, int flags, size_t *usize,
- size_t *alignment, bool *zero, tcache_t **tcache, arena_t **arena)
-{
-
- if ((flags & MALLOCX_LG_ALIGN_MASK) == 0) {
- *alignment = 0;
- *usize = s2u(size);
- } else {
- *alignment = MALLOCX_ALIGN_GET_SPECIFIED(flags);
- *usize = sa2u(size, *alignment);
- }
- assert(*usize != 0);
- *zero = MALLOCX_ZERO_GET(flags);
- if ((flags & MALLOCX_TCACHE_MASK) != 0) {
- if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE)
- *tcache = NULL;
- else
- *tcache = tcaches_get(tsd, MALLOCX_TCACHE_GET(flags));
- } else
- *tcache = tcache_get(tsd, true);
- if ((flags & MALLOCX_ARENA_MASK) != 0) {
- unsigned arena_ind = MALLOCX_ARENA_GET(flags);
- *arena = arena_get(tsd, arena_ind, true, true);
- if (unlikely(*arena == NULL))
- return (true);
- } else
- *arena = NULL;
- return (false);
-}
-
-JEMALLOC_ALWAYS_INLINE_C bool
-imallocx_flags_decode(tsd_t *tsd, size_t size, int flags, size_t *usize,
- size_t *alignment, bool *zero, tcache_t **tcache, arena_t **arena)
-{
-
- if (likely(flags == 0)) {
- *usize = s2u(size);
- assert(*usize != 0);
- *alignment = 0;
- *zero = false;
- *tcache = tcache_get(tsd, true);
- *arena = NULL;
- return (false);
- } else {
- return (imallocx_flags_decode_hard(tsd, size, flags, usize,
- alignment, zero, tcache, arena));
- }
-}
+JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
+void JEMALLOC_NOTHROW *
+JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1)
+je_mallocx(size_t size, int flags) {
+ void *ret;
+ static_opts_t sopts;
+ dynamic_opts_t dopts;
-JEMALLOC_ALWAYS_INLINE_C void *
-imallocx_flags(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, arena_t *arena)
-{
+ LOG("core.mallocx.entry", "size: %zu, flags: %d", size, flags);
- if (unlikely(alignment != 0))
- return (ipalloct(tsd, usize, alignment, zero, tcache, arena));
- if (unlikely(zero))
- return (icalloct(tsd, usize, tcache, arena));
- return (imalloct(tsd, usize, tcache, arena));
-}
+ static_opts_init(&sopts);
+ dynamic_opts_init(&dopts);
-static void *
-imallocx_prof_sample(tsd_t *tsd, size_t usize, size_t alignment, bool zero,
- tcache_t *tcache, arena_t *arena)
-{
- void *p;
+ sopts.assert_nonempty_alloc = true;
+ sopts.null_out_result_on_error = true;
+ sopts.oom_string = "<jemalloc>: Error in mallocx(): out of memory\n";
- if (usize <= SMALL_MAXCLASS) {
- assert(((alignment == 0) ? s2u(LARGE_MINCLASS) :
- sa2u(LARGE_MINCLASS, alignment)) == LARGE_MINCLASS);
- p = imallocx_flags(tsd, LARGE_MINCLASS, alignment, zero, tcache,
- arena);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
- } else
- p = imallocx_flags(tsd, usize, alignment, zero, tcache, arena);
-
- return (p);
-}
-
-JEMALLOC_ALWAYS_INLINE_C void *
-imallocx_prof(tsd_t *tsd, size_t size, int flags, size_t *usize)
-{
- void *p;
- size_t alignment;
- bool zero;
- tcache_t *tcache;
- arena_t *arena;
- prof_tctx_t *tctx;
-
- if (unlikely(imallocx_flags_decode(tsd, size, flags, usize, &alignment,
- &zero, &tcache, &arena)))
- return (NULL);
- tctx = prof_alloc_prep(tsd, *usize, prof_active_get_unlocked(), true);
- if (likely((uintptr_t)tctx == (uintptr_t)1U))
- p = imallocx_flags(tsd, *usize, alignment, zero, tcache, arena);
- else if ((uintptr_t)tctx > (uintptr_t)1U) {
- p = imallocx_prof_sample(tsd, *usize, alignment, zero, tcache,
- arena);
- } else
- p = NULL;
- if (unlikely(p == NULL)) {
- prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
- }
- prof_malloc(p, *usize, tctx);
+ dopts.result = &ret;
+ dopts.num_items = 1;
+ dopts.item_size = size;
+ if (unlikely(flags != 0)) {
+ if ((flags & MALLOCX_LG_ALIGN_MASK) != 0) {
+ dopts.alignment = MALLOCX_ALIGN_GET_SPECIFIED(flags);
+ }
- assert(alignment == 0 || ((uintptr_t)p & (alignment - 1)) == ZU(0));
- return (p);
-}
+ dopts.zero = MALLOCX_ZERO_GET(flags);
-JEMALLOC_ALWAYS_INLINE_C void *
-imallocx_no_prof(tsd_t *tsd, size_t size, int flags, size_t *usize)
-{
- void *p;
- size_t alignment;
- bool zero;
- tcache_t *tcache;
- arena_t *arena;
+ if ((flags & MALLOCX_TCACHE_MASK) != 0) {
+ if ((flags & MALLOCX_TCACHE_MASK)
+ == MALLOCX_TCACHE_NONE) {
+ dopts.tcache_ind = TCACHE_IND_NONE;
+ } else {
+ dopts.tcache_ind = MALLOCX_TCACHE_GET(flags);
+ }
+ } else {
+ dopts.tcache_ind = TCACHE_IND_AUTOMATIC;
+ }
- if (likely(flags == 0)) {
- if (config_stats || (config_valgrind && unlikely(in_valgrind)))
- *usize = s2u(size);
- return (imalloc(tsd, size));
+ if ((flags & MALLOCX_ARENA_MASK) != 0)
+ dopts.arena_ind = MALLOCX_ARENA_GET(flags);
}
- if (unlikely(imallocx_flags_decode_hard(tsd, size, flags, usize,
- &alignment, &zero, &tcache, &arena)))
- return (NULL);
- p = imallocx_flags(tsd, *usize, alignment, zero, tcache, arena);
- assert(alignment == 0 || ((uintptr_t)p & (alignment - 1)) == ZU(0));
- return (p);
-}
-
-JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
-void JEMALLOC_NOTHROW *
-JEMALLOC_ATTR(malloc) JEMALLOC_ALLOC_SIZE(1)
-je_mallocx(size_t size, int flags)
-{
- tsd_t *tsd;
- void *p;
- size_t usize;
-
- assert(size != 0);
-
- if (unlikely(malloc_init()))
- goto label_oom;
- tsd = tsd_fetch();
-
- if (config_prof && opt_prof)
- p = imallocx_prof(tsd, size, flags, &usize);
- else
- p = imallocx_no_prof(tsd, size, flags, &usize);
- if (unlikely(p == NULL))
- goto label_oom;
+ imalloc(&sopts, &dopts);
- if (config_stats) {
- assert(usize == isalloc(p, config_prof));
- *tsd_thread_allocatedp_get(tsd) += usize;
- }
- UTRACE(0, size, p);
- JEMALLOC_VALGRIND_MALLOC(true, p, usize, MALLOCX_ZERO_GET(flags));
- return (p);
-label_oom:
- if (config_xmalloc && unlikely(opt_xmalloc)) {
- malloc_write("<jemalloc>: Error in mallocx(): out of memory\n");
- abort();
- }
- UTRACE(0, size, 0);
- return (NULL);
+ LOG("core.mallocx.exit", "result: %p", ret);
+ return ret;
}
static void *
-irallocx_prof_sample(tsd_t *tsd, void *old_ptr, size_t old_usize,
+irallocx_prof_sample(tsdn_t *tsdn, void *old_ptr, size_t old_usize,
size_t usize, size_t alignment, bool zero, tcache_t *tcache, arena_t *arena,
- prof_tctx_t *tctx)
-{
+ prof_tctx_t *tctx) {
void *p;
- if (tctx == NULL)
- return (NULL);
+ if (tctx == NULL) {
+ return NULL;
+ }
if (usize <= SMALL_MAXCLASS) {
- p = iralloct(tsd, old_ptr, old_usize, LARGE_MINCLASS, alignment,
- zero, tcache, arena);
- if (p == NULL)
- return (NULL);
- arena_prof_promoted(p, usize);
+ p = iralloct(tsdn, old_ptr, old_usize, LARGE_MINCLASS,
+ alignment, zero, tcache, arena);
+ if (p == NULL) {
+ return NULL;
+ }
+ arena_prof_promote(tsdn, p, usize);
} else {
- p = iralloct(tsd, old_ptr, old_usize, usize, alignment, zero,
+ p = iralloct(tsdn, old_ptr, old_usize, usize, alignment, zero,
tcache, arena);
}
- return (p);
+ return p;
}
-JEMALLOC_ALWAYS_INLINE_C void *
+JEMALLOC_ALWAYS_INLINE void *
irallocx_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t size,
size_t alignment, size_t *usize, bool zero, tcache_t *tcache,
- arena_t *arena)
-{
+ arena_t *arena, alloc_ctx_t *alloc_ctx) {
void *p;
bool prof_active;
prof_tctx_t *old_tctx, *tctx;
prof_active = prof_active_get_unlocked();
- old_tctx = prof_tctx_get(old_ptr);
- tctx = prof_alloc_prep(tsd, *usize, prof_active, true);
+ old_tctx = prof_tctx_get(tsd_tsdn(tsd), old_ptr, alloc_ctx);
+ tctx = prof_alloc_prep(tsd, *usize, prof_active, false);
if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) {
- p = irallocx_prof_sample(tsd, old_ptr, old_usize, *usize,
- alignment, zero, tcache, arena, tctx);
+ p = irallocx_prof_sample(tsd_tsdn(tsd), old_ptr, old_usize,
+ *usize, alignment, zero, tcache, arena, tctx);
} else {
- p = iralloct(tsd, old_ptr, old_usize, size, alignment, zero,
- tcache, arena);
+ p = iralloct(tsd_tsdn(tsd), old_ptr, old_usize, size, alignment,
+ zero, tcache, arena);
}
if (unlikely(p == NULL)) {
- prof_alloc_rollback(tsd, tctx, true);
- return (NULL);
+ prof_alloc_rollback(tsd, tctx, false);
+ return NULL;
}
if (p == old_ptr && alignment != 0) {
@@ -2141,69 +2647,84 @@ irallocx_prof(tsd_t *tsd, void *old_ptr, size_t old_usize, size_t size,
* be the same as the current usize because of in-place large
* reallocation. Therefore, query the actual value of usize.
*/
- *usize = isalloc(p, config_prof);
+ *usize = isalloc(tsd_tsdn(tsd), p);
}
- prof_realloc(tsd, p, *usize, tctx, prof_active, true, old_ptr,
+ prof_realloc(tsd, p, *usize, tctx, prof_active, false, old_ptr,
old_usize, old_tctx);
- return (p);
+ return p;
}
JEMALLOC_EXPORT JEMALLOC_ALLOCATOR JEMALLOC_RESTRICT_RETURN
void JEMALLOC_NOTHROW *
JEMALLOC_ALLOC_SIZE(2)
-je_rallocx(void *ptr, size_t size, int flags)
-{
+je_rallocx(void *ptr, size_t size, int flags) {
void *p;
tsd_t *tsd;
size_t usize;
size_t old_usize;
- UNUSED size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
size_t alignment = MALLOCX_ALIGN_GET(flags);
bool zero = flags & MALLOCX_ZERO;
arena_t *arena;
tcache_t *tcache;
+ LOG("core.rallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr,
+ size, flags);
+
+
assert(ptr != NULL);
assert(size != 0);
assert(malloc_initialized() || IS_INITIALIZER);
- malloc_thread_init();
tsd = tsd_fetch();
+ check_entry_exit_locking(tsd_tsdn(tsd));
if (unlikely((flags & MALLOCX_ARENA_MASK) != 0)) {
unsigned arena_ind = MALLOCX_ARENA_GET(flags);
- arena = arena_get(tsd, arena_ind, true, true);
- if (unlikely(arena == NULL))
+ arena = arena_get(tsd_tsdn(tsd), arena_ind, true);
+ if (unlikely(arena == NULL)) {
goto label_oom;
- } else
+ }
+ } else {
arena = NULL;
+ }
if (unlikely((flags & MALLOCX_TCACHE_MASK) != 0)) {
- if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE)
+ if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE) {
tcache = NULL;
- else
+ } else {
tcache = tcaches_get(tsd, MALLOCX_TCACHE_GET(flags));
- } else
- tcache = tcache_get(tsd, true);
-
- old_usize = isalloc(ptr, config_prof);
- if (config_valgrind && unlikely(in_valgrind))
- old_rzsize = u2rz(old_usize);
+ }
+ } else {
+ tcache = tcache_get(tsd);
+ }
+ alloc_ctx_t alloc_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind != NSIZES);
+ old_usize = sz_index2size(alloc_ctx.szind);
+ assert(old_usize == isalloc(tsd_tsdn(tsd), ptr));
if (config_prof && opt_prof) {
- usize = (alignment == 0) ? s2u(size) : sa2u(size, alignment);
- assert(usize != 0);
+ usize = (alignment == 0) ?
+ sz_s2u(size) : sz_sa2u(size, alignment);
+ if (unlikely(usize == 0 || usize > LARGE_MAXCLASS)) {
+ goto label_oom;
+ }
p = irallocx_prof(tsd, ptr, old_usize, size, alignment, &usize,
- zero, tcache, arena);
- if (unlikely(p == NULL))
+ zero, tcache, arena, &alloc_ctx);
+ if (unlikely(p == NULL)) {
goto label_oom;
+ }
} else {
- p = iralloct(tsd, ptr, old_usize, size, alignment, zero,
- tcache, arena);
- if (unlikely(p == NULL))
+ p = iralloct(tsd_tsdn(tsd), ptr, old_usize, size, alignment,
+ zero, tcache, arena);
+ if (unlikely(p == NULL)) {
goto label_oom;
- if (config_stats || (config_valgrind && unlikely(in_valgrind)))
- usize = isalloc(p, config_prof);
+ }
+ if (config_stats) {
+ usize = isalloc(tsd_tsdn(tsd), p);
+ }
}
assert(alignment == 0 || ((uintptr_t)p & (alignment - 1)) == ZU(0));
@@ -2212,277 +2733,426 @@ je_rallocx(void *ptr, size_t size, int flags)
*tsd_thread_deallocatedp_get(tsd) += old_usize;
}
UTRACE(ptr, size, p);
- JEMALLOC_VALGRIND_REALLOC(true, p, usize, false, ptr, old_usize,
- old_rzsize, false, zero);
- return (p);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.rallocx.exit", "result: %p", p);
+ return p;
label_oom:
if (config_xmalloc && unlikely(opt_xmalloc)) {
malloc_write("<jemalloc>: Error in rallocx(): out of memory\n");
abort();
}
UTRACE(ptr, size, 0);
- return (NULL);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.rallocx.exit", "result: %p", NULL);
+ return NULL;
}
-JEMALLOC_ALWAYS_INLINE_C size_t
-ixallocx_helper(void *ptr, size_t old_usize, size_t size, size_t extra,
- size_t alignment, bool zero)
-{
+JEMALLOC_ALWAYS_INLINE size_t
+ixallocx_helper(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size,
+ size_t extra, size_t alignment, bool zero) {
size_t usize;
- if (ixalloc(ptr, old_usize, size, extra, alignment, zero))
- return (old_usize);
- usize = isalloc(ptr, config_prof);
+ if (ixalloc(tsdn, ptr, old_usize, size, extra, alignment, zero)) {
+ return old_usize;
+ }
+ usize = isalloc(tsdn, ptr);
- return (usize);
+ return usize;
}
static size_t
-ixallocx_prof_sample(void *ptr, size_t old_usize, size_t size, size_t extra,
- size_t alignment, bool zero, prof_tctx_t *tctx)
-{
+ixallocx_prof_sample(tsdn_t *tsdn, void *ptr, size_t old_usize, size_t size,
+ size_t extra, size_t alignment, bool zero, prof_tctx_t *tctx) {
size_t usize;
- if (tctx == NULL)
- return (old_usize);
- usize = ixallocx_helper(ptr, old_usize, size, extra, alignment, zero);
+ if (tctx == NULL) {
+ return old_usize;
+ }
+ usize = ixallocx_helper(tsdn, ptr, old_usize, size, extra, alignment,
+ zero);
- return (usize);
+ return usize;
}
-JEMALLOC_ALWAYS_INLINE_C size_t
+JEMALLOC_ALWAYS_INLINE size_t
ixallocx_prof(tsd_t *tsd, void *ptr, size_t old_usize, size_t size,
- size_t extra, size_t alignment, bool zero)
-{
+ size_t extra, size_t alignment, bool zero, alloc_ctx_t *alloc_ctx) {
size_t usize_max, usize;
bool prof_active;
prof_tctx_t *old_tctx, *tctx;
prof_active = prof_active_get_unlocked();
- old_tctx = prof_tctx_get(ptr);
+ old_tctx = prof_tctx_get(tsd_tsdn(tsd), ptr, alloc_ctx);
/*
* usize isn't knowable before ixalloc() returns when extra is non-zero.
* Therefore, compute its maximum possible value and use that in
* prof_alloc_prep() to decide whether to capture a backtrace.
* prof_realloc() will use the actual usize to decide whether to sample.
*/
- usize_max = (alignment == 0) ? s2u(size+extra) : sa2u(size+extra,
- alignment);
- assert(usize_max != 0);
+ if (alignment == 0) {
+ usize_max = sz_s2u(size+extra);
+ assert(usize_max > 0 && usize_max <= LARGE_MAXCLASS);
+ } else {
+ usize_max = sz_sa2u(size+extra, alignment);
+ if (unlikely(usize_max == 0 || usize_max > LARGE_MAXCLASS)) {
+ /*
+ * usize_max is out of range, and chances are that
+ * allocation will fail, but use the maximum possible
+ * value and carry on with prof_alloc_prep(), just in
+ * case allocation succeeds.
+ */
+ usize_max = LARGE_MAXCLASS;
+ }
+ }
tctx = prof_alloc_prep(tsd, usize_max, prof_active, false);
+
if (unlikely((uintptr_t)tctx != (uintptr_t)1U)) {
- usize = ixallocx_prof_sample(ptr, old_usize, size, extra,
- alignment, zero, tctx);
+ usize = ixallocx_prof_sample(tsd_tsdn(tsd), ptr, old_usize,
+ size, extra, alignment, zero, tctx);
} else {
- usize = ixallocx_helper(ptr, old_usize, size, extra, alignment,
- zero);
+ usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size,
+ extra, alignment, zero);
}
if (usize == old_usize) {
prof_alloc_rollback(tsd, tctx, false);
- return (usize);
+ return usize;
}
prof_realloc(tsd, ptr, usize, tctx, prof_active, false, ptr, old_usize,
old_tctx);
- return (usize);
+ return usize;
}
JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW
-je_xallocx(void *ptr, size_t size, size_t extra, int flags)
-{
+je_xallocx(void *ptr, size_t size, size_t extra, int flags) {
tsd_t *tsd;
size_t usize, old_usize;
- UNUSED size_t old_rzsize JEMALLOC_CC_SILENCE_INIT(0);
size_t alignment = MALLOCX_ALIGN_GET(flags);
bool zero = flags & MALLOCX_ZERO;
+ LOG("core.xallocx.entry", "ptr: %p, size: %zu, extra: %zu, "
+ "flags: %d", ptr, size, extra, flags);
+
assert(ptr != NULL);
assert(size != 0);
assert(SIZE_T_MAX - size >= extra);
assert(malloc_initialized() || IS_INITIALIZER);
- malloc_thread_init();
tsd = tsd_fetch();
-
- old_usize = isalloc(ptr, config_prof);
-
- /* Clamp extra if necessary to avoid (size + extra) overflow. */
- if (unlikely(size + extra > HUGE_MAXCLASS)) {
- /* Check for size overflow. */
- if (unlikely(size > HUGE_MAXCLASS)) {
- usize = old_usize;
- goto label_not_resized;
- }
- extra = HUGE_MAXCLASS - size;
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ alloc_ctx_t alloc_ctx;
+ rtree_ctx_t *rtree_ctx = tsd_rtree_ctx(tsd);
+ rtree_szind_slab_read(tsd_tsdn(tsd), &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, true, &alloc_ctx.szind, &alloc_ctx.slab);
+ assert(alloc_ctx.szind != NSIZES);
+ old_usize = sz_index2size(alloc_ctx.szind);
+ assert(old_usize == isalloc(tsd_tsdn(tsd), ptr));
+ /*
+ * The API explicitly absolves itself of protecting against (size +
+ * extra) numerical overflow, but we may need to clamp extra to avoid
+ * exceeding LARGE_MAXCLASS.
+ *
+ * Ordinarily, size limit checking is handled deeper down, but here we
+ * have to check as part of (size + extra) clamping, since we need the
+ * clamped value in the above helper functions.
+ */
+ if (unlikely(size > LARGE_MAXCLASS)) {
+ usize = old_usize;
+ goto label_not_resized;
+ }
+ if (unlikely(LARGE_MAXCLASS - size < extra)) {
+ extra = LARGE_MAXCLASS - size;
}
-
- if (config_valgrind && unlikely(in_valgrind))
- old_rzsize = u2rz(old_usize);
if (config_prof && opt_prof) {
usize = ixallocx_prof(tsd, ptr, old_usize, size, extra,
- alignment, zero);
+ alignment, zero, &alloc_ctx);
} else {
- usize = ixallocx_helper(ptr, old_usize, size, extra, alignment,
- zero);
+ usize = ixallocx_helper(tsd_tsdn(tsd), ptr, old_usize, size,
+ extra, alignment, zero);
}
- if (unlikely(usize == old_usize))
+ if (unlikely(usize == old_usize)) {
goto label_not_resized;
+ }
if (config_stats) {
*tsd_thread_allocatedp_get(tsd) += usize;
*tsd_thread_deallocatedp_get(tsd) += old_usize;
}
- JEMALLOC_VALGRIND_REALLOC(false, ptr, usize, false, ptr, old_usize,
- old_rzsize, false, zero);
label_not_resized:
UTRACE(ptr, size, ptr);
- return (usize);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.xallocx.exit", "result: %zu", usize);
+ return usize;
}
JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW
JEMALLOC_ATTR(pure)
-je_sallocx(const void *ptr, int flags)
-{
+je_sallocx(const void *ptr, UNUSED int flags) {
size_t usize;
+ tsdn_t *tsdn;
+
+ LOG("core.sallocx.entry", "ptr: %p, flags: %d", ptr, flags);
assert(malloc_initialized() || IS_INITIALIZER);
- malloc_thread_init();
+ assert(ptr != NULL);
+
+ tsdn = tsdn_fetch();
+ check_entry_exit_locking(tsdn);
+
+ if (config_debug || force_ivsalloc) {
+ usize = ivsalloc(tsdn, ptr);
+ assert(force_ivsalloc || usize != 0);
+ } else {
+ usize = isalloc(tsdn, ptr);
+ }
- if (config_ivsalloc)
- usize = ivsalloc(ptr, config_prof);
- else
- usize = isalloc(ptr, config_prof);
+ check_entry_exit_locking(tsdn);
- return (usize);
+ LOG("core.sallocx.exit", "result: %zu", usize);
+ return usize;
}
JEMALLOC_EXPORT void JEMALLOC_NOTHROW
-je_dallocx(void *ptr, int flags)
-{
- tsd_t *tsd;
- tcache_t *tcache;
+je_dallocx(void *ptr, int flags) {
+ LOG("core.dallocx.entry", "ptr: %p, flags: %d", ptr, flags);
assert(ptr != NULL);
assert(malloc_initialized() || IS_INITIALIZER);
- tsd = tsd_fetch();
+ tsd_t *tsd = tsd_fetch();
+ bool fast = tsd_fast(tsd);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ tcache_t *tcache;
if (unlikely((flags & MALLOCX_TCACHE_MASK) != 0)) {
- if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE)
+ /* Not allowed to be reentrant and specify a custom tcache. */
+ assert(tsd_reentrancy_level_get(tsd) == 0);
+ if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE) {
tcache = NULL;
- else
+ } else {
tcache = tcaches_get(tsd, MALLOCX_TCACHE_GET(flags));
- } else
- tcache = tcache_get(tsd, false);
+ }
+ } else {
+ if (likely(fast)) {
+ tcache = tsd_tcachep_get(tsd);
+ assert(tcache == tcache_get(tsd));
+ } else {
+ if (likely(tsd_reentrancy_level_get(tsd) == 0)) {
+ tcache = tcache_get(tsd);
+ } else {
+ tcache = NULL;
+ }
+ }
+ }
UTRACE(ptr, 0, 0);
- ifree(tsd_fetch(), ptr, tcache);
+ if (likely(fast)) {
+ tsd_assert_fast(tsd);
+ ifree(tsd, ptr, tcache, false);
+ } else {
+ ifree(tsd, ptr, tcache, true);
+ }
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.dallocx.exit", "");
}
-JEMALLOC_ALWAYS_INLINE_C size_t
-inallocx(size_t size, int flags)
-{
- size_t usize;
+JEMALLOC_ALWAYS_INLINE size_t
+inallocx(tsdn_t *tsdn, size_t size, int flags) {
+ check_entry_exit_locking(tsdn);
- if (likely((flags & MALLOCX_LG_ALIGN_MASK) == 0))
- usize = s2u(size);
- else
- usize = sa2u(size, MALLOCX_ALIGN_GET_SPECIFIED(flags));
- assert(usize != 0);
- return (usize);
+ size_t usize;
+ if (likely((flags & MALLOCX_LG_ALIGN_MASK) == 0)) {
+ usize = sz_s2u(size);
+ } else {
+ usize = sz_sa2u(size, MALLOCX_ALIGN_GET_SPECIFIED(flags));
+ }
+ check_entry_exit_locking(tsdn);
+ return usize;
}
JEMALLOC_EXPORT void JEMALLOC_NOTHROW
-je_sdallocx(void *ptr, size_t size, int flags)
-{
- tsd_t *tsd;
- tcache_t *tcache;
- size_t usize;
-
+je_sdallocx(void *ptr, size_t size, int flags) {
assert(ptr != NULL);
assert(malloc_initialized() || IS_INITIALIZER);
- usize = inallocx(size, flags);
- assert(usize == isalloc(ptr, config_prof));
- tsd = tsd_fetch();
+ LOG("core.sdallocx.entry", "ptr: %p, size: %zu, flags: %d", ptr,
+ size, flags);
+
+ tsd_t *tsd = tsd_fetch();
+ bool fast = tsd_fast(tsd);
+ size_t usize = inallocx(tsd_tsdn(tsd), size, flags);
+ assert(usize == isalloc(tsd_tsdn(tsd), ptr));
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ tcache_t *tcache;
if (unlikely((flags & MALLOCX_TCACHE_MASK) != 0)) {
- if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE)
+ /* Not allowed to be reentrant and specify a custom tcache. */
+ assert(tsd_reentrancy_level_get(tsd) == 0);
+ if ((flags & MALLOCX_TCACHE_MASK) == MALLOCX_TCACHE_NONE) {
tcache = NULL;
- else
+ } else {
tcache = tcaches_get(tsd, MALLOCX_TCACHE_GET(flags));
- } else
- tcache = tcache_get(tsd, false);
+ }
+ } else {
+ if (likely(fast)) {
+ tcache = tsd_tcachep_get(tsd);
+ assert(tcache == tcache_get(tsd));
+ } else {
+ if (likely(tsd_reentrancy_level_get(tsd) == 0)) {
+ tcache = tcache_get(tsd);
+ } else {
+ tcache = NULL;
+ }
+ }
+ }
UTRACE(ptr, 0, 0);
- isfree(tsd, ptr, usize, tcache);
+ if (likely(fast)) {
+ tsd_assert_fast(tsd);
+ isfree(tsd, ptr, usize, tcache, false);
+ } else {
+ isfree(tsd, ptr, usize, tcache, true);
+ }
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.sdallocx.exit", "");
}
JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW
JEMALLOC_ATTR(pure)
-je_nallocx(size_t size, int flags)
-{
+je_nallocx(size_t size, int flags) {
+ size_t usize;
+ tsdn_t *tsdn;
assert(size != 0);
- if (unlikely(malloc_init()))
- return (0);
+ if (unlikely(malloc_init())) {
+ LOG("core.nallocx.exit", "result: %zu", ZU(0));
+ return 0;
+ }
- return (inallocx(size, flags));
+ tsdn = tsdn_fetch();
+ check_entry_exit_locking(tsdn);
+
+ usize = inallocx(tsdn, size, flags);
+ if (unlikely(usize > LARGE_MAXCLASS)) {
+ LOG("core.nallocx.exit", "result: %zu", ZU(0));
+ return 0;
+ }
+
+ check_entry_exit_locking(tsdn);
+ LOG("core.nallocx.exit", "result: %zu", usize);
+ return usize;
}
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
je_mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp,
- size_t newlen)
-{
+ size_t newlen) {
+ int ret;
+ tsd_t *tsd;
+
+ LOG("core.mallctl.entry", "name: %s", name);
+
+ if (unlikely(malloc_init())) {
+ LOG("core.mallctl.exit", "result: %d", EAGAIN);
+ return EAGAIN;
+ }
- if (unlikely(malloc_init()))
- return (EAGAIN);
+ tsd = tsd_fetch();
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ ret = ctl_byname(tsd, name, oldp, oldlenp, newp, newlen);
+ check_entry_exit_locking(tsd_tsdn(tsd));
- return (ctl_byname(name, oldp, oldlenp, newp, newlen));
+ LOG("core.mallctl.exit", "result: %d", ret);
+ return ret;
}
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
-je_mallctlnametomib(const char *name, size_t *mibp, size_t *miblenp)
-{
+je_mallctlnametomib(const char *name, size_t *mibp, size_t *miblenp) {
+ int ret;
- if (unlikely(malloc_init()))
- return (EAGAIN);
+ LOG("core.mallctlnametomib.entry", "name: %s", name);
- return (ctl_nametomib(name, mibp, miblenp));
+ if (unlikely(malloc_init())) {
+ LOG("core.mallctlnametomib.exit", "result: %d", EAGAIN);
+ return EAGAIN;
+ }
+
+ tsd_t *tsd = tsd_fetch();
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ ret = ctl_nametomib(tsd, name, mibp, miblenp);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+
+ LOG("core.mallctlnametomib.exit", "result: %d", ret);
+ return ret;
}
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
je_mallctlbymib(const size_t *mib, size_t miblen, void *oldp, size_t *oldlenp,
- void *newp, size_t newlen)
-{
+ void *newp, size_t newlen) {
+ int ret;
+ tsd_t *tsd;
- if (unlikely(malloc_init()))
- return (EAGAIN);
+ LOG("core.mallctlbymib.entry", "");
- return (ctl_bymib(mib, miblen, oldp, oldlenp, newp, newlen));
+ if (unlikely(malloc_init())) {
+ LOG("core.mallctlbymib.exit", "result: %d", EAGAIN);
+ return EAGAIN;
+ }
+
+ tsd = tsd_fetch();
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ ret = ctl_bymib(tsd, mib, miblen, oldp, oldlenp, newp, newlen);
+ check_entry_exit_locking(tsd_tsdn(tsd));
+ LOG("core.mallctlbymib.exit", "result: %d", ret);
+ return ret;
}
JEMALLOC_EXPORT void JEMALLOC_NOTHROW
je_malloc_stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
- const char *opts)
-{
+ const char *opts) {
+ tsdn_t *tsdn;
+
+ LOG("core.malloc_stats_print.entry", "");
+ tsdn = tsdn_fetch();
+ check_entry_exit_locking(tsdn);
stats_print(write_cb, cbopaque, opts);
+ check_entry_exit_locking(tsdn);
+ LOG("core.malloc_stats_print.exit", "");
}
JEMALLOC_EXPORT size_t JEMALLOC_NOTHROW
-je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr)
-{
+je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr) {
size_t ret;
+ tsdn_t *tsdn;
+
+ LOG("core.malloc_usable_size.entry", "ptr: %p", ptr);
assert(malloc_initialized() || IS_INITIALIZER);
- malloc_thread_init();
- if (config_ivsalloc)
- ret = ivsalloc(ptr, config_prof);
- else
- ret = (ptr == NULL) ? 0 : isalloc(ptr, config_prof);
+ tsdn = tsdn_fetch();
+ check_entry_exit_locking(tsdn);
- return (ret);
+ if (unlikely(ptr == NULL)) {
+ ret = 0;
+ } else {
+ if (config_debug || force_ivsalloc) {
+ ret = ivsalloc(tsdn, ptr);
+ assert(force_ivsalloc || ret != 0);
+ } else {
+ ret = isalloc(tsdn, ptr);
+ }
+ }
+
+ check_entry_exit_locking(tsdn);
+ LOG("core.malloc_usable_size.exit", "result: %zu", ret);
+ return ret;
}
/*
@@ -2507,13 +3177,13 @@ je_malloc_usable_size(JEMALLOC_USABLE_SIZE_CONST void *ptr)
* to trigger the deadlock described above, but doing so would involve forking
* via a library constructor that runs before jemalloc's runs.
*/
+#ifndef JEMALLOC_JET
JEMALLOC_ATTR(constructor)
static void
-jemalloc_constructor(void)
-{
-
+jemalloc_constructor(void) {
malloc_init();
}
+#endif
#ifndef JEMALLOC_MUTEX_INIT_CB
void
@@ -2523,24 +3193,69 @@ JEMALLOC_EXPORT void
_malloc_prefork(void)
#endif
{
- unsigned i;
+ tsd_t *tsd;
+ unsigned i, j, narenas;
+ arena_t *arena;
#ifdef JEMALLOC_MUTEX_INIT_CB
- if (!malloc_initialized())
+ if (!malloc_initialized()) {
return;
+ }
#endif
assert(malloc_initialized());
+ tsd = tsd_fetch();
+
+ narenas = narenas_total_get();
+
+ witness_prefork(tsd_witness_tsdp_get(tsd));
/* Acquire all mutexes in a safe order. */
- ctl_prefork();
- prof_prefork();
- malloc_mutex_prefork(&arenas_lock);
- for (i = 0; i < narenas_total; i++) {
- if (arenas[i] != NULL)
- arena_prefork(arenas[i]);
+ ctl_prefork(tsd_tsdn(tsd));
+ tcache_prefork(tsd_tsdn(tsd));
+ malloc_mutex_prefork(tsd_tsdn(tsd), &arenas_lock);
+ if (have_background_thread) {
+ background_thread_prefork0(tsd_tsdn(tsd));
+ }
+ prof_prefork0(tsd_tsdn(tsd));
+ if (have_background_thread) {
+ background_thread_prefork1(tsd_tsdn(tsd));
+ }
+ /* Break arena prefork into stages to preserve lock order. */
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < narenas; j++) {
+ if ((arena = arena_get(tsd_tsdn(tsd), j, false)) !=
+ NULL) {
+ switch (i) {
+ case 0:
+ arena_prefork0(tsd_tsdn(tsd), arena);
+ break;
+ case 1:
+ arena_prefork1(tsd_tsdn(tsd), arena);
+ break;
+ case 2:
+ arena_prefork2(tsd_tsdn(tsd), arena);
+ break;
+ case 3:
+ arena_prefork3(tsd_tsdn(tsd), arena);
+ break;
+ case 4:
+ arena_prefork4(tsd_tsdn(tsd), arena);
+ break;
+ case 5:
+ arena_prefork5(tsd_tsdn(tsd), arena);
+ break;
+ case 6:
+ arena_prefork6(tsd_tsdn(tsd), arena);
+ break;
+ case 7:
+ arena_prefork7(tsd_tsdn(tsd), arena);
+ break;
+ default: not_reached();
+ }
+ }
+ }
}
- chunk_prefork();
- base_prefork();
+ prof_prefork1(tsd_tsdn(tsd));
}
#ifndef JEMALLOC_MUTEX_INIT_CB
@@ -2551,43 +3266,61 @@ JEMALLOC_EXPORT void
_malloc_postfork(void)
#endif
{
- unsigned i;
+ tsd_t *tsd;
+ unsigned i, narenas;
#ifdef JEMALLOC_MUTEX_INIT_CB
- if (!malloc_initialized())
+ if (!malloc_initialized()) {
return;
+ }
#endif
assert(malloc_initialized());
+ tsd = tsd_fetch();
+
+ witness_postfork_parent(tsd_witness_tsdp_get(tsd));
/* Release all mutexes, now that fork() has completed. */
- base_postfork_parent();
- chunk_postfork_parent();
- for (i = 0; i < narenas_total; i++) {
- if (arenas[i] != NULL)
- arena_postfork_parent(arenas[i]);
+ for (i = 0, narenas = narenas_total_get(); i < narenas; i++) {
+ arena_t *arena;
+
+ if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) {
+ arena_postfork_parent(tsd_tsdn(tsd), arena);
+ }
}
- malloc_mutex_postfork_parent(&arenas_lock);
- prof_postfork_parent();
- ctl_postfork_parent();
+ prof_postfork_parent(tsd_tsdn(tsd));
+ if (have_background_thread) {
+ background_thread_postfork_parent(tsd_tsdn(tsd));
+ }
+ malloc_mutex_postfork_parent(tsd_tsdn(tsd), &arenas_lock);
+ tcache_postfork_parent(tsd_tsdn(tsd));
+ ctl_postfork_parent(tsd_tsdn(tsd));
}
void
-jemalloc_postfork_child(void)
-{
- unsigned i;
+jemalloc_postfork_child(void) {
+ tsd_t *tsd;
+ unsigned i, narenas;
assert(malloc_initialized());
+ tsd = tsd_fetch();
+
+ witness_postfork_child(tsd_witness_tsdp_get(tsd));
/* Release all mutexes, now that fork() has completed. */
- base_postfork_child();
- chunk_postfork_child();
- for (i = 0; i < narenas_total; i++) {
- if (arenas[i] != NULL)
- arena_postfork_child(arenas[i]);
+ for (i = 0, narenas = narenas_total_get(); i < narenas; i++) {
+ arena_t *arena;
+
+ if ((arena = arena_get(tsd_tsdn(tsd), i, false)) != NULL) {
+ arena_postfork_child(tsd_tsdn(tsd), arena);
+ }
+ }
+ prof_postfork_child(tsd_tsdn(tsd));
+ if (have_background_thread) {
+ background_thread_postfork_child(tsd_tsdn(tsd));
}
- malloc_mutex_postfork_child(&arenas_lock);
- prof_postfork_child();
- ctl_postfork_child();
+ malloc_mutex_postfork_child(tsd_tsdn(tsd), &arenas_lock);
+ tcache_postfork_child(tsd_tsdn(tsd));
+ ctl_postfork_child(tsd_tsdn(tsd));
}
/******************************************************************************/
@@ -2598,28 +3331,7 @@ jemalloc_postfork_child(void)
* returns the bin utilization and run utilization both in fixed point 16:16.
* If the application decides to re-allocate it should use MALLOCX_TCACHE_NONE when doing so. */
JEMALLOC_EXPORT int JEMALLOC_NOTHROW
-je_get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
- int defrag = 0;
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (likely(chunk != ptr)) { /* indication that this is not a HUGE alloc */
- size_t pageind = ((uintptr_t)ptr - (uintptr_t)chunk) >> LG_PAGE;
- size_t mapbits = arena_mapbits_get(chunk, pageind);
- if (likely((mapbits & CHUNK_MAP_LARGE) == 0)) { /* indication that this is not a LARGE alloc */
- arena_t *arena = extent_node_arena_get(&chunk->node);
- size_t rpages_ind = pageind - arena_mapbits_small_runind_get(chunk, pageind);
- arena_run_t *run = &arena_miscelm_get(chunk, rpages_ind)->run;
- arena_bin_t *bin = &arena->bins[run->binind];
- malloc_mutex_lock(&bin->lock);
- /* runs that are in the same chunk in as the current chunk, are likely to be the next currun */
- if (chunk != (arena_chunk_t *)CHUNK_ADDR2BASE(bin->runcur)) {
- arena_bin_info_t *bin_info = &arena_bin_info[run->binind];
- size_t availregs = bin_info->nregs * bin->stats.curruns;
- *bin_util = (bin->stats.curregs<<16) / availregs;
- *run_util = ((bin_info->nregs - run->nfree)<<16) / bin_info->nregs;
- defrag = 1;
- }
- malloc_mutex_unlock(&bin->lock);
- }
- }
- return defrag;
+get_defrag_hint(void* ptr, int *bin_util, int *run_util) {
+ assert(ptr != NULL);
+ return iget_defrag_hint(TSDN_NULL, ptr, bin_util, run_util);
}
diff --git a/deps/jemalloc/src/jemalloc_cpp.cpp b/deps/jemalloc/src/jemalloc_cpp.cpp
new file mode 100644
index 000000000..f0ceddae3
--- /dev/null
+++ b/deps/jemalloc/src/jemalloc_cpp.cpp
@@ -0,0 +1,141 @@
+#include <mutex>
+#include <new>
+
+#define JEMALLOC_CPP_CPP_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#ifdef __cplusplus
+}
+#endif
+
+// All operators in this file are exported.
+
+// Possibly alias hidden versions of malloc and sdallocx to avoid an extra plt
+// thunk?
+//
+// extern __typeof (sdallocx) sdallocx_int
+// __attribute ((alias ("sdallocx"),
+// visibility ("hidden")));
+//
+// ... but it needs to work with jemalloc namespaces.
+
+void *operator new(std::size_t size);
+void *operator new[](std::size_t size);
+void *operator new(std::size_t size, const std::nothrow_t &) noexcept;
+void *operator new[](std::size_t size, const std::nothrow_t &) noexcept;
+void operator delete(void *ptr) noexcept;
+void operator delete[](void *ptr) noexcept;
+void operator delete(void *ptr, const std::nothrow_t &) noexcept;
+void operator delete[](void *ptr, const std::nothrow_t &) noexcept;
+
+#if __cpp_sized_deallocation >= 201309
+/* C++14's sized-delete operators. */
+void operator delete(void *ptr, std::size_t size) noexcept;
+void operator delete[](void *ptr, std::size_t size) noexcept;
+#endif
+
+JEMALLOC_NOINLINE
+static void *
+handleOOM(std::size_t size, bool nothrow) {
+ void *ptr = nullptr;
+
+ while (ptr == nullptr) {
+ std::new_handler handler;
+ // GCC-4.8 and clang 4.0 do not have std::get_new_handler.
+ {
+ static std::mutex mtx;
+ std::lock_guard<std::mutex> lock(mtx);
+
+ handler = std::set_new_handler(nullptr);
+ std::set_new_handler(handler);
+ }
+ if (handler == nullptr)
+ break;
+
+ try {
+ handler();
+ } catch (const std::bad_alloc &) {
+ break;
+ }
+
+ ptr = je_malloc(size);
+ }
+
+ if (ptr == nullptr && !nothrow)
+ std::__throw_bad_alloc();
+ return ptr;
+}
+
+template <bool IsNoExcept>
+JEMALLOC_ALWAYS_INLINE
+void *
+newImpl(std::size_t size) noexcept(IsNoExcept) {
+ void *ptr = je_malloc(size);
+ if (likely(ptr != nullptr))
+ return ptr;
+
+ return handleOOM(size, IsNoExcept);
+}
+
+void *
+operator new(std::size_t size) {
+ return newImpl<false>(size);
+}
+
+void *
+operator new[](std::size_t size) {
+ return newImpl<false>(size);
+}
+
+void *
+operator new(std::size_t size, const std::nothrow_t &) noexcept {
+ return newImpl<true>(size);
+}
+
+void *
+operator new[](std::size_t size, const std::nothrow_t &) noexcept {
+ return newImpl<true>(size);
+}
+
+void
+operator delete(void *ptr) noexcept {
+ je_free(ptr);
+}
+
+void
+operator delete[](void *ptr) noexcept {
+ je_free(ptr);
+}
+
+void
+operator delete(void *ptr, const std::nothrow_t &) noexcept {
+ je_free(ptr);
+}
+
+void operator delete[](void *ptr, const std::nothrow_t &) noexcept {
+ je_free(ptr);
+}
+
+#if __cpp_sized_deallocation >= 201309
+
+void
+operator delete(void *ptr, std::size_t size) noexcept {
+ if (unlikely(ptr == nullptr)) {
+ return;
+ }
+ je_sdallocx(ptr, size, /*flags=*/0);
+}
+
+void operator delete[](void *ptr, std::size_t size) noexcept {
+ if (unlikely(ptr == nullptr)) {
+ return;
+ }
+ je_sdallocx(ptr, size, /*flags=*/0);
+}
+
+#endif // __cpp_sized_deallocation
diff --git a/deps/jemalloc/src/large.c b/deps/jemalloc/src/large.c
new file mode 100644
index 000000000..27a2c6798
--- /dev/null
+++ b/deps/jemalloc/src/large.c
@@ -0,0 +1,371 @@
+#define JEMALLOC_LARGE_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree.h"
+#include "jemalloc/internal/util.h"
+
+/******************************************************************************/
+
+void *
+large_malloc(tsdn_t *tsdn, arena_t *arena, size_t usize, bool zero) {
+ assert(usize == sz_s2u(usize));
+
+ return large_palloc(tsdn, arena, usize, CACHELINE, zero);
+}
+
+void *
+large_palloc(tsdn_t *tsdn, arena_t *arena, size_t usize, size_t alignment,
+ bool zero) {
+ size_t ausize;
+ extent_t *extent;
+ bool is_zeroed;
+ UNUSED bool idump JEMALLOC_CC_SILENCE_INIT(false);
+
+ assert(!tsdn_null(tsdn) || arena != NULL);
+
+ ausize = sz_sa2u(usize, alignment);
+ if (unlikely(ausize == 0 || ausize > LARGE_MAXCLASS)) {
+ return NULL;
+ }
+
+ if (config_fill && unlikely(opt_zero)) {
+ zero = true;
+ }
+ /*
+ * Copy zero into is_zeroed and pass the copy when allocating the
+ * extent, so that it is possible to make correct junk/zero fill
+ * decisions below, even if is_zeroed ends up true when zero is false.
+ */
+ is_zeroed = zero;
+ if (likely(!tsdn_null(tsdn))) {
+ arena = arena_choose(tsdn_tsd(tsdn), arena);
+ }
+ if (unlikely(arena == NULL) || (extent = arena_extent_alloc_large(tsdn,
+ arena, usize, alignment, &is_zeroed)) == NULL) {
+ return NULL;
+ }
+
+ /* See comments in arena_bin_slabs_full_insert(). */
+ if (!arena_is_auto(arena)) {
+ /* Insert extent into large. */
+ malloc_mutex_lock(tsdn, &arena->large_mtx);
+ extent_list_append(&arena->large, extent);
+ malloc_mutex_unlock(tsdn, &arena->large_mtx);
+ }
+ if (config_prof && arena_prof_accum(tsdn, arena, usize)) {
+ prof_idump(tsdn);
+ }
+
+ if (zero) {
+ assert(is_zeroed);
+ } else if (config_fill && unlikely(opt_junk_alloc)) {
+ memset(extent_addr_get(extent), JEMALLOC_ALLOC_JUNK,
+ extent_usize_get(extent));
+ }
+
+ arena_decay_tick(tsdn, arena);
+ return extent_addr_get(extent);
+}
+
+static void
+large_dalloc_junk_impl(void *ptr, size_t size) {
+ memset(ptr, JEMALLOC_FREE_JUNK, size);
+}
+large_dalloc_junk_t *JET_MUTABLE large_dalloc_junk = large_dalloc_junk_impl;
+
+static void
+large_dalloc_maybe_junk_impl(void *ptr, size_t size) {
+ if (config_fill && have_dss && unlikely(opt_junk_free)) {
+ /*
+ * Only bother junk filling if the extent isn't about to be
+ * unmapped.
+ */
+ if (opt_retain || (have_dss && extent_in_dss(ptr))) {
+ large_dalloc_junk(ptr, size);
+ }
+ }
+}
+large_dalloc_maybe_junk_t *JET_MUTABLE large_dalloc_maybe_junk =
+ large_dalloc_maybe_junk_impl;
+
+static bool
+large_ralloc_no_move_shrink(tsdn_t *tsdn, extent_t *extent, size_t usize) {
+ arena_t *arena = extent_arena_get(extent);
+ size_t oldusize = extent_usize_get(extent);
+ extent_hooks_t *extent_hooks = extent_hooks_get(arena);
+ size_t diff = extent_size_get(extent) - (usize + sz_large_pad);
+
+ assert(oldusize > usize);
+
+ if (extent_hooks->split == NULL) {
+ return true;
+ }
+
+ /* Split excess pages. */
+ if (diff != 0) {
+ extent_t *trail = extent_split_wrapper(tsdn, arena,
+ &extent_hooks, extent, usize + sz_large_pad,
+ sz_size2index(usize), false, diff, NSIZES, false);
+ if (trail == NULL) {
+ return true;
+ }
+
+ if (config_fill && unlikely(opt_junk_free)) {
+ large_dalloc_maybe_junk(extent_addr_get(trail),
+ extent_size_get(trail));
+ }
+
+ arena_extents_dirty_dalloc(tsdn, arena, &extent_hooks, trail);
+ }
+
+ arena_extent_ralloc_large_shrink(tsdn, arena, extent, oldusize);
+
+ return false;
+}
+
+static bool
+large_ralloc_no_move_expand(tsdn_t *tsdn, extent_t *extent, size_t usize,
+ bool zero) {
+ arena_t *arena = extent_arena_get(extent);
+ size_t oldusize = extent_usize_get(extent);
+ extent_hooks_t *extent_hooks = extent_hooks_get(arena);
+ size_t trailsize = usize - oldusize;
+
+ if (extent_hooks->merge == NULL) {
+ return true;
+ }
+
+ if (config_fill && unlikely(opt_zero)) {
+ zero = true;
+ }
+ /*
+ * Copy zero into is_zeroed_trail and pass the copy when allocating the
+ * extent, so that it is possible to make correct junk/zero fill
+ * decisions below, even if is_zeroed_trail ends up true when zero is
+ * false.
+ */
+ bool is_zeroed_trail = zero;
+ bool commit = true;
+ extent_t *trail;
+ bool new_mapping;
+ if ((trail = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_dirty, extent_past_get(extent), trailsize, 0,
+ CACHELINE, false, NSIZES, &is_zeroed_trail, &commit)) != NULL
+ || (trail = extents_alloc(tsdn, arena, &extent_hooks,
+ &arena->extents_muzzy, extent_past_get(extent), trailsize, 0,
+ CACHELINE, false, NSIZES, &is_zeroed_trail, &commit)) != NULL) {
+ if (config_stats) {
+ new_mapping = false;
+ }
+ } else {
+ if ((trail = extent_alloc_wrapper(tsdn, arena, &extent_hooks,
+ extent_past_get(extent), trailsize, 0, CACHELINE, false,
+ NSIZES, &is_zeroed_trail, &commit)) == NULL) {
+ return true;
+ }
+ if (config_stats) {
+ new_mapping = true;
+ }
+ }
+
+ if (extent_merge_wrapper(tsdn, arena, &extent_hooks, extent, trail)) {
+ extent_dalloc_wrapper(tsdn, arena, &extent_hooks, trail);
+ return true;
+ }
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+ szind_t szind = sz_size2index(usize);
+ extent_szind_set(extent, szind);
+ rtree_szind_slab_update(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)extent_addr_get(extent), szind, false);
+
+ if (config_stats && new_mapping) {
+ arena_stats_mapped_add(tsdn, &arena->stats, trailsize);
+ }
+
+ if (zero) {
+ if (config_cache_oblivious) {
+ /*
+ * Zero the trailing bytes of the original allocation's
+ * last page, since they are in an indeterminate state.
+ * There will always be trailing bytes, because ptr's
+ * offset from the beginning of the extent is a multiple
+ * of CACHELINE in [0 .. PAGE).
+ */
+ void *zbase = (void *)
+ ((uintptr_t)extent_addr_get(extent) + oldusize);
+ void *zpast = PAGE_ADDR2BASE((void *)((uintptr_t)zbase +
+ PAGE));
+ size_t nzero = (uintptr_t)zpast - (uintptr_t)zbase;
+ assert(nzero > 0);
+ memset(zbase, 0, nzero);
+ }
+ assert(is_zeroed_trail);
+ } else if (config_fill && unlikely(opt_junk_alloc)) {
+ memset((void *)((uintptr_t)extent_addr_get(extent) + oldusize),
+ JEMALLOC_ALLOC_JUNK, usize - oldusize);
+ }
+
+ arena_extent_ralloc_large_expand(tsdn, arena, extent, oldusize);
+
+ return false;
+}
+
+bool
+large_ralloc_no_move(tsdn_t *tsdn, extent_t *extent, size_t usize_min,
+ size_t usize_max, bool zero) {
+ size_t oldusize = extent_usize_get(extent);
+
+ /* The following should have been caught by callers. */
+ assert(usize_min > 0 && usize_max <= LARGE_MAXCLASS);
+ /* Both allocation sizes must be large to avoid a move. */
+ assert(oldusize >= LARGE_MINCLASS && usize_max >= LARGE_MINCLASS);
+
+ if (usize_max > oldusize) {
+ /* Attempt to expand the allocation in-place. */
+ if (!large_ralloc_no_move_expand(tsdn, extent, usize_max,
+ zero)) {
+ arena_decay_tick(tsdn, extent_arena_get(extent));
+ return false;
+ }
+ /* Try again, this time with usize_min. */
+ if (usize_min < usize_max && usize_min > oldusize &&
+ large_ralloc_no_move_expand(tsdn, extent, usize_min,
+ zero)) {
+ arena_decay_tick(tsdn, extent_arena_get(extent));
+ return false;
+ }
+ }
+
+ /*
+ * Avoid moving the allocation if the existing extent size accommodates
+ * the new size.
+ */
+ if (oldusize >= usize_min && oldusize <= usize_max) {
+ arena_decay_tick(tsdn, extent_arena_get(extent));
+ return false;
+ }
+
+ /* Attempt to shrink the allocation in-place. */
+ if (oldusize > usize_max) {
+ if (!large_ralloc_no_move_shrink(tsdn, extent, usize_max)) {
+ arena_decay_tick(tsdn, extent_arena_get(extent));
+ return false;
+ }
+ }
+ return true;
+}
+
+static void *
+large_ralloc_move_helper(tsdn_t *tsdn, arena_t *arena, size_t usize,
+ size_t alignment, bool zero) {
+ if (alignment <= CACHELINE) {
+ return large_malloc(tsdn, arena, usize, zero);
+ }
+ return large_palloc(tsdn, arena, usize, alignment, zero);
+}
+
+void *
+large_ralloc(tsdn_t *tsdn, arena_t *arena, extent_t *extent, size_t usize,
+ size_t alignment, bool zero, tcache_t *tcache) {
+ size_t oldusize = extent_usize_get(extent);
+
+ /* The following should have been caught by callers. */
+ assert(usize > 0 && usize <= LARGE_MAXCLASS);
+ /* Both allocation sizes must be large to avoid a move. */
+ assert(oldusize >= LARGE_MINCLASS && usize >= LARGE_MINCLASS);
+
+ /* Try to avoid moving the allocation. */
+ if (!large_ralloc_no_move(tsdn, extent, usize, usize, zero)) {
+ return extent_addr_get(extent);
+ }
+
+ /*
+ * usize and old size are different enough that we need to use a
+ * different size class. In that case, fall back to allocating new
+ * space and copying.
+ */
+ void *ret = large_ralloc_move_helper(tsdn, arena, usize, alignment,
+ zero);
+ if (ret == NULL) {
+ return NULL;
+ }
+
+ size_t copysize = (usize < oldusize) ? usize : oldusize;
+ memcpy(ret, extent_addr_get(extent), copysize);
+ isdalloct(tsdn, extent_addr_get(extent), oldusize, tcache, NULL, true);
+ return ret;
+}
+
+/*
+ * junked_locked indicates whether the extent's data have been junk-filled, and
+ * whether the arena's large_mtx is currently held.
+ */
+static void
+large_dalloc_prep_impl(tsdn_t *tsdn, arena_t *arena, extent_t *extent,
+ bool junked_locked) {
+ if (!junked_locked) {
+ /* See comments in arena_bin_slabs_full_insert(). */
+ if (!arena_is_auto(arena)) {
+ malloc_mutex_lock(tsdn, &arena->large_mtx);
+ extent_list_remove(&arena->large, extent);
+ malloc_mutex_unlock(tsdn, &arena->large_mtx);
+ }
+ large_dalloc_maybe_junk(extent_addr_get(extent),
+ extent_usize_get(extent));
+ } else {
+ malloc_mutex_assert_owner(tsdn, &arena->large_mtx);
+ if (!arena_is_auto(arena)) {
+ extent_list_remove(&arena->large, extent);
+ }
+ }
+ arena_extent_dalloc_large_prep(tsdn, arena, extent);
+}
+
+static void
+large_dalloc_finish_impl(tsdn_t *tsdn, arena_t *arena, extent_t *extent) {
+ extent_hooks_t *extent_hooks = EXTENT_HOOKS_INITIALIZER;
+ arena_extents_dirty_dalloc(tsdn, arena, &extent_hooks, extent);
+}
+
+void
+large_dalloc_prep_junked_locked(tsdn_t *tsdn, extent_t *extent) {
+ large_dalloc_prep_impl(tsdn, extent_arena_get(extent), extent, true);
+}
+
+void
+large_dalloc_finish(tsdn_t *tsdn, extent_t *extent) {
+ large_dalloc_finish_impl(tsdn, extent_arena_get(extent), extent);
+}
+
+void
+large_dalloc(tsdn_t *tsdn, extent_t *extent) {
+ arena_t *arena = extent_arena_get(extent);
+ large_dalloc_prep_impl(tsdn, arena, extent, false);
+ large_dalloc_finish_impl(tsdn, arena, extent);
+ arena_decay_tick(tsdn, arena);
+}
+
+size_t
+large_salloc(tsdn_t *tsdn, const extent_t *extent) {
+ return extent_usize_get(extent);
+}
+
+prof_tctx_t *
+large_prof_tctx_get(tsdn_t *tsdn, const extent_t *extent) {
+ return extent_prof_tctx_get(extent);
+}
+
+void
+large_prof_tctx_set(tsdn_t *tsdn, extent_t *extent, prof_tctx_t *tctx) {
+ extent_prof_tctx_set(extent, tctx);
+}
+
+void
+large_prof_tctx_reset(tsdn_t *tsdn, extent_t *extent) {
+ large_prof_tctx_set(tsdn, extent, (prof_tctx_t *)(uintptr_t)1U);
+}
diff --git a/deps/jemalloc/src/log.c b/deps/jemalloc/src/log.c
new file mode 100644
index 000000000..778902fb9
--- /dev/null
+++ b/deps/jemalloc/src/log.c
@@ -0,0 +1,78 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/log.h"
+
+char log_var_names[JEMALLOC_LOG_VAR_BUFSIZE];
+atomic_b_t log_init_done = ATOMIC_INIT(false);
+
+/*
+ * Returns true if we were able to pick out a segment. Fills in r_segment_end
+ * with a pointer to the first character after the end of the string.
+ */
+static const char *
+log_var_extract_segment(const char* segment_begin) {
+ const char *end;
+ for (end = segment_begin; *end != '\0' && *end != '|'; end++) {
+ }
+ return end;
+}
+
+static bool
+log_var_matches_segment(const char *segment_begin, const char *segment_end,
+ const char *log_var_begin, const char *log_var_end) {
+ assert(segment_begin <= segment_end);
+ assert(log_var_begin < log_var_end);
+
+ ptrdiff_t segment_len = segment_end - segment_begin;
+ ptrdiff_t log_var_len = log_var_end - log_var_begin;
+ /* The special '.' segment matches everything. */
+ if (segment_len == 1 && *segment_begin == '.') {
+ return true;
+ }
+ if (segment_len == log_var_len) {
+ return strncmp(segment_begin, log_var_begin, segment_len) == 0;
+ } else if (segment_len < log_var_len) {
+ return strncmp(segment_begin, log_var_begin, segment_len) == 0
+ && log_var_begin[segment_len] == '.';
+ } else {
+ return false;
+ }
+}
+
+unsigned
+log_var_update_state(log_var_t *log_var) {
+ const char *log_var_begin = log_var->name;
+ const char *log_var_end = log_var->name + strlen(log_var->name);
+
+ /* Pointer to one before the beginning of the current segment. */
+ const char *segment_begin = log_var_names;
+
+ /*
+ * If log_init done is false, we haven't parsed the malloc conf yet. To
+ * avoid log-spew, we default to not displaying anything.
+ */
+ if (!atomic_load_b(&log_init_done, ATOMIC_ACQUIRE)) {
+ return LOG_INITIALIZED_NOT_ENABLED;
+ }
+
+ while (true) {
+ const char *segment_end = log_var_extract_segment(
+ segment_begin);
+ assert(segment_end < log_var_names + JEMALLOC_LOG_VAR_BUFSIZE);
+ if (log_var_matches_segment(segment_begin, segment_end,
+ log_var_begin, log_var_end)) {
+ atomic_store_u(&log_var->state, LOG_ENABLED,
+ ATOMIC_RELAXED);
+ return LOG_ENABLED;
+ }
+ if (*segment_end == '\0') {
+ /* Hit the end of the segment string with no match. */
+ atomic_store_u(&log_var->state,
+ LOG_INITIALIZED_NOT_ENABLED, ATOMIC_RELAXED);
+ return LOG_INITIALIZED_NOT_ENABLED;
+ }
+ /* Otherwise, skip the delimiter and continue. */
+ segment_begin = segment_end + 1;
+ }
+}
diff --git a/deps/jemalloc/src/util.c b/deps/jemalloc/src/malloc_io.c
index 4cb0d6c1e..7bdc13f95 100644
--- a/deps/jemalloc/src/util.c
+++ b/deps/jemalloc/src/malloc_io.c
@@ -1,59 +1,76 @@
-#define assert(e) do { \
+#define JEMALLOC_MALLOC_IO_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/util.h"
+
+#ifdef assert
+# undef assert
+#endif
+#ifdef not_reached
+# undef not_reached
+#endif
+#ifdef not_implemented
+# undef not_implemented
+#endif
+#ifdef assert_not_implemented
+# undef assert_not_implemented
+#endif
+
+/*
+ * Define simple versions of assertion macros that won't recurse in case
+ * of assertion failures in malloc_*printf().
+ */
+#define assert(e) do { \
if (config_debug && !(e)) { \
malloc_write("<jemalloc>: Failed assertion\n"); \
abort(); \
} \
} while (0)
-#define not_reached() do { \
+#define not_reached() do { \
if (config_debug) { \
malloc_write("<jemalloc>: Unreachable code reached\n"); \
abort(); \
} \
+ unreachable(); \
} while (0)
-#define not_implemented() do { \
+#define not_implemented() do { \
if (config_debug) { \
malloc_write("<jemalloc>: Not implemented\n"); \
abort(); \
} \
} while (0)
-#define JEMALLOC_UTIL_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define assert_not_implemented(e) do { \
+ if (unlikely(config_debug && !(e))) { \
+ not_implemented(); \
+ } \
+} while (0)
/******************************************************************************/
/* Function prototypes for non-inline static functions. */
-static void wrtmessage(void *cbopaque, const char *s);
-#define U2S_BUFSIZE ((1U << (LG_SIZEOF_INTMAX_T + 3)) + 1)
-static char *u2s(uintmax_t x, unsigned base, bool uppercase, char *s,
+static void wrtmessage(void *cbopaque, const char *s);
+#define U2S_BUFSIZE ((1U << (LG_SIZEOF_INTMAX_T + 3)) + 1)
+static char *u2s(uintmax_t x, unsigned base, bool uppercase, char *s,
size_t *slen_p);
-#define D2S_BUFSIZE (1 + U2S_BUFSIZE)
-static char *d2s(intmax_t x, char sign, char *s, size_t *slen_p);
-#define O2S_BUFSIZE (1 + U2S_BUFSIZE)
-static char *o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p);
-#define X2S_BUFSIZE (2 + U2S_BUFSIZE)
-static char *x2s(uintmax_t x, bool alt_form, bool uppercase, char *s,
+#define D2S_BUFSIZE (1 + U2S_BUFSIZE)
+static char *d2s(intmax_t x, char sign, char *s, size_t *slen_p);
+#define O2S_BUFSIZE (1 + U2S_BUFSIZE)
+static char *o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p);
+#define X2S_BUFSIZE (2 + U2S_BUFSIZE)
+static char *x2s(uintmax_t x, bool alt_form, bool uppercase, char *s,
size_t *slen_p);
/******************************************************************************/
/* malloc_message() setup. */
static void
-wrtmessage(void *cbopaque, const char *s)
-{
-
-#ifdef SYS_write
- /*
- * Use syscall(2) rather than write(2) when possible in order to avoid
- * the possibility of memory allocation within libc. This is necessary
- * on FreeBSD; most operating systems do not have this problem though.
- */
- UNUSED int result = syscall(SYS_write, STDERR_FILENO, s, strlen(s));
-#else
- UNUSED int result = write(STDERR_FILENO, s, strlen(s));
-#endif
+wrtmessage(void *cbopaque, const char *s) {
+ malloc_write_fd(STDERR_FILENO, s, strlen(s));
}
JEMALLOC_EXPORT void (*je_malloc_message)(void *, const char *s);
@@ -63,13 +80,12 @@ JEMALLOC_EXPORT void (*je_malloc_message)(void *, const char *s);
* je_malloc_message(...) throughout the code.
*/
void
-malloc_write(const char *s)
-{
-
- if (je_malloc_message != NULL)
+malloc_write(const char *s) {
+ if (je_malloc_message != NULL) {
je_malloc_message(NULL, s);
- else
+ } else {
wrtmessage(NULL, s);
+ }
}
/*
@@ -77,28 +93,25 @@ malloc_write(const char *s)
* provide a wrapper.
*/
int
-buferror(int err, char *buf, size_t buflen)
-{
-
+buferror(int err, char *buf, size_t buflen) {
#ifdef _WIN32
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, err, 0,
- (LPSTR)buf, buflen, NULL);
- return (0);
-#elif defined(__GLIBC__) && defined(_GNU_SOURCE)
+ (LPSTR)buf, (DWORD)buflen, NULL);
+ return 0;
+#elif defined(JEMALLOC_STRERROR_R_RETURNS_CHAR_WITH_GNU_SOURCE) && defined(_GNU_SOURCE)
char *b = strerror_r(err, buf, buflen);
if (b != buf) {
strncpy(buf, b, buflen);
buf[buflen-1] = '\0';
}
- return (0);
+ return 0;
#else
- return (strerror_r(err, buf, buflen));
+ return strerror_r(err, buf, buflen);
#endif
}
uintmax_t
-malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base)
-{
+malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base) {
uintmax_t ret, digit;
unsigned b;
bool neg;
@@ -143,10 +156,12 @@ malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base)
switch (p[1]) {
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7':
- if (b == 0)
+ if (b == 0) {
b = 8;
- if (b == 8)
+ }
+ if (b == 8) {
p++;
+ }
break;
case 'X': case 'x':
switch (p[2]) {
@@ -156,10 +171,12 @@ malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base)
case 'F':
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f':
- if (b == 0)
+ if (b == 0) {
b = 16;
- if (b == 16)
+ }
+ if (b == 16) {
p += 2;
+ }
break;
default:
break;
@@ -171,8 +188,9 @@ malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base)
goto label_return;
}
}
- if (b == 0)
+ if (b == 0) {
b = 10;
+ }
/* Convert. */
ret = 0;
@@ -190,8 +208,9 @@ malloc_strtoumax(const char *restrict nptr, char **restrict endptr, int base)
}
p++;
}
- if (neg)
- ret = -ret;
+ if (neg) {
+ ret = (uintmax_t)(-((intmax_t)ret));
+ }
if (p == ns) {
/* No conversion performed. */
@@ -205,15 +224,15 @@ label_return:
if (p == ns) {
/* No characters were converted. */
*endptr = (char *)nptr;
- } else
+ } else {
*endptr = (char *)p;
+ }
}
- return (ret);
+ return ret;
}
static char *
-u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p)
-{
+u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p) {
unsigned i;
i = U2S_BUFSIZE - 1;
@@ -251,23 +270,25 @@ u2s(uintmax_t x, unsigned base, bool uppercase, char *s, size_t *slen_p)
}}
*slen_p = U2S_BUFSIZE - 1 - i;
- return (&s[i]);
+ return &s[i];
}
static char *
-d2s(intmax_t x, char sign, char *s, size_t *slen_p)
-{
+d2s(intmax_t x, char sign, char *s, size_t *slen_p) {
bool neg;
- if ((neg = (x < 0)))
+ if ((neg = (x < 0))) {
x = -x;
+ }
s = u2s(x, 10, false, s, slen_p);
- if (neg)
+ if (neg) {
sign = '-';
+ }
switch (sign) {
case '-':
- if (!neg)
+ if (!neg) {
break;
+ }
/* Fall through. */
case ' ':
case '+':
@@ -277,73 +298,70 @@ d2s(intmax_t x, char sign, char *s, size_t *slen_p)
break;
default: not_reached();
}
- return (s);
+ return s;
}
static char *
-o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p)
-{
-
+o2s(uintmax_t x, bool alt_form, char *s, size_t *slen_p) {
s = u2s(x, 8, false, s, slen_p);
if (alt_form && *s != '0') {
s--;
(*slen_p)++;
*s = '0';
}
- return (s);
+ return s;
}
static char *
-x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, size_t *slen_p)
-{
-
+x2s(uintmax_t x, bool alt_form, bool uppercase, char *s, size_t *slen_p) {
s = u2s(x, 16, uppercase, s, slen_p);
if (alt_form) {
s -= 2;
(*slen_p) += 2;
memcpy(s, uppercase ? "0X" : "0x", 2);
}
- return (s);
+ return s;
}
-int
-malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
-{
- int ret;
+size_t
+malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap) {
size_t i;
const char *f;
-#define APPEND_C(c) do { \
- if (i < size) \
+#define APPEND_C(c) do { \
+ if (i < size) { \
str[i] = (c); \
+ } \
i++; \
} while (0)
-#define APPEND_S(s, slen) do { \
+#define APPEND_S(s, slen) do { \
if (i < size) { \
size_t cpylen = (slen <= size - i) ? slen : size - i; \
memcpy(&str[i], s, cpylen); \
} \
i += slen; \
} while (0)
-#define APPEND_PADDED_S(s, slen, width, left_justify) do { \
+#define APPEND_PADDED_S(s, slen, width, left_justify) do { \
/* Left padding. */ \
size_t pad_len = (width == -1) ? 0 : ((slen < (size_t)width) ? \
(size_t)width - slen : 0); \
if (!left_justify && pad_len != 0) { \
size_t j; \
- for (j = 0; j < pad_len; j++) \
+ for (j = 0; j < pad_len; j++) { \
APPEND_C(' '); \
+ } \
} \
/* Value. */ \
APPEND_S(s, slen); \
/* Right padding. */ \
if (left_justify && pad_len != 0) { \
size_t j; \
- for (j = 0; j < pad_len; j++) \
+ for (j = 0; j < pad_len; j++) { \
APPEND_C(' '); \
+ } \
} \
} while (0)
-#define GET_ARG_NUMERIC(val, len) do { \
+#define GET_ARG_NUMERIC(val, len) do { \
switch (len) { \
case '?': \
val = va_arg(ap, int); \
@@ -400,6 +418,8 @@ malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
int prec = -1;
int width = -1;
unsigned char len = '?';
+ char *s;
+ size_t slen;
f++;
/* Flags. */
@@ -449,10 +469,11 @@ malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
break;
}
/* Width/precision separator. */
- if (*f == '.')
+ if (*f == '.') {
f++;
- else
+ } else {
goto label_length;
+ }
/* Precision. */
switch (*f) {
case '*':
@@ -479,8 +500,9 @@ malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
if (*f == 'l') {
len = 'q';
f++;
- } else
+ } else {
len = 'l';
+ }
break;
case 'q': case 'j': case 't': case 'z':
len = *f;
@@ -490,8 +512,6 @@ malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
}
/* Conversion specifier. */
switch (*f) {
- char *s;
- size_t slen;
case '%':
/* %% */
APPEND_C(*f);
@@ -573,37 +593,35 @@ malloc_vsnprintf(char *str, size_t size, const char *format, va_list ap)
}}
}
label_out:
- if (i < size)
+ if (i < size) {
str[i] = '\0';
- else
+ } else {
str[size - 1] = '\0';
- ret = i;
+ }
#undef APPEND_C
#undef APPEND_S
#undef APPEND_PADDED_S
#undef GET_ARG_NUMERIC
- return (ret);
+ return i;
}
JEMALLOC_FORMAT_PRINTF(3, 4)
-int
-malloc_snprintf(char *str, size_t size, const char *format, ...)
-{
- int ret;
+size_t
+malloc_snprintf(char *str, size_t size, const char *format, ...) {
+ size_t ret;
va_list ap;
va_start(ap, format);
ret = malloc_vsnprintf(str, size, format, ap);
va_end(ap);
- return (ret);
+ return ret;
}
void
malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
- const char *format, va_list ap)
-{
+ const char *format, va_list ap) {
char buf[MALLOC_PRINTF_BUFSIZE];
if (write_cb == NULL) {
@@ -628,8 +646,7 @@ malloc_vcprintf(void (*write_cb)(void *, const char *), void *cbopaque,
JEMALLOC_FORMAT_PRINTF(3, 4)
void
malloc_cprintf(void (*write_cb)(void *, const char *), void *cbopaque,
- const char *format, ...)
-{
+ const char *format, ...) {
va_list ap;
va_start(ap, format);
@@ -640,11 +657,20 @@ malloc_cprintf(void (*write_cb)(void *, const char *), void *cbopaque,
/* Print to stderr in such a way as to avoid memory allocation. */
JEMALLOC_FORMAT_PRINTF(1, 2)
void
-malloc_printf(const char *format, ...)
-{
+malloc_printf(const char *format, ...) {
va_list ap;
va_start(ap, format);
malloc_vcprintf(NULL, NULL, format, ap);
va_end(ap);
}
+
+/*
+ * Restore normal assertion macros, in order to make it possible to compile all
+ * C files as a single concatenation.
+ */
+#undef assert
+#undef not_reached
+#undef not_implemented
+#undef assert_not_implemented
+#include "jemalloc/internal/assert.h"
diff --git a/deps/jemalloc/src/mb.c b/deps/jemalloc/src/mb.c
deleted file mode 100644
index dc2c0a256..000000000
--- a/deps/jemalloc/src/mb.c
+++ /dev/null
@@ -1,2 +0,0 @@
-#define JEMALLOC_MB_C_
-#include "jemalloc/internal/jemalloc_internal.h"
diff --git a/deps/jemalloc/src/mutex.c b/deps/jemalloc/src/mutex.c
index 2d47af976..30222b3e5 100644
--- a/deps/jemalloc/src/mutex.c
+++ b/deps/jemalloc/src/mutex.c
@@ -1,12 +1,13 @@
-#define JEMALLOC_MUTEX_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_MUTEX_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
-#if defined(JEMALLOC_LAZY_LOCK) && !defined(_WIN32)
-#include <dlfcn.h>
-#endif
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/spin.h"
#ifndef _CRT_SPINCOUNT
-#define _CRT_SPINCOUNT 4000
+#define _CRT_SPINCOUNT 4000
#endif
/******************************************************************************/
@@ -20,10 +21,6 @@ static bool postpone_init = true;
static malloc_mutex_t *postponed_mutexes = NULL;
#endif
-#if defined(JEMALLOC_LAZY_LOCK) && !defined(_WIN32)
-static void pthread_create_once(void);
-#endif
-
/******************************************************************************/
/*
* We intercept pthread_create() calls in order to toggle isthreaded if the
@@ -31,33 +28,11 @@ static void pthread_create_once(void);
*/
#if defined(JEMALLOC_LAZY_LOCK) && !defined(_WIN32)
-static int (*pthread_create_fptr)(pthread_t *__restrict, const pthread_attr_t *,
- void *(*)(void *), void *__restrict);
-
-static void
-pthread_create_once(void)
-{
-
- pthread_create_fptr = dlsym(RTLD_NEXT, "pthread_create");
- if (pthread_create_fptr == NULL) {
- malloc_write("<jemalloc>: Error in dlsym(RTLD_NEXT, "
- "\"pthread_create\")\n");
- abort();
- }
-
- isthreaded = true;
-}
-
JEMALLOC_EXPORT int
pthread_create(pthread_t *__restrict thread,
const pthread_attr_t *__restrict attr, void *(*start_routine)(void *),
- void *__restrict arg)
-{
- static pthread_once_t once_control = PTHREAD_ONCE_INIT;
-
- pthread_once(&once_control, pthread_create_once);
-
- return (pthread_create_fptr(thread, attr, start_routine, arg));
+ void *__restrict arg) {
+ return pthread_create_wrapper(thread, attr, start_routine, arg);
}
#endif
@@ -68,18 +43,108 @@ JEMALLOC_EXPORT int _pthread_mutex_init_calloc_cb(pthread_mutex_t *mutex,
void *(calloc_cb)(size_t, size_t));
#endif
-bool
-malloc_mutex_init(malloc_mutex_t *mutex)
-{
+void
+malloc_mutex_lock_slow(malloc_mutex_t *mutex) {
+ mutex_prof_data_t *data = &mutex->prof_data;
+ UNUSED nstime_t before = NSTIME_ZERO_INITIALIZER;
+
+ if (ncpus == 1) {
+ goto label_spin_done;
+ }
+
+ int cnt = 0, max_cnt = MALLOC_MUTEX_MAX_SPIN;
+ do {
+ spin_cpu_spinwait();
+ if (!malloc_mutex_trylock_final(mutex)) {
+ data->n_spin_acquired++;
+ return;
+ }
+ } while (cnt++ < max_cnt);
+
+ if (!config_stats) {
+ /* Only spin is useful when stats is off. */
+ malloc_mutex_lock_final(mutex);
+ return;
+ }
+label_spin_done:
+ nstime_update(&before);
+ /* Copy before to after to avoid clock skews. */
+ nstime_t after;
+ nstime_copy(&after, &before);
+ uint32_t n_thds = atomic_fetch_add_u32(&data->n_waiting_thds, 1,
+ ATOMIC_RELAXED) + 1;
+ /* One last try as above two calls may take quite some cycles. */
+ if (!malloc_mutex_trylock_final(mutex)) {
+ atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED);
+ data->n_spin_acquired++;
+ return;
+ }
+
+ /* True slow path. */
+ malloc_mutex_lock_final(mutex);
+ /* Update more slow-path only counters. */
+ atomic_fetch_sub_u32(&data->n_waiting_thds, 1, ATOMIC_RELAXED);
+ nstime_update(&after);
+
+ nstime_t delta;
+ nstime_copy(&delta, &after);
+ nstime_subtract(&delta, &before);
+ data->n_wait_times++;
+ nstime_add(&data->tot_wait_time, &delta);
+ if (nstime_compare(&data->max_wait_time, &delta) < 0) {
+ nstime_copy(&data->max_wait_time, &delta);
+ }
+ if (n_thds > data->max_n_thds) {
+ data->max_n_thds = n_thds;
+ }
+}
+
+static void
+mutex_prof_data_init(mutex_prof_data_t *data) {
+ memset(data, 0, sizeof(mutex_prof_data_t));
+ nstime_init(&data->max_wait_time, 0);
+ nstime_init(&data->tot_wait_time, 0);
+ data->prev_owner = NULL;
+}
+
+void
+malloc_mutex_prof_data_reset(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ malloc_mutex_assert_owner(tsdn, mutex);
+ mutex_prof_data_init(&mutex->prof_data);
+}
+
+static int
+mutex_addr_comp(const witness_t *witness1, void *mutex1,
+ const witness_t *witness2, void *mutex2) {
+ assert(mutex1 != NULL);
+ assert(mutex2 != NULL);
+ uintptr_t mu1int = (uintptr_t)mutex1;
+ uintptr_t mu2int = (uintptr_t)mutex2;
+ if (mu1int < mu2int) {
+ return -1;
+ } else if (mu1int == mu2int) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+bool
+malloc_mutex_init(malloc_mutex_t *mutex, const char *name,
+ witness_rank_t rank, malloc_mutex_lock_order_t lock_order) {
+ mutex_prof_data_init(&mutex->prof_data);
#ifdef _WIN32
# if _WIN32_WINNT >= 0x0600
InitializeSRWLock(&mutex->lock);
# else
if (!InitializeCriticalSectionAndSpinCount(&mutex->lock,
- _CRT_SPINCOUNT))
- return (true);
+ _CRT_SPINCOUNT)) {
+ return true;
+ }
# endif
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ mutex->lock = OS_UNFAIR_LOCK_INIT;
#elif (defined(JEMALLOC_OSSPIN))
mutex->lock = 0;
#elif (defined(JEMALLOC_MUTEX_INIT_CB))
@@ -88,66 +153,72 @@ malloc_mutex_init(malloc_mutex_t *mutex)
postponed_mutexes = mutex;
} else {
if (_pthread_mutex_init_calloc_cb(&mutex->lock,
- bootstrap_calloc) != 0)
- return (true);
+ bootstrap_calloc) != 0) {
+ return true;
+ }
}
#else
pthread_mutexattr_t attr;
- if (pthread_mutexattr_init(&attr) != 0)
- return (true);
+ if (pthread_mutexattr_init(&attr) != 0) {
+ return true;
+ }
pthread_mutexattr_settype(&attr, MALLOC_MUTEX_TYPE);
if (pthread_mutex_init(&mutex->lock, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
- return (true);
+ return true;
}
pthread_mutexattr_destroy(&attr);
#endif
- return (false);
+ if (config_debug) {
+ mutex->lock_order = lock_order;
+ if (lock_order == malloc_mutex_address_ordered) {
+ witness_init(&mutex->witness, name, rank,
+ mutex_addr_comp, mutex);
+ } else {
+ witness_init(&mutex->witness, name, rank, NULL, NULL);
+ }
+ }
+ return false;
}
void
-malloc_mutex_prefork(malloc_mutex_t *mutex)
-{
-
- malloc_mutex_lock(mutex);
+malloc_mutex_prefork(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ malloc_mutex_lock(tsdn, mutex);
}
void
-malloc_mutex_postfork_parent(malloc_mutex_t *mutex)
-{
-
- malloc_mutex_unlock(mutex);
+malloc_mutex_postfork_parent(tsdn_t *tsdn, malloc_mutex_t *mutex) {
+ malloc_mutex_unlock(tsdn, mutex);
}
void
-malloc_mutex_postfork_child(malloc_mutex_t *mutex)
-{
-
+malloc_mutex_postfork_child(tsdn_t *tsdn, malloc_mutex_t *mutex) {
#ifdef JEMALLOC_MUTEX_INIT_CB
- malloc_mutex_unlock(mutex);
+ malloc_mutex_unlock(tsdn, mutex);
#else
- if (malloc_mutex_init(mutex)) {
+ if (malloc_mutex_init(mutex, mutex->witness.name,
+ mutex->witness.rank, mutex->lock_order)) {
malloc_printf("<jemalloc>: Error re-initializing mutex in "
"child\n");
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
}
#endif
}
bool
-mutex_boot(void)
-{
-
+malloc_mutex_boot(void) {
#ifdef JEMALLOC_MUTEX_INIT_CB
postpone_init = false;
while (postponed_mutexes != NULL) {
if (_pthread_mutex_init_calloc_cb(&postponed_mutexes->lock,
- bootstrap_calloc) != 0)
- return (true);
+ bootstrap_calloc) != 0) {
+ return true;
+ }
postponed_mutexes = postponed_mutexes->postponed_next;
}
#endif
- return (false);
+ return false;
}
diff --git a/deps/jemalloc/src/mutex_pool.c b/deps/jemalloc/src/mutex_pool.c
new file mode 100644
index 000000000..f24d10e44
--- /dev/null
+++ b/deps/jemalloc/src/mutex_pool.c
@@ -0,0 +1,18 @@
+#define JEMALLOC_MUTEX_POOL_C_
+
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_pool.h"
+
+bool
+mutex_pool_init(mutex_pool_t *pool, const char *name, witness_rank_t rank) {
+ for (int i = 0; i < MUTEX_POOL_SIZE; ++i) {
+ if (malloc_mutex_init(&pool->mutexes[i], name, rank,
+ malloc_mutex_address_ordered)) {
+ return true;
+ }
+ }
+ return false;
+}
diff --git a/deps/jemalloc/src/nstime.c b/deps/jemalloc/src/nstime.c
new file mode 100644
index 000000000..71db35396
--- /dev/null
+++ b/deps/jemalloc/src/nstime.c
@@ -0,0 +1,170 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/nstime.h"
+
+#include "jemalloc/internal/assert.h"
+
+#define BILLION UINT64_C(1000000000)
+#define MILLION UINT64_C(1000000)
+
+void
+nstime_init(nstime_t *time, uint64_t ns) {
+ time->ns = ns;
+}
+
+void
+nstime_init2(nstime_t *time, uint64_t sec, uint64_t nsec) {
+ time->ns = sec * BILLION + nsec;
+}
+
+uint64_t
+nstime_ns(const nstime_t *time) {
+ return time->ns;
+}
+
+uint64_t
+nstime_msec(const nstime_t *time) {
+ return time->ns / MILLION;
+}
+
+uint64_t
+nstime_sec(const nstime_t *time) {
+ return time->ns / BILLION;
+}
+
+uint64_t
+nstime_nsec(const nstime_t *time) {
+ return time->ns % BILLION;
+}
+
+void
+nstime_copy(nstime_t *time, const nstime_t *source) {
+ *time = *source;
+}
+
+int
+nstime_compare(const nstime_t *a, const nstime_t *b) {
+ return (a->ns > b->ns) - (a->ns < b->ns);
+}
+
+void
+nstime_add(nstime_t *time, const nstime_t *addend) {
+ assert(UINT64_MAX - time->ns >= addend->ns);
+
+ time->ns += addend->ns;
+}
+
+void
+nstime_iadd(nstime_t *time, uint64_t addend) {
+ assert(UINT64_MAX - time->ns >= addend);
+
+ time->ns += addend;
+}
+
+void
+nstime_subtract(nstime_t *time, const nstime_t *subtrahend) {
+ assert(nstime_compare(time, subtrahend) >= 0);
+
+ time->ns -= subtrahend->ns;
+}
+
+void
+nstime_isubtract(nstime_t *time, uint64_t subtrahend) {
+ assert(time->ns >= subtrahend);
+
+ time->ns -= subtrahend;
+}
+
+void
+nstime_imultiply(nstime_t *time, uint64_t multiplier) {
+ assert((((time->ns | multiplier) & (UINT64_MAX << (sizeof(uint64_t) <<
+ 2))) == 0) || ((time->ns * multiplier) / multiplier == time->ns));
+
+ time->ns *= multiplier;
+}
+
+void
+nstime_idivide(nstime_t *time, uint64_t divisor) {
+ assert(divisor != 0);
+
+ time->ns /= divisor;
+}
+
+uint64_t
+nstime_divide(const nstime_t *time, const nstime_t *divisor) {
+ assert(divisor->ns != 0);
+
+ return time->ns / divisor->ns;
+}
+
+#ifdef _WIN32
+# define NSTIME_MONOTONIC true
+static void
+nstime_get(nstime_t *time) {
+ FILETIME ft;
+ uint64_t ticks_100ns;
+
+ GetSystemTimeAsFileTime(&ft);
+ ticks_100ns = (((uint64_t)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
+
+ nstime_init(time, ticks_100ns * 100);
+}
+#elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC_COARSE)
+# define NSTIME_MONOTONIC true
+static void
+nstime_get(nstime_t *time) {
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+ nstime_init2(time, ts.tv_sec, ts.tv_nsec);
+}
+#elif defined(JEMALLOC_HAVE_CLOCK_MONOTONIC)
+# define NSTIME_MONOTONIC true
+static void
+nstime_get(nstime_t *time) {
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ nstime_init2(time, ts.tv_sec, ts.tv_nsec);
+}
+#elif defined(JEMALLOC_HAVE_MACH_ABSOLUTE_TIME)
+# define NSTIME_MONOTONIC true
+static void
+nstime_get(nstime_t *time) {
+ nstime_init(time, mach_absolute_time());
+}
+#else
+# define NSTIME_MONOTONIC false
+static void
+nstime_get(nstime_t *time) {
+ struct timeval tv;
+
+ gettimeofday(&tv, NULL);
+ nstime_init2(time, tv.tv_sec, tv.tv_usec * 1000);
+}
+#endif
+
+static bool
+nstime_monotonic_impl(void) {
+ return NSTIME_MONOTONIC;
+#undef NSTIME_MONOTONIC
+}
+nstime_monotonic_t *JET_MUTABLE nstime_monotonic = nstime_monotonic_impl;
+
+static bool
+nstime_update_impl(nstime_t *time) {
+ nstime_t old_time;
+
+ nstime_copy(&old_time, time);
+ nstime_get(time);
+
+ /* Handle non-monotonic clocks. */
+ if (unlikely(nstime_compare(&old_time, time) > 0)) {
+ nstime_copy(time, &old_time);
+ return true;
+ }
+
+ return false;
+}
+nstime_update_t *JET_MUTABLE nstime_update = nstime_update_impl;
diff --git a/deps/jemalloc/src/pages.c b/deps/jemalloc/src/pages.c
index 83a167f67..26002692d 100644
--- a/deps/jemalloc/src/pages.c
+++ b/deps/jemalloc/src/pages.c
@@ -1,49 +1,133 @@
-#define JEMALLOC_PAGES_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_PAGES_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+
+#include "jemalloc/internal/pages.h"
+
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/malloc_io.h"
+
+#ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT
+#include <sys/sysctl.h>
+#ifdef __FreeBSD__
+#include <vm/vm_param.h>
+#endif
+#endif
/******************************************************************************/
+/* Data. */
-void *
-pages_map(void *addr, size_t size)
-{
- void *ret;
+/* Actual operating system page size, detected during bootstrap, <= PAGE. */
+static size_t os_page;
+
+#ifndef _WIN32
+# define PAGES_PROT_COMMIT (PROT_READ | PROT_WRITE)
+# define PAGES_PROT_DECOMMIT (PROT_NONE)
+static int mmap_flags;
+#endif
+static bool os_overcommits;
+
+const char *thp_mode_names[] = {
+ "default",
+ "always",
+ "never",
+ "not supported"
+};
+thp_mode_t opt_thp = THP_MODE_DEFAULT;
+thp_mode_t init_system_thp_mode;
+
+/* Runtime support for lazy purge. Irrelevant when !pages_can_purge_lazy. */
+static bool pages_can_purge_lazy_runtime = true;
+/******************************************************************************/
+/*
+ * Function prototypes for static functions that are referenced prior to
+ * definition.
+ */
+
+static void os_pages_unmap(void *addr, size_t size);
+
+/******************************************************************************/
+
+static void *
+os_pages_map(void *addr, size_t size, size_t alignment, bool *commit) {
+ assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr);
+ assert(ALIGNMENT_CEILING(size, os_page) == size);
assert(size != 0);
+ if (os_overcommits) {
+ *commit = true;
+ }
+
+ void *ret;
#ifdef _WIN32
/*
* If VirtualAlloc can't allocate at the given address when one is
* given, it fails and returns NULL.
*/
- ret = VirtualAlloc(addr, size, MEM_COMMIT | MEM_RESERVE,
+ ret = VirtualAlloc(addr, size, MEM_RESERVE | (*commit ? MEM_COMMIT : 0),
PAGE_READWRITE);
#else
/*
* We don't use MAP_FIXED here, because it can cause the *replacement*
* of existing mappings, and we only want to create new mappings.
*/
- ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
- -1, 0);
+ {
+ int prot = *commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT;
+
+ ret = mmap(addr, size, prot, mmap_flags, -1, 0);
+ }
assert(ret != NULL);
- if (ret == MAP_FAILED)
+ if (ret == MAP_FAILED) {
ret = NULL;
- else if (addr != NULL && ret != addr) {
+ } else if (addr != NULL && ret != addr) {
/*
* We succeeded in mapping memory, but not in the right place.
*/
- pages_unmap(ret, size);
+ os_pages_unmap(ret, size);
ret = NULL;
}
#endif
- assert(ret == NULL || (addr == NULL && ret != addr)
- || (addr != NULL && ret == addr));
- return (ret);
+ assert(ret == NULL || (addr == NULL && ret != addr) || (addr != NULL &&
+ ret == addr));
+ return ret;
}
-void
-pages_unmap(void *addr, size_t size)
-{
+static void *
+os_pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size,
+ bool *commit) {
+ void *ret = (void *)((uintptr_t)addr + leadsize);
+
+ assert(alloc_size >= leadsize + size);
+#ifdef _WIN32
+ os_pages_unmap(addr, alloc_size);
+ void *new_addr = os_pages_map(ret, size, PAGE, commit);
+ if (new_addr == ret) {
+ return ret;
+ }
+ if (new_addr != NULL) {
+ os_pages_unmap(new_addr, size);
+ }
+ return NULL;
+#else
+ size_t trailsize = alloc_size - leadsize - size;
+
+ if (leadsize != 0) {
+ os_pages_unmap(addr, leadsize);
+ }
+ if (trailsize != 0) {
+ os_pages_unmap((void *)((uintptr_t)ret + size), trailsize);
+ }
+ return ret;
+#endif
+}
+
+static void
+os_pages_unmap(void *addr, size_t size) {
+ assert(ALIGNMENT_ADDR2BASE(addr, os_page) == addr);
+ assert(ALIGNMENT_CEILING(size, os_page) == size);
#ifdef _WIN32
if (VirtualFree(addr, 0, MEM_RELEASE) == 0)
@@ -56,118 +140,467 @@ pages_unmap(void *addr, size_t size)
buferror(get_errno(), buf, sizeof(buf));
malloc_printf("<jemalloc>: Error in "
#ifdef _WIN32
- "VirtualFree"
+ "VirtualFree"
#else
- "munmap"
+ "munmap"
#endif
- "(): %s\n", buf);
- if (opt_abort)
+ "(): %s\n", buf);
+ if (opt_abort) {
abort();
+ }
+ }
+}
+
+static void *
+pages_map_slow(size_t size, size_t alignment, bool *commit) {
+ size_t alloc_size = size + alignment - os_page;
+ /* Beware size_t wrap-around. */
+ if (alloc_size < size) {
+ return NULL;
}
+
+ void *ret;
+ do {
+ void *pages = os_pages_map(NULL, alloc_size, alignment, commit);
+ if (pages == NULL) {
+ return NULL;
+ }
+ size_t leadsize = ALIGNMENT_CEILING((uintptr_t)pages, alignment)
+ - (uintptr_t)pages;
+ ret = os_pages_trim(pages, alloc_size, leadsize, size, commit);
+ } while (ret == NULL);
+
+ assert(ret != NULL);
+ assert(PAGE_ADDR2BASE(ret) == ret);
+ return ret;
}
void *
-pages_trim(void *addr, size_t alloc_size, size_t leadsize, size_t size)
-{
- void *ret = (void *)((uintptr_t)addr + leadsize);
+pages_map(void *addr, size_t size, size_t alignment, bool *commit) {
+ assert(alignment >= PAGE);
+ assert(ALIGNMENT_ADDR2BASE(addr, alignment) == addr);
- assert(alloc_size >= leadsize + size);
-#ifdef _WIN32
- {
- void *new_addr;
+ /*
+ * Ideally, there would be a way to specify alignment to mmap() (like
+ * NetBSD has), but in the absence of such a feature, we have to work
+ * hard to efficiently create aligned mappings. The reliable, but
+ * slow method is to create a mapping that is over-sized, then trim the
+ * excess. However, that always results in one or two calls to
+ * os_pages_unmap(), and it can leave holes in the process's virtual
+ * memory map if memory grows downward.
+ *
+ * Optimistically try mapping precisely the right amount before falling
+ * back to the slow method, with the expectation that the optimistic
+ * approach works most of the time.
+ */
- pages_unmap(addr, alloc_size);
- new_addr = pages_map(ret, size);
- if (new_addr == ret)
- return (ret);
- if (new_addr)
- pages_unmap(new_addr, size);
- return (NULL);
+ void *ret = os_pages_map(addr, size, os_page, commit);
+ if (ret == NULL || ret == addr) {
+ return ret;
}
-#else
- {
- size_t trailsize = alloc_size - leadsize - size;
-
- if (leadsize != 0)
- pages_unmap(addr, leadsize);
- if (trailsize != 0)
- pages_unmap((void *)((uintptr_t)ret + size), trailsize);
- return (ret);
+ assert(addr == NULL);
+ if (ALIGNMENT_ADDR2OFFSET(ret, alignment) != 0) {
+ os_pages_unmap(ret, size);
+ return pages_map_slow(size, alignment, commit);
}
-#endif
+
+ assert(PAGE_ADDR2BASE(ret) == ret);
+ return ret;
+}
+
+void
+pages_unmap(void *addr, size_t size) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
+
+ os_pages_unmap(addr, size);
}
static bool
-pages_commit_impl(void *addr, size_t size, bool commit)
-{
+pages_commit_impl(void *addr, size_t size, bool commit) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
-#ifndef _WIN32
- /*
- * The following decommit/commit implementation is functional, but
- * always disabled because it doesn't add value beyong improved
- * debugging (at the cost of extra system calls) on systems that
- * overcommit.
- */
- if (false) {
- int prot = commit ? (PROT_READ | PROT_WRITE) : PROT_NONE;
- void *result = mmap(addr, size, prot, MAP_PRIVATE | MAP_ANON |
- MAP_FIXED, -1, 0);
- if (result == MAP_FAILED)
- return (true);
+ if (os_overcommits) {
+ return true;
+ }
+
+#ifdef _WIN32
+ return (commit ? (addr != VirtualAlloc(addr, size, MEM_COMMIT,
+ PAGE_READWRITE)) : (!VirtualFree(addr, size, MEM_DECOMMIT)));
+#else
+ {
+ int prot = commit ? PAGES_PROT_COMMIT : PAGES_PROT_DECOMMIT;
+ void *result = mmap(addr, size, prot, mmap_flags | MAP_FIXED,
+ -1, 0);
+ if (result == MAP_FAILED) {
+ return true;
+ }
if (result != addr) {
/*
* We succeeded in mapping memory, but not in the right
* place.
*/
- pages_unmap(result, size);
- return (true);
+ os_pages_unmap(result, size);
+ return true;
}
- return (false);
+ return false;
}
#endif
- return (true);
}
bool
-pages_commit(void *addr, size_t size)
-{
-
- return (pages_commit_impl(addr, size, true));
+pages_commit(void *addr, size_t size) {
+ return pages_commit_impl(addr, size, true);
}
bool
-pages_decommit(void *addr, size_t size)
-{
-
- return (pages_commit_impl(addr, size, false));
+pages_decommit(void *addr, size_t size) {
+ return pages_commit_impl(addr, size, false);
}
bool
-pages_purge(void *addr, size_t size)
-{
- bool unzeroed;
+pages_purge_lazy(void *addr, size_t size) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
+
+ if (!pages_can_purge_lazy) {
+ return true;
+ }
+ if (!pages_can_purge_lazy_runtime) {
+ /*
+ * Built with lazy purge enabled, but detected it was not
+ * supported on the current system.
+ */
+ return true;
+ }
#ifdef _WIN32
VirtualAlloc(addr, size, MEM_RESET, PAGE_READWRITE);
- unzeroed = true;
-#elif defined(JEMALLOC_HAVE_MADVISE)
-# ifdef JEMALLOC_PURGE_MADVISE_DONTNEED
-# define JEMALLOC_MADV_PURGE MADV_DONTNEED
-# define JEMALLOC_MADV_ZEROS true
-# elif defined(JEMALLOC_PURGE_MADVISE_FREE)
-# define JEMALLOC_MADV_PURGE MADV_FREE
-# define JEMALLOC_MADV_ZEROS false
+ return false;
+#elif defined(JEMALLOC_PURGE_MADVISE_FREE)
+ return (madvise(addr, size,
+# ifdef MADV_FREE
+ MADV_FREE
# else
-# error "No madvise(2) flag defined for purging unused dirty pages."
+ JEMALLOC_MADV_FREE
# endif
- int err = madvise(addr, size, JEMALLOC_MADV_PURGE);
- unzeroed = (!JEMALLOC_MADV_ZEROS || err != 0);
-# undef JEMALLOC_MADV_PURGE
-# undef JEMALLOC_MADV_ZEROS
+ ) != 0);
+#elif defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \
+ !defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS)
+ return (madvise(addr, size, MADV_DONTNEED) != 0);
+#else
+ not_reached();
+#endif
+}
+
+bool
+pages_purge_forced(void *addr, size_t size) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
+
+ if (!pages_can_purge_forced) {
+ return true;
+ }
+
+#if defined(JEMALLOC_PURGE_MADVISE_DONTNEED) && \
+ defined(JEMALLOC_PURGE_MADVISE_DONTNEED_ZEROS)
+ return (madvise(addr, size, MADV_DONTNEED) != 0);
+#elif defined(JEMALLOC_MAPS_COALESCE)
+ /* Try to overlay a new demand-zeroed mapping. */
+ return pages_commit(addr, size);
+#else
+ not_reached();
+#endif
+}
+
+static bool
+pages_huge_impl(void *addr, size_t size, bool aligned) {
+ if (aligned) {
+ assert(HUGEPAGE_ADDR2BASE(addr) == addr);
+ assert(HUGEPAGE_CEILING(size) == size);
+ }
+#ifdef JEMALLOC_HAVE_MADVISE_HUGE
+ return (madvise(addr, size, MADV_HUGEPAGE) != 0);
+#else
+ return true;
+#endif
+}
+
+bool
+pages_huge(void *addr, size_t size) {
+ return pages_huge_impl(addr, size, true);
+}
+
+static bool
+pages_huge_unaligned(void *addr, size_t size) {
+ return pages_huge_impl(addr, size, false);
+}
+
+static bool
+pages_nohuge_impl(void *addr, size_t size, bool aligned) {
+ if (aligned) {
+ assert(HUGEPAGE_ADDR2BASE(addr) == addr);
+ assert(HUGEPAGE_CEILING(size) == size);
+ }
+
+#ifdef JEMALLOC_HAVE_MADVISE_HUGE
+ return (madvise(addr, size, MADV_NOHUGEPAGE) != 0);
+#else
+ return false;
+#endif
+}
+
+bool
+pages_nohuge(void *addr, size_t size) {
+ return pages_nohuge_impl(addr, size, true);
+}
+
+static bool
+pages_nohuge_unaligned(void *addr, size_t size) {
+ return pages_nohuge_impl(addr, size, false);
+}
+
+bool
+pages_dontdump(void *addr, size_t size) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
+#ifdef JEMALLOC_MADVISE_DONTDUMP
+ return madvise(addr, size, MADV_DONTDUMP) != 0;
+#else
+ return false;
+#endif
+}
+
+bool
+pages_dodump(void *addr, size_t size) {
+ assert(PAGE_ADDR2BASE(addr) == addr);
+ assert(PAGE_CEILING(size) == size);
+#ifdef JEMALLOC_MADVISE_DONTDUMP
+ return madvise(addr, size, MADV_DODUMP) != 0;
+#else
+ return false;
+#endif
+}
+
+
+static size_t
+os_page_detect(void) {
+#ifdef _WIN32
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ return si.dwPageSize;
+#elif defined(__FreeBSD__)
+ return getpagesize();
#else
- /* Last resort no-op. */
- unzeroed = true;
+ long result = sysconf(_SC_PAGESIZE);
+ if (result == -1) {
+ return LG_PAGE;
+ }
+ return (size_t)result;
#endif
- return (unzeroed);
}
+#ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT
+static bool
+os_overcommits_sysctl(void) {
+ int vm_overcommit;
+ size_t sz;
+
+ sz = sizeof(vm_overcommit);
+#if defined(__FreeBSD__) && defined(VM_OVERCOMMIT)
+ int mib[2];
+
+ mib[0] = CTL_VM;
+ mib[1] = VM_OVERCOMMIT;
+ if (sysctl(mib, 2, &vm_overcommit, &sz, NULL, 0) != 0) {
+ return false; /* Error. */
+ }
+#else
+ if (sysctlbyname("vm.overcommit", &vm_overcommit, &sz, NULL, 0) != 0) {
+ return false; /* Error. */
+ }
+#endif
+
+ return ((vm_overcommit & 0x3) == 0);
+}
+#endif
+
+#ifdef JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY
+/*
+ * Use syscall(2) rather than {open,read,close}(2) when possible to avoid
+ * reentry during bootstrapping if another library has interposed system call
+ * wrappers.
+ */
+static bool
+os_overcommits_proc(void) {
+ int fd;
+ char buf[1];
+
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open)
+ #if defined(O_CLOEXEC)
+ fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY |
+ O_CLOEXEC);
+ #else
+ fd = (int)syscall(SYS_open, "/proc/sys/vm/overcommit_memory", O_RDONLY);
+ if (fd != -1) {
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+ }
+ #endif
+#elif defined(JEMALLOC_USE_SYSCALL) && defined(SYS_openat)
+ #if defined(O_CLOEXEC)
+ fd = (int)syscall(SYS_openat,
+ AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC);
+ #else
+ fd = (int)syscall(SYS_openat,
+ AT_FDCWD, "/proc/sys/vm/overcommit_memory", O_RDONLY);
+ if (fd != -1) {
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+ }
+ #endif
+#else
+ #if defined(O_CLOEXEC)
+ fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY | O_CLOEXEC);
+ #else
+ fd = open("/proc/sys/vm/overcommit_memory", O_RDONLY);
+ if (fd != -1) {
+ fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);
+ }
+ #endif
+#endif
+
+ if (fd == -1) {
+ return false; /* Error. */
+ }
+
+ ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf));
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close)
+ syscall(SYS_close, fd);
+#else
+ close(fd);
+#endif
+
+ if (nread < 1) {
+ return false; /* Error. */
+ }
+ /*
+ * /proc/sys/vm/overcommit_memory meanings:
+ * 0: Heuristic overcommit.
+ * 1: Always overcommit.
+ * 2: Never overcommit.
+ */
+ return (buf[0] == '0' || buf[0] == '1');
+}
+#endif
+
+void
+pages_set_thp_state (void *ptr, size_t size) {
+ if (opt_thp == thp_mode_default || opt_thp == init_system_thp_mode) {
+ return;
+ }
+ assert(opt_thp != thp_mode_not_supported &&
+ init_system_thp_mode != thp_mode_not_supported);
+
+ if (opt_thp == thp_mode_always
+ && init_system_thp_mode != thp_mode_never) {
+ assert(init_system_thp_mode == thp_mode_default);
+ pages_huge_unaligned(ptr, size);
+ } else if (opt_thp == thp_mode_never) {
+ assert(init_system_thp_mode == thp_mode_default ||
+ init_system_thp_mode == thp_mode_always);
+ pages_nohuge_unaligned(ptr, size);
+ }
+}
+
+static void
+init_thp_state(void) {
+ if (!have_madvise_huge) {
+ if (metadata_thp_enabled() && opt_abort) {
+ malloc_write("<jemalloc>: no MADV_HUGEPAGE support\n");
+ abort();
+ }
+ goto label_error;
+ }
+
+ static const char sys_state_madvise[] = "always [madvise] never\n";
+ static const char sys_state_always[] = "[always] madvise never\n";
+ static const char sys_state_never[] = "always madvise [never]\n";
+ char buf[sizeof(sys_state_madvise)];
+
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_open)
+ int fd = (int)syscall(SYS_open,
+ "/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY);
+#else
+ int fd = open("/sys/kernel/mm/transparent_hugepage/enabled", O_RDONLY);
+#endif
+ if (fd == -1) {
+ goto label_error;
+ }
+
+ ssize_t nread = malloc_read_fd(fd, &buf, sizeof(buf));
+#if defined(JEMALLOC_USE_SYSCALL) && defined(SYS_close)
+ syscall(SYS_close, fd);
+#else
+ close(fd);
+#endif
+
+ if (strncmp(buf, sys_state_madvise, (size_t)nread) == 0) {
+ init_system_thp_mode = thp_mode_default;
+ } else if (strncmp(buf, sys_state_always, (size_t)nread) == 0) {
+ init_system_thp_mode = thp_mode_always;
+ } else if (strncmp(buf, sys_state_never, (size_t)nread) == 0) {
+ init_system_thp_mode = thp_mode_never;
+ } else {
+ goto label_error;
+ }
+ return;
+label_error:
+ opt_thp = init_system_thp_mode = thp_mode_not_supported;
+}
+
+bool
+pages_boot(void) {
+ os_page = os_page_detect();
+ if (os_page > PAGE) {
+ malloc_write("<jemalloc>: Unsupported system page size\n");
+ if (opt_abort) {
+ abort();
+ }
+ return true;
+ }
+
+#ifndef _WIN32
+ mmap_flags = MAP_PRIVATE | MAP_ANON;
+#endif
+
+#ifdef JEMALLOC_SYSCTL_VM_OVERCOMMIT
+ os_overcommits = os_overcommits_sysctl();
+#elif defined(JEMALLOC_PROC_SYS_VM_OVERCOMMIT_MEMORY)
+ os_overcommits = os_overcommits_proc();
+# ifdef MAP_NORESERVE
+ if (os_overcommits) {
+ mmap_flags |= MAP_NORESERVE;
+ }
+# endif
+#else
+ os_overcommits = false;
+#endif
+
+ init_thp_state();
+
+ /* Detect lazy purge runtime support. */
+ if (pages_can_purge_lazy) {
+ bool committed = false;
+ void *madv_free_page = os_pages_map(NULL, PAGE, PAGE, &committed);
+ if (madv_free_page == NULL) {
+ return true;
+ }
+ assert(pages_can_purge_lazy_runtime);
+ if (pages_purge_lazy(madv_free_page, PAGE)) {
+ pages_can_purge_lazy_runtime = false;
+ }
+ os_pages_unmap(madv_free_page, PAGE);
+ }
+
+ return false;
+}
diff --git a/deps/jemalloc/src/prng.c b/deps/jemalloc/src/prng.c
new file mode 100644
index 000000000..83c04bf9b
--- /dev/null
+++ b/deps/jemalloc/src/prng.c
@@ -0,0 +1,3 @@
+#define JEMALLOC_PRNG_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
diff --git a/deps/jemalloc/src/prof.c b/deps/jemalloc/src/prof.c
index 5d2b9598f..13df641a0 100644
--- a/deps/jemalloc/src/prof.c
+++ b/deps/jemalloc/src/prof.c
@@ -1,14 +1,29 @@
-#define JEMALLOC_PROF_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_PROF_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/ckh.h"
+#include "jemalloc/internal/hash.h"
+#include "jemalloc/internal/malloc_io.h"
+#include "jemalloc/internal/mutex.h"
+
/******************************************************************************/
#ifdef JEMALLOC_PROF_LIBUNWIND
-#define UNW_LOCAL_ONLY
+#define UNW_LOCAL_ONLY
#include <libunwind.h>
#endif
#ifdef JEMALLOC_PROF_LIBGCC
+/*
+ * We have a circular dependency -- jemalloc_internal.h tells us if we should
+ * use libgcc's unwinding functionality, but after we've included that, we've
+ * already hooked _Unwind_Backtrace. We'll temporarily disable hooking.
+ */
+#undef _Unwind_Backtrace
#include <unwind.h>
+#define _Unwind_Backtrace JEMALLOC_HOOK(_Unwind_Backtrace, hooks_libc_hook)
#endif
/******************************************************************************/
@@ -63,7 +78,7 @@ size_t lg_prof_sample;
* creating/destroying mutexes.
*/
static malloc_mutex_t *gctx_locks;
-static unsigned cum_gctxs; /* Atomic counter. */
+static atomic_u_t cum_gctxs; /* Atomic counter. */
/*
* Table of mutexes that are shared among tdata's. No operations require
@@ -78,7 +93,8 @@ static malloc_mutex_t *tdata_locks;
* structure that knows about all backtraces currently captured.
*/
static ckh_t bt2gctx;
-static malloc_mutex_t bt2gctx_mtx;
+/* Non static to enable profiling. */
+malloc_mutex_t bt2gctx_mtx;
/*
* Tree of all extant prof_tdata_t structures, regardless of state,
@@ -109,7 +125,7 @@ static char prof_dump_buf[
1
#endif
];
-static unsigned prof_dump_buf_end;
+static size_t prof_dump_buf_end;
static int prof_dump_fd;
/* Do not dump any profiles until bootstrapping is complete. */
@@ -121,20 +137,19 @@ static bool prof_booted = false;
* definition.
*/
-static bool prof_tctx_should_destroy(prof_tctx_t *tctx);
+static bool prof_tctx_should_destroy(tsdn_t *tsdn, prof_tctx_t *tctx);
static void prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx);
-static bool prof_tdata_should_destroy(prof_tdata_t *tdata,
+static bool prof_tdata_should_destroy(tsdn_t *tsdn, prof_tdata_t *tdata,
bool even_if_attached);
static void prof_tdata_destroy(tsd_t *tsd, prof_tdata_t *tdata,
bool even_if_attached);
-static char *prof_thread_name_alloc(tsd_t *tsd, const char *thread_name);
+static char *prof_thread_name_alloc(tsdn_t *tsdn, const char *thread_name);
/******************************************************************************/
/* Red-black trees. */
-JEMALLOC_INLINE_C int
-prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b)
-{
+static int
+prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b) {
uint64_t a_thr_uid = a->thr_uid;
uint64_t b_thr_uid = b->thr_uid;
int ret = (a_thr_uid > b_thr_uid) - (a_thr_uid < b_thr_uid);
@@ -150,30 +165,29 @@ prof_tctx_comp(const prof_tctx_t *a, const prof_tctx_t *b)
b_tctx_uid);
}
}
- return (ret);
+ return ret;
}
rb_gen(static UNUSED, tctx_tree_, prof_tctx_tree_t, prof_tctx_t,
tctx_link, prof_tctx_comp)
-JEMALLOC_INLINE_C int
-prof_gctx_comp(const prof_gctx_t *a, const prof_gctx_t *b)
-{
+static int
+prof_gctx_comp(const prof_gctx_t *a, const prof_gctx_t *b) {
unsigned a_len = a->bt.len;
unsigned b_len = b->bt.len;
unsigned comp_len = (a_len < b_len) ? a_len : b_len;
int ret = memcmp(a->bt.vec, b->bt.vec, comp_len * sizeof(void *));
- if (ret == 0)
+ if (ret == 0) {
ret = (a_len > b_len) - (a_len < b_len);
- return (ret);
+ }
+ return ret;
}
rb_gen(static UNUSED, gctx_tree_, prof_gctx_tree_t, prof_gctx_t, dump_link,
prof_gctx_comp)
-JEMALLOC_INLINE_C int
-prof_tdata_comp(const prof_tdata_t *a, const prof_tdata_t *b)
-{
+static int
+prof_tdata_comp(const prof_tdata_t *a, const prof_tdata_t *b) {
int ret;
uint64_t a_uid = a->thr_uid;
uint64_t b_uid = b->thr_uid;
@@ -185,7 +199,7 @@ prof_tdata_comp(const prof_tdata_t *a, const prof_tdata_t *b)
ret = ((a_discrim > b_discrim) - (a_discrim < b_discrim));
}
- return (ret);
+ return ret;
}
rb_gen(static UNUSED, tdata_tree_, prof_tdata_tree_t, prof_tdata_t, tdata_link,
@@ -194,8 +208,7 @@ rb_gen(static UNUSED, tdata_tree_, prof_tdata_tree_t, prof_tdata_t, tdata_link,
/******************************************************************************/
void
-prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx, bool updated)
-{
+prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx, bool updated) {
prof_tdata_t *tdata;
cassert(config_prof);
@@ -208,27 +221,28 @@ prof_alloc_rollback(tsd_t *tsd, prof_tctx_t *tctx, bool updated)
* programs.
*/
tdata = prof_tdata_get(tsd, true);
- if (tdata != NULL)
+ if (tdata != NULL) {
prof_sample_threshold_update(tdata);
+ }
}
if ((uintptr_t)tctx > (uintptr_t)1U) {
- malloc_mutex_lock(tctx->tdata->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock);
tctx->prepared = false;
- if (prof_tctx_should_destroy(tctx))
+ if (prof_tctx_should_destroy(tsd_tsdn(tsd), tctx)) {
prof_tctx_destroy(tsd, tctx);
- else
- malloc_mutex_unlock(tctx->tdata->lock);
+ } else {
+ malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock);
+ }
}
}
void
-prof_malloc_sample_object(const void *ptr, size_t usize, prof_tctx_t *tctx)
-{
+prof_malloc_sample_object(tsdn_t *tsdn, const void *ptr, size_t usize,
+ prof_tctx_t *tctx) {
+ prof_tctx_set(tsdn, ptr, usize, NULL, tctx);
- prof_tctx_set(ptr, usize, tctx);
-
- malloc_mutex_lock(tctx->tdata->lock);
+ malloc_mutex_lock(tsdn, tctx->tdata->lock);
tctx->cnts.curobjs++;
tctx->cnts.curbytes += usize;
if (opt_prof_accum) {
@@ -236,39 +250,34 @@ prof_malloc_sample_object(const void *ptr, size_t usize, prof_tctx_t *tctx)
tctx->cnts.accumbytes += usize;
}
tctx->prepared = false;
- malloc_mutex_unlock(tctx->tdata->lock);
+ malloc_mutex_unlock(tsdn, tctx->tdata->lock);
}
void
-prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_tctx_t *tctx)
-{
-
- malloc_mutex_lock(tctx->tdata->lock);
+prof_free_sampled_object(tsd_t *tsd, size_t usize, prof_tctx_t *tctx) {
+ malloc_mutex_lock(tsd_tsdn(tsd), tctx->tdata->lock);
assert(tctx->cnts.curobjs > 0);
assert(tctx->cnts.curbytes >= usize);
tctx->cnts.curobjs--;
tctx->cnts.curbytes -= usize;
- if (prof_tctx_should_destroy(tctx))
+ if (prof_tctx_should_destroy(tsd_tsdn(tsd), tctx)) {
prof_tctx_destroy(tsd, tctx);
- else
- malloc_mutex_unlock(tctx->tdata->lock);
+ } else {
+ malloc_mutex_unlock(tsd_tsdn(tsd), tctx->tdata->lock);
+ }
}
void
-bt_init(prof_bt_t *bt, void **vec)
-{
-
+bt_init(prof_bt_t *bt, void **vec) {
cassert(config_prof);
bt->vec = vec;
bt->len = 0;
}
-JEMALLOC_INLINE_C void
-prof_enter(tsd_t *tsd, prof_tdata_t *tdata)
-{
-
+static void
+prof_enter(tsd_t *tsd, prof_tdata_t *tdata) {
cassert(config_prof);
assert(tdata == prof_tdata_get(tsd, false));
@@ -277,17 +286,15 @@ prof_enter(tsd_t *tsd, prof_tdata_t *tdata)
tdata->enq = true;
}
- malloc_mutex_lock(&bt2gctx_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx);
}
-JEMALLOC_INLINE_C void
-prof_leave(tsd_t *tsd, prof_tdata_t *tdata)
-{
-
+static void
+prof_leave(tsd_t *tsd, prof_tdata_t *tdata) {
cassert(config_prof);
assert(tdata == prof_tdata_get(tsd, false));
- malloc_mutex_unlock(&bt2gctx_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx);
if (tdata != NULL) {
bool idump, gdump;
@@ -299,17 +306,18 @@ prof_leave(tsd_t *tsd, prof_tdata_t *tdata)
gdump = tdata->enq_gdump;
tdata->enq_gdump = false;
- if (idump)
- prof_idump();
- if (gdump)
- prof_gdump();
+ if (idump) {
+ prof_idump(tsd_tsdn(tsd));
+ }
+ if (gdump) {
+ prof_gdump(tsd_tsdn(tsd));
+ }
}
}
#ifdef JEMALLOC_PROF_LIBUNWIND
void
-prof_backtrace(prof_bt_t *bt)
-{
+prof_backtrace(prof_bt_t *bt) {
int nframes;
cassert(config_prof);
@@ -317,42 +325,41 @@ prof_backtrace(prof_bt_t *bt)
assert(bt->vec != NULL);
nframes = unw_backtrace(bt->vec, PROF_BT_MAX);
- if (nframes <= 0)
+ if (nframes <= 0) {
return;
+ }
bt->len = nframes;
}
#elif (defined(JEMALLOC_PROF_LIBGCC))
static _Unwind_Reason_Code
-prof_unwind_init_callback(struct _Unwind_Context *context, void *arg)
-{
-
+prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) {
cassert(config_prof);
- return (_URC_NO_REASON);
+ return _URC_NO_REASON;
}
static _Unwind_Reason_Code
-prof_unwind_callback(struct _Unwind_Context *context, void *arg)
-{
+prof_unwind_callback(struct _Unwind_Context *context, void *arg) {
prof_unwind_data_t *data = (prof_unwind_data_t *)arg;
void *ip;
cassert(config_prof);
ip = (void *)_Unwind_GetIP(context);
- if (ip == NULL)
- return (_URC_END_OF_STACK);
+ if (ip == NULL) {
+ return _URC_END_OF_STACK;
+ }
data->bt->vec[data->bt->len] = ip;
data->bt->len++;
- if (data->bt->len == data->max)
- return (_URC_END_OF_STACK);
+ if (data->bt->len == data->max) {
+ return _URC_END_OF_STACK;
+ }
- return (_URC_NO_REASON);
+ return _URC_NO_REASON;
}
void
-prof_backtrace(prof_bt_t *bt)
-{
+prof_backtrace(prof_bt_t *bt) {
prof_unwind_data_t data = {bt, PROF_BT_MAX};
cassert(config_prof);
@@ -361,20 +368,22 @@ prof_backtrace(prof_bt_t *bt)
}
#elif (defined(JEMALLOC_PROF_GCC))
void
-prof_backtrace(prof_bt_t *bt)
-{
-#define BT_FRAME(i) \
+prof_backtrace(prof_bt_t *bt) {
+#define BT_FRAME(i) \
if ((i) < PROF_BT_MAX) { \
void *p; \
- if (__builtin_frame_address(i) == 0) \
+ if (__builtin_frame_address(i) == 0) { \
return; \
+ } \
p = __builtin_return_address(i); \
- if (p == NULL) \
+ if (p == NULL) { \
return; \
+ } \
bt->vec[(i)] = p; \
bt->len = (i) + 1; \
- } else \
- return;
+ } else { \
+ return; \
+ }
cassert(config_prof);
@@ -522,40 +531,36 @@ prof_backtrace(prof_bt_t *bt)
}
#else
void
-prof_backtrace(prof_bt_t *bt)
-{
-
+prof_backtrace(prof_bt_t *bt) {
cassert(config_prof);
not_reached();
}
#endif
static malloc_mutex_t *
-prof_gctx_mutex_choose(void)
-{
- unsigned ngctxs = atomic_add_u(&cum_gctxs, 1);
+prof_gctx_mutex_choose(void) {
+ unsigned ngctxs = atomic_fetch_add_u(&cum_gctxs, 1, ATOMIC_RELAXED);
- return (&gctx_locks[(ngctxs - 1) % PROF_NCTX_LOCKS]);
+ return &gctx_locks[(ngctxs - 1) % PROF_NCTX_LOCKS];
}
static malloc_mutex_t *
-prof_tdata_mutex_choose(uint64_t thr_uid)
-{
-
- return (&tdata_locks[thr_uid % PROF_NTDATA_LOCKS]);
+prof_tdata_mutex_choose(uint64_t thr_uid) {
+ return &tdata_locks[thr_uid % PROF_NTDATA_LOCKS];
}
static prof_gctx_t *
-prof_gctx_create(tsd_t *tsd, prof_bt_t *bt)
-{
+prof_gctx_create(tsdn_t *tsdn, prof_bt_t *bt) {
/*
* Create a single allocation that has space for vec of length bt->len.
*/
- prof_gctx_t *gctx = (prof_gctx_t *)iallocztm(tsd, offsetof(prof_gctx_t,
- vec) + (bt->len * sizeof(void *)), false, tcache_get(tsd, true),
- true, NULL);
- if (gctx == NULL)
- return (NULL);
+ size_t size = offsetof(prof_gctx_t, vec) + (bt->len * sizeof(void *));
+ prof_gctx_t *gctx = (prof_gctx_t *)iallocztm(tsdn, size,
+ sz_size2index(size), false, NULL, true, arena_get(TSDN_NULL, 0, true),
+ true);
+ if (gctx == NULL) {
+ return NULL;
+ }
gctx->lock = prof_gctx_mutex_choose();
/*
* Set nlimbo to 1, in order to avoid a race condition with
@@ -567,14 +572,12 @@ prof_gctx_create(tsd_t *tsd, prof_bt_t *bt)
memcpy(gctx->vec, bt->vec, bt->len * sizeof(void *));
gctx->bt.vec = gctx->vec;
gctx->bt.len = bt->len;
- return (gctx);
+ return gctx;
}
static void
prof_gctx_try_destroy(tsd_t *tsd, prof_tdata_t *tdata_self, prof_gctx_t *gctx,
- prof_tdata_t *tdata)
-{
-
+ prof_tdata_t *tdata) {
cassert(config_prof);
/*
@@ -585,62 +588,66 @@ prof_gctx_try_destroy(tsd_t *tsd, prof_tdata_t *tdata_self, prof_gctx_t *gctx,
* into this function.
*/
prof_enter(tsd, tdata_self);
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock);
assert(gctx->nlimbo != 0);
if (tctx_tree_empty(&gctx->tctxs) && gctx->nlimbo == 1) {
/* Remove gctx from bt2gctx. */
- if (ckh_remove(tsd, &bt2gctx, &gctx->bt, NULL, NULL))
+ if (ckh_remove(tsd, &bt2gctx, &gctx->bt, NULL, NULL)) {
not_reached();
+ }
prof_leave(tsd, tdata_self);
/* Destroy gctx. */
- malloc_mutex_unlock(gctx->lock);
- idalloctm(tsd, gctx, tcache_get(tsd, false), true);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
+ idalloctm(tsd_tsdn(tsd), gctx, NULL, NULL, true, true);
} else {
/*
* Compensate for increment in prof_tctx_destroy() or
* prof_lookup().
*/
gctx->nlimbo--;
- malloc_mutex_unlock(gctx->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
prof_leave(tsd, tdata_self);
}
}
-/* tctx->tdata->lock must be held. */
static bool
-prof_tctx_should_destroy(prof_tctx_t *tctx)
-{
+prof_tctx_should_destroy(tsdn_t *tsdn, prof_tctx_t *tctx) {
+ malloc_mutex_assert_owner(tsdn, tctx->tdata->lock);
- if (opt_prof_accum)
- return (false);
- if (tctx->cnts.curobjs != 0)
- return (false);
- if (tctx->prepared)
- return (false);
- return (true);
+ if (opt_prof_accum) {
+ return false;
+ }
+ if (tctx->cnts.curobjs != 0) {
+ return false;
+ }
+ if (tctx->prepared) {
+ return false;
+ }
+ return true;
}
static bool
-prof_gctx_should_destroy(prof_gctx_t *gctx)
-{
-
- if (opt_prof_accum)
- return (false);
- if (!tctx_tree_empty(&gctx->tctxs))
- return (false);
- if (gctx->nlimbo != 0)
- return (false);
- return (true);
+prof_gctx_should_destroy(prof_gctx_t *gctx) {
+ if (opt_prof_accum) {
+ return false;
+ }
+ if (!tctx_tree_empty(&gctx->tctxs)) {
+ return false;
+ }
+ if (gctx->nlimbo != 0) {
+ return false;
+ }
+ return true;
}
-/* tctx->tdata->lock is held upon entry, and released before return. */
static void
-prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx)
-{
+prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx) {
prof_tdata_t *tdata = tctx->tdata;
prof_gctx_t *gctx = tctx->gctx;
bool destroy_tdata, destroy_tctx, destroy_gctx;
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock);
+
assert(tctx->cnts.curobjs == 0);
assert(tctx->cnts.curbytes == 0);
assert(!opt_prof_accum);
@@ -648,10 +655,10 @@ prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx)
assert(tctx->cnts.accumbytes == 0);
ckh_remove(tsd, &tdata->bt2tctx, &gctx->bt, NULL, NULL);
- destroy_tdata = prof_tdata_should_destroy(tdata, false);
- malloc_mutex_unlock(tdata->lock);
+ destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), tdata, false);
+ malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock);
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock);
switch (tctx->state) {
case prof_tctx_state_nominal:
tctx_tree_remove(&gctx->tctxs, tctx);
@@ -673,8 +680,9 @@ prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx)
*/
gctx->nlimbo++;
destroy_gctx = true;
- } else
+ } else {
destroy_gctx = false;
+ }
break;
case prof_tctx_state_dumping:
/*
@@ -691,27 +699,30 @@ prof_tctx_destroy(tsd_t *tsd, prof_tctx_t *tctx)
destroy_tctx = false;
destroy_gctx = false;
}
- malloc_mutex_unlock(gctx->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
if (destroy_gctx) {
prof_gctx_try_destroy(tsd, prof_tdata_get(tsd, false), gctx,
tdata);
}
- if (destroy_tdata)
+ malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tctx->tdata->lock);
+
+ if (destroy_tdata) {
prof_tdata_destroy(tsd, tdata, false);
+ }
- if (destroy_tctx)
- idalloctm(tsd, tctx, tcache_get(tsd, false), true);
+ if (destroy_tctx) {
+ idalloctm(tsd_tsdn(tsd), tctx, NULL, NULL, true, true);
+ }
}
static bool
prof_lookup_global(tsd_t *tsd, prof_bt_t *bt, prof_tdata_t *tdata,
- void **p_btkey, prof_gctx_t **p_gctx, bool *p_new_gctx)
-{
+ void **p_btkey, prof_gctx_t **p_gctx, bool *p_new_gctx) {
union {
prof_gctx_t *p;
void *v;
- } gctx;
+ } gctx, tgctx;
union {
prof_bt_t *p;
void *v;
@@ -721,40 +732,57 @@ prof_lookup_global(tsd_t *tsd, prof_bt_t *bt, prof_tdata_t *tdata,
prof_enter(tsd, tdata);
if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) {
/* bt has never been seen before. Insert it. */
- gctx.p = prof_gctx_create(tsd, bt);
- if (gctx.v == NULL) {
- prof_leave(tsd, tdata);
- return (true);
+ prof_leave(tsd, tdata);
+ tgctx.p = prof_gctx_create(tsd_tsdn(tsd), bt);
+ if (tgctx.v == NULL) {
+ return true;
}
- btkey.p = &gctx.p->bt;
- if (ckh_insert(tsd, &bt2gctx, btkey.v, gctx.v)) {
- /* OOM. */
- prof_leave(tsd, tdata);
- idalloctm(tsd, gctx.v, tcache_get(tsd, false), true);
- return (true);
+ prof_enter(tsd, tdata);
+ if (ckh_search(&bt2gctx, bt, &btkey.v, &gctx.v)) {
+ gctx.p = tgctx.p;
+ btkey.p = &gctx.p->bt;
+ if (ckh_insert(tsd, &bt2gctx, btkey.v, gctx.v)) {
+ /* OOM. */
+ prof_leave(tsd, tdata);
+ idalloctm(tsd_tsdn(tsd), gctx.v, NULL, NULL,
+ true, true);
+ return true;
+ }
+ new_gctx = true;
+ } else {
+ new_gctx = false;
}
- new_gctx = true;
} else {
+ tgctx.v = NULL;
+ new_gctx = false;
+ }
+
+ if (!new_gctx) {
/*
* Increment nlimbo, in order to avoid a race condition with
* prof_tctx_destroy()/prof_gctx_try_destroy().
*/
- malloc_mutex_lock(gctx.p->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), gctx.p->lock);
gctx.p->nlimbo++;
- malloc_mutex_unlock(gctx.p->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx.p->lock);
new_gctx = false;
+
+ if (tgctx.v != NULL) {
+ /* Lost race to insert. */
+ idalloctm(tsd_tsdn(tsd), tgctx.v, NULL, NULL, true,
+ true);
+ }
}
prof_leave(tsd, tdata);
*p_btkey = btkey.v;
*p_gctx = gctx.p;
*p_new_gctx = new_gctx;
- return (false);
+ return false;
}
prof_tctx_t *
-prof_lookup(tsd_t *tsd, prof_bt_t *bt)
-{
+prof_lookup(tsd_t *tsd, prof_bt_t *bt) {
union {
prof_tctx_t *p;
void *v;
@@ -765,16 +793,17 @@ prof_lookup(tsd_t *tsd, prof_bt_t *bt)
cassert(config_prof);
tdata = prof_tdata_get(tsd, false);
- if (tdata == NULL)
- return (NULL);
+ if (tdata == NULL) {
+ return NULL;
+ }
- malloc_mutex_lock(tdata->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock);
not_found = ckh_search(&tdata->bt2tctx, bt, NULL, &ret.v);
- if (!not_found) /* Note double negative! */
+ if (!not_found) { /* Note double negative! */
ret.p->prepared = true;
- malloc_mutex_unlock(tdata->lock);
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock);
if (not_found) {
- tcache_t *tcache;
void *btkey;
prof_gctx_t *gctx;
bool new_gctx, error;
@@ -784,17 +813,19 @@ prof_lookup(tsd_t *tsd, prof_bt_t *bt)
* cache.
*/
if (prof_lookup_global(tsd, bt, tdata, &btkey, &gctx,
- &new_gctx))
- return (NULL);
+ &new_gctx)) {
+ return NULL;
+ }
/* Link a prof_tctx_t into gctx for this thread. */
- tcache = tcache_get(tsd, true);
- ret.v = iallocztm(tsd, sizeof(prof_tctx_t), false, tcache, true,
- NULL);
+ ret.v = iallocztm(tsd_tsdn(tsd), sizeof(prof_tctx_t),
+ sz_size2index(sizeof(prof_tctx_t)), false, NULL, true,
+ arena_ichoose(tsd, NULL), true);
if (ret.p == NULL) {
- if (new_gctx)
+ if (new_gctx) {
prof_gctx_try_destroy(tsd, tdata, gctx, tdata);
- return (NULL);
+ }
+ return NULL;
}
ret.p->tdata = tdata;
ret.p->thr_uid = tdata->thr_uid;
@@ -804,47 +835,48 @@ prof_lookup(tsd_t *tsd, prof_bt_t *bt)
ret.p->tctx_uid = tdata->tctx_uid_next++;
ret.p->prepared = true;
ret.p->state = prof_tctx_state_initializing;
- malloc_mutex_lock(tdata->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock);
error = ckh_insert(tsd, &tdata->bt2tctx, btkey, ret.v);
- malloc_mutex_unlock(tdata->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock);
if (error) {
- if (new_gctx)
+ if (new_gctx) {
prof_gctx_try_destroy(tsd, tdata, gctx, tdata);
- idalloctm(tsd, ret.v, tcache, true);
- return (NULL);
+ }
+ idalloctm(tsd_tsdn(tsd), ret.v, NULL, NULL, true, true);
+ return NULL;
}
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock);
ret.p->state = prof_tctx_state_nominal;
tctx_tree_insert(&gctx->tctxs, ret.p);
gctx->nlimbo--;
- malloc_mutex_unlock(gctx->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
}
- return (ret.p);
+ return ret.p;
}
+/*
+ * The bodies of this function and prof_leakcheck() are compiled out unless heap
+ * profiling is enabled, so that it is possible to compile jemalloc with
+ * floating point support completely disabled. Avoiding floating point code is
+ * important on memory-constrained systems, but it also enables a workaround for
+ * versions of glibc that don't properly save/restore floating point registers
+ * during dynamic lazy symbol loading (which internally calls into whatever
+ * malloc implementation happens to be integrated into the application). Note
+ * that some compilers (e.g. gcc 4.8) may use floating point registers for fast
+ * memory moves, so jemalloc must be compiled with such optimizations disabled
+ * (e.g.
+ * -mno-sse) in order for the workaround to be complete.
+ */
void
-prof_sample_threshold_update(prof_tdata_t *tdata)
-{
- /*
- * The body of this function is compiled out unless heap profiling is
- * enabled, so that it is possible to compile jemalloc with floating
- * point support completely disabled. Avoiding floating point code is
- * important on memory-constrained systems, but it also enables a
- * workaround for versions of glibc that don't properly save/restore
- * floating point registers during dynamic lazy symbol loading (which
- * internally calls into whatever malloc implementation happens to be
- * integrated into the application). Note that some compilers (e.g.
- * gcc 4.8) may use floating point registers for fast memory moves, so
- * jemalloc must be compiled with such optimizations disabled (e.g.
- * -mno-sse) in order for the workaround to be complete.
- */
+prof_sample_threshold_update(prof_tdata_t *tdata) {
#ifdef JEMALLOC_PROF
uint64_t r;
double u;
- if (!config_prof)
+ if (!config_prof) {
return;
+ }
if (lg_prof_sample == 0) {
tdata->bytes_until_sample = 0;
@@ -869,8 +901,7 @@ prof_sample_threshold_update(prof_tdata_t *tdata)
* pp 500
* (http://luc.devroye.org/rnbookindex.html)
*/
- prng64(r, 53, tdata->prng_state, UINT64_C(6364136223846793005),
- UINT64_C(1442695040888963407));
+ r = prng_lg_range_u64(&tdata->prng_state, 53);
u = (double)r * (1.0/9007199254740992.0L);
tdata->bytes_until_sample = (uint64_t)(log(u) /
log(1.0 - (1.0 / (double)((uint64_t)1U << lg_prof_sample))))
@@ -880,101 +911,91 @@ prof_sample_threshold_update(prof_tdata_t *tdata)
#ifdef JEMALLOC_JET
static prof_tdata_t *
-prof_tdata_count_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
-{
+prof_tdata_count_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
+ void *arg) {
size_t *tdata_count = (size_t *)arg;
(*tdata_count)++;
- return (NULL);
+ return NULL;
}
size_t
-prof_tdata_count(void)
-{
+prof_tdata_count(void) {
size_t tdata_count = 0;
+ tsdn_t *tsdn;
- malloc_mutex_lock(&tdatas_mtx);
+ tsdn = tsdn_fetch();
+ malloc_mutex_lock(tsdn, &tdatas_mtx);
tdata_tree_iter(&tdatas, NULL, prof_tdata_count_iter,
(void *)&tdata_count);
- malloc_mutex_unlock(&tdatas_mtx);
+ malloc_mutex_unlock(tsdn, &tdatas_mtx);
- return (tdata_count);
+ return tdata_count;
}
-#endif
-#ifdef JEMALLOC_JET
size_t
-prof_bt_count(void)
-{
+prof_bt_count(void) {
size_t bt_count;
tsd_t *tsd;
prof_tdata_t *tdata;
tsd = tsd_fetch();
tdata = prof_tdata_get(tsd, false);
- if (tdata == NULL)
- return (0);
+ if (tdata == NULL) {
+ return 0;
+ }
- malloc_mutex_lock(&bt2gctx_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bt2gctx_mtx);
bt_count = ckh_count(&bt2gctx);
- malloc_mutex_unlock(&bt2gctx_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bt2gctx_mtx);
- return (bt_count);
+ return bt_count;
}
#endif
-#ifdef JEMALLOC_JET
-#undef prof_dump_open
-#define prof_dump_open JEMALLOC_N(prof_dump_open_impl)
-#endif
static int
-prof_dump_open(bool propagate_err, const char *filename)
-{
+prof_dump_open_impl(bool propagate_err, const char *filename) {
int fd;
fd = creat(filename, 0644);
if (fd == -1 && !propagate_err) {
malloc_printf("<jemalloc>: creat(\"%s\"), 0644) failed\n",
filename);
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
}
- return (fd);
+ return fd;
}
-#ifdef JEMALLOC_JET
-#undef prof_dump_open
-#define prof_dump_open JEMALLOC_N(prof_dump_open)
-prof_dump_open_t *prof_dump_open = JEMALLOC_N(prof_dump_open_impl);
-#endif
+prof_dump_open_t *JET_MUTABLE prof_dump_open = prof_dump_open_impl;
static bool
-prof_dump_flush(bool propagate_err)
-{
+prof_dump_flush(bool propagate_err) {
bool ret = false;
ssize_t err;
cassert(config_prof);
- err = write(prof_dump_fd, prof_dump_buf, prof_dump_buf_end);
+ err = malloc_write_fd(prof_dump_fd, prof_dump_buf, prof_dump_buf_end);
if (err == -1) {
if (!propagate_err) {
malloc_write("<jemalloc>: write() failed during heap "
"profile flush\n");
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
}
ret = true;
}
prof_dump_buf_end = 0;
- return (ret);
+ return ret;
}
static bool
-prof_dump_close(bool propagate_err)
-{
+prof_dump_close(bool propagate_err) {
bool ret;
assert(prof_dump_fd != -1);
@@ -982,13 +1003,12 @@ prof_dump_close(bool propagate_err)
close(prof_dump_fd);
prof_dump_fd = -1;
- return (ret);
+ return ret;
}
static bool
-prof_dump_write(bool propagate_err, const char *s)
-{
- unsigned i, slen, n;
+prof_dump_write(bool propagate_err, const char *s) {
+ size_t i, slen, n;
cassert(config_prof);
@@ -996,9 +1016,11 @@ prof_dump_write(bool propagate_err, const char *s)
slen = strlen(s);
while (i < slen) {
/* Flush the buffer if it is full. */
- if (prof_dump_buf_end == PROF_DUMP_BUFSIZE)
- if (prof_dump_flush(propagate_err) && propagate_err)
- return (true);
+ if (prof_dump_buf_end == PROF_DUMP_BUFSIZE) {
+ if (prof_dump_flush(propagate_err) && propagate_err) {
+ return true;
+ }
+ }
if (prof_dump_buf_end + slen <= PROF_DUMP_BUFSIZE) {
/* Finish writing. */
@@ -1012,13 +1034,12 @@ prof_dump_write(bool propagate_err, const char *s)
i += n;
}
- return (false);
+ return false;
}
JEMALLOC_FORMAT_PRINTF(2, 3)
static bool
-prof_dump_printf(bool propagate_err, const char *format, ...)
-{
+prof_dump_printf(bool propagate_err, const char *format, ...) {
bool ret;
va_list ap;
char buf[PROF_PRINTF_BUFSIZE];
@@ -1028,23 +1049,22 @@ prof_dump_printf(bool propagate_err, const char *format, ...)
va_end(ap);
ret = prof_dump_write(propagate_err, buf);
- return (ret);
+ return ret;
}
-/* tctx->tdata->lock is held. */
static void
-prof_tctx_merge_tdata(prof_tctx_t *tctx, prof_tdata_t *tdata)
-{
+prof_tctx_merge_tdata(tsdn_t *tsdn, prof_tctx_t *tctx, prof_tdata_t *tdata) {
+ malloc_mutex_assert_owner(tsdn, tctx->tdata->lock);
- malloc_mutex_lock(tctx->gctx->lock);
+ malloc_mutex_lock(tsdn, tctx->gctx->lock);
switch (tctx->state) {
case prof_tctx_state_initializing:
- malloc_mutex_unlock(tctx->gctx->lock);
+ malloc_mutex_unlock(tsdn, tctx->gctx->lock);
return;
case prof_tctx_state_nominal:
tctx->state = prof_tctx_state_dumping;
- malloc_mutex_unlock(tctx->gctx->lock);
+ malloc_mutex_unlock(tsdn, tctx->gctx->lock);
memcpy(&tctx->dump_cnts, &tctx->cnts, sizeof(prof_cnt_t));
@@ -1063,10 +1083,9 @@ prof_tctx_merge_tdata(prof_tctx_t *tctx, prof_tdata_t *tdata)
}
}
-/* gctx->lock is held. */
static void
-prof_tctx_merge_gctx(prof_tctx_t *tctx, prof_gctx_t *gctx)
-{
+prof_tctx_merge_gctx(tsdn_t *tsdn, prof_tctx_t *tctx, prof_gctx_t *gctx) {
+ malloc_mutex_assert_owner(tsdn, gctx->lock);
gctx->cnt_summed.curobjs += tctx->dump_cnts.curobjs;
gctx->cnt_summed.curbytes += tctx->dump_cnts.curbytes;
@@ -1076,10 +1095,11 @@ prof_tctx_merge_gctx(prof_tctx_t *tctx, prof_gctx_t *gctx)
}
}
-/* tctx->gctx is held. */
static prof_tctx_t *
-prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
-{
+prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) {
+ tsdn_t *tsdn = (tsdn_t *)arg;
+
+ malloc_mutex_assert_owner(tsdn, tctx->gctx->lock);
switch (tctx->state) {
case prof_tctx_state_nominal:
@@ -1087,20 +1107,26 @@ prof_tctx_merge_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
break;
case prof_tctx_state_dumping:
case prof_tctx_state_purgatory:
- prof_tctx_merge_gctx(tctx, tctx->gctx);
+ prof_tctx_merge_gctx(tsdn, tctx, tctx->gctx);
break;
default:
not_reached();
}
- return (NULL);
+ return NULL;
}
-/* gctx->lock is held. */
+struct prof_tctx_dump_iter_arg_s {
+ tsdn_t *tsdn;
+ bool propagate_err;
+};
+
static prof_tctx_t *
-prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
-{
- bool propagate_err = *(bool *)arg;
+prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *opaque) {
+ struct prof_tctx_dump_iter_arg_s *arg =
+ (struct prof_tctx_dump_iter_arg_s *)opaque;
+
+ malloc_mutex_assert_owner(arg->tsdn, tctx->gctx->lock);
switch (tctx->state) {
case prof_tctx_state_initializing:
@@ -1109,25 +1135,27 @@ prof_tctx_dump_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
break;
case prof_tctx_state_dumping:
case prof_tctx_state_purgatory:
- if (prof_dump_printf(propagate_err,
+ if (prof_dump_printf(arg->propagate_err,
" t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": "
"%"FMTu64"]\n", tctx->thr_uid, tctx->dump_cnts.curobjs,
tctx->dump_cnts.curbytes, tctx->dump_cnts.accumobjs,
- tctx->dump_cnts.accumbytes))
- return (tctx);
+ tctx->dump_cnts.accumbytes)) {
+ return tctx;
+ }
break;
default:
not_reached();
}
- return (NULL);
+ return NULL;
}
-/* tctx->gctx is held. */
static prof_tctx_t *
-prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
-{
+prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg) {
+ tsdn_t *tsdn = (tsdn_t *)arg;
prof_tctx_t *ret;
+ malloc_mutex_assert_owner(tsdn, tctx->gctx->lock);
+
switch (tctx->state) {
case prof_tctx_state_nominal:
/* New since dumping started; ignore. */
@@ -1144,16 +1172,14 @@ prof_tctx_finish_iter(prof_tctx_tree_t *tctxs, prof_tctx_t *tctx, void *arg)
ret = NULL;
label_return:
- return (ret);
+ return ret;
}
static void
-prof_dump_gctx_prep(prof_gctx_t *gctx, prof_gctx_tree_t *gctxs)
-{
-
+prof_dump_gctx_prep(tsdn_t *tsdn, prof_gctx_t *gctx, prof_gctx_tree_t *gctxs) {
cassert(config_prof);
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(tsdn, gctx->lock);
/*
* Increment nlimbo so that gctx won't go away before dump.
@@ -1165,26 +1191,32 @@ prof_dump_gctx_prep(prof_gctx_t *gctx, prof_gctx_tree_t *gctxs)
memset(&gctx->cnt_summed, 0, sizeof(prof_cnt_t));
- malloc_mutex_unlock(gctx->lock);
+ malloc_mutex_unlock(tsdn, gctx->lock);
}
-static prof_gctx_t *
-prof_gctx_merge_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *arg)
-{
- size_t *leak_ngctx = (size_t *)arg;
+struct prof_gctx_merge_iter_arg_s {
+ tsdn_t *tsdn;
+ size_t leak_ngctx;
+};
- malloc_mutex_lock(gctx->lock);
- tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_merge_iter, NULL);
- if (gctx->cnt_summed.curobjs != 0)
- (*leak_ngctx)++;
- malloc_mutex_unlock(gctx->lock);
+static prof_gctx_t *
+prof_gctx_merge_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) {
+ struct prof_gctx_merge_iter_arg_s *arg =
+ (struct prof_gctx_merge_iter_arg_s *)opaque;
+
+ malloc_mutex_lock(arg->tsdn, gctx->lock);
+ tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_merge_iter,
+ (void *)arg->tsdn);
+ if (gctx->cnt_summed.curobjs != 0) {
+ arg->leak_ngctx++;
+ }
+ malloc_mutex_unlock(arg->tsdn, gctx->lock);
- return (NULL);
+ return NULL;
}
static void
-prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs)
-{
+prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs) {
prof_tdata_t *tdata = prof_tdata_get(tsd, false);
prof_gctx_t *gctx;
@@ -1196,7 +1228,7 @@ prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs)
*/
while ((gctx = gctx_tree_first(gctxs)) != NULL) {
gctx_tree_remove(gctxs, gctx);
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), gctx->lock);
{
prof_tctx_t *next;
@@ -1204,34 +1236,43 @@ prof_gctx_finish(tsd_t *tsd, prof_gctx_tree_t *gctxs)
do {
prof_tctx_t *to_destroy =
tctx_tree_iter(&gctx->tctxs, next,
- prof_tctx_finish_iter, NULL);
+ prof_tctx_finish_iter,
+ (void *)tsd_tsdn(tsd));
if (to_destroy != NULL) {
next = tctx_tree_next(&gctx->tctxs,
to_destroy);
tctx_tree_remove(&gctx->tctxs,
to_destroy);
- idalloctm(tsd, to_destroy,
- tcache_get(tsd, false), true);
- } else
+ idalloctm(tsd_tsdn(tsd), to_destroy,
+ NULL, NULL, true, true);
+ } else {
next = NULL;
+ }
} while (next != NULL);
}
gctx->nlimbo--;
if (prof_gctx_should_destroy(gctx)) {
gctx->nlimbo++;
- malloc_mutex_unlock(gctx->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
prof_gctx_try_destroy(tsd, tdata, gctx, tdata);
- } else
- malloc_mutex_unlock(gctx->lock);
+ } else {
+ malloc_mutex_unlock(tsd_tsdn(tsd), gctx->lock);
+ }
}
}
+struct prof_tdata_merge_iter_arg_s {
+ tsdn_t *tsdn;
+ prof_cnt_t cnt_all;
+};
+
static prof_tdata_t *
-prof_tdata_merge_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
-{
- prof_cnt_t *cnt_all = (prof_cnt_t *)arg;
+prof_tdata_merge_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
+ void *opaque) {
+ struct prof_tdata_merge_iter_arg_s *arg =
+ (struct prof_tdata_merge_iter_arg_s *)opaque;
- malloc_mutex_lock(tdata->lock);
+ malloc_mutex_lock(arg->tsdn, tdata->lock);
if (!tdata->expired) {
size_t tabind;
union {
@@ -1242,29 +1283,32 @@ prof_tdata_merge_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
tdata->dumping = true;
memset(&tdata->cnt_summed, 0, sizeof(prof_cnt_t));
for (tabind = 0; !ckh_iter(&tdata->bt2tctx, &tabind, NULL,
- &tctx.v);)
- prof_tctx_merge_tdata(tctx.p, tdata);
+ &tctx.v);) {
+ prof_tctx_merge_tdata(arg->tsdn, tctx.p, tdata);
+ }
- cnt_all->curobjs += tdata->cnt_summed.curobjs;
- cnt_all->curbytes += tdata->cnt_summed.curbytes;
+ arg->cnt_all.curobjs += tdata->cnt_summed.curobjs;
+ arg->cnt_all.curbytes += tdata->cnt_summed.curbytes;
if (opt_prof_accum) {
- cnt_all->accumobjs += tdata->cnt_summed.accumobjs;
- cnt_all->accumbytes += tdata->cnt_summed.accumbytes;
+ arg->cnt_all.accumobjs += tdata->cnt_summed.accumobjs;
+ arg->cnt_all.accumbytes += tdata->cnt_summed.accumbytes;
}
- } else
+ } else {
tdata->dumping = false;
- malloc_mutex_unlock(tdata->lock);
+ }
+ malloc_mutex_unlock(arg->tsdn, tdata->lock);
- return (NULL);
+ return NULL;
}
static prof_tdata_t *
-prof_tdata_dump_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
-{
+prof_tdata_dump_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
+ void *arg) {
bool propagate_err = *(bool *)arg;
- if (!tdata->dumping)
- return (NULL);
+ if (!tdata->dumping) {
+ return NULL;
+ }
if (prof_dump_printf(propagate_err,
" t%"FMTu64": %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]%s%s\n",
@@ -1272,48 +1316,42 @@ prof_tdata_dump_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
tdata->cnt_summed.curbytes, tdata->cnt_summed.accumobjs,
tdata->cnt_summed.accumbytes,
(tdata->thread_name != NULL) ? " " : "",
- (tdata->thread_name != NULL) ? tdata->thread_name : ""))
- return (tdata);
- return (NULL);
+ (tdata->thread_name != NULL) ? tdata->thread_name : "")) {
+ return tdata;
+ }
+ return NULL;
}
-#ifdef JEMALLOC_JET
-#undef prof_dump_header
-#define prof_dump_header JEMALLOC_N(prof_dump_header_impl)
-#endif
static bool
-prof_dump_header(bool propagate_err, const prof_cnt_t *cnt_all)
-{
+prof_dump_header_impl(tsdn_t *tsdn, bool propagate_err,
+ const prof_cnt_t *cnt_all) {
bool ret;
if (prof_dump_printf(propagate_err,
"heap_v2/%"FMTu64"\n"
" t*: %"FMTu64": %"FMTu64" [%"FMTu64": %"FMTu64"]\n",
((uint64_t)1U << lg_prof_sample), cnt_all->curobjs,
- cnt_all->curbytes, cnt_all->accumobjs, cnt_all->accumbytes))
- return (true);
+ cnt_all->curbytes, cnt_all->accumobjs, cnt_all->accumbytes)) {
+ return true;
+ }
- malloc_mutex_lock(&tdatas_mtx);
+ malloc_mutex_lock(tsdn, &tdatas_mtx);
ret = (tdata_tree_iter(&tdatas, NULL, prof_tdata_dump_iter,
(void *)&propagate_err) != NULL);
- malloc_mutex_unlock(&tdatas_mtx);
- return (ret);
+ malloc_mutex_unlock(tsdn, &tdatas_mtx);
+ return ret;
}
-#ifdef JEMALLOC_JET
-#undef prof_dump_header
-#define prof_dump_header JEMALLOC_N(prof_dump_header)
-prof_dump_header_t *prof_dump_header = JEMALLOC_N(prof_dump_header_impl);
-#endif
+prof_dump_header_t *JET_MUTABLE prof_dump_header = prof_dump_header_impl;
-/* gctx->lock is held. */
static bool
-prof_dump_gctx(bool propagate_err, prof_gctx_t *gctx, const prof_bt_t *bt,
- prof_gctx_tree_t *gctxs)
-{
+prof_dump_gctx(tsdn_t *tsdn, bool propagate_err, prof_gctx_t *gctx,
+ const prof_bt_t *bt, prof_gctx_tree_t *gctxs) {
bool ret;
unsigned i;
+ struct prof_tctx_dump_iter_arg_s prof_tctx_dump_iter_arg;
cassert(config_prof);
+ malloc_mutex_assert_owner(tsdn, gctx->lock);
/* Avoid dumping such gctx's that have no useful data. */
if ((!opt_prof_accum && gctx->cnt_summed.curobjs == 0) ||
@@ -1347,21 +1385,23 @@ prof_dump_gctx(bool propagate_err, prof_gctx_t *gctx, const prof_bt_t *bt,
goto label_return;
}
+ prof_tctx_dump_iter_arg.tsdn = tsdn;
+ prof_tctx_dump_iter_arg.propagate_err = propagate_err;
if (tctx_tree_iter(&gctx->tctxs, NULL, prof_tctx_dump_iter,
- (void *)&propagate_err) != NULL) {
+ (void *)&prof_tctx_dump_iter_arg) != NULL) {
ret = true;
goto label_return;
}
ret = false;
label_return:
- return (ret);
+ return ret;
}
+#ifndef _WIN32
JEMALLOC_FORMAT_PRINTF(1, 2)
static int
-prof_open_maps(const char *format, ...)
-{
+prof_open_maps(const char *format, ...) {
int mfd;
va_list ap;
char filename[PATH_MAX + 1];
@@ -1369,27 +1409,47 @@ prof_open_maps(const char *format, ...)
va_start(ap, format);
malloc_vsnprintf(filename, sizeof(filename), format, ap);
va_end(ap);
+
+#if defined(O_CLOEXEC)
+ mfd = open(filename, O_RDONLY | O_CLOEXEC);
+#else
mfd = open(filename, O_RDONLY);
+ if (mfd != -1) {
+ fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
+ }
+#endif
+
+ return mfd;
+}
+#endif
- return (mfd);
+static int
+prof_getpid(void) {
+#ifdef _WIN32
+ return GetCurrentProcessId();
+#else
+ return getpid();
+#endif
}
static bool
-prof_dump_maps(bool propagate_err)
-{
+prof_dump_maps(bool propagate_err) {
bool ret;
int mfd;
cassert(config_prof);
#ifdef __FreeBSD__
mfd = prof_open_maps("/proc/curproc/map");
+#elif defined(_WIN32)
+ mfd = -1; // Not implemented
#else
{
- int pid = getpid();
+ int pid = prof_getpid();
mfd = prof_open_maps("/proc/%d/task/%d/maps", pid, pid);
- if (mfd == -1)
+ if (mfd == -1) {
mfd = prof_open_maps("/proc/%d/maps", pid);
+ }
}
#endif
if (mfd != -1) {
@@ -1411,8 +1471,9 @@ prof_dump_maps(bool propagate_err)
goto label_return;
}
}
- nread = read(mfd, &prof_dump_buf[prof_dump_buf_end],
- PROF_DUMP_BUFSIZE - prof_dump_buf_end);
+ nread = malloc_read_fd(mfd,
+ &prof_dump_buf[prof_dump_buf_end], PROF_DUMP_BUFSIZE
+ - prof_dump_buf_end);
} while (nread > 0);
} else {
ret = true;
@@ -1421,152 +1482,263 @@ prof_dump_maps(bool propagate_err)
ret = false;
label_return:
- if (mfd != -1)
+ if (mfd != -1) {
close(mfd);
- return (ret);
+ }
+ return ret;
}
+/*
+ * See prof_sample_threshold_update() comment for why the body of this function
+ * is conditionally compiled.
+ */
static void
prof_leakcheck(const prof_cnt_t *cnt_all, size_t leak_ngctx,
- const char *filename)
-{
-
+ const char *filename) {
+#ifdef JEMALLOC_PROF
+ /*
+ * Scaling is equivalent AdjustSamples() in jeprof, but the result may
+ * differ slightly from what jeprof reports, because here we scale the
+ * summary values, whereas jeprof scales each context individually and
+ * reports the sums of the scaled values.
+ */
if (cnt_all->curbytes != 0) {
- malloc_printf("<jemalloc>: Leak summary: %"FMTu64" byte%s, %"
- FMTu64" object%s, %zu context%s\n",
- cnt_all->curbytes, (cnt_all->curbytes != 1) ? "s" : "",
- cnt_all->curobjs, (cnt_all->curobjs != 1) ? "s" : "",
- leak_ngctx, (leak_ngctx != 1) ? "s" : "");
+ double sample_period = (double)((uint64_t)1 << lg_prof_sample);
+ double ratio = (((double)cnt_all->curbytes) /
+ (double)cnt_all->curobjs) / sample_period;
+ double scale_factor = 1.0 / (1.0 - exp(-ratio));
+ uint64_t curbytes = (uint64_t)round(((double)cnt_all->curbytes)
+ * scale_factor);
+ uint64_t curobjs = (uint64_t)round(((double)cnt_all->curobjs) *
+ scale_factor);
+
+ malloc_printf("<jemalloc>: Leak approximation summary: ~%"FMTu64
+ " byte%s, ~%"FMTu64" object%s, >= %zu context%s\n",
+ curbytes, (curbytes != 1) ? "s" : "", curobjs, (curobjs !=
+ 1) ? "s" : "", leak_ngctx, (leak_ngctx != 1) ? "s" : "");
malloc_printf(
"<jemalloc>: Run jeprof on \"%s\" for leak detail\n",
filename);
}
+#endif
}
+struct prof_gctx_dump_iter_arg_s {
+ tsdn_t *tsdn;
+ bool propagate_err;
+};
+
static prof_gctx_t *
-prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *arg)
-{
+prof_gctx_dump_iter(prof_gctx_tree_t *gctxs, prof_gctx_t *gctx, void *opaque) {
prof_gctx_t *ret;
- bool propagate_err = *(bool *)arg;
+ struct prof_gctx_dump_iter_arg_s *arg =
+ (struct prof_gctx_dump_iter_arg_s *)opaque;
- malloc_mutex_lock(gctx->lock);
+ malloc_mutex_lock(arg->tsdn, gctx->lock);
- if (prof_dump_gctx(propagate_err, gctx, &gctx->bt, gctxs)) {
+ if (prof_dump_gctx(arg->tsdn, arg->propagate_err, gctx, &gctx->bt,
+ gctxs)) {
ret = gctx;
goto label_return;
}
ret = NULL;
label_return:
- malloc_mutex_unlock(gctx->lock);
- return (ret);
+ malloc_mutex_unlock(arg->tsdn, gctx->lock);
+ return ret;
}
-static bool
-prof_dump(tsd_t *tsd, bool propagate_err, const char *filename, bool leakcheck)
-{
- prof_tdata_t *tdata;
- prof_cnt_t cnt_all;
+static void
+prof_dump_prep(tsd_t *tsd, prof_tdata_t *tdata,
+ struct prof_tdata_merge_iter_arg_s *prof_tdata_merge_iter_arg,
+ struct prof_gctx_merge_iter_arg_s *prof_gctx_merge_iter_arg,
+ prof_gctx_tree_t *gctxs) {
size_t tabind;
union {
prof_gctx_t *p;
void *v;
} gctx;
- size_t leak_ngctx;
- prof_gctx_tree_t gctxs;
-
- cassert(config_prof);
- tdata = prof_tdata_get(tsd, true);
- if (tdata == NULL)
- return (true);
-
- malloc_mutex_lock(&prof_dump_mtx);
prof_enter(tsd, tdata);
/*
* Put gctx's in limbo and clear their counters in preparation for
* summing.
*/
- gctx_tree_new(&gctxs);
- for (tabind = 0; !ckh_iter(&bt2gctx, &tabind, NULL, &gctx.v);)
- prof_dump_gctx_prep(gctx.p, &gctxs);
+ gctx_tree_new(gctxs);
+ for (tabind = 0; !ckh_iter(&bt2gctx, &tabind, NULL, &gctx.v);) {
+ prof_dump_gctx_prep(tsd_tsdn(tsd), gctx.p, gctxs);
+ }
/*
* Iterate over tdatas, and for the non-expired ones snapshot their tctx
* stats and merge them into the associated gctx's.
*/
- memset(&cnt_all, 0, sizeof(prof_cnt_t));
- malloc_mutex_lock(&tdatas_mtx);
- tdata_tree_iter(&tdatas, NULL, prof_tdata_merge_iter, (void *)&cnt_all);
- malloc_mutex_unlock(&tdatas_mtx);
+ prof_tdata_merge_iter_arg->tsdn = tsd_tsdn(tsd);
+ memset(&prof_tdata_merge_iter_arg->cnt_all, 0, sizeof(prof_cnt_t));
+ malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx);
+ tdata_tree_iter(&tdatas, NULL, prof_tdata_merge_iter,
+ (void *)prof_tdata_merge_iter_arg);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx);
/* Merge tctx stats into gctx's. */
- leak_ngctx = 0;
- gctx_tree_iter(&gctxs, NULL, prof_gctx_merge_iter, (void *)&leak_ngctx);
+ prof_gctx_merge_iter_arg->tsdn = tsd_tsdn(tsd);
+ prof_gctx_merge_iter_arg->leak_ngctx = 0;
+ gctx_tree_iter(gctxs, NULL, prof_gctx_merge_iter,
+ (void *)prof_gctx_merge_iter_arg);
prof_leave(tsd, tdata);
+}
+static bool
+prof_dump_file(tsd_t *tsd, bool propagate_err, const char *filename,
+ bool leakcheck, prof_tdata_t *tdata,
+ struct prof_tdata_merge_iter_arg_s *prof_tdata_merge_iter_arg,
+ struct prof_gctx_merge_iter_arg_s *prof_gctx_merge_iter_arg,
+ struct prof_gctx_dump_iter_arg_s *prof_gctx_dump_iter_arg,
+ prof_gctx_tree_t *gctxs) {
/* Create dump file. */
- if ((prof_dump_fd = prof_dump_open(propagate_err, filename)) == -1)
- goto label_open_close_error;
+ if ((prof_dump_fd = prof_dump_open(propagate_err, filename)) == -1) {
+ return true;
+ }
/* Dump profile header. */
- if (prof_dump_header(propagate_err, &cnt_all))
+ if (prof_dump_header(tsd_tsdn(tsd), propagate_err,
+ &prof_tdata_merge_iter_arg->cnt_all)) {
goto label_write_error;
+ }
/* Dump per gctx profile stats. */
- if (gctx_tree_iter(&gctxs, NULL, prof_gctx_dump_iter,
- (void *)&propagate_err) != NULL)
+ prof_gctx_dump_iter_arg->tsdn = tsd_tsdn(tsd);
+ prof_gctx_dump_iter_arg->propagate_err = propagate_err;
+ if (gctx_tree_iter(gctxs, NULL, prof_gctx_dump_iter,
+ (void *)prof_gctx_dump_iter_arg) != NULL) {
goto label_write_error;
+ }
/* Dump /proc/<pid>/maps if possible. */
- if (prof_dump_maps(propagate_err))
+ if (prof_dump_maps(propagate_err)) {
goto label_write_error;
+ }
+
+ if (prof_dump_close(propagate_err)) {
+ return true;
+ }
+
+ return false;
+label_write_error:
+ prof_dump_close(propagate_err);
+ return true;
+}
- if (prof_dump_close(propagate_err))
- goto label_open_close_error;
+static bool
+prof_dump(tsd_t *tsd, bool propagate_err, const char *filename,
+ bool leakcheck) {
+ cassert(config_prof);
+ assert(tsd_reentrancy_level_get(tsd) == 0);
+ prof_tdata_t * tdata = prof_tdata_get(tsd, true);
+ if (tdata == NULL) {
+ return true;
+ }
+
+ pre_reentrancy(tsd, NULL);
+ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx);
+
+ prof_gctx_tree_t gctxs;
+ struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg;
+ struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg;
+ struct prof_gctx_dump_iter_arg_s prof_gctx_dump_iter_arg;
+ prof_dump_prep(tsd, tdata, &prof_tdata_merge_iter_arg,
+ &prof_gctx_merge_iter_arg, &gctxs);
+ bool err = prof_dump_file(tsd, propagate_err, filename, leakcheck, tdata,
+ &prof_tdata_merge_iter_arg, &prof_gctx_merge_iter_arg,
+ &prof_gctx_dump_iter_arg, &gctxs);
prof_gctx_finish(tsd, &gctxs);
- malloc_mutex_unlock(&prof_dump_mtx);
- if (leakcheck)
- prof_leakcheck(&cnt_all, leak_ngctx, filename);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
+ post_reentrancy(tsd);
- return (false);
-label_write_error:
- prof_dump_close(propagate_err);
-label_open_close_error:
+ if (err) {
+ return true;
+ }
+
+ if (leakcheck) {
+ prof_leakcheck(&prof_tdata_merge_iter_arg.cnt_all,
+ prof_gctx_merge_iter_arg.leak_ngctx, filename);
+ }
+ return false;
+}
+
+#ifdef JEMALLOC_JET
+void
+prof_cnt_all(uint64_t *curobjs, uint64_t *curbytes, uint64_t *accumobjs,
+ uint64_t *accumbytes) {
+ tsd_t *tsd;
+ prof_tdata_t *tdata;
+ struct prof_tdata_merge_iter_arg_s prof_tdata_merge_iter_arg;
+ struct prof_gctx_merge_iter_arg_s prof_gctx_merge_iter_arg;
+ prof_gctx_tree_t gctxs;
+
+ tsd = tsd_fetch();
+ tdata = prof_tdata_get(tsd, false);
+ if (tdata == NULL) {
+ if (curobjs != NULL) {
+ *curobjs = 0;
+ }
+ if (curbytes != NULL) {
+ *curbytes = 0;
+ }
+ if (accumobjs != NULL) {
+ *accumobjs = 0;
+ }
+ if (accumbytes != NULL) {
+ *accumbytes = 0;
+ }
+ return;
+ }
+
+ prof_dump_prep(tsd, tdata, &prof_tdata_merge_iter_arg,
+ &prof_gctx_merge_iter_arg, &gctxs);
prof_gctx_finish(tsd, &gctxs);
- malloc_mutex_unlock(&prof_dump_mtx);
- return (true);
+
+ if (curobjs != NULL) {
+ *curobjs = prof_tdata_merge_iter_arg.cnt_all.curobjs;
+ }
+ if (curbytes != NULL) {
+ *curbytes = prof_tdata_merge_iter_arg.cnt_all.curbytes;
+ }
+ if (accumobjs != NULL) {
+ *accumobjs = prof_tdata_merge_iter_arg.cnt_all.accumobjs;
+ }
+ if (accumbytes != NULL) {
+ *accumbytes = prof_tdata_merge_iter_arg.cnt_all.accumbytes;
+ }
}
+#endif
-#define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1)
-#define VSEQ_INVALID UINT64_C(0xffffffffffffffff)
+#define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1)
+#define VSEQ_INVALID UINT64_C(0xffffffffffffffff)
static void
-prof_dump_filename(char *filename, char v, uint64_t vseq)
-{
-
+prof_dump_filename(char *filename, char v, uint64_t vseq) {
cassert(config_prof);
if (vseq != VSEQ_INVALID) {
/* "<prefix>.<pid>.<seq>.v<vseq>.heap" */
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
"%s.%d.%"FMTu64".%c%"FMTu64".heap",
- opt_prof_prefix, (int)getpid(), prof_dump_seq, v, vseq);
+ opt_prof_prefix, prof_getpid(), prof_dump_seq, v, vseq);
} else {
/* "<prefix>.<pid>.<seq>.<v>.heap" */
malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
"%s.%d.%"FMTu64".%c.heap",
- opt_prof_prefix, (int)getpid(), prof_dump_seq, v);
+ opt_prof_prefix, prof_getpid(), prof_dump_seq, v);
}
prof_dump_seq++;
}
static void
-prof_fdump(void)
-{
+prof_fdump(void) {
tsd_t *tsd;
char filename[DUMP_FILENAME_BUFSIZE];
@@ -1574,30 +1746,53 @@ prof_fdump(void)
assert(opt_prof_final);
assert(opt_prof_prefix[0] != '\0');
- if (!prof_booted)
+ if (!prof_booted) {
return;
+ }
tsd = tsd_fetch();
+ assert(tsd_reentrancy_level_get(tsd) == 0);
- malloc_mutex_lock(&prof_dump_seq_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
prof_dump_filename(filename, 'f', VSEQ_INVALID);
- malloc_mutex_unlock(&prof_dump_seq_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
prof_dump(tsd, false, filename, opt_prof_leak);
}
+bool
+prof_accum_init(tsdn_t *tsdn, prof_accum_t *prof_accum) {
+ cassert(config_prof);
+
+#ifndef JEMALLOC_ATOMIC_U64
+ if (malloc_mutex_init(&prof_accum->mtx, "prof_accum",
+ WITNESS_RANK_PROF_ACCUM, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+ prof_accum->accumbytes = 0;
+#else
+ atomic_store_u64(&prof_accum->accumbytes, 0, ATOMIC_RELAXED);
+#endif
+ return false;
+}
+
void
-prof_idump(void)
-{
+prof_idump(tsdn_t *tsdn) {
tsd_t *tsd;
prof_tdata_t *tdata;
cassert(config_prof);
- if (!prof_booted)
+ if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) {
return;
- tsd = tsd_fetch();
+ }
+ tsd = tsdn_tsd(tsdn);
+ if (tsd_reentrancy_level_get(tsd) > 0) {
+ return;
+ }
+
tdata = prof_tdata_get(tsd, false);
- if (tdata == NULL)
+ if (tdata == NULL) {
return;
+ }
if (tdata->enq) {
tdata->enq_idump = true;
return;
@@ -1605,53 +1800,56 @@ prof_idump(void)
if (opt_prof_prefix[0] != '\0') {
char filename[PATH_MAX + 1];
- malloc_mutex_lock(&prof_dump_seq_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
prof_dump_filename(filename, 'i', prof_dump_iseq);
prof_dump_iseq++;
- malloc_mutex_unlock(&prof_dump_seq_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
prof_dump(tsd, false, filename, false);
}
}
bool
-prof_mdump(const char *filename)
-{
- tsd_t *tsd;
- char filename_buf[DUMP_FILENAME_BUFSIZE];
-
+prof_mdump(tsd_t *tsd, const char *filename) {
cassert(config_prof);
+ assert(tsd_reentrancy_level_get(tsd) == 0);
- if (!opt_prof || !prof_booted)
- return (true);
- tsd = tsd_fetch();
-
+ if (!opt_prof || !prof_booted) {
+ return true;
+ }
+ char filename_buf[DUMP_FILENAME_BUFSIZE];
if (filename == NULL) {
/* No filename specified, so automatically generate one. */
- if (opt_prof_prefix[0] == '\0')
- return (true);
- malloc_mutex_lock(&prof_dump_seq_mtx);
+ if (opt_prof_prefix[0] == '\0') {
+ return true;
+ }
+ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
prof_dump_filename(filename_buf, 'm', prof_dump_mseq);
prof_dump_mseq++;
- malloc_mutex_unlock(&prof_dump_seq_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_seq_mtx);
filename = filename_buf;
}
- return (prof_dump(tsd, true, filename, false));
+ return prof_dump(tsd, true, filename, false);
}
void
-prof_gdump(void)
-{
+prof_gdump(tsdn_t *tsdn) {
tsd_t *tsd;
prof_tdata_t *tdata;
cassert(config_prof);
- if (!prof_booted)
+ if (!prof_booted || tsdn_null(tsdn) || !prof_active_get_unlocked()) {
return;
- tsd = tsd_fetch();
+ }
+ tsd = tsdn_tsd(tsdn);
+ if (tsd_reentrancy_level_get(tsd) > 0) {
+ return;
+ }
+
tdata = prof_tdata_get(tsd, false);
- if (tdata == NULL)
+ if (tdata == NULL) {
return;
+ }
if (tdata->enq) {
tdata->enq_gdump = true;
return;
@@ -1659,17 +1857,16 @@ prof_gdump(void)
if (opt_prof_prefix[0] != '\0') {
char filename[DUMP_FILENAME_BUFSIZE];
- malloc_mutex_lock(&prof_dump_seq_mtx);
+ malloc_mutex_lock(tsdn, &prof_dump_seq_mtx);
prof_dump_filename(filename, 'u', prof_dump_useq);
prof_dump_useq++;
- malloc_mutex_unlock(&prof_dump_seq_mtx);
+ malloc_mutex_unlock(tsdn, &prof_dump_seq_mtx);
prof_dump(tsd, false, filename, false);
}
}
static void
-prof_bt_hash(const void *key, size_t r_hash[2])
-{
+prof_bt_hash(const void *key, size_t r_hash[2]) {
prof_bt_t *bt = (prof_bt_t *)key;
cassert(config_prof);
@@ -1678,46 +1875,44 @@ prof_bt_hash(const void *key, size_t r_hash[2])
}
static bool
-prof_bt_keycomp(const void *k1, const void *k2)
-{
+prof_bt_keycomp(const void *k1, const void *k2) {
const prof_bt_t *bt1 = (prof_bt_t *)k1;
const prof_bt_t *bt2 = (prof_bt_t *)k2;
cassert(config_prof);
- if (bt1->len != bt2->len)
- return (false);
+ if (bt1->len != bt2->len) {
+ return false;
+ }
return (memcmp(bt1->vec, bt2->vec, bt1->len * sizeof(void *)) == 0);
}
-JEMALLOC_INLINE_C uint64_t
-prof_thr_uid_alloc(void)
-{
+static uint64_t
+prof_thr_uid_alloc(tsdn_t *tsdn) {
uint64_t thr_uid;
- malloc_mutex_lock(&next_thr_uid_mtx);
+ malloc_mutex_lock(tsdn, &next_thr_uid_mtx);
thr_uid = next_thr_uid;
next_thr_uid++;
- malloc_mutex_unlock(&next_thr_uid_mtx);
+ malloc_mutex_unlock(tsdn, &next_thr_uid_mtx);
- return (thr_uid);
+ return thr_uid;
}
static prof_tdata_t *
prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim,
- char *thread_name, bool active)
-{
+ char *thread_name, bool active) {
prof_tdata_t *tdata;
- tcache_t *tcache;
cassert(config_prof);
/* Initialize an empty cache for this thread. */
- tcache = tcache_get(tsd, true);
- tdata = (prof_tdata_t *)iallocztm(tsd, sizeof(prof_tdata_t), false,
- tcache, true, NULL);
- if (tdata == NULL)
- return (NULL);
+ tdata = (prof_tdata_t *)iallocztm(tsd_tsdn(tsd), sizeof(prof_tdata_t),
+ sz_size2index(sizeof(prof_tdata_t)), false, NULL, true,
+ arena_get(TSDN_NULL, 0, true), true);
+ if (tdata == NULL) {
+ return NULL;
+ }
tdata->lock = prof_tdata_mutex_choose(thr_uid);
tdata->thr_uid = thr_uid;
@@ -1727,10 +1922,10 @@ prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim,
tdata->expired = false;
tdata->tctx_uid_next = 0;
- if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS,
- prof_bt_hash, prof_bt_keycomp)) {
- idalloctm(tsd, tdata, tcache, true);
- return (NULL);
+ if (ckh_new(tsd, &tdata->bt2tctx, PROF_CKH_MINITEMS, prof_bt_hash,
+ prof_bt_keycomp)) {
+ idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true);
+ return NULL;
}
tdata->prng_state = (uint64_t)(uintptr_t)tdata;
@@ -1743,328 +1938,326 @@ prof_tdata_init_impl(tsd_t *tsd, uint64_t thr_uid, uint64_t thr_discrim,
tdata->dumping = false;
tdata->active = active;
- malloc_mutex_lock(&tdatas_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx);
tdata_tree_insert(&tdatas, tdata);
- malloc_mutex_unlock(&tdatas_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx);
- return (tdata);
+ return tdata;
}
prof_tdata_t *
-prof_tdata_init(tsd_t *tsd)
-{
+prof_tdata_init(tsd_t *tsd) {
+ return prof_tdata_init_impl(tsd, prof_thr_uid_alloc(tsd_tsdn(tsd)), 0,
+ NULL, prof_thread_active_init_get(tsd_tsdn(tsd)));
+}
- return (prof_tdata_init_impl(tsd, prof_thr_uid_alloc(), 0, NULL,
- prof_thread_active_init_get()));
+static bool
+prof_tdata_should_destroy_unlocked(prof_tdata_t *tdata, bool even_if_attached) {
+ if (tdata->attached && !even_if_attached) {
+ return false;
+ }
+ if (ckh_count(&tdata->bt2tctx) != 0) {
+ return false;
+ }
+ return true;
}
-/* tdata->lock must be held. */
static bool
-prof_tdata_should_destroy(prof_tdata_t *tdata, bool even_if_attached)
-{
+prof_tdata_should_destroy(tsdn_t *tsdn, prof_tdata_t *tdata,
+ bool even_if_attached) {
+ malloc_mutex_assert_owner(tsdn, tdata->lock);
- if (tdata->attached && !even_if_attached)
- return (false);
- if (ckh_count(&tdata->bt2tctx) != 0)
- return (false);
- return (true);
+ return prof_tdata_should_destroy_unlocked(tdata, even_if_attached);
}
-/* tdatas_mtx must be held. */
static void
prof_tdata_destroy_locked(tsd_t *tsd, prof_tdata_t *tdata,
- bool even_if_attached)
-{
- tcache_t *tcache;
-
- assert(prof_tdata_should_destroy(tdata, even_if_attached));
- assert(tsd_prof_tdata_get(tsd) != tdata);
+ bool even_if_attached) {
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &tdatas_mtx);
tdata_tree_remove(&tdatas, tdata);
- tcache = tcache_get(tsd, false);
- if (tdata->thread_name != NULL)
- idalloctm(tsd, tdata->thread_name, tcache, true);
+ assert(prof_tdata_should_destroy_unlocked(tdata, even_if_attached));
+
+ if (tdata->thread_name != NULL) {
+ idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true,
+ true);
+ }
ckh_delete(tsd, &tdata->bt2tctx);
- idalloctm(tsd, tdata, tcache, true);
+ idalloctm(tsd_tsdn(tsd), tdata, NULL, NULL, true, true);
}
static void
-prof_tdata_destroy(tsd_t *tsd, prof_tdata_t *tdata, bool even_if_attached)
-{
-
- malloc_mutex_lock(&tdatas_mtx);
+prof_tdata_destroy(tsd_t *tsd, prof_tdata_t *tdata, bool even_if_attached) {
+ malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx);
prof_tdata_destroy_locked(tsd, tdata, even_if_attached);
- malloc_mutex_unlock(&tdatas_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx);
}
static void
-prof_tdata_detach(tsd_t *tsd, prof_tdata_t *tdata)
-{
+prof_tdata_detach(tsd_t *tsd, prof_tdata_t *tdata) {
bool destroy_tdata;
- malloc_mutex_lock(tdata->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), tdata->lock);
if (tdata->attached) {
- destroy_tdata = prof_tdata_should_destroy(tdata, true);
+ destroy_tdata = prof_tdata_should_destroy(tsd_tsdn(tsd), tdata,
+ true);
/*
* Only detach if !destroy_tdata, because detaching would allow
* another thread to win the race to destroy tdata.
*/
- if (!destroy_tdata)
+ if (!destroy_tdata) {
tdata->attached = false;
+ }
tsd_prof_tdata_set(tsd, NULL);
- } else
+ } else {
destroy_tdata = false;
- malloc_mutex_unlock(tdata->lock);
- if (destroy_tdata)
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), tdata->lock);
+ if (destroy_tdata) {
prof_tdata_destroy(tsd, tdata, true);
+ }
}
prof_tdata_t *
-prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata)
-{
+prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata) {
uint64_t thr_uid = tdata->thr_uid;
uint64_t thr_discrim = tdata->thr_discrim + 1;
char *thread_name = (tdata->thread_name != NULL) ?
- prof_thread_name_alloc(tsd, tdata->thread_name) : NULL;
+ prof_thread_name_alloc(tsd_tsdn(tsd), tdata->thread_name) : NULL;
bool active = tdata->active;
prof_tdata_detach(tsd, tdata);
- return (prof_tdata_init_impl(tsd, thr_uid, thr_discrim, thread_name,
- active));
+ return prof_tdata_init_impl(tsd, thr_uid, thr_discrim, thread_name,
+ active);
}
static bool
-prof_tdata_expire(prof_tdata_t *tdata)
-{
+prof_tdata_expire(tsdn_t *tsdn, prof_tdata_t *tdata) {
bool destroy_tdata;
- malloc_mutex_lock(tdata->lock);
+ malloc_mutex_lock(tsdn, tdata->lock);
if (!tdata->expired) {
tdata->expired = true;
destroy_tdata = tdata->attached ? false :
- prof_tdata_should_destroy(tdata, false);
- } else
+ prof_tdata_should_destroy(tsdn, tdata, false);
+ } else {
destroy_tdata = false;
- malloc_mutex_unlock(tdata->lock);
+ }
+ malloc_mutex_unlock(tsdn, tdata->lock);
- return (destroy_tdata);
+ return destroy_tdata;
}
static prof_tdata_t *
-prof_tdata_reset_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata, void *arg)
-{
+prof_tdata_reset_iter(prof_tdata_tree_t *tdatas, prof_tdata_t *tdata,
+ void *arg) {
+ tsdn_t *tsdn = (tsdn_t *)arg;
- return (prof_tdata_expire(tdata) ? tdata : NULL);
+ return (prof_tdata_expire(tsdn, tdata) ? tdata : NULL);
}
void
-prof_reset(tsd_t *tsd, size_t lg_sample)
-{
+prof_reset(tsd_t *tsd, size_t lg_sample) {
prof_tdata_t *next;
assert(lg_sample < (sizeof(uint64_t) << 3));
- malloc_mutex_lock(&prof_dump_mtx);
- malloc_mutex_lock(&tdatas_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx);
+ malloc_mutex_lock(tsd_tsdn(tsd), &tdatas_mtx);
lg_prof_sample = lg_sample;
next = NULL;
do {
prof_tdata_t *to_destroy = tdata_tree_iter(&tdatas, next,
- prof_tdata_reset_iter, NULL);
+ prof_tdata_reset_iter, (void *)tsd);
if (to_destroy != NULL) {
next = tdata_tree_next(&tdatas, to_destroy);
prof_tdata_destroy_locked(tsd, to_destroy, false);
- } else
+ } else {
next = NULL;
+ }
} while (next != NULL);
- malloc_mutex_unlock(&tdatas_mtx);
- malloc_mutex_unlock(&prof_dump_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tdatas_mtx);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
}
void
-prof_tdata_cleanup(tsd_t *tsd)
-{
+prof_tdata_cleanup(tsd_t *tsd) {
prof_tdata_t *tdata;
- if (!config_prof)
+ if (!config_prof) {
return;
+ }
tdata = tsd_prof_tdata_get(tsd);
- if (tdata != NULL)
+ if (tdata != NULL) {
prof_tdata_detach(tsd, tdata);
+ }
}
bool
-prof_active_get(void)
-{
+prof_active_get(tsdn_t *tsdn) {
bool prof_active_current;
- malloc_mutex_lock(&prof_active_mtx);
+ malloc_mutex_lock(tsdn, &prof_active_mtx);
prof_active_current = prof_active;
- malloc_mutex_unlock(&prof_active_mtx);
- return (prof_active_current);
+ malloc_mutex_unlock(tsdn, &prof_active_mtx);
+ return prof_active_current;
}
bool
-prof_active_set(bool active)
-{
+prof_active_set(tsdn_t *tsdn, bool active) {
bool prof_active_old;
- malloc_mutex_lock(&prof_active_mtx);
+ malloc_mutex_lock(tsdn, &prof_active_mtx);
prof_active_old = prof_active;
prof_active = active;
- malloc_mutex_unlock(&prof_active_mtx);
- return (prof_active_old);
+ malloc_mutex_unlock(tsdn, &prof_active_mtx);
+ return prof_active_old;
}
const char *
-prof_thread_name_get(void)
-{
- tsd_t *tsd;
+prof_thread_name_get(tsd_t *tsd) {
prof_tdata_t *tdata;
- tsd = tsd_fetch();
tdata = prof_tdata_get(tsd, true);
- if (tdata == NULL)
- return ("");
+ if (tdata == NULL) {
+ return "";
+ }
return (tdata->thread_name != NULL ? tdata->thread_name : "");
}
static char *
-prof_thread_name_alloc(tsd_t *tsd, const char *thread_name)
-{
+prof_thread_name_alloc(tsdn_t *tsdn, const char *thread_name) {
char *ret;
size_t size;
- if (thread_name == NULL)
- return (NULL);
+ if (thread_name == NULL) {
+ return NULL;
+ }
size = strlen(thread_name) + 1;
- if (size == 1)
- return ("");
+ if (size == 1) {
+ return "";
+ }
- ret = iallocztm(tsd, size, false, tcache_get(tsd, true), true, NULL);
- if (ret == NULL)
- return (NULL);
+ ret = iallocztm(tsdn, size, sz_size2index(size), false, NULL, true,
+ arena_get(TSDN_NULL, 0, true), true);
+ if (ret == NULL) {
+ return NULL;
+ }
memcpy(ret, thread_name, size);
- return (ret);
+ return ret;
}
int
-prof_thread_name_set(tsd_t *tsd, const char *thread_name)
-{
+prof_thread_name_set(tsd_t *tsd, const char *thread_name) {
prof_tdata_t *tdata;
unsigned i;
char *s;
tdata = prof_tdata_get(tsd, true);
- if (tdata == NULL)
- return (EAGAIN);
+ if (tdata == NULL) {
+ return EAGAIN;
+ }
/* Validate input. */
- if (thread_name == NULL)
- return (EFAULT);
+ if (thread_name == NULL) {
+ return EFAULT;
+ }
for (i = 0; thread_name[i] != '\0'; i++) {
char c = thread_name[i];
- if (!isgraph(c) && !isblank(c))
- return (EFAULT);
+ if (!isgraph(c) && !isblank(c)) {
+ return EFAULT;
+ }
}
- s = prof_thread_name_alloc(tsd, thread_name);
- if (s == NULL)
- return (EAGAIN);
+ s = prof_thread_name_alloc(tsd_tsdn(tsd), thread_name);
+ if (s == NULL) {
+ return EAGAIN;
+ }
if (tdata->thread_name != NULL) {
- idalloctm(tsd, tdata->thread_name, tcache_get(tsd, false),
+ idalloctm(tsd_tsdn(tsd), tdata->thread_name, NULL, NULL, true,
true);
tdata->thread_name = NULL;
}
- if (strlen(s) > 0)
+ if (strlen(s) > 0) {
tdata->thread_name = s;
- return (0);
+ }
+ return 0;
}
bool
-prof_thread_active_get(void)
-{
- tsd_t *tsd;
+prof_thread_active_get(tsd_t *tsd) {
prof_tdata_t *tdata;
- tsd = tsd_fetch();
tdata = prof_tdata_get(tsd, true);
- if (tdata == NULL)
- return (false);
- return (tdata->active);
+ if (tdata == NULL) {
+ return false;
+ }
+ return tdata->active;
}
bool
-prof_thread_active_set(bool active)
-{
- tsd_t *tsd;
+prof_thread_active_set(tsd_t *tsd, bool active) {
prof_tdata_t *tdata;
- tsd = tsd_fetch();
tdata = prof_tdata_get(tsd, true);
- if (tdata == NULL)
- return (true);
+ if (tdata == NULL) {
+ return true;
+ }
tdata->active = active;
- return (false);
+ return false;
}
bool
-prof_thread_active_init_get(void)
-{
+prof_thread_active_init_get(tsdn_t *tsdn) {
bool active_init;
- malloc_mutex_lock(&prof_thread_active_init_mtx);
+ malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx);
active_init = prof_thread_active_init;
- malloc_mutex_unlock(&prof_thread_active_init_mtx);
- return (active_init);
+ malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx);
+ return active_init;
}
bool
-prof_thread_active_init_set(bool active_init)
-{
+prof_thread_active_init_set(tsdn_t *tsdn, bool active_init) {
bool active_init_old;
- malloc_mutex_lock(&prof_thread_active_init_mtx);
+ malloc_mutex_lock(tsdn, &prof_thread_active_init_mtx);
active_init_old = prof_thread_active_init;
prof_thread_active_init = active_init;
- malloc_mutex_unlock(&prof_thread_active_init_mtx);
- return (active_init_old);
+ malloc_mutex_unlock(tsdn, &prof_thread_active_init_mtx);
+ return active_init_old;
}
bool
-prof_gdump_get(void)
-{
+prof_gdump_get(tsdn_t *tsdn) {
bool prof_gdump_current;
- malloc_mutex_lock(&prof_gdump_mtx);
+ malloc_mutex_lock(tsdn, &prof_gdump_mtx);
prof_gdump_current = prof_gdump_val;
- malloc_mutex_unlock(&prof_gdump_mtx);
- return (prof_gdump_current);
+ malloc_mutex_unlock(tsdn, &prof_gdump_mtx);
+ return prof_gdump_current;
}
bool
-prof_gdump_set(bool gdump)
-{
+prof_gdump_set(tsdn_t *tsdn, bool gdump) {
bool prof_gdump_old;
- malloc_mutex_lock(&prof_gdump_mtx);
+ malloc_mutex_lock(tsdn, &prof_gdump_mtx);
prof_gdump_old = prof_gdump_val;
prof_gdump_val = gdump;
- malloc_mutex_unlock(&prof_gdump_mtx);
- return (prof_gdump_old);
+ malloc_mutex_unlock(tsdn, &prof_gdump_mtx);
+ return prof_gdump_old;
}
void
-prof_boot0(void)
-{
-
+prof_boot0(void) {
cassert(config_prof);
memcpy(opt_prof_prefix, PROF_PREFIX_DEFAULT,
@@ -2072,9 +2265,7 @@ prof_boot0(void)
}
void
-prof_boot1(void)
-{
-
+prof_boot1(void) {
cassert(config_prof);
/*
@@ -2098,72 +2289,98 @@ prof_boot1(void)
}
bool
-prof_boot2(void)
-{
-
+prof_boot2(tsd_t *tsd) {
cassert(config_prof);
if (opt_prof) {
- tsd_t *tsd;
unsigned i;
lg_prof_sample = opt_lg_prof_sample;
prof_active = opt_prof_active;
- if (malloc_mutex_init(&prof_active_mtx))
- return (true);
+ if (malloc_mutex_init(&prof_active_mtx, "prof_active",
+ WITNESS_RANK_PROF_ACTIVE, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
prof_gdump_val = opt_prof_gdump;
- if (malloc_mutex_init(&prof_gdump_mtx))
- return (true);
+ if (malloc_mutex_init(&prof_gdump_mtx, "prof_gdump",
+ WITNESS_RANK_PROF_GDUMP, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
prof_thread_active_init = opt_prof_thread_active_init;
- if (malloc_mutex_init(&prof_thread_active_init_mtx))
- return (true);
+ if (malloc_mutex_init(&prof_thread_active_init_mtx,
+ "prof_thread_active_init",
+ WITNESS_RANK_PROF_THREAD_ACTIVE_INIT,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
- tsd = tsd_fetch();
if (ckh_new(tsd, &bt2gctx, PROF_CKH_MINITEMS, prof_bt_hash,
- prof_bt_keycomp))
- return (true);
- if (malloc_mutex_init(&bt2gctx_mtx))
- return (true);
+ prof_bt_keycomp)) {
+ return true;
+ }
+ if (malloc_mutex_init(&bt2gctx_mtx, "prof_bt2gctx",
+ WITNESS_RANK_PROF_BT2GCTX, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
tdata_tree_new(&tdatas);
- if (malloc_mutex_init(&tdatas_mtx))
- return (true);
+ if (malloc_mutex_init(&tdatas_mtx, "prof_tdatas",
+ WITNESS_RANK_PROF_TDATAS, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
next_thr_uid = 0;
- if (malloc_mutex_init(&next_thr_uid_mtx))
- return (true);
+ if (malloc_mutex_init(&next_thr_uid_mtx, "prof_next_thr_uid",
+ WITNESS_RANK_PROF_NEXT_THR_UID, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
- if (malloc_mutex_init(&prof_dump_seq_mtx))
- return (true);
- if (malloc_mutex_init(&prof_dump_mtx))
- return (true);
+ if (malloc_mutex_init(&prof_dump_seq_mtx, "prof_dump_seq",
+ WITNESS_RANK_PROF_DUMP_SEQ, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+ if (malloc_mutex_init(&prof_dump_mtx, "prof_dump",
+ WITNESS_RANK_PROF_DUMP, malloc_mutex_rank_exclusive)) {
+ return true;
+ }
if (opt_prof_final && opt_prof_prefix[0] != '\0' &&
atexit(prof_fdump) != 0) {
malloc_write("<jemalloc>: Error in atexit()\n");
- if (opt_abort)
+ if (opt_abort) {
abort();
+ }
}
- gctx_locks = (malloc_mutex_t *)base_alloc(PROF_NCTX_LOCKS *
- sizeof(malloc_mutex_t));
- if (gctx_locks == NULL)
- return (true);
+ gctx_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd),
+ b0get(), PROF_NCTX_LOCKS * sizeof(malloc_mutex_t),
+ CACHELINE);
+ if (gctx_locks == NULL) {
+ return true;
+ }
for (i = 0; i < PROF_NCTX_LOCKS; i++) {
- if (malloc_mutex_init(&gctx_locks[i]))
- return (true);
+ if (malloc_mutex_init(&gctx_locks[i], "prof_gctx",
+ WITNESS_RANK_PROF_GCTX,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
}
- tdata_locks = (malloc_mutex_t *)base_alloc(PROF_NTDATA_LOCKS *
- sizeof(malloc_mutex_t));
- if (tdata_locks == NULL)
- return (true);
+ tdata_locks = (malloc_mutex_t *)base_alloc(tsd_tsdn(tsd),
+ b0get(), PROF_NTDATA_LOCKS * sizeof(malloc_mutex_t),
+ CACHELINE);
+ if (tdata_locks == NULL) {
+ return true;
+ }
for (i = 0; i < PROF_NTDATA_LOCKS; i++) {
- if (malloc_mutex_init(&tdata_locks[i]))
- return (true);
+ if (malloc_mutex_init(&tdata_locks[i], "prof_tdata",
+ WITNESS_RANK_PROF_TDATA,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
}
}
@@ -2177,60 +2394,79 @@ prof_boot2(void)
prof_booted = true;
- return (false);
+ return false;
}
void
-prof_prefork(void)
-{
-
- if (opt_prof) {
+prof_prefork0(tsdn_t *tsdn) {
+ if (config_prof && opt_prof) {
unsigned i;
- malloc_mutex_prefork(&tdatas_mtx);
- malloc_mutex_prefork(&bt2gctx_mtx);
- malloc_mutex_prefork(&next_thr_uid_mtx);
- malloc_mutex_prefork(&prof_dump_seq_mtx);
- for (i = 0; i < PROF_NCTX_LOCKS; i++)
- malloc_mutex_prefork(&gctx_locks[i]);
- for (i = 0; i < PROF_NTDATA_LOCKS; i++)
- malloc_mutex_prefork(&tdata_locks[i]);
+ malloc_mutex_prefork(tsdn, &prof_dump_mtx);
+ malloc_mutex_prefork(tsdn, &bt2gctx_mtx);
+ malloc_mutex_prefork(tsdn, &tdatas_mtx);
+ for (i = 0; i < PROF_NTDATA_LOCKS; i++) {
+ malloc_mutex_prefork(tsdn, &tdata_locks[i]);
+ }
+ for (i = 0; i < PROF_NCTX_LOCKS; i++) {
+ malloc_mutex_prefork(tsdn, &gctx_locks[i]);
+ }
}
}
void
-prof_postfork_parent(void)
-{
+prof_prefork1(tsdn_t *tsdn) {
+ if (config_prof && opt_prof) {
+ malloc_mutex_prefork(tsdn, &prof_active_mtx);
+ malloc_mutex_prefork(tsdn, &prof_dump_seq_mtx);
+ malloc_mutex_prefork(tsdn, &prof_gdump_mtx);
+ malloc_mutex_prefork(tsdn, &next_thr_uid_mtx);
+ malloc_mutex_prefork(tsdn, &prof_thread_active_init_mtx);
+ }
+}
- if (opt_prof) {
+void
+prof_postfork_parent(tsdn_t *tsdn) {
+ if (config_prof && opt_prof) {
unsigned i;
- for (i = 0; i < PROF_NTDATA_LOCKS; i++)
- malloc_mutex_postfork_parent(&tdata_locks[i]);
- for (i = 0; i < PROF_NCTX_LOCKS; i++)
- malloc_mutex_postfork_parent(&gctx_locks[i]);
- malloc_mutex_postfork_parent(&prof_dump_seq_mtx);
- malloc_mutex_postfork_parent(&next_thr_uid_mtx);
- malloc_mutex_postfork_parent(&bt2gctx_mtx);
- malloc_mutex_postfork_parent(&tdatas_mtx);
+ malloc_mutex_postfork_parent(tsdn,
+ &prof_thread_active_init_mtx);
+ malloc_mutex_postfork_parent(tsdn, &next_thr_uid_mtx);
+ malloc_mutex_postfork_parent(tsdn, &prof_gdump_mtx);
+ malloc_mutex_postfork_parent(tsdn, &prof_dump_seq_mtx);
+ malloc_mutex_postfork_parent(tsdn, &prof_active_mtx);
+ for (i = 0; i < PROF_NCTX_LOCKS; i++) {
+ malloc_mutex_postfork_parent(tsdn, &gctx_locks[i]);
+ }
+ for (i = 0; i < PROF_NTDATA_LOCKS; i++) {
+ malloc_mutex_postfork_parent(tsdn, &tdata_locks[i]);
+ }
+ malloc_mutex_postfork_parent(tsdn, &tdatas_mtx);
+ malloc_mutex_postfork_parent(tsdn, &bt2gctx_mtx);
+ malloc_mutex_postfork_parent(tsdn, &prof_dump_mtx);
}
}
void
-prof_postfork_child(void)
-{
-
- if (opt_prof) {
+prof_postfork_child(tsdn_t *tsdn) {
+ if (config_prof && opt_prof) {
unsigned i;
- for (i = 0; i < PROF_NTDATA_LOCKS; i++)
- malloc_mutex_postfork_child(&tdata_locks[i]);
- for (i = 0; i < PROF_NCTX_LOCKS; i++)
- malloc_mutex_postfork_child(&gctx_locks[i]);
- malloc_mutex_postfork_child(&prof_dump_seq_mtx);
- malloc_mutex_postfork_child(&next_thr_uid_mtx);
- malloc_mutex_postfork_child(&bt2gctx_mtx);
- malloc_mutex_postfork_child(&tdatas_mtx);
+ malloc_mutex_postfork_child(tsdn, &prof_thread_active_init_mtx);
+ malloc_mutex_postfork_child(tsdn, &next_thr_uid_mtx);
+ malloc_mutex_postfork_child(tsdn, &prof_gdump_mtx);
+ malloc_mutex_postfork_child(tsdn, &prof_dump_seq_mtx);
+ malloc_mutex_postfork_child(tsdn, &prof_active_mtx);
+ for (i = 0; i < PROF_NCTX_LOCKS; i++) {
+ malloc_mutex_postfork_child(tsdn, &gctx_locks[i]);
+ }
+ for (i = 0; i < PROF_NTDATA_LOCKS; i++) {
+ malloc_mutex_postfork_child(tsdn, &tdata_locks[i]);
+ }
+ malloc_mutex_postfork_child(tsdn, &tdatas_mtx);
+ malloc_mutex_postfork_child(tsdn, &bt2gctx_mtx);
+ malloc_mutex_postfork_child(tsdn, &prof_dump_mtx);
}
}
diff --git a/deps/jemalloc/src/quarantine.c b/deps/jemalloc/src/quarantine.c
deleted file mode 100644
index 6c43dfcaa..000000000
--- a/deps/jemalloc/src/quarantine.c
+++ /dev/null
@@ -1,183 +0,0 @@
-#define JEMALLOC_QUARANTINE_C_
-#include "jemalloc/internal/jemalloc_internal.h"
-
-/*
- * Quarantine pointers close to NULL are used to encode state information that
- * is used for cleaning up during thread shutdown.
- */
-#define QUARANTINE_STATE_REINCARNATED ((quarantine_t *)(uintptr_t)1)
-#define QUARANTINE_STATE_PURGATORY ((quarantine_t *)(uintptr_t)2)
-#define QUARANTINE_STATE_MAX QUARANTINE_STATE_PURGATORY
-
-/******************************************************************************/
-/* Function prototypes for non-inline static functions. */
-
-static quarantine_t *quarantine_grow(tsd_t *tsd, quarantine_t *quarantine);
-static void quarantine_drain_one(tsd_t *tsd, quarantine_t *quarantine);
-static void quarantine_drain(tsd_t *tsd, quarantine_t *quarantine,
- size_t upper_bound);
-
-/******************************************************************************/
-
-static quarantine_t *
-quarantine_init(tsd_t *tsd, size_t lg_maxobjs)
-{
- quarantine_t *quarantine;
-
- assert(tsd_nominal(tsd));
-
- quarantine = (quarantine_t *)iallocztm(tsd, offsetof(quarantine_t, objs)
- + ((ZU(1) << lg_maxobjs) * sizeof(quarantine_obj_t)), false,
- tcache_get(tsd, true), true, NULL);
- if (quarantine == NULL)
- return (NULL);
- quarantine->curbytes = 0;
- quarantine->curobjs = 0;
- quarantine->first = 0;
- quarantine->lg_maxobjs = lg_maxobjs;
-
- return (quarantine);
-}
-
-void
-quarantine_alloc_hook_work(tsd_t *tsd)
-{
- quarantine_t *quarantine;
-
- if (!tsd_nominal(tsd))
- return;
-
- quarantine = quarantine_init(tsd, LG_MAXOBJS_INIT);
- /*
- * Check again whether quarantine has been initialized, because
- * quarantine_init() may have triggered recursive initialization.
- */
- if (tsd_quarantine_get(tsd) == NULL)
- tsd_quarantine_set(tsd, quarantine);
- else
- idalloctm(tsd, quarantine, tcache_get(tsd, false), true);
-}
-
-static quarantine_t *
-quarantine_grow(tsd_t *tsd, quarantine_t *quarantine)
-{
- quarantine_t *ret;
-
- ret = quarantine_init(tsd, quarantine->lg_maxobjs + 1);
- if (ret == NULL) {
- quarantine_drain_one(tsd, quarantine);
- return (quarantine);
- }
-
- ret->curbytes = quarantine->curbytes;
- ret->curobjs = quarantine->curobjs;
- if (quarantine->first + quarantine->curobjs <= (ZU(1) <<
- quarantine->lg_maxobjs)) {
- /* objs ring buffer data are contiguous. */
- memcpy(ret->objs, &quarantine->objs[quarantine->first],
- quarantine->curobjs * sizeof(quarantine_obj_t));
- } else {
- /* objs ring buffer data wrap around. */
- size_t ncopy_a = (ZU(1) << quarantine->lg_maxobjs) -
- quarantine->first;
- size_t ncopy_b = quarantine->curobjs - ncopy_a;
-
- memcpy(ret->objs, &quarantine->objs[quarantine->first], ncopy_a
- * sizeof(quarantine_obj_t));
- memcpy(&ret->objs[ncopy_a], quarantine->objs, ncopy_b *
- sizeof(quarantine_obj_t));
- }
- idalloctm(tsd, quarantine, tcache_get(tsd, false), true);
-
- tsd_quarantine_set(tsd, ret);
- return (ret);
-}
-
-static void
-quarantine_drain_one(tsd_t *tsd, quarantine_t *quarantine)
-{
- quarantine_obj_t *obj = &quarantine->objs[quarantine->first];
- assert(obj->usize == isalloc(obj->ptr, config_prof));
- idalloctm(tsd, obj->ptr, NULL, false);
- quarantine->curbytes -= obj->usize;
- quarantine->curobjs--;
- quarantine->first = (quarantine->first + 1) & ((ZU(1) <<
- quarantine->lg_maxobjs) - 1);
-}
-
-static void
-quarantine_drain(tsd_t *tsd, quarantine_t *quarantine, size_t upper_bound)
-{
-
- while (quarantine->curbytes > upper_bound && quarantine->curobjs > 0)
- quarantine_drain_one(tsd, quarantine);
-}
-
-void
-quarantine(tsd_t *tsd, void *ptr)
-{
- quarantine_t *quarantine;
- size_t usize = isalloc(ptr, config_prof);
-
- cassert(config_fill);
- assert(opt_quarantine);
-
- if ((quarantine = tsd_quarantine_get(tsd)) == NULL) {
- idalloctm(tsd, ptr, NULL, false);
- return;
- }
- /*
- * Drain one or more objects if the quarantine size limit would be
- * exceeded by appending ptr.
- */
- if (quarantine->curbytes + usize > opt_quarantine) {
- size_t upper_bound = (opt_quarantine >= usize) ? opt_quarantine
- - usize : 0;
- quarantine_drain(tsd, quarantine, upper_bound);
- }
- /* Grow the quarantine ring buffer if it's full. */
- if (quarantine->curobjs == (ZU(1) << quarantine->lg_maxobjs))
- quarantine = quarantine_grow(tsd, quarantine);
- /* quarantine_grow() must free a slot if it fails to grow. */
- assert(quarantine->curobjs < (ZU(1) << quarantine->lg_maxobjs));
- /* Append ptr if its size doesn't exceed the quarantine size. */
- if (quarantine->curbytes + usize <= opt_quarantine) {
- size_t offset = (quarantine->first + quarantine->curobjs) &
- ((ZU(1) << quarantine->lg_maxobjs) - 1);
- quarantine_obj_t *obj = &quarantine->objs[offset];
- obj->ptr = ptr;
- obj->usize = usize;
- quarantine->curbytes += usize;
- quarantine->curobjs++;
- if (config_fill && unlikely(opt_junk_free)) {
- /*
- * Only do redzone validation if Valgrind isn't in
- * operation.
- */
- if ((!config_valgrind || likely(!in_valgrind))
- && usize <= SMALL_MAXCLASS)
- arena_quarantine_junk_small(ptr, usize);
- else
- memset(ptr, 0x5a, usize);
- }
- } else {
- assert(quarantine->curbytes == 0);
- idalloctm(tsd, ptr, NULL, false);
- }
-}
-
-void
-quarantine_cleanup(tsd_t *tsd)
-{
- quarantine_t *quarantine;
-
- if (!config_fill)
- return;
-
- quarantine = tsd_quarantine_get(tsd);
- if (quarantine != NULL) {
- quarantine_drain(tsd, quarantine, 0);
- idalloctm(tsd, quarantine, tcache_get(tsd, false), true);
- tsd_quarantine_set(tsd, NULL);
- }
-}
diff --git a/deps/jemalloc/src/rtree.c b/deps/jemalloc/src/rtree.c
index af0d97e75..53702cf72 100644
--- a/deps/jemalloc/src/rtree.c
+++ b/deps/jemalloc/src/rtree.c
@@ -1,127 +1,320 @@
-#define JEMALLOC_RTREE_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_RTREE_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
-static unsigned
-hmin(unsigned ha, unsigned hb)
-{
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/mutex.h"
- return (ha < hb ? ha : hb);
+/*
+ * Only the most significant bits of keys passed to rtree_{read,write}() are
+ * used.
+ */
+bool
+rtree_new(rtree_t *rtree, bool zeroed) {
+#ifdef JEMALLOC_JET
+ if (!zeroed) {
+ memset(rtree, 0, sizeof(rtree_t)); /* Clear root. */
+ }
+#else
+ assert(zeroed);
+#endif
+
+ if (malloc_mutex_init(&rtree->init_lock, "rtree", WITNESS_RANK_RTREE,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
+
+ return false;
}
-/* Only the most significant bits of keys passed to rtree_[gs]et() are used. */
-bool
-rtree_new(rtree_t *rtree, unsigned bits, rtree_node_alloc_t *alloc,
- rtree_node_dalloc_t *dalloc)
-{
- unsigned bits_in_leaf, height, i;
-
- assert(bits > 0 && bits <= (sizeof(uintptr_t) << 3));
-
- bits_in_leaf = (bits % RTREE_BITS_PER_LEVEL) == 0 ? RTREE_BITS_PER_LEVEL
- : (bits % RTREE_BITS_PER_LEVEL);
- if (bits > bits_in_leaf) {
- height = 1 + (bits - bits_in_leaf) / RTREE_BITS_PER_LEVEL;
- if ((height-1) * RTREE_BITS_PER_LEVEL + bits_in_leaf != bits)
- height++;
- } else
- height = 1;
- assert((height-1) * RTREE_BITS_PER_LEVEL + bits_in_leaf == bits);
-
- rtree->alloc = alloc;
- rtree->dalloc = dalloc;
- rtree->height = height;
-
- /* Root level. */
- rtree->levels[0].subtree = NULL;
- rtree->levels[0].bits = (height > 1) ? RTREE_BITS_PER_LEVEL :
- bits_in_leaf;
- rtree->levels[0].cumbits = rtree->levels[0].bits;
- /* Interior levels. */
- for (i = 1; i < height-1; i++) {
- rtree->levels[i].subtree = NULL;
- rtree->levels[i].bits = RTREE_BITS_PER_LEVEL;
- rtree->levels[i].cumbits = rtree->levels[i-1].cumbits +
- RTREE_BITS_PER_LEVEL;
- }
- /* Leaf level. */
- if (height > 1) {
- rtree->levels[height-1].subtree = NULL;
- rtree->levels[height-1].bits = bits_in_leaf;
- rtree->levels[height-1].cumbits = bits;
- }
-
- /* Compute lookup table to be used by rtree_start_level(). */
- for (i = 0; i < RTREE_HEIGHT_MAX; i++) {
- rtree->start_level[i] = hmin(RTREE_HEIGHT_MAX - 1 - i, height -
- 1);
- }
-
- return (false);
+static rtree_node_elm_t *
+rtree_node_alloc_impl(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) {
+ return (rtree_node_elm_t *)base_alloc(tsdn, b0get(), nelms *
+ sizeof(rtree_node_elm_t), CACHELINE);
}
+rtree_node_alloc_t *JET_MUTABLE rtree_node_alloc = rtree_node_alloc_impl;
static void
-rtree_delete_subtree(rtree_t *rtree, rtree_node_elm_t *node, unsigned level)
-{
+rtree_node_dalloc_impl(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *node) {
+ /* Nodes are never deleted during normal operation. */
+ not_reached();
+}
+UNUSED rtree_node_dalloc_t *JET_MUTABLE rtree_node_dalloc =
+ rtree_node_dalloc_impl;
- if (level + 1 < rtree->height) {
- size_t nchildren, i;
+static rtree_leaf_elm_t *
+rtree_leaf_alloc_impl(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) {
+ return (rtree_leaf_elm_t *)base_alloc(tsdn, b0get(), nelms *
+ sizeof(rtree_leaf_elm_t), CACHELINE);
+}
+rtree_leaf_alloc_t *JET_MUTABLE rtree_leaf_alloc = rtree_leaf_alloc_impl;
- nchildren = ZU(1) << rtree->levels[level].bits;
- for (i = 0; i < nchildren; i++) {
- rtree_node_elm_t *child = node[i].child;
- if (child != NULL)
- rtree_delete_subtree(rtree, child, level + 1);
+static void
+rtree_leaf_dalloc_impl(tsdn_t *tsdn, rtree_t *rtree, rtree_leaf_elm_t *leaf) {
+ /* Leaves are never deleted during normal operation. */
+ not_reached();
+}
+UNUSED rtree_leaf_dalloc_t *JET_MUTABLE rtree_leaf_dalloc =
+ rtree_leaf_dalloc_impl;
+
+#ifdef JEMALLOC_JET
+# if RTREE_HEIGHT > 1
+static void
+rtree_delete_subtree(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *subtree,
+ unsigned level) {
+ size_t nchildren = ZU(1) << rtree_levels[level].bits;
+ if (level + 2 < RTREE_HEIGHT) {
+ for (size_t i = 0; i < nchildren; i++) {
+ rtree_node_elm_t *node =
+ (rtree_node_elm_t *)atomic_load_p(&subtree[i].child,
+ ATOMIC_RELAXED);
+ if (node != NULL) {
+ rtree_delete_subtree(tsdn, rtree, node, level +
+ 1);
+ }
}
+ } else {
+ for (size_t i = 0; i < nchildren; i++) {
+ rtree_leaf_elm_t *leaf =
+ (rtree_leaf_elm_t *)atomic_load_p(&subtree[i].child,
+ ATOMIC_RELAXED);
+ if (leaf != NULL) {
+ rtree_leaf_dalloc(tsdn, rtree, leaf);
+ }
+ }
+ }
+
+ if (subtree != rtree->root) {
+ rtree_node_dalloc(tsdn, rtree, subtree);
}
- rtree->dalloc(node);
}
+# endif
void
-rtree_delete(rtree_t *rtree)
-{
- unsigned i;
+rtree_delete(tsdn_t *tsdn, rtree_t *rtree) {
+# if RTREE_HEIGHT > 1
+ rtree_delete_subtree(tsdn, rtree, rtree->root, 0);
+# endif
+}
+#endif
+
+static rtree_node_elm_t *
+rtree_node_init(tsdn_t *tsdn, rtree_t *rtree, unsigned level,
+ atomic_p_t *elmp) {
+ malloc_mutex_lock(tsdn, &rtree->init_lock);
+ /*
+ * If *elmp is non-null, then it was initialized with the init lock
+ * held, so we can get by with 'relaxed' here.
+ */
+ rtree_node_elm_t *node = atomic_load_p(elmp, ATOMIC_RELAXED);
+ if (node == NULL) {
+ node = rtree_node_alloc(tsdn, rtree, ZU(1) <<
+ rtree_levels[level].bits);
+ if (node == NULL) {
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+ return NULL;
+ }
+ /*
+ * Even though we hold the lock, a later reader might not; we
+ * need release semantics.
+ */
+ atomic_store_p(elmp, node, ATOMIC_RELEASE);
+ }
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+
+ return node;
+}
- for (i = 0; i < rtree->height; i++) {
- rtree_node_elm_t *subtree = rtree->levels[i].subtree;
- if (subtree != NULL)
- rtree_delete_subtree(rtree, subtree, i);
+static rtree_leaf_elm_t *
+rtree_leaf_init(tsdn_t *tsdn, rtree_t *rtree, atomic_p_t *elmp) {
+ malloc_mutex_lock(tsdn, &rtree->init_lock);
+ /*
+ * If *elmp is non-null, then it was initialized with the init lock
+ * held, so we can get by with 'relaxed' here.
+ */
+ rtree_leaf_elm_t *leaf = atomic_load_p(elmp, ATOMIC_RELAXED);
+ if (leaf == NULL) {
+ leaf = rtree_leaf_alloc(tsdn, rtree, ZU(1) <<
+ rtree_levels[RTREE_HEIGHT-1].bits);
+ if (leaf == NULL) {
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+ return NULL;
+ }
+ /*
+ * Even though we hold the lock, a later reader might not; we
+ * need release semantics.
+ */
+ atomic_store_p(elmp, leaf, ATOMIC_RELEASE);
}
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+
+ return leaf;
+}
+
+static bool
+rtree_node_valid(rtree_node_elm_t *node) {
+ return ((uintptr_t)node != (uintptr_t)0);
+}
+
+static bool
+rtree_leaf_valid(rtree_leaf_elm_t *leaf) {
+ return ((uintptr_t)leaf != (uintptr_t)0);
}
static rtree_node_elm_t *
-rtree_node_init(rtree_t *rtree, unsigned level, rtree_node_elm_t **elmp)
-{
+rtree_child_node_tryread(rtree_node_elm_t *elm, bool dependent) {
rtree_node_elm_t *node;
- if (atomic_cas_p((void **)elmp, NULL, RTREE_NODE_INITIALIZING)) {
- /*
- * Another thread is already in the process of initializing.
- * Spin-wait until initialization is complete.
- */
- do {
- CPU_SPINWAIT;
- node = atomic_read_p((void **)elmp);
- } while (node == RTREE_NODE_INITIALIZING);
+ if (dependent) {
+ node = (rtree_node_elm_t *)atomic_load_p(&elm->child,
+ ATOMIC_RELAXED);
+ } else {
+ node = (rtree_node_elm_t *)atomic_load_p(&elm->child,
+ ATOMIC_ACQUIRE);
+ }
+
+ assert(!dependent || node != NULL);
+ return node;
+}
+
+static rtree_node_elm_t *
+rtree_child_node_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm,
+ unsigned level, bool dependent) {
+ rtree_node_elm_t *node;
+
+ node = rtree_child_node_tryread(elm, dependent);
+ if (!dependent && unlikely(!rtree_node_valid(node))) {
+ node = rtree_node_init(tsdn, rtree, level + 1, &elm->child);
+ }
+ assert(!dependent || node != NULL);
+ return node;
+}
+
+static rtree_leaf_elm_t *
+rtree_child_leaf_tryread(rtree_node_elm_t *elm, bool dependent) {
+ rtree_leaf_elm_t *leaf;
+
+ if (dependent) {
+ leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child,
+ ATOMIC_RELAXED);
} else {
- node = rtree->alloc(ZU(1) << rtree->levels[level].bits);
- if (node == NULL)
- return (NULL);
- atomic_write_p((void **)elmp, node);
+ leaf = (rtree_leaf_elm_t *)atomic_load_p(&elm->child,
+ ATOMIC_ACQUIRE);
}
- return (node);
+ assert(!dependent || leaf != NULL);
+ return leaf;
}
-rtree_node_elm_t *
-rtree_subtree_read_hard(rtree_t *rtree, unsigned level)
-{
+static rtree_leaf_elm_t *
+rtree_child_leaf_read(tsdn_t *tsdn, rtree_t *rtree, rtree_node_elm_t *elm,
+ unsigned level, bool dependent) {
+ rtree_leaf_elm_t *leaf;
- return (rtree_node_init(rtree, level, &rtree->levels[level].subtree));
+ leaf = rtree_child_leaf_tryread(elm, dependent);
+ if (!dependent && unlikely(!rtree_leaf_valid(leaf))) {
+ leaf = rtree_leaf_init(tsdn, rtree, &elm->child);
+ }
+ assert(!dependent || leaf != NULL);
+ return leaf;
}
-rtree_node_elm_t *
-rtree_child_read_hard(rtree_t *rtree, rtree_node_elm_t *elm, unsigned level)
-{
+rtree_leaf_elm_t *
+rtree_leaf_elm_lookup_hard(tsdn_t *tsdn, rtree_t *rtree, rtree_ctx_t *rtree_ctx,
+ uintptr_t key, bool dependent, bool init_missing) {
+ rtree_node_elm_t *node;
+ rtree_leaf_elm_t *leaf;
+#if RTREE_HEIGHT > 1
+ node = rtree->root;
+#else
+ leaf = rtree->root;
+#endif
+
+ if (config_debug) {
+ uintptr_t leafkey = rtree_leafkey(key);
+ for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) {
+ assert(rtree_ctx->cache[i].leafkey != leafkey);
+ }
+ for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) {
+ assert(rtree_ctx->l2_cache[i].leafkey != leafkey);
+ }
+ }
+
+#define RTREE_GET_CHILD(level) { \
+ assert(level < RTREE_HEIGHT-1); \
+ if (level != 0 && !dependent && \
+ unlikely(!rtree_node_valid(node))) { \
+ return NULL; \
+ } \
+ uintptr_t subkey = rtree_subkey(key, level); \
+ if (level + 2 < RTREE_HEIGHT) { \
+ node = init_missing ? \
+ rtree_child_node_read(tsdn, rtree, \
+ &node[subkey], level, dependent) : \
+ rtree_child_node_tryread(&node[subkey], \
+ dependent); \
+ } else { \
+ leaf = init_missing ? \
+ rtree_child_leaf_read(tsdn, rtree, \
+ &node[subkey], level, dependent) : \
+ rtree_child_leaf_tryread(&node[subkey], \
+ dependent); \
+ } \
+ }
+ /*
+ * Cache replacement upon hard lookup (i.e. L1 & L2 rtree cache miss):
+ * (1) evict last entry in L2 cache; (2) move the collision slot from L1
+ * cache down to L2; and 3) fill L1.
+ */
+#define RTREE_GET_LEAF(level) { \
+ assert(level == RTREE_HEIGHT-1); \
+ if (!dependent && unlikely(!rtree_leaf_valid(leaf))) { \
+ return NULL; \
+ } \
+ if (RTREE_CTX_NCACHE_L2 > 1) { \
+ memmove(&rtree_ctx->l2_cache[1], \
+ &rtree_ctx->l2_cache[0], \
+ sizeof(rtree_ctx_cache_elm_t) * \
+ (RTREE_CTX_NCACHE_L2 - 1)); \
+ } \
+ size_t slot = rtree_cache_direct_map(key); \
+ rtree_ctx->l2_cache[0].leafkey = \
+ rtree_ctx->cache[slot].leafkey; \
+ rtree_ctx->l2_cache[0].leaf = \
+ rtree_ctx->cache[slot].leaf; \
+ uintptr_t leafkey = rtree_leafkey(key); \
+ rtree_ctx->cache[slot].leafkey = leafkey; \
+ rtree_ctx->cache[slot].leaf = leaf; \
+ uintptr_t subkey = rtree_subkey(key, level); \
+ return &leaf[subkey]; \
+ }
+ if (RTREE_HEIGHT > 1) {
+ RTREE_GET_CHILD(0)
+ }
+ if (RTREE_HEIGHT > 2) {
+ RTREE_GET_CHILD(1)
+ }
+ if (RTREE_HEIGHT > 3) {
+ for (unsigned i = 2; i < RTREE_HEIGHT-1; i++) {
+ RTREE_GET_CHILD(i)
+ }
+ }
+ RTREE_GET_LEAF(RTREE_HEIGHT-1)
+#undef RTREE_GET_CHILD
+#undef RTREE_GET_LEAF
+ not_reached();
+}
- return (rtree_node_init(rtree, level, &elm->child));
+void
+rtree_ctx_data_init(rtree_ctx_t *ctx) {
+ for (unsigned i = 0; i < RTREE_CTX_NCACHE; i++) {
+ rtree_ctx_cache_elm_t *cache = &ctx->cache[i];
+ cache->leafkey = RTREE_LEAFKEY_INVALID;
+ cache->leaf = NULL;
+ }
+ for (unsigned i = 0; i < RTREE_CTX_NCACHE_L2; i++) {
+ rtree_ctx_cache_elm_t *cache = &ctx->l2_cache[i];
+ cache->leafkey = RTREE_LEAFKEY_INVALID;
+ cache->leaf = NULL;
+ }
}
diff --git a/deps/jemalloc/src/stats.c b/deps/jemalloc/src/stats.c
index 154c3e74c..08b9507cf 100644
--- a/deps/jemalloc/src/stats.c
+++ b/deps/jemalloc/src/stats.c
@@ -1,374 +1,1235 @@
-#define JEMALLOC_STATS_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_STATS_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
-#define CTL_GET(n, v, t) do { \
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/ctl.h"
+#include "jemalloc/internal/emitter.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/mutex_prof.h"
+
+const char *global_mutex_names[mutex_prof_num_global_mutexes] = {
+#define OP(mtx) #mtx,
+ MUTEX_PROF_GLOBAL_MUTEXES
+#undef OP
+};
+
+const char *arena_mutex_names[mutex_prof_num_arena_mutexes] = {
+#define OP(mtx) #mtx,
+ MUTEX_PROF_ARENA_MUTEXES
+#undef OP
+};
+
+#define CTL_GET(n, v, t) do { \
size_t sz = sizeof(t); \
- xmallctl(n, v, &sz, NULL, 0); \
+ xmallctl(n, (void *)v, &sz, NULL, 0); \
} while (0)
-#define CTL_M2_GET(n, i, v, t) do { \
- size_t mib[6]; \
+#define CTL_M2_GET(n, i, v, t) do { \
+ size_t mib[CTL_MAX_DEPTH]; \
size_t miblen = sizeof(mib) / sizeof(size_t); \
size_t sz = sizeof(t); \
xmallctlnametomib(n, mib, &miblen); \
mib[2] = (i); \
- xmallctlbymib(mib, miblen, v, &sz, NULL, 0); \
+ xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0); \
} while (0)
-#define CTL_M2_M4_GET(n, i, j, v, t) do { \
- size_t mib[6]; \
+#define CTL_M2_M4_GET(n, i, j, v, t) do { \
+ size_t mib[CTL_MAX_DEPTH]; \
size_t miblen = sizeof(mib) / sizeof(size_t); \
size_t sz = sizeof(t); \
xmallctlnametomib(n, mib, &miblen); \
mib[2] = (i); \
mib[4] = (j); \
- xmallctlbymib(mib, miblen, v, &sz, NULL, 0); \
+ xmallctlbymib(mib, miblen, (void *)v, &sz, NULL, 0); \
} while (0)
/******************************************************************************/
/* Data. */
-bool opt_stats_print = false;
-
-size_t stats_cactive = 0;
+bool opt_stats_print = false;
+char opt_stats_print_opts[stats_print_tot_num_options+1] = "";
/******************************************************************************/
-/* Function prototypes for non-inline static functions. */
-static void stats_arena_bins_print(void (*write_cb)(void *, const char *),
- void *cbopaque, unsigned i);
-static void stats_arena_lruns_print(void (*write_cb)(void *, const char *),
- void *cbopaque, unsigned i);
-static void stats_arena_hchunks_print(
- void (*write_cb)(void *, const char *), void *cbopaque, unsigned i);
-static void stats_arena_print(void (*write_cb)(void *, const char *),
- void *cbopaque, unsigned i, bool bins, bool large, bool huge);
+/* Calculate x.yyy and output a string (takes a fixed sized char array). */
+static bool
+get_rate_str(uint64_t dividend, uint64_t divisor, char str[6]) {
+ if (divisor == 0 || dividend > divisor) {
+ /* The rate is not supposed to be greater than 1. */
+ return true;
+ }
+ if (dividend > 0) {
+ assert(UINT64_MAX / dividend >= 1000);
+ }
+
+ unsigned n = (unsigned)((dividend * 1000) / divisor);
+ if (n < 10) {
+ malloc_snprintf(str, 6, "0.00%u", n);
+ } else if (n < 100) {
+ malloc_snprintf(str, 6, "0.0%u", n);
+ } else if (n < 1000) {
+ malloc_snprintf(str, 6, "0.%u", n);
+ } else {
+ malloc_snprintf(str, 6, "1");
+ }
-/******************************************************************************/
+ return false;
+}
+
+#define MUTEX_CTL_STR_MAX_LENGTH 128
+static void
+gen_mutex_ctl_str(char *str, size_t buf_len, const char *prefix,
+ const char *mutex, const char *counter) {
+ malloc_snprintf(str, buf_len, "stats.%s.%s.%s", prefix, mutex, counter);
+}
+
+static void
+mutex_stats_init_cols(emitter_row_t *row, const char *table_name,
+ emitter_col_t *name,
+ emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters],
+ emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) {
+ mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0;
+ mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0;
+
+ emitter_col_t *col;
+
+ if (name != NULL) {
+ emitter_col_init(name, row);
+ name->justify = emitter_justify_left;
+ name->width = 21;
+ name->type = emitter_type_title;
+ name->str_val = table_name;
+ }
+
+#define WIDTH_uint32_t 12
+#define WIDTH_uint64_t 16
+#define OP(counter, counter_type, human) \
+ col = &col_##counter_type[k_##counter_type]; \
+ ++k_##counter_type; \
+ emitter_col_init(col, row); \
+ col->justify = emitter_justify_right; \
+ col->width = WIDTH_##counter_type; \
+ col->type = emitter_type_title; \
+ col->str_val = human;
+ MUTEX_PROF_COUNTERS
+#undef OP
+#undef WIDTH_uint32_t
+#undef WIDTH_uint64_t
+}
+
+static void
+mutex_stats_read_global(const char *name, emitter_col_t *col_name,
+ emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters],
+ emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) {
+ char cmd[MUTEX_CTL_STR_MAX_LENGTH];
+
+ col_name->str_val = name;
+
+ emitter_col_t *dst;
+#define EMITTER_TYPE_uint32_t emitter_type_uint32
+#define EMITTER_TYPE_uint64_t emitter_type_uint64
+#define OP(counter, counter_type, human) \
+ dst = &col_##counter_type[mutex_counter_##counter]; \
+ dst->type = EMITTER_TYPE_##counter_type; \
+ gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH, \
+ "mutexes", name, #counter); \
+ CTL_GET(cmd, (counter_type *)&dst->bool_val, counter_type);
+ MUTEX_PROF_COUNTERS
+#undef OP
+#undef EMITTER_TYPE_uint32_t
+#undef EMITTER_TYPE_uint64_t
+}
static void
-stats_arena_bins_print(void (*write_cb)(void *, const char *), void *cbopaque,
- unsigned i)
-{
+mutex_stats_read_arena(unsigned arena_ind, mutex_prof_arena_ind_t mutex_ind,
+ const char *name, emitter_col_t *col_name,
+ emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters],
+ emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) {
+ char cmd[MUTEX_CTL_STR_MAX_LENGTH];
+
+ col_name->str_val = name;
+
+ emitter_col_t *dst;
+#define EMITTER_TYPE_uint32_t emitter_type_uint32
+#define EMITTER_TYPE_uint64_t emitter_type_uint64
+#define OP(counter, counter_type, human) \
+ dst = &col_##counter_type[mutex_counter_##counter]; \
+ dst->type = EMITTER_TYPE_##counter_type; \
+ gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH, \
+ "arenas.0.mutexes", arena_mutex_names[mutex_ind], #counter);\
+ CTL_M2_GET(cmd, arena_ind, \
+ (counter_type *)&dst->bool_val, counter_type);
+ MUTEX_PROF_COUNTERS
+#undef OP
+#undef EMITTER_TYPE_uint32_t
+#undef EMITTER_TYPE_uint64_t
+}
+
+static void
+mutex_stats_read_arena_bin(unsigned arena_ind, unsigned bin_ind,
+ emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters],
+ emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) {
+ char cmd[MUTEX_CTL_STR_MAX_LENGTH];
+ emitter_col_t *dst;
+
+#define EMITTER_TYPE_uint32_t emitter_type_uint32
+#define EMITTER_TYPE_uint64_t emitter_type_uint64
+#define OP(counter, counter_type, human) \
+ dst = &col_##counter_type[mutex_counter_##counter]; \
+ dst->type = EMITTER_TYPE_##counter_type; \
+ gen_mutex_ctl_str(cmd, MUTEX_CTL_STR_MAX_LENGTH, \
+ "arenas.0.bins.0","mutex", #counter); \
+ CTL_M2_M4_GET(cmd, arena_ind, bin_ind, \
+ (counter_type *)&dst->bool_val, counter_type);
+ MUTEX_PROF_COUNTERS
+#undef OP
+#undef EMITTER_TYPE_uint32_t
+#undef EMITTER_TYPE_uint64_t
+}
+
+/* "row" can be NULL to avoid emitting in table mode. */
+static void
+mutex_stats_emit(emitter_t *emitter, emitter_row_t *row,
+ emitter_col_t col_uint64_t[mutex_prof_num_uint64_t_counters],
+ emitter_col_t col_uint32_t[mutex_prof_num_uint32_t_counters]) {
+ if (row != NULL) {
+ emitter_table_row(emitter, row);
+ }
+
+ mutex_prof_uint64_t_counter_ind_t k_uint64_t = 0;
+ mutex_prof_uint32_t_counter_ind_t k_uint32_t = 0;
+
+ emitter_col_t *col;
+
+#define EMITTER_TYPE_uint32_t emitter_type_uint32
+#define EMITTER_TYPE_uint64_t emitter_type_uint64
+#define OP(counter, type, human) \
+ col = &col_##type[k_##type]; \
+ ++k_##type; \
+ emitter_json_kv(emitter, #counter, EMITTER_TYPE_##type, \
+ (const void *)&col->bool_val);
+ MUTEX_PROF_COUNTERS;
+#undef OP
+#undef EMITTER_TYPE_uint32_t
+#undef EMITTER_TYPE_uint64_t
+}
+
+static void
+stats_arena_bins_print(emitter_t *emitter, bool mutex, unsigned i) {
size_t page;
- bool config_tcache, in_gap;
+ bool in_gap, in_gap_prev;
unsigned nbins, j;
CTL_GET("arenas.page", &page, size_t);
- CTL_GET("config.tcache", &config_tcache, bool);
- if (config_tcache) {
- malloc_cprintf(write_cb, cbopaque,
- "bins: size ind allocated nmalloc"
- " ndalloc nrequests curregs curruns regs"
- " pgs util nfills nflushes newruns"
- " reruns\n");
- } else {
- malloc_cprintf(write_cb, cbopaque,
- "bins: size ind allocated nmalloc"
- " ndalloc nrequests curregs curruns regs"
- " pgs util newruns reruns\n");
- }
CTL_GET("arenas.nbins", &nbins, unsigned);
+
+ emitter_row_t header_row;
+ emitter_row_init(&header_row);
+
+ emitter_row_t row;
+ emitter_row_init(&row);
+#define COL(name, left_or_right, col_width, etype) \
+ emitter_col_t col_##name; \
+ emitter_col_init(&col_##name, &row); \
+ col_##name.justify = emitter_justify_##left_or_right; \
+ col_##name.width = col_width; \
+ col_##name.type = emitter_type_##etype; \
+ emitter_col_t header_col_##name; \
+ emitter_col_init(&header_col_##name, &header_row); \
+ header_col_##name.justify = emitter_justify_##left_or_right; \
+ header_col_##name.width = col_width; \
+ header_col_##name.type = emitter_type_title; \
+ header_col_##name.str_val = #name;
+
+ COL(size, right, 20, size)
+ COL(ind, right, 4, unsigned)
+ COL(allocated, right, 13, uint64)
+ COL(nmalloc, right, 13, uint64)
+ COL(ndalloc, right, 13, uint64)
+ COL(nrequests, right, 13, uint64)
+ COL(curregs, right, 13, size)
+ COL(curslabs, right, 13, size)
+ COL(regs, right, 5, unsigned)
+ COL(pgs, right, 4, size)
+ /* To buffer a right- and left-justified column. */
+ COL(justify_spacer, right, 1, title)
+ COL(util, right, 6, title)
+ COL(nfills, right, 13, uint64)
+ COL(nflushes, right, 13, uint64)
+ COL(nslabs, right, 13, uint64)
+ COL(nreslabs, right, 13, uint64)
+#undef COL
+
+ /* Don't want to actually print the name. */
+ header_col_justify_spacer.str_val = " ";
+ col_justify_spacer.str_val = " ";
+
+
+ emitter_col_t col_mutex64[mutex_prof_num_uint64_t_counters];
+ emitter_col_t col_mutex32[mutex_prof_num_uint32_t_counters];
+
+ emitter_col_t header_mutex64[mutex_prof_num_uint64_t_counters];
+ emitter_col_t header_mutex32[mutex_prof_num_uint32_t_counters];
+
+ if (mutex) {
+ mutex_stats_init_cols(&row, NULL, NULL, col_mutex64,
+ col_mutex32);
+ mutex_stats_init_cols(&header_row, NULL, NULL, header_mutex64,
+ header_mutex32);
+ }
+
+ /*
+ * We print a "bins:" header as part of the table row; we need to adjust
+ * the header size column to compensate.
+ */
+ header_col_size.width -=5;
+ emitter_table_printf(emitter, "bins:");
+ emitter_table_row(emitter, &header_row);
+ emitter_json_arr_begin(emitter, "bins");
+
for (j = 0, in_gap = false; j < nbins; j++) {
- uint64_t nruns;
+ uint64_t nslabs;
+ size_t reg_size, slab_size, curregs;
+ size_t curslabs;
+ uint32_t nregs;
+ uint64_t nmalloc, ndalloc, nrequests, nfills, nflushes;
+ uint64_t nreslabs;
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nruns", i, j, &nruns,
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nslabs", i, j, &nslabs,
uint64_t);
- if (nruns == 0)
- in_gap = true;
- else {
- size_t reg_size, run_size, curregs, availregs, milli;
- size_t curruns;
- uint32_t nregs;
- uint64_t nmalloc, ndalloc, nrequests, nfills, nflushes;
- uint64_t reruns;
- char util[6]; /* "x.yyy". */
-
- if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
- in_gap = false;
- }
- CTL_M2_GET("arenas.bin.0.size", j, &reg_size, size_t);
- CTL_M2_GET("arenas.bin.0.nregs", j, &nregs, uint32_t);
- CTL_M2_GET("arenas.bin.0.run_size", j, &run_size,
- size_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nmalloc", i, j,
- &nmalloc, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.ndalloc", i, j,
- &ndalloc, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.curregs", i, j,
- &curregs, size_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nrequests", i, j,
- &nrequests, uint64_t);
- if (config_tcache) {
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nfills", i,
- j, &nfills, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nflushes",
- i, j, &nflushes, uint64_t);
- }
- CTL_M2_M4_GET("stats.arenas.0.bins.0.nreruns", i, j,
- &reruns, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.bins.0.curruns", i, j,
- &curruns, size_t);
-
- availregs = nregs * curruns;
- milli = (availregs != 0) ? (1000 * curregs) / availregs
- : 1000;
- assert(milli <= 1000);
- if (milli < 10) {
- malloc_snprintf(util, sizeof(util),
- "0.00%zu", milli);
- } else if (milli < 100) {
- malloc_snprintf(util, sizeof(util), "0.0%zu",
- milli);
- } else if (milli < 1000) {
- malloc_snprintf(util, sizeof(util), "0.%zu",
- milli);
- } else
- malloc_snprintf(util, sizeof(util), "1");
+ in_gap_prev = in_gap;
+ in_gap = (nslabs == 0);
- if (config_tcache) {
- malloc_cprintf(write_cb, cbopaque,
- "%20zu %3u %12zu %12"FMTu64
- " %12"FMTu64" %12"FMTu64" %12zu"
- " %12zu %4u %3zu %-5s %12"FMTu64
- " %12"FMTu64" %12"FMTu64" %12"FMTu64"\n",
- reg_size, j, curregs * reg_size, nmalloc,
- ndalloc, nrequests, curregs, curruns, nregs,
- run_size / page, util, nfills, nflushes,
- nruns, reruns);
- } else {
- malloc_cprintf(write_cb, cbopaque,
- "%20zu %3u %12zu %12"FMTu64
- " %12"FMTu64" %12"FMTu64" %12zu"
- " %12zu %4u %3zu %-5s %12"FMTu64
- " %12"FMTu64"\n",
- reg_size, j, curregs * reg_size, nmalloc,
- ndalloc, nrequests, curregs, curruns, nregs,
- run_size / page, util, nruns, reruns);
- }
+ if (in_gap_prev && !in_gap) {
+ emitter_table_printf(emitter,
+ " ---\n");
}
- }
- if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
- }
-}
-static void
-stats_arena_lruns_print(void (*write_cb)(void *, const char *), void *cbopaque,
- unsigned i)
-{
- unsigned nbins, nlruns, j;
- bool in_gap;
-
- malloc_cprintf(write_cb, cbopaque,
- "large: size ind allocated nmalloc ndalloc"
- " nrequests curruns\n");
- CTL_GET("arenas.nbins", &nbins, unsigned);
- CTL_GET("arenas.nlruns", &nlruns, unsigned);
- for (j = 0, in_gap = false; j < nlruns; j++) {
- uint64_t nmalloc, ndalloc, nrequests;
- size_t run_size, curruns;
+ CTL_M2_GET("arenas.bin.0.size", j, &reg_size, size_t);
+ CTL_M2_GET("arenas.bin.0.nregs", j, &nregs, uint32_t);
+ CTL_M2_GET("arenas.bin.0.slab_size", j, &slab_size, size_t);
- CTL_M2_M4_GET("stats.arenas.0.lruns.0.nmalloc", i, j, &nmalloc,
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nmalloc", i, j, &nmalloc,
uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.lruns.0.ndalloc", i, j, &ndalloc,
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.ndalloc", i, j, &ndalloc,
uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.lruns.0.nrequests", i, j,
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.curregs", i, j, &curregs,
+ size_t);
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nrequests", i, j,
&nrequests, uint64_t);
- if (nrequests == 0)
- in_gap = true;
- else {
- CTL_M2_GET("arenas.lrun.0.size", j, &run_size, size_t);
- CTL_M2_M4_GET("stats.arenas.0.lruns.0.curruns", i, j,
- &curruns, size_t);
- if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
- in_gap = false;
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nfills", i, j, &nfills,
+ uint64_t);
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nflushes", i, j, &nflushes,
+ uint64_t);
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.nreslabs", i, j, &nreslabs,
+ uint64_t);
+ CTL_M2_M4_GET("stats.arenas.0.bins.0.curslabs", i, j, &curslabs,
+ size_t);
+
+ if (mutex) {
+ mutex_stats_read_arena_bin(i, j, col_mutex64,
+ col_mutex32);
+ }
+
+ emitter_json_arr_obj_begin(emitter);
+ emitter_json_kv(emitter, "nmalloc", emitter_type_uint64,
+ &nmalloc);
+ emitter_json_kv(emitter, "ndalloc", emitter_type_uint64,
+ &ndalloc);
+ emitter_json_kv(emitter, "curregs", emitter_type_size,
+ &curregs);
+ emitter_json_kv(emitter, "nrequests", emitter_type_uint64,
+ &nrequests);
+ emitter_json_kv(emitter, "nfills", emitter_type_uint64,
+ &nfills);
+ emitter_json_kv(emitter, "nflushes", emitter_type_uint64,
+ &nflushes);
+ emitter_json_kv(emitter, "nreslabs", emitter_type_uint64,
+ &nreslabs);
+ emitter_json_kv(emitter, "curslabs", emitter_type_size,
+ &curslabs);
+ if (mutex) {
+ emitter_json_dict_begin(emitter, "mutex");
+ mutex_stats_emit(emitter, NULL, col_mutex64,
+ col_mutex32);
+ emitter_json_dict_end(emitter);
+ }
+ emitter_json_arr_obj_end(emitter);
+
+ size_t availregs = nregs * curslabs;
+ char util[6];
+ if (get_rate_str((uint64_t)curregs, (uint64_t)availregs, util))
+ {
+ if (availregs == 0) {
+ malloc_snprintf(util, sizeof(util), "1");
+ } else if (curregs > availregs) {
+ /*
+ * Race detected: the counters were read in
+ * separate mallctl calls and concurrent
+ * operations happened in between. In this case
+ * no meaningful utilization can be computed.
+ */
+ malloc_snprintf(util, sizeof(util), " race");
+ } else {
+ not_reached();
}
- malloc_cprintf(write_cb, cbopaque,
- "%20zu %3u %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64" %12zu\n",
- run_size, nbins + j, curruns * run_size, nmalloc,
- ndalloc, nrequests, curruns);
}
+
+ col_size.size_val = reg_size;
+ col_ind.unsigned_val = j;
+ col_allocated.size_val = curregs * reg_size;
+ col_nmalloc.uint64_val = nmalloc;
+ col_ndalloc.uint64_val = ndalloc;
+ col_nrequests.uint64_val = nrequests;
+ col_curregs.size_val = curregs;
+ col_curslabs.size_val = curslabs;
+ col_regs.unsigned_val = nregs;
+ col_pgs.size_val = slab_size / page;
+ col_util.str_val = util;
+ col_nfills.uint64_val = nfills;
+ col_nflushes.uint64_val = nflushes;
+ col_nslabs.uint64_val = nslabs;
+ col_nreslabs.uint64_val = nreslabs;
+
+ /*
+ * Note that mutex columns were initialized above, if mutex ==
+ * true.
+ */
+
+ emitter_table_row(emitter, &row);
}
+ emitter_json_arr_end(emitter); /* Close "bins". */
+
if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
+ emitter_table_printf(emitter, " ---\n");
}
}
static void
-stats_arena_hchunks_print(void (*write_cb)(void *, const char *),
- void *cbopaque, unsigned i)
-{
- unsigned nbins, nlruns, nhchunks, j;
- bool in_gap;
-
- malloc_cprintf(write_cb, cbopaque,
- "huge: size ind allocated nmalloc ndalloc"
- " nrequests curhchunks\n");
+stats_arena_lextents_print(emitter_t *emitter, unsigned i) {
+ unsigned nbins, nlextents, j;
+ bool in_gap, in_gap_prev;
+
CTL_GET("arenas.nbins", &nbins, unsigned);
- CTL_GET("arenas.nlruns", &nlruns, unsigned);
- CTL_GET("arenas.nhchunks", &nhchunks, unsigned);
- for (j = 0, in_gap = false; j < nhchunks; j++) {
+ CTL_GET("arenas.nlextents", &nlextents, unsigned);
+
+ emitter_row_t header_row;
+ emitter_row_init(&header_row);
+ emitter_row_t row;
+ emitter_row_init(&row);
+
+#define COL(name, left_or_right, col_width, etype) \
+ emitter_col_t header_##name; \
+ emitter_col_init(&header_##name, &header_row); \
+ header_##name.justify = emitter_justify_##left_or_right; \
+ header_##name.width = col_width; \
+ header_##name.type = emitter_type_title; \
+ header_##name.str_val = #name; \
+ \
+ emitter_col_t col_##name; \
+ emitter_col_init(&col_##name, &row); \
+ col_##name.justify = emitter_justify_##left_or_right; \
+ col_##name.width = col_width; \
+ col_##name.type = emitter_type_##etype;
+
+ COL(size, right, 20, size)
+ COL(ind, right, 4, unsigned)
+ COL(allocated, right, 13, size)
+ COL(nmalloc, right, 13, uint64)
+ COL(ndalloc, right, 13, uint64)
+ COL(nrequests, right, 13, uint64)
+ COL(curlextents, right, 13, size)
+#undef COL
+
+ /* As with bins, we label the large extents table. */
+ header_size.width -= 6;
+ emitter_table_printf(emitter, "large:");
+ emitter_table_row(emitter, &header_row);
+ emitter_json_arr_begin(emitter, "lextents");
+
+ for (j = 0, in_gap = false; j < nlextents; j++) {
uint64_t nmalloc, ndalloc, nrequests;
- size_t hchunk_size, curhchunks;
+ size_t lextent_size, curlextents;
- CTL_M2_M4_GET("stats.arenas.0.hchunks.0.nmalloc", i, j,
+ CTL_M2_M4_GET("stats.arenas.0.lextents.0.nmalloc", i, j,
&nmalloc, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.hchunks.0.ndalloc", i, j,
+ CTL_M2_M4_GET("stats.arenas.0.lextents.0.ndalloc", i, j,
&ndalloc, uint64_t);
- CTL_M2_M4_GET("stats.arenas.0.hchunks.0.nrequests", i, j,
+ CTL_M2_M4_GET("stats.arenas.0.lextents.0.nrequests", i, j,
&nrequests, uint64_t);
- if (nrequests == 0)
- in_gap = true;
- else {
- CTL_M2_GET("arenas.hchunk.0.size", j, &hchunk_size,
- size_t);
- CTL_M2_M4_GET("stats.arenas.0.hchunks.0.curhchunks", i,
- j, &curhchunks, size_t);
- if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
- in_gap = false;
- }
- malloc_cprintf(write_cb, cbopaque,
- "%20zu %3u %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64" %12zu\n",
- hchunk_size, nbins + nlruns + j,
- curhchunks * hchunk_size, nmalloc, ndalloc,
- nrequests, curhchunks);
+ in_gap_prev = in_gap;
+ in_gap = (nrequests == 0);
+
+ if (in_gap_prev && !in_gap) {
+ emitter_table_printf(emitter,
+ " ---\n");
+ }
+
+ CTL_M2_GET("arenas.lextent.0.size", j, &lextent_size, size_t);
+ CTL_M2_M4_GET("stats.arenas.0.lextents.0.curlextents", i, j,
+ &curlextents, size_t);
+
+ emitter_json_arr_obj_begin(emitter);
+ emitter_json_kv(emitter, "curlextents", emitter_type_size,
+ &curlextents);
+ emitter_json_arr_obj_end(emitter);
+
+ col_size.size_val = lextent_size;
+ col_ind.unsigned_val = nbins + j;
+ col_allocated.size_val = curlextents * lextent_size;
+ col_nmalloc.uint64_val = nmalloc;
+ col_ndalloc.uint64_val = ndalloc;
+ col_nrequests.uint64_val = nrequests;
+ col_curlextents.size_val = curlextents;
+
+ if (!in_gap) {
+ emitter_table_row(emitter, &row);
}
}
+ emitter_json_arr_end(emitter); /* Close "lextents". */
if (in_gap) {
- malloc_cprintf(write_cb, cbopaque,
- " ---\n");
+ emitter_table_printf(emitter, " ---\n");
}
}
static void
-stats_arena_print(void (*write_cb)(void *, const char *), void *cbopaque,
- unsigned i, bool bins, bool large, bool huge)
-{
+stats_arena_mutexes_print(emitter_t *emitter, unsigned arena_ind) {
+ emitter_row_t row;
+ emitter_col_t col_name;
+ emitter_col_t col64[mutex_prof_num_uint64_t_counters];
+ emitter_col_t col32[mutex_prof_num_uint32_t_counters];
+
+ emitter_row_init(&row);
+ mutex_stats_init_cols(&row, "", &col_name, col64, col32);
+
+ emitter_json_dict_begin(emitter, "mutexes");
+ emitter_table_row(emitter, &row);
+
+ for (mutex_prof_arena_ind_t i = 0; i < mutex_prof_num_arena_mutexes;
+ i++) {
+ const char *name = arena_mutex_names[i];
+ emitter_json_dict_begin(emitter, name);
+ mutex_stats_read_arena(arena_ind, i, name, &col_name, col64,
+ col32);
+ mutex_stats_emit(emitter, &row, col64, col32);
+ emitter_json_dict_end(emitter); /* Close the mutex dict. */
+ }
+ emitter_json_dict_end(emitter); /* End "mutexes". */
+}
+
+static void
+stats_arena_print(emitter_t *emitter, unsigned i, bool bins, bool large,
+ bool mutex) {
unsigned nthreads;
const char *dss;
- ssize_t lg_dirty_mult;
- size_t page, pactive, pdirty, mapped;
- size_t metadata_mapped, metadata_allocated;
- uint64_t npurge, nmadvise, purged;
+ ssize_t dirty_decay_ms, muzzy_decay_ms;
+ size_t page, pactive, pdirty, pmuzzy, mapped, retained;
+ size_t base, internal, resident, metadata_thp;
+ uint64_t dirty_npurge, dirty_nmadvise, dirty_purged;
+ uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged;
size_t small_allocated;
uint64_t small_nmalloc, small_ndalloc, small_nrequests;
size_t large_allocated;
uint64_t large_nmalloc, large_ndalloc, large_nrequests;
- size_t huge_allocated;
- uint64_t huge_nmalloc, huge_ndalloc, huge_nrequests;
+ size_t tcache_bytes;
+ uint64_t uptime;
CTL_GET("arenas.page", &page, size_t);
CTL_M2_GET("stats.arenas.0.nthreads", i, &nthreads, unsigned);
- malloc_cprintf(write_cb, cbopaque,
- "assigned threads: %u\n", nthreads);
+ emitter_kv(emitter, "nthreads", "assigned threads",
+ emitter_type_unsigned, &nthreads);
+
+ CTL_M2_GET("stats.arenas.0.uptime", i, &uptime, uint64_t);
+ emitter_kv(emitter, "uptime_ns", "uptime", emitter_type_uint64,
+ &uptime);
+
CTL_M2_GET("stats.arenas.0.dss", i, &dss, const char *);
- malloc_cprintf(write_cb, cbopaque, "dss allocation precedence: %s\n",
- dss);
- CTL_M2_GET("stats.arenas.0.lg_dirty_mult", i, &lg_dirty_mult, ssize_t);
- if (lg_dirty_mult >= 0) {
- malloc_cprintf(write_cb, cbopaque,
- "min active:dirty page ratio: %u:1\n",
- (1U << lg_dirty_mult));
- } else {
- malloc_cprintf(write_cb, cbopaque,
- "min active:dirty page ratio: N/A\n");
- }
+ emitter_kv(emitter, "dss", "dss allocation precedence",
+ emitter_type_string, &dss);
+
+ CTL_M2_GET("stats.arenas.0.dirty_decay_ms", i, &dirty_decay_ms,
+ ssize_t);
+ CTL_M2_GET("stats.arenas.0.muzzy_decay_ms", i, &muzzy_decay_ms,
+ ssize_t);
CTL_M2_GET("stats.arenas.0.pactive", i, &pactive, size_t);
CTL_M2_GET("stats.arenas.0.pdirty", i, &pdirty, size_t);
- CTL_M2_GET("stats.arenas.0.npurge", i, &npurge, uint64_t);
- CTL_M2_GET("stats.arenas.0.nmadvise", i, &nmadvise, uint64_t);
- CTL_M2_GET("stats.arenas.0.purged", i, &purged, uint64_t);
- malloc_cprintf(write_cb, cbopaque,
- "dirty pages: %zu:%zu active:dirty, %"FMTu64" sweep%s, %"FMTu64
- " madvise%s, %"FMTu64" purged\n", pactive, pdirty, npurge, npurge ==
- 1 ? "" : "s", nmadvise, nmadvise == 1 ? "" : "s", purged);
-
- malloc_cprintf(write_cb, cbopaque,
- " allocated nmalloc ndalloc"
- " nrequests\n");
- CTL_M2_GET("stats.arenas.0.small.allocated", i, &small_allocated,
- size_t);
- CTL_M2_GET("stats.arenas.0.small.nmalloc", i, &small_nmalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.small.ndalloc", i, &small_ndalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.small.nrequests", i, &small_nrequests,
- uint64_t);
- malloc_cprintf(write_cb, cbopaque,
- "small: %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64"\n",
- small_allocated, small_nmalloc, small_ndalloc, small_nrequests);
- CTL_M2_GET("stats.arenas.0.large.allocated", i, &large_allocated,
- size_t);
- CTL_M2_GET("stats.arenas.0.large.nmalloc", i, &large_nmalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.large.ndalloc", i, &large_ndalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.large.nrequests", i, &large_nrequests,
+ CTL_M2_GET("stats.arenas.0.pmuzzy", i, &pmuzzy, size_t);
+ CTL_M2_GET("stats.arenas.0.dirty_npurge", i, &dirty_npurge, uint64_t);
+ CTL_M2_GET("stats.arenas.0.dirty_nmadvise", i, &dirty_nmadvise,
uint64_t);
- malloc_cprintf(write_cb, cbopaque,
- "large: %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64"\n",
- large_allocated, large_nmalloc, large_ndalloc, large_nrequests);
- CTL_M2_GET("stats.arenas.0.huge.allocated", i, &huge_allocated, size_t);
- CTL_M2_GET("stats.arenas.0.huge.nmalloc", i, &huge_nmalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.huge.ndalloc", i, &huge_ndalloc, uint64_t);
- CTL_M2_GET("stats.arenas.0.huge.nrequests", i, &huge_nrequests,
+ CTL_M2_GET("stats.arenas.0.dirty_purged", i, &dirty_purged, uint64_t);
+ CTL_M2_GET("stats.arenas.0.muzzy_npurge", i, &muzzy_npurge, uint64_t);
+ CTL_M2_GET("stats.arenas.0.muzzy_nmadvise", i, &muzzy_nmadvise,
uint64_t);
- malloc_cprintf(write_cb, cbopaque,
- "huge: %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64"\n",
- huge_allocated, huge_nmalloc, huge_ndalloc, huge_nrequests);
- malloc_cprintf(write_cb, cbopaque,
- "total: %12zu %12"FMTu64" %12"FMTu64
- " %12"FMTu64"\n",
- small_allocated + large_allocated + huge_allocated,
- small_nmalloc + large_nmalloc + huge_nmalloc,
- small_ndalloc + large_ndalloc + huge_ndalloc,
- small_nrequests + large_nrequests + huge_nrequests);
- malloc_cprintf(write_cb, cbopaque,
- "active: %12zu\n", pactive * page);
- CTL_M2_GET("stats.arenas.0.mapped", i, &mapped, size_t);
- malloc_cprintf(write_cb, cbopaque,
- "mapped: %12zu\n", mapped);
- CTL_M2_GET("stats.arenas.0.metadata.mapped", i, &metadata_mapped,
- size_t);
- CTL_M2_GET("stats.arenas.0.metadata.allocated", i, &metadata_allocated,
- size_t);
- malloc_cprintf(write_cb, cbopaque,
- "metadata: mapped: %zu, allocated: %zu\n",
- metadata_mapped, metadata_allocated);
-
- if (bins)
- stats_arena_bins_print(write_cb, cbopaque, i);
- if (large)
- stats_arena_lruns_print(write_cb, cbopaque, i);
- if (huge)
- stats_arena_hchunks_print(write_cb, cbopaque, i);
+ CTL_M2_GET("stats.arenas.0.muzzy_purged", i, &muzzy_purged, uint64_t);
+
+ emitter_row_t decay_row;
+ emitter_row_init(&decay_row);
+
+ /* JSON-style emission. */
+ emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize,
+ &dirty_decay_ms);
+ emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize,
+ &muzzy_decay_ms);
+
+ emitter_json_kv(emitter, "pactive", emitter_type_size, &pactive);
+ emitter_json_kv(emitter, "pdirty", emitter_type_size, &pdirty);
+ emitter_json_kv(emitter, "pmuzzy", emitter_type_size, &pmuzzy);
+
+ emitter_json_kv(emitter, "dirty_npurge", emitter_type_uint64,
+ &dirty_npurge);
+ emitter_json_kv(emitter, "dirty_nmadvise", emitter_type_uint64,
+ &dirty_nmadvise);
+ emitter_json_kv(emitter, "dirty_purged", emitter_type_uint64,
+ &dirty_purged);
+
+ emitter_json_kv(emitter, "muzzy_npurge", emitter_type_uint64,
+ &muzzy_npurge);
+ emitter_json_kv(emitter, "muzzy_nmadvise", emitter_type_uint64,
+ &muzzy_nmadvise);
+ emitter_json_kv(emitter, "muzzy_purged", emitter_type_uint64,
+ &muzzy_purged);
+
+ /* Table-style emission. */
+ emitter_col_t decay_type;
+ emitter_col_init(&decay_type, &decay_row);
+ decay_type.justify = emitter_justify_right;
+ decay_type.width = 9;
+ decay_type.type = emitter_type_title;
+ decay_type.str_val = "decaying:";
+
+ emitter_col_t decay_time;
+ emitter_col_init(&decay_time, &decay_row);
+ decay_time.justify = emitter_justify_right;
+ decay_time.width = 6;
+ decay_time.type = emitter_type_title;
+ decay_time.str_val = "time";
+
+ emitter_col_t decay_npages;
+ emitter_col_init(&decay_npages, &decay_row);
+ decay_npages.justify = emitter_justify_right;
+ decay_npages.width = 13;
+ decay_npages.type = emitter_type_title;
+ decay_npages.str_val = "npages";
+
+ emitter_col_t decay_sweeps;
+ emitter_col_init(&decay_sweeps, &decay_row);
+ decay_sweeps.justify = emitter_justify_right;
+ decay_sweeps.width = 13;
+ decay_sweeps.type = emitter_type_title;
+ decay_sweeps.str_val = "sweeps";
+
+ emitter_col_t decay_madvises;
+ emitter_col_init(&decay_madvises, &decay_row);
+ decay_madvises.justify = emitter_justify_right;
+ decay_madvises.width = 13;
+ decay_madvises.type = emitter_type_title;
+ decay_madvises.str_val = "madvises";
+
+ emitter_col_t decay_purged;
+ emitter_col_init(&decay_purged, &decay_row);
+ decay_purged.justify = emitter_justify_right;
+ decay_purged.width = 13;
+ decay_purged.type = emitter_type_title;
+ decay_purged.str_val = "purged";
+
+ /* Title row. */
+ emitter_table_row(emitter, &decay_row);
+
+ /* Dirty row. */
+ decay_type.str_val = "dirty:";
+
+ if (dirty_decay_ms >= 0) {
+ decay_time.type = emitter_type_ssize;
+ decay_time.ssize_val = dirty_decay_ms;
+ } else {
+ decay_time.type = emitter_type_title;
+ decay_time.str_val = "N/A";
+ }
+
+ decay_npages.type = emitter_type_size;
+ decay_npages.size_val = pdirty;
+
+ decay_sweeps.type = emitter_type_uint64;
+ decay_sweeps.uint64_val = dirty_npurge;
+
+ decay_madvises.type = emitter_type_uint64;
+ decay_madvises.uint64_val = dirty_nmadvise;
+
+ decay_purged.type = emitter_type_uint64;
+ decay_purged.uint64_val = dirty_purged;
+
+ emitter_table_row(emitter, &decay_row);
+
+ /* Muzzy row. */
+ decay_type.str_val = "muzzy:";
+
+ if (muzzy_decay_ms >= 0) {
+ decay_time.type = emitter_type_ssize;
+ decay_time.ssize_val = muzzy_decay_ms;
+ } else {
+ decay_time.type = emitter_type_title;
+ decay_time.str_val = "N/A";
+ }
+
+ decay_npages.type = emitter_type_size;
+ decay_npages.size_val = pmuzzy;
+
+ decay_sweeps.type = emitter_type_uint64;
+ decay_sweeps.uint64_val = muzzy_npurge;
+
+ decay_madvises.type = emitter_type_uint64;
+ decay_madvises.uint64_val = muzzy_nmadvise;
+
+ decay_purged.type = emitter_type_uint64;
+ decay_purged.uint64_val = muzzy_purged;
+
+ emitter_table_row(emitter, &decay_row);
+
+ /* Small / large / total allocation counts. */
+ emitter_row_t alloc_count_row;
+ emitter_row_init(&alloc_count_row);
+
+ emitter_col_t alloc_count_title;
+ emitter_col_init(&alloc_count_title, &alloc_count_row);
+ alloc_count_title.justify = emitter_justify_left;
+ alloc_count_title.width = 25;
+ alloc_count_title.type = emitter_type_title;
+ alloc_count_title.str_val = "";
+
+ emitter_col_t alloc_count_allocated;
+ emitter_col_init(&alloc_count_allocated, &alloc_count_row);
+ alloc_count_allocated.justify = emitter_justify_right;
+ alloc_count_allocated.width = 12;
+ alloc_count_allocated.type = emitter_type_title;
+ alloc_count_allocated.str_val = "allocated";
+
+ emitter_col_t alloc_count_nmalloc;
+ emitter_col_init(&alloc_count_nmalloc, &alloc_count_row);
+ alloc_count_nmalloc.justify = emitter_justify_right;
+ alloc_count_nmalloc.width = 12;
+ alloc_count_nmalloc.type = emitter_type_title;
+ alloc_count_nmalloc.str_val = "nmalloc";
+
+ emitter_col_t alloc_count_ndalloc;
+ emitter_col_init(&alloc_count_ndalloc, &alloc_count_row);
+ alloc_count_ndalloc.justify = emitter_justify_right;
+ alloc_count_ndalloc.width = 12;
+ alloc_count_ndalloc.type = emitter_type_title;
+ alloc_count_ndalloc.str_val = "ndalloc";
+
+ emitter_col_t alloc_count_nrequests;
+ emitter_col_init(&alloc_count_nrequests, &alloc_count_row);
+ alloc_count_nrequests.justify = emitter_justify_right;
+ alloc_count_nrequests.width = 12;
+ alloc_count_nrequests.type = emitter_type_title;
+ alloc_count_nrequests.str_val = "nrequests";
+
+ emitter_table_row(emitter, &alloc_count_row);
+
+#define GET_AND_EMIT_ALLOC_STAT(small_or_large, name, valtype) \
+ CTL_M2_GET("stats.arenas.0." #small_or_large "." #name, i, \
+ &small_or_large##_##name, valtype##_t); \
+ emitter_json_kv(emitter, #name, emitter_type_##valtype, \
+ &small_or_large##_##name); \
+ alloc_count_##name.type = emitter_type_##valtype; \
+ alloc_count_##name.valtype##_val = small_or_large##_##name;
+
+ emitter_json_dict_begin(emitter, "small");
+ alloc_count_title.str_val = "small:";
+
+ GET_AND_EMIT_ALLOC_STAT(small, allocated, size)
+ GET_AND_EMIT_ALLOC_STAT(small, nmalloc, uint64)
+ GET_AND_EMIT_ALLOC_STAT(small, ndalloc, uint64)
+ GET_AND_EMIT_ALLOC_STAT(small, nrequests, uint64)
+
+ emitter_table_row(emitter, &alloc_count_row);
+ emitter_json_dict_end(emitter); /* Close "small". */
+
+ emitter_json_dict_begin(emitter, "large");
+ alloc_count_title.str_val = "large:";
+
+ GET_AND_EMIT_ALLOC_STAT(large, allocated, size)
+ GET_AND_EMIT_ALLOC_STAT(large, nmalloc, uint64)
+ GET_AND_EMIT_ALLOC_STAT(large, ndalloc, uint64)
+ GET_AND_EMIT_ALLOC_STAT(large, nrequests, uint64)
+
+ emitter_table_row(emitter, &alloc_count_row);
+ emitter_json_dict_end(emitter); /* Close "large". */
+
+#undef GET_AND_EMIT_ALLOC_STAT
+
+ /* Aggregated small + large stats are emitter only in table mode. */
+ alloc_count_title.str_val = "total:";
+ alloc_count_allocated.size_val = small_allocated + large_allocated;
+ alloc_count_nmalloc.uint64_val = small_nmalloc + large_nmalloc;
+ alloc_count_ndalloc.uint64_val = small_ndalloc + large_ndalloc;
+ alloc_count_nrequests.uint64_val = small_nrequests + large_nrequests;
+ emitter_table_row(emitter, &alloc_count_row);
+
+ emitter_row_t mem_count_row;
+ emitter_row_init(&mem_count_row);
+
+ emitter_col_t mem_count_title;
+ emitter_col_init(&mem_count_title, &mem_count_row);
+ mem_count_title.justify = emitter_justify_left;
+ mem_count_title.width = 25;
+ mem_count_title.type = emitter_type_title;
+ mem_count_title.str_val = "";
+
+ emitter_col_t mem_count_val;
+ emitter_col_init(&mem_count_val, &mem_count_row);
+ mem_count_val.justify = emitter_justify_right;
+ mem_count_val.width = 12;
+ mem_count_val.type = emitter_type_title;
+ mem_count_val.str_val = "";
+
+ emitter_table_row(emitter, &mem_count_row);
+ mem_count_val.type = emitter_type_size;
+
+ /* Active count in bytes is emitted only in table mode. */
+ mem_count_title.str_val = "active:";
+ mem_count_val.size_val = pactive * page;
+ emitter_table_row(emitter, &mem_count_row);
+
+#define GET_AND_EMIT_MEM_STAT(stat) \
+ CTL_M2_GET("stats.arenas.0."#stat, i, &stat, size_t); \
+ emitter_json_kv(emitter, #stat, emitter_type_size, &stat); \
+ mem_count_title.str_val = #stat":"; \
+ mem_count_val.size_val = stat; \
+ emitter_table_row(emitter, &mem_count_row);
+
+ GET_AND_EMIT_MEM_STAT(mapped)
+ GET_AND_EMIT_MEM_STAT(retained)
+ GET_AND_EMIT_MEM_STAT(base)
+ GET_AND_EMIT_MEM_STAT(internal)
+ GET_AND_EMIT_MEM_STAT(metadata_thp)
+ GET_AND_EMIT_MEM_STAT(tcache_bytes)
+ GET_AND_EMIT_MEM_STAT(resident)
+#undef GET_AND_EMIT_MEM_STAT
+
+ if (mutex) {
+ stats_arena_mutexes_print(emitter, i);
+ }
+ if (bins) {
+ stats_arena_bins_print(emitter, mutex, i);
+ }
+ if (large) {
+ stats_arena_lextents_print(emitter, i);
+ }
+}
+
+static void
+stats_general_print(emitter_t *emitter) {
+ const char *cpv;
+ bool bv, bv2;
+ unsigned uv;
+ uint32_t u32v;
+ uint64_t u64v;
+ ssize_t ssv, ssv2;
+ size_t sv, bsz, usz, ssz, sssz, cpsz;
+
+ bsz = sizeof(bool);
+ usz = sizeof(unsigned);
+ ssz = sizeof(size_t);
+ sssz = sizeof(ssize_t);
+ cpsz = sizeof(const char *);
+
+ CTL_GET("version", &cpv, const char *);
+ emitter_kv(emitter, "version", "Version", emitter_type_string, &cpv);
+
+ /* config. */
+ emitter_dict_begin(emitter, "config", "Build-time option settings");
+#define CONFIG_WRITE_BOOL(name) \
+ do { \
+ CTL_GET("config."#name, &bv, bool); \
+ emitter_kv(emitter, #name, "config."#name, \
+ emitter_type_bool, &bv); \
+ } while (0)
+
+ CONFIG_WRITE_BOOL(cache_oblivious);
+ CONFIG_WRITE_BOOL(debug);
+ CONFIG_WRITE_BOOL(fill);
+ CONFIG_WRITE_BOOL(lazy_lock);
+ emitter_kv(emitter, "malloc_conf", "config.malloc_conf",
+ emitter_type_string, &config_malloc_conf);
+
+ CONFIG_WRITE_BOOL(prof);
+ CONFIG_WRITE_BOOL(prof_libgcc);
+ CONFIG_WRITE_BOOL(prof_libunwind);
+ CONFIG_WRITE_BOOL(stats);
+ CONFIG_WRITE_BOOL(utrace);
+ CONFIG_WRITE_BOOL(xmalloc);
+#undef CONFIG_WRITE_BOOL
+ emitter_dict_end(emitter); /* Close "config" dict. */
+
+ /* opt. */
+#define OPT_WRITE(name, var, size, emitter_type) \
+ if (je_mallctl("opt."name, (void *)&var, &size, NULL, 0) == \
+ 0) { \
+ emitter_kv(emitter, name, "opt."name, emitter_type, \
+ &var); \
+ }
+
+#define OPT_WRITE_MUTABLE(name, var1, var2, size, emitter_type, \
+ altname) \
+ if (je_mallctl("opt."name, (void *)&var1, &size, NULL, 0) == \
+ 0 && je_mallctl(altname, (void *)&var2, &size, NULL, 0) \
+ == 0) { \
+ emitter_kv_note(emitter, name, "opt."name, \
+ emitter_type, &var1, altname, emitter_type, \
+ &var2); \
+ }
+
+#define OPT_WRITE_BOOL(name) OPT_WRITE(name, bv, bsz, emitter_type_bool)
+#define OPT_WRITE_BOOL_MUTABLE(name, altname) \
+ OPT_WRITE_MUTABLE(name, bv, bv2, bsz, emitter_type_bool, altname)
+
+#define OPT_WRITE_UNSIGNED(name) \
+ OPT_WRITE(name, uv, usz, emitter_type_unsigned)
+
+#define OPT_WRITE_SSIZE_T(name) \
+ OPT_WRITE(name, ssv, sssz, emitter_type_ssize)
+#define OPT_WRITE_SSIZE_T_MUTABLE(name, altname) \
+ OPT_WRITE_MUTABLE(name, ssv, ssv2, sssz, emitter_type_ssize, \
+ altname)
+
+#define OPT_WRITE_CHAR_P(name) \
+ OPT_WRITE(name, cpv, cpsz, emitter_type_string)
+
+ emitter_dict_begin(emitter, "opt", "Run-time option settings");
+
+ OPT_WRITE_BOOL("abort")
+ OPT_WRITE_BOOL("abort_conf")
+ OPT_WRITE_BOOL("retain")
+ OPT_WRITE_CHAR_P("dss")
+ OPT_WRITE_UNSIGNED("narenas")
+ OPT_WRITE_CHAR_P("percpu_arena")
+ OPT_WRITE_CHAR_P("metadata_thp")
+ OPT_WRITE_BOOL_MUTABLE("background_thread", "background_thread")
+ OPT_WRITE_SSIZE_T_MUTABLE("dirty_decay_ms", "arenas.dirty_decay_ms")
+ OPT_WRITE_SSIZE_T_MUTABLE("muzzy_decay_ms", "arenas.muzzy_decay_ms")
+ OPT_WRITE_UNSIGNED("lg_extent_max_active_fit")
+ OPT_WRITE_CHAR_P("junk")
+ OPT_WRITE_BOOL("zero")
+ OPT_WRITE_BOOL("utrace")
+ OPT_WRITE_BOOL("xmalloc")
+ OPT_WRITE_BOOL("tcache")
+ OPT_WRITE_SSIZE_T("lg_tcache_max")
+ OPT_WRITE_CHAR_P("thp")
+ OPT_WRITE_BOOL("prof")
+ OPT_WRITE_CHAR_P("prof_prefix")
+ OPT_WRITE_BOOL_MUTABLE("prof_active", "prof.active")
+ OPT_WRITE_BOOL_MUTABLE("prof_thread_active_init",
+ "prof.thread_active_init")
+ OPT_WRITE_SSIZE_T_MUTABLE("lg_prof_sample", "prof.lg_sample")
+ OPT_WRITE_BOOL("prof_accum")
+ OPT_WRITE_SSIZE_T("lg_prof_interval")
+ OPT_WRITE_BOOL("prof_gdump")
+ OPT_WRITE_BOOL("prof_final")
+ OPT_WRITE_BOOL("prof_leak")
+ OPT_WRITE_BOOL("stats_print")
+ OPT_WRITE_CHAR_P("stats_print_opts")
+
+ emitter_dict_end(emitter);
+
+#undef OPT_WRITE
+#undef OPT_WRITE_MUTABLE
+#undef OPT_WRITE_BOOL
+#undef OPT_WRITE_BOOL_MUTABLE
+#undef OPT_WRITE_UNSIGNED
+#undef OPT_WRITE_SSIZE_T
+#undef OPT_WRITE_SSIZE_T_MUTABLE
+#undef OPT_WRITE_CHAR_P
+
+ /* prof. */
+ if (config_prof) {
+ emitter_dict_begin(emitter, "prof", "Profiling settings");
+
+ CTL_GET("prof.thread_active_init", &bv, bool);
+ emitter_kv(emitter, "thread_active_init",
+ "prof.thread_active_init", emitter_type_bool, &bv);
+
+ CTL_GET("prof.active", &bv, bool);
+ emitter_kv(emitter, "active", "prof.active", emitter_type_bool,
+ &bv);
+
+ CTL_GET("prof.gdump", &bv, bool);
+ emitter_kv(emitter, "gdump", "prof.gdump", emitter_type_bool,
+ &bv);
+
+ CTL_GET("prof.interval", &u64v, uint64_t);
+ emitter_kv(emitter, "interval", "prof.interval",
+ emitter_type_uint64, &u64v);
+
+ CTL_GET("prof.lg_sample", &ssv, ssize_t);
+ emitter_kv(emitter, "lg_sample", "prof.lg_sample",
+ emitter_type_ssize, &ssv);
+
+ emitter_dict_end(emitter); /* Close "prof". */
+ }
+
+ /* arenas. */
+ /*
+ * The json output sticks arena info into an "arenas" dict; the table
+ * output puts them at the top-level.
+ */
+ emitter_json_dict_begin(emitter, "arenas");
+
+ CTL_GET("arenas.narenas", &uv, unsigned);
+ emitter_kv(emitter, "narenas", "Arenas", emitter_type_unsigned, &uv);
+
+ /*
+ * Decay settings are emitted only in json mode; in table mode, they're
+ * emitted as notes with the opt output, above.
+ */
+ CTL_GET("arenas.dirty_decay_ms", &ssv, ssize_t);
+ emitter_json_kv(emitter, "dirty_decay_ms", emitter_type_ssize, &ssv);
+
+ CTL_GET("arenas.muzzy_decay_ms", &ssv, ssize_t);
+ emitter_json_kv(emitter, "muzzy_decay_ms", emitter_type_ssize, &ssv);
+
+ CTL_GET("arenas.quantum", &sv, size_t);
+ emitter_kv(emitter, "quantum", "Quantum size", emitter_type_size, &sv);
+
+ CTL_GET("arenas.page", &sv, size_t);
+ emitter_kv(emitter, "page", "Page size", emitter_type_size, &sv);
+
+ if (je_mallctl("arenas.tcache_max", (void *)&sv, &ssz, NULL, 0) == 0) {
+ emitter_kv(emitter, "tcache_max",
+ "Maximum thread-cached size class", emitter_type_size, &sv);
+ }
+
+ unsigned nbins;
+ CTL_GET("arenas.nbins", &nbins, unsigned);
+ emitter_kv(emitter, "nbins", "Number of bin size classes",
+ emitter_type_unsigned, &nbins);
+
+ unsigned nhbins;
+ CTL_GET("arenas.nhbins", &nhbins, unsigned);
+ emitter_kv(emitter, "nhbins", "Number of thread-cache bin size classes",
+ emitter_type_unsigned, &nhbins);
+
+ /*
+ * We do enough mallctls in a loop that we actually want to omit them
+ * (not just omit the printing).
+ */
+ if (emitter->output == emitter_output_json) {
+ emitter_json_arr_begin(emitter, "bin");
+ for (unsigned i = 0; i < nbins; i++) {
+ emitter_json_arr_obj_begin(emitter);
+
+ CTL_M2_GET("arenas.bin.0.size", i, &sv, size_t);
+ emitter_json_kv(emitter, "size", emitter_type_size,
+ &sv);
+
+ CTL_M2_GET("arenas.bin.0.nregs", i, &u32v, uint32_t);
+ emitter_json_kv(emitter, "nregs", emitter_type_uint32,
+ &u32v);
+
+ CTL_M2_GET("arenas.bin.0.slab_size", i, &sv, size_t);
+ emitter_json_kv(emitter, "slab_size", emitter_type_size,
+ &sv);
+
+ emitter_json_arr_obj_end(emitter);
+ }
+ emitter_json_arr_end(emitter); /* Close "bin". */
+ }
+
+ unsigned nlextents;
+ CTL_GET("arenas.nlextents", &nlextents, unsigned);
+ emitter_kv(emitter, "nlextents", "Number of large size classes",
+ emitter_type_unsigned, &nlextents);
+
+ if (emitter->output == emitter_output_json) {
+ emitter_json_arr_begin(emitter, "lextent");
+ for (unsigned i = 0; i < nlextents; i++) {
+ emitter_json_arr_obj_begin(emitter);
+
+ CTL_M2_GET("arenas.lextent.0.size", i, &sv, size_t);
+ emitter_json_kv(emitter, "size", emitter_type_size,
+ &sv);
+
+ emitter_json_arr_obj_end(emitter);
+ }
+ emitter_json_arr_end(emitter); /* Close "lextent". */
+ }
+
+ emitter_json_dict_end(emitter); /* Close "arenas" */
+}
+
+static void
+stats_print_helper(emitter_t *emitter, bool merged, bool destroyed,
+ bool unmerged, bool bins, bool large, bool mutex) {
+ /*
+ * These should be deleted. We keep them around for a while, to aid in
+ * the transition to the emitter code.
+ */
+ size_t allocated, active, metadata, metadata_thp, resident, mapped,
+ retained;
+ size_t num_background_threads;
+ uint64_t background_thread_num_runs, background_thread_run_interval;
+
+ CTL_GET("stats.allocated", &allocated, size_t);
+ CTL_GET("stats.active", &active, size_t);
+ CTL_GET("stats.metadata", &metadata, size_t);
+ CTL_GET("stats.metadata_thp", &metadata_thp, size_t);
+ CTL_GET("stats.resident", &resident, size_t);
+ CTL_GET("stats.mapped", &mapped, size_t);
+ CTL_GET("stats.retained", &retained, size_t);
+
+ if (have_background_thread) {
+ CTL_GET("stats.background_thread.num_threads",
+ &num_background_threads, size_t);
+ CTL_GET("stats.background_thread.num_runs",
+ &background_thread_num_runs, uint64_t);
+ CTL_GET("stats.background_thread.run_interval",
+ &background_thread_run_interval, uint64_t);
+ } else {
+ num_background_threads = 0;
+ background_thread_num_runs = 0;
+ background_thread_run_interval = 0;
+ }
+
+ /* Generic global stats. */
+ emitter_json_dict_begin(emitter, "stats");
+ emitter_json_kv(emitter, "allocated", emitter_type_size, &allocated);
+ emitter_json_kv(emitter, "active", emitter_type_size, &active);
+ emitter_json_kv(emitter, "metadata", emitter_type_size, &metadata);
+ emitter_json_kv(emitter, "metadata_thp", emitter_type_size,
+ &metadata_thp);
+ emitter_json_kv(emitter, "resident", emitter_type_size, &resident);
+ emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped);
+ emitter_json_kv(emitter, "retained", emitter_type_size, &retained);
+
+ emitter_table_printf(emitter, "Allocated: %zu, active: %zu, "
+ "metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, "
+ "retained: %zu\n", allocated, active, metadata, metadata_thp,
+ resident, mapped, retained);
+
+ /* Background thread stats. */
+ emitter_json_dict_begin(emitter, "background_thread");
+ emitter_json_kv(emitter, "num_threads", emitter_type_size,
+ &num_background_threads);
+ emitter_json_kv(emitter, "num_runs", emitter_type_uint64,
+ &background_thread_num_runs);
+ emitter_json_kv(emitter, "run_interval", emitter_type_uint64,
+ &background_thread_run_interval);
+ emitter_json_dict_end(emitter); /* Close "background_thread". */
+
+ emitter_table_printf(emitter, "Background threads: %zu, "
+ "num_runs: %"FMTu64", run_interval: %"FMTu64" ns\n",
+ num_background_threads, background_thread_num_runs,
+ background_thread_run_interval);
+
+ if (mutex) {
+ emitter_row_t row;
+ emitter_col_t name;
+ emitter_col_t col64[mutex_prof_num_uint64_t_counters];
+ emitter_col_t col32[mutex_prof_num_uint32_t_counters];
+
+ emitter_row_init(&row);
+ mutex_stats_init_cols(&row, "", &name, col64, col32);
+
+ emitter_table_row(emitter, &row);
+ emitter_json_dict_begin(emitter, "mutexes");
+
+ for (int i = 0; i < mutex_prof_num_global_mutexes; i++) {
+ mutex_stats_read_global(global_mutex_names[i], &name,
+ col64, col32);
+ emitter_json_dict_begin(emitter, global_mutex_names[i]);
+ mutex_stats_emit(emitter, &row, col64, col32);
+ emitter_json_dict_end(emitter);
+ }
+
+ emitter_json_dict_end(emitter); /* Close "mutexes". */
+ }
+
+ emitter_json_dict_end(emitter); /* Close "stats". */
+
+ if (merged || destroyed || unmerged) {
+ unsigned narenas;
+
+ emitter_json_dict_begin(emitter, "stats.arenas");
+
+ CTL_GET("arenas.narenas", &narenas, unsigned);
+ size_t mib[3];
+ size_t miblen = sizeof(mib) / sizeof(size_t);
+ size_t sz;
+ VARIABLE_ARRAY(bool, initialized, narenas);
+ bool destroyed_initialized;
+ unsigned i, j, ninitialized;
+
+ xmallctlnametomib("arena.0.initialized", mib, &miblen);
+ for (i = ninitialized = 0; i < narenas; i++) {
+ mib[1] = i;
+ sz = sizeof(bool);
+ xmallctlbymib(mib, miblen, &initialized[i], &sz,
+ NULL, 0);
+ if (initialized[i]) {
+ ninitialized++;
+ }
+ }
+ mib[1] = MALLCTL_ARENAS_DESTROYED;
+ sz = sizeof(bool);
+ xmallctlbymib(mib, miblen, &destroyed_initialized, &sz,
+ NULL, 0);
+
+ /* Merged stats. */
+ if (merged && (ninitialized > 1 || !unmerged)) {
+ /* Print merged arena stats. */
+ emitter_table_printf(emitter, "Merged arenas stats:\n");
+ emitter_json_dict_begin(emitter, "merged");
+ stats_arena_print(emitter, MALLCTL_ARENAS_ALL, bins,
+ large, mutex);
+ emitter_json_dict_end(emitter); /* Close "merged". */
+ }
+
+ /* Destroyed stats. */
+ if (destroyed_initialized && destroyed) {
+ /* Print destroyed arena stats. */
+ emitter_table_printf(emitter,
+ "Destroyed arenas stats:\n");
+ emitter_json_dict_begin(emitter, "destroyed");
+ stats_arena_print(emitter, MALLCTL_ARENAS_DESTROYED,
+ bins, large, mutex);
+ emitter_json_dict_end(emitter); /* Close "destroyed". */
+ }
+
+ /* Unmerged stats. */
+ if (unmerged) {
+ for (i = j = 0; i < narenas; i++) {
+ if (initialized[i]) {
+ char arena_ind_str[20];
+ malloc_snprintf(arena_ind_str,
+ sizeof(arena_ind_str), "%u", i);
+ emitter_json_dict_begin(emitter,
+ arena_ind_str);
+ emitter_table_printf(emitter,
+ "arenas[%s]:\n", arena_ind_str);
+ stats_arena_print(emitter, i, bins,
+ large, mutex);
+ /* Close "<arena-ind>". */
+ emitter_json_dict_end(emitter);
+ }
+ }
+ }
+ emitter_json_dict_end(emitter); /* Close "stats.arenas". */
+ }
}
void
stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
- const char *opts)
-{
+ const char *opts) {
int err;
uint64_t epoch;
size_t u64sz;
- bool general = true;
- bool merged = true;
- bool unmerged = true;
- bool bins = true;
- bool large = true;
- bool huge = true;
+#define OPTION(o, v, d, s) bool v = d;
+ STATS_PRINT_OPTIONS
+#undef OPTION
/*
* Refresh stats, in case mallctl() was called by the application.
@@ -379,7 +1240,8 @@ stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
* */
epoch = 1;
u64sz = sizeof(uint64_t);
- err = je_mallctl("epoch", &epoch, &u64sz, &epoch, sizeof(uint64_t));
+ err = je_mallctl("epoch", (void *)&epoch, &u64sz, (void *)&epoch,
+ sizeof(uint64_t));
if (err != 0) {
if (err == EAGAIN) {
malloc_write("<jemalloc>: Memory allocation failure in "
@@ -392,249 +1254,33 @@ stats_print(void (*write_cb)(void *, const char *), void *cbopaque,
}
if (opts != NULL) {
- unsigned i;
-
- for (i = 0; opts[i] != '\0'; i++) {
+ for (unsigned i = 0; opts[i] != '\0'; i++) {
switch (opts[i]) {
- case 'g':
- general = false;
- break;
- case 'm':
- merged = false;
- break;
- case 'a':
- unmerged = false;
- break;
- case 'b':
- bins = false;
- break;
- case 'l':
- large = false;
- break;
- case 'h':
- huge = false;
- break;
+#define OPTION(o, v, d, s) case o: v = s; break;
+ STATS_PRINT_OPTIONS
+#undef OPTION
default:;
}
}
}
- malloc_cprintf(write_cb, cbopaque,
- "___ Begin jemalloc statistics ___\n");
- if (general) {
- const char *cpv;
- bool bv;
- unsigned uv;
- ssize_t ssv;
- size_t sv, bsz, ssz, sssz, cpsz;
-
- bsz = sizeof(bool);
- ssz = sizeof(size_t);
- sssz = sizeof(ssize_t);
- cpsz = sizeof(const char *);
-
- CTL_GET("version", &cpv, const char *);
- malloc_cprintf(write_cb, cbopaque, "Version: %s\n", cpv);
- CTL_GET("config.debug", &bv, bool);
- malloc_cprintf(write_cb, cbopaque, "Assertions %s\n",
- bv ? "enabled" : "disabled");
-
-#define OPT_WRITE_BOOL(n) \
- if (je_mallctl("opt."#n, &bv, &bsz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": %s\n", bv ? "true" : "false"); \
- }
-#define OPT_WRITE_BOOL_MUTABLE(n, m) { \
- bool bv2; \
- if (je_mallctl("opt."#n, &bv, &bsz, NULL, 0) == 0 && \
- je_mallctl(#m, &bv2, &bsz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": %s ("#m": %s)\n", bv ? "true" \
- : "false", bv2 ? "true" : "false"); \
- } \
-}
-#define OPT_WRITE_SIZE_T(n) \
- if (je_mallctl("opt."#n, &sv, &ssz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": %zu\n", sv); \
- }
-#define OPT_WRITE_SSIZE_T(n) \
- if (je_mallctl("opt."#n, &ssv, &sssz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": %zd\n", ssv); \
- }
-#define OPT_WRITE_SSIZE_T_MUTABLE(n, m) { \
- ssize_t ssv2; \
- if (je_mallctl("opt."#n, &ssv, &sssz, NULL, 0) == 0 && \
- je_mallctl(#m, &ssv2, &sssz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": %zd ("#m": %zd)\n", \
- ssv, ssv2); \
- } \
-}
-#define OPT_WRITE_CHAR_P(n) \
- if (je_mallctl("opt."#n, &cpv, &cpsz, NULL, 0) == 0) { \
- malloc_cprintf(write_cb, cbopaque, \
- " opt."#n": \"%s\"\n", cpv); \
- }
-
- malloc_cprintf(write_cb, cbopaque,
- "Run-time option settings:\n");
- OPT_WRITE_BOOL(abort)
- OPT_WRITE_SIZE_T(lg_chunk)
- OPT_WRITE_CHAR_P(dss)
- OPT_WRITE_SIZE_T(narenas)
- OPT_WRITE_SSIZE_T_MUTABLE(lg_dirty_mult, arenas.lg_dirty_mult)
- OPT_WRITE_BOOL(stats_print)
- OPT_WRITE_CHAR_P(junk)
- OPT_WRITE_SIZE_T(quarantine)
- OPT_WRITE_BOOL(redzone)
- OPT_WRITE_BOOL(zero)
- OPT_WRITE_BOOL(utrace)
- OPT_WRITE_BOOL(valgrind)
- OPT_WRITE_BOOL(xmalloc)
- OPT_WRITE_BOOL(tcache)
- OPT_WRITE_SSIZE_T(lg_tcache_max)
- OPT_WRITE_BOOL(prof)
- OPT_WRITE_CHAR_P(prof_prefix)
- OPT_WRITE_BOOL_MUTABLE(prof_active, prof.active)
- OPT_WRITE_BOOL_MUTABLE(prof_thread_active_init,
- prof.thread_active_init)
- OPT_WRITE_SSIZE_T(lg_prof_sample)
- OPT_WRITE_BOOL(prof_accum)
- OPT_WRITE_SSIZE_T(lg_prof_interval)
- OPT_WRITE_BOOL(prof_gdump)
- OPT_WRITE_BOOL(prof_final)
- OPT_WRITE_BOOL(prof_leak)
-
-#undef OPT_WRITE_BOOL
-#undef OPT_WRITE_BOOL_MUTABLE
-#undef OPT_WRITE_SIZE_T
-#undef OPT_WRITE_SSIZE_T
-#undef OPT_WRITE_CHAR_P
-
- malloc_cprintf(write_cb, cbopaque, "CPUs: %u\n", ncpus);
-
- CTL_GET("arenas.narenas", &uv, unsigned);
- malloc_cprintf(write_cb, cbopaque, "Arenas: %u\n", uv);
-
- malloc_cprintf(write_cb, cbopaque, "Pointer size: %zu\n",
- sizeof(void *));
-
- CTL_GET("arenas.quantum", &sv, size_t);
- malloc_cprintf(write_cb, cbopaque, "Quantum size: %zu\n",
- sv);
-
- CTL_GET("arenas.page", &sv, size_t);
- malloc_cprintf(write_cb, cbopaque, "Page size: %zu\n", sv);
+ emitter_t emitter;
+ emitter_init(&emitter,
+ json ? emitter_output_json : emitter_output_table, write_cb,
+ cbopaque);
+ emitter_begin(&emitter);
+ emitter_table_printf(&emitter, "___ Begin jemalloc statistics ___\n");
+ emitter_json_dict_begin(&emitter, "jemalloc");
- CTL_GET("arenas.lg_dirty_mult", &ssv, ssize_t);
- if (ssv >= 0) {
- malloc_cprintf(write_cb, cbopaque,
- "Min active:dirty page ratio per arena: %u:1\n",
- (1U << ssv));
- } else {
- malloc_cprintf(write_cb, cbopaque,
- "Min active:dirty page ratio per arena: N/A\n");
- }
- if (je_mallctl("arenas.tcache_max", &sv, &ssz, NULL, 0) == 0) {
- malloc_cprintf(write_cb, cbopaque,
- "Maximum thread-cached size class: %zu\n", sv);
- }
- if (je_mallctl("opt.prof", &bv, &bsz, NULL, 0) == 0 && bv) {
- CTL_GET("prof.lg_sample", &sv, size_t);
- malloc_cprintf(write_cb, cbopaque,
- "Average profile sample interval: %"FMTu64
- " (2^%zu)\n", (((uint64_t)1U) << sv), sv);
-
- CTL_GET("opt.lg_prof_interval", &ssv, ssize_t);
- if (ssv >= 0) {
- malloc_cprintf(write_cb, cbopaque,
- "Average profile dump interval: %"FMTu64
- " (2^%zd)\n",
- (((uint64_t)1U) << ssv), ssv);
- } else {
- malloc_cprintf(write_cb, cbopaque,
- "Average profile dump interval: N/A\n");
- }
- }
- CTL_GET("opt.lg_chunk", &sv, size_t);
- malloc_cprintf(write_cb, cbopaque,
- "Chunk size: %zu (2^%zu)\n", (ZU(1) << sv), sv);
+ if (general) {
+ stats_general_print(&emitter);
}
-
if (config_stats) {
- size_t *cactive;
- size_t allocated, active, metadata, resident, mapped;
-
- CTL_GET("stats.cactive", &cactive, size_t *);
- CTL_GET("stats.allocated", &allocated, size_t);
- CTL_GET("stats.active", &active, size_t);
- CTL_GET("stats.metadata", &metadata, size_t);
- CTL_GET("stats.resident", &resident, size_t);
- CTL_GET("stats.mapped", &mapped, size_t);
- malloc_cprintf(write_cb, cbopaque,
- "Allocated: %zu, active: %zu, metadata: %zu,"
- " resident: %zu, mapped: %zu\n",
- allocated, active, metadata, resident, mapped);
- malloc_cprintf(write_cb, cbopaque,
- "Current active ceiling: %zu\n",
- atomic_read_z(cactive));
-
- if (merged) {
- unsigned narenas;
-
- CTL_GET("arenas.narenas", &narenas, unsigned);
- {
- VARIABLE_ARRAY(bool, initialized, narenas);
- size_t isz;
- unsigned i, ninitialized;
-
- isz = sizeof(bool) * narenas;
- xmallctl("arenas.initialized", initialized,
- &isz, NULL, 0);
- for (i = ninitialized = 0; i < narenas; i++) {
- if (initialized[i])
- ninitialized++;
- }
-
- if (ninitialized > 1 || !unmerged) {
- /* Print merged arena stats. */
- malloc_cprintf(write_cb, cbopaque,
- "\nMerged arenas stats:\n");
- stats_arena_print(write_cb, cbopaque,
- narenas, bins, large, huge);
- }
- }
- }
-
- if (unmerged) {
- unsigned narenas;
-
- /* Print stats for each arena. */
-
- CTL_GET("arenas.narenas", &narenas, unsigned);
- {
- VARIABLE_ARRAY(bool, initialized, narenas);
- size_t isz;
- unsigned i;
-
- isz = sizeof(bool) * narenas;
- xmallctl("arenas.initialized", initialized,
- &isz, NULL, 0);
-
- for (i = 0; i < narenas; i++) {
- if (initialized[i]) {
- malloc_cprintf(write_cb,
- cbopaque,
- "\narenas[%u]:\n", i);
- stats_arena_print(write_cb,
- cbopaque, i, bins, large,
- huge);
- }
- }
- }
- }
+ stats_print_helper(&emitter, merged, destroyed, unmerged,
+ bins, large, mutex);
}
- malloc_cprintf(write_cb, cbopaque, "--- End jemalloc statistics ---\n");
+
+ emitter_json_dict_end(&emitter); /* Closes the "jemalloc" dict. */
+ emitter_table_printf(&emitter, "--- End jemalloc statistics ---\n");
+ emitter_end(&emitter);
}
diff --git a/deps/jemalloc/src/sz.c b/deps/jemalloc/src/sz.c
new file mode 100644
index 000000000..9de77e45f
--- /dev/null
+++ b/deps/jemalloc/src/sz.c
@@ -0,0 +1,107 @@
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/sz.h"
+
+JEMALLOC_ALIGNED(CACHELINE)
+const size_t sz_pind2sz_tab[NPSIZES+1] = {
+#define PSZ_yes(lg_grp, ndelta, lg_delta) \
+ (((ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta))),
+#define PSZ_no(lg_grp, ndelta, lg_delta)
+#define SC(index, lg_grp, lg_delta, ndelta, psz, bin, pgs, lg_delta_lookup) \
+ PSZ_##psz(lg_grp, ndelta, lg_delta)
+ SIZE_CLASSES
+#undef PSZ_yes
+#undef PSZ_no
+#undef SC
+ (LARGE_MAXCLASS + PAGE)
+};
+
+JEMALLOC_ALIGNED(CACHELINE)
+const size_t sz_index2size_tab[NSIZES] = {
+#define SC(index, lg_grp, lg_delta, ndelta, psz, bin, pgs, lg_delta_lookup) \
+ ((ZU(1)<<lg_grp) + (ZU(ndelta)<<lg_delta)),
+ SIZE_CLASSES
+#undef SC
+};
+
+JEMALLOC_ALIGNED(CACHELINE)
+const uint8_t sz_size2index_tab[] = {
+#if LG_TINY_MIN == 0
+/* The div module doesn't support division by 1. */
+#error "Unsupported LG_TINY_MIN"
+#define S2B_0(i) i,
+#elif LG_TINY_MIN == 1
+#warning "Dangerous LG_TINY_MIN"
+#define S2B_1(i) i,
+#elif LG_TINY_MIN == 2
+#warning "Dangerous LG_TINY_MIN"
+#define S2B_2(i) i,
+#elif LG_TINY_MIN == 3
+#define S2B_3(i) i,
+#elif LG_TINY_MIN == 4
+#define S2B_4(i) i,
+#elif LG_TINY_MIN == 5
+#define S2B_5(i) i,
+#elif LG_TINY_MIN == 6
+#define S2B_6(i) i,
+#elif LG_TINY_MIN == 7
+#define S2B_7(i) i,
+#elif LG_TINY_MIN == 8
+#define S2B_8(i) i,
+#elif LG_TINY_MIN == 9
+#define S2B_9(i) i,
+#elif LG_TINY_MIN == 10
+#define S2B_10(i) i,
+#elif LG_TINY_MIN == 11
+#define S2B_11(i) i,
+#else
+#error "Unsupported LG_TINY_MIN"
+#endif
+#if LG_TINY_MIN < 1
+#define S2B_1(i) S2B_0(i) S2B_0(i)
+#endif
+#if LG_TINY_MIN < 2
+#define S2B_2(i) S2B_1(i) S2B_1(i)
+#endif
+#if LG_TINY_MIN < 3
+#define S2B_3(i) S2B_2(i) S2B_2(i)
+#endif
+#if LG_TINY_MIN < 4
+#define S2B_4(i) S2B_3(i) S2B_3(i)
+#endif
+#if LG_TINY_MIN < 5
+#define S2B_5(i) S2B_4(i) S2B_4(i)
+#endif
+#if LG_TINY_MIN < 6
+#define S2B_6(i) S2B_5(i) S2B_5(i)
+#endif
+#if LG_TINY_MIN < 7
+#define S2B_7(i) S2B_6(i) S2B_6(i)
+#endif
+#if LG_TINY_MIN < 8
+#define S2B_8(i) S2B_7(i) S2B_7(i)
+#endif
+#if LG_TINY_MIN < 9
+#define S2B_9(i) S2B_8(i) S2B_8(i)
+#endif
+#if LG_TINY_MIN < 10
+#define S2B_10(i) S2B_9(i) S2B_9(i)
+#endif
+#if LG_TINY_MIN < 11
+#define S2B_11(i) S2B_10(i) S2B_10(i)
+#endif
+#define S2B_no(i)
+#define SC(index, lg_grp, lg_delta, ndelta, psz, bin, pgs, lg_delta_lookup) \
+ S2B_##lg_delta_lookup(index)
+ SIZE_CLASSES
+#undef S2B_3
+#undef S2B_4
+#undef S2B_5
+#undef S2B_6
+#undef S2B_7
+#undef S2B_8
+#undef S2B_9
+#undef S2B_10
+#undef S2B_11
+#undef S2B_no
+#undef SC
+};
diff --git a/deps/jemalloc/src/tcache.c b/deps/jemalloc/src/tcache.c
index fdafd0c62..a769a6b17 100644
--- a/deps/jemalloc/src/tcache.c
+++ b/deps/jemalloc/src/tcache.c
@@ -1,5 +1,10 @@
-#define JEMALLOC_TCACHE_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_TCACHE_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/size_classes.h"
/******************************************************************************/
/* Data. */
@@ -7,10 +12,10 @@
bool opt_tcache = true;
ssize_t opt_lg_tcache_max = LG_TCACHE_MAXCLASS_DEFAULT;
-tcache_bin_info_t *tcache_bin_info;
+cache_bin_info_t *tcache_bin_info;
static unsigned stack_nelms; /* Total stack elms per tcache. */
-size_t nhbins;
+unsigned nhbins;
size_t tcache_maxclass;
tcaches_t *tcaches;
@@ -21,21 +26,26 @@ static unsigned tcaches_past;
/* Head of singly linked list tracking available tcaches elements. */
static tcaches_t *tcaches_avail;
-/******************************************************************************/
+/* Protects tcaches{,_past,_avail}. */
+static malloc_mutex_t tcaches_mtx;
-size_t tcache_salloc(const void *ptr)
-{
+/******************************************************************************/
- return (arena_salloc(ptr, false));
+size_t
+tcache_salloc(tsdn_t *tsdn, const void *ptr) {
+ return arena_salloc(tsdn, ptr);
}
void
-tcache_event_hard(tsd_t *tsd, tcache_t *tcache)
-{
+tcache_event_hard(tsd_t *tsd, tcache_t *tcache) {
szind_t binind = tcache->next_gc_bin;
- tcache_bin_t *tbin = &tcache->tbins[binind];
- tcache_bin_info_t *tbin_info = &tcache_bin_info[binind];
+ cache_bin_t *tbin;
+ if (binind < NBINS) {
+ tbin = tcache_small_bin_get(tcache, binind);
+ } else {
+ tbin = tcache_large_bin_get(tcache, binind);
+ }
if (tbin->low_water > 0) {
/*
* Flush (ceiling) 3/4 of the objects below the low water mark.
@@ -44,75 +54,84 @@ tcache_event_hard(tsd_t *tsd, tcache_t *tcache)
tcache_bin_flush_small(tsd, tcache, tbin, binind,
tbin->ncached - tbin->low_water + (tbin->low_water
>> 2));
+ /*
+ * Reduce fill count by 2X. Limit lg_fill_div such that
+ * the fill count is always at least 1.
+ */
+ cache_bin_info_t *tbin_info = &tcache_bin_info[binind];
+ if ((tbin_info->ncached_max >>
+ (tcache->lg_fill_div[binind] + 1)) >= 1) {
+ tcache->lg_fill_div[binind]++;
+ }
} else {
tcache_bin_flush_large(tsd, tbin, binind, tbin->ncached
- tbin->low_water + (tbin->low_water >> 2), tcache);
}
- /*
- * Reduce fill count by 2X. Limit lg_fill_div such that the
- * fill count is always at least 1.
- */
- if ((tbin_info->ncached_max >> (tbin->lg_fill_div+1)) >= 1)
- tbin->lg_fill_div++;
} else if (tbin->low_water < 0) {
/*
- * Increase fill count by 2X. Make sure lg_fill_div stays
- * greater than 0.
+ * Increase fill count by 2X for small bins. Make sure
+ * lg_fill_div stays greater than 0.
*/
- if (tbin->lg_fill_div > 1)
- tbin->lg_fill_div--;
+ if (binind < NBINS && tcache->lg_fill_div[binind] > 1) {
+ tcache->lg_fill_div[binind]--;
+ }
}
tbin->low_water = tbin->ncached;
tcache->next_gc_bin++;
- if (tcache->next_gc_bin == nhbins)
+ if (tcache->next_gc_bin == nhbins) {
tcache->next_gc_bin = 0;
- tcache->ev_cnt = 0;
+ }
}
void *
-tcache_alloc_small_hard(tsd_t *tsd, arena_t *arena, tcache_t *tcache,
- tcache_bin_t *tbin, szind_t binind)
-{
+tcache_alloc_small_hard(tsdn_t *tsdn, arena_t *arena, tcache_t *tcache,
+ cache_bin_t *tbin, szind_t binind, bool *tcache_success) {
void *ret;
- arena_tcache_fill_small(arena, tbin, binind, config_prof ?
- tcache->prof_accumbytes : 0);
- if (config_prof)
+ assert(tcache->arena != NULL);
+ arena_tcache_fill_small(tsdn, arena, tcache, tbin, binind,
+ config_prof ? tcache->prof_accumbytes : 0);
+ if (config_prof) {
tcache->prof_accumbytes = 0;
- ret = tcache_alloc_easy(tbin);
+ }
+ ret = cache_bin_alloc_easy(tbin, tcache_success);
- return (ret);
+ return ret;
}
void
-tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, tcache_bin_t *tbin,
- szind_t binind, unsigned rem)
-{
- arena_t *arena;
- void *ptr;
- unsigned i, nflush, ndeferred;
+tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, cache_bin_t *tbin,
+ szind_t binind, unsigned rem) {
bool merged_stats = false;
assert(binind < NBINS);
- assert(rem <= tbin->ncached);
+ assert((cache_bin_sz_t)rem <= tbin->ncached);
- arena = arena_choose(tsd, NULL);
+ arena_t *arena = tcache->arena;
assert(arena != NULL);
- for (nflush = tbin->ncached - rem; nflush > 0; nflush = ndeferred) {
+ unsigned nflush = tbin->ncached - rem;
+ VARIABLE_ARRAY(extent_t *, item_extent, nflush);
+ /* Look up extent once per item. */
+ for (unsigned i = 0 ; i < nflush; i++) {
+ item_extent[i] = iealloc(tsd_tsdn(tsd), *(tbin->avail - 1 - i));
+ }
+
+ while (nflush > 0) {
/* Lock the arena bin associated with the first object. */
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(
- tbin->avail[0]);
- arena_t *bin_arena = extent_node_arena_get(&chunk->node);
- arena_bin_t *bin = &bin_arena->bins[binind];
+ extent_t *extent = item_extent[0];
+ arena_t *bin_arena = extent_arena_get(extent);
+ bin_t *bin = &bin_arena->bins[binind];
if (config_prof && bin_arena == arena) {
- if (arena_prof_accum(arena, tcache->prof_accumbytes))
- prof_idump();
+ if (arena_prof_accum(tsd_tsdn(tsd), arena,
+ tcache->prof_accumbytes)) {
+ prof_idump(tsd_tsdn(tsd));
+ }
tcache->prof_accumbytes = 0;
}
- malloc_mutex_lock(&bin->lock);
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
if (config_stats && bin_arena == arena) {
assert(!merged_stats);
merged_stats = true;
@@ -120,18 +139,15 @@ tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, tcache_bin_t *tbin,
bin->stats.nrequests += tbin->tstats.nrequests;
tbin->tstats.nrequests = 0;
}
- ndeferred = 0;
- for (i = 0; i < nflush; i++) {
- ptr = tbin->avail[i];
- assert(ptr != NULL);
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (extent_node_arena_get(&chunk->node) == bin_arena) {
- size_t pageind = ((uintptr_t)ptr -
- (uintptr_t)chunk) >> LG_PAGE;
- arena_chunk_map_bits_t *bitselm =
- arena_bitselm_get(chunk, pageind);
- arena_dalloc_bin_junked_locked(bin_arena, chunk,
- ptr, bitselm);
+ unsigned ndeferred = 0;
+ for (unsigned i = 0; i < nflush; i++) {
+ void *ptr = *(tbin->avail - 1 - i);
+ extent = item_extent[i];
+ assert(ptr != NULL && extent != NULL);
+
+ if (extent_arena_get(extent) == bin_arena) {
+ arena_dalloc_bin_junked_locked(tsd_tsdn(tsd),
+ bin_arena, extent, ptr);
} else {
/*
* This object was allocated via a different
@@ -139,80 +155,97 @@ tcache_bin_flush_small(tsd_t *tsd, tcache_t *tcache, tcache_bin_t *tbin,
* locked. Stash the object, so that it can be
* handled in a future pass.
*/
- tbin->avail[ndeferred] = ptr;
+ *(tbin->avail - 1 - ndeferred) = ptr;
+ item_extent[ndeferred] = extent;
ndeferred++;
}
}
- malloc_mutex_unlock(&bin->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
+ arena_decay_ticks(tsd_tsdn(tsd), bin_arena, nflush - ndeferred);
+ nflush = ndeferred;
}
if (config_stats && !merged_stats) {
/*
* The flush loop didn't happen to flush to this thread's
* arena, so the stats didn't get merged. Manually do so now.
*/
- arena_bin_t *bin = &arena->bins[binind];
- malloc_mutex_lock(&bin->lock);
+ bin_t *bin = &arena->bins[binind];
+ malloc_mutex_lock(tsd_tsdn(tsd), &bin->lock);
bin->stats.nflushes++;
bin->stats.nrequests += tbin->tstats.nrequests;
tbin->tstats.nrequests = 0;
- malloc_mutex_unlock(&bin->lock);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &bin->lock);
}
- memmove(tbin->avail, &tbin->avail[tbin->ncached - rem],
- rem * sizeof(void *));
+ memmove(tbin->avail - rem, tbin->avail - tbin->ncached, rem *
+ sizeof(void *));
tbin->ncached = rem;
- if ((int)tbin->ncached < tbin->low_water)
+ if (tbin->ncached < tbin->low_water) {
tbin->low_water = tbin->ncached;
+ }
}
void
-tcache_bin_flush_large(tsd_t *tsd, tcache_bin_t *tbin, szind_t binind,
- unsigned rem, tcache_t *tcache)
-{
- arena_t *arena;
- void *ptr;
- unsigned i, nflush, ndeferred;
+tcache_bin_flush_large(tsd_t *tsd, cache_bin_t *tbin, szind_t binind,
+ unsigned rem, tcache_t *tcache) {
bool merged_stats = false;
assert(binind < nhbins);
- assert(rem <= tbin->ncached);
+ assert((cache_bin_sz_t)rem <= tbin->ncached);
- arena = arena_choose(tsd, NULL);
+ arena_t *arena = tcache->arena;
assert(arena != NULL);
- for (nflush = tbin->ncached - rem; nflush > 0; nflush = ndeferred) {
+ unsigned nflush = tbin->ncached - rem;
+ VARIABLE_ARRAY(extent_t *, item_extent, nflush);
+ /* Look up extent once per item. */
+ for (unsigned i = 0 ; i < nflush; i++) {
+ item_extent[i] = iealloc(tsd_tsdn(tsd), *(tbin->avail - 1 - i));
+ }
+
+ while (nflush > 0) {
/* Lock the arena associated with the first object. */
- arena_chunk_t *chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(
- tbin->avail[0]);
- arena_t *locked_arena = extent_node_arena_get(&chunk->node);
+ extent_t *extent = item_extent[0];
+ arena_t *locked_arena = extent_arena_get(extent);
UNUSED bool idump;
- if (config_prof)
+ if (config_prof) {
idump = false;
- malloc_mutex_lock(&locked_arena->lock);
+ }
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &locked_arena->large_mtx);
+ for (unsigned i = 0; i < nflush; i++) {
+ void *ptr = *(tbin->avail - 1 - i);
+ assert(ptr != NULL);
+ extent = item_extent[i];
+ if (extent_arena_get(extent) == locked_arena) {
+ large_dalloc_prep_junked_locked(tsd_tsdn(tsd),
+ extent);
+ }
+ }
if ((config_prof || config_stats) && locked_arena == arena) {
if (config_prof) {
- idump = arena_prof_accum_locked(arena,
+ idump = arena_prof_accum(tsd_tsdn(tsd), arena,
tcache->prof_accumbytes);
tcache->prof_accumbytes = 0;
}
if (config_stats) {
merged_stats = true;
- arena->stats.nrequests_large +=
- tbin->tstats.nrequests;
- arena->stats.lstats[binind - NBINS].nrequests +=
- tbin->tstats.nrequests;
+ arena_stats_large_nrequests_add(tsd_tsdn(tsd),
+ &arena->stats, binind,
+ tbin->tstats.nrequests);
tbin->tstats.nrequests = 0;
}
}
- ndeferred = 0;
- for (i = 0; i < nflush; i++) {
- ptr = tbin->avail[i];
- assert(ptr != NULL);
- chunk = (arena_chunk_t *)CHUNK_ADDR2BASE(ptr);
- if (extent_node_arena_get(&chunk->node) ==
- locked_arena) {
- arena_dalloc_large_junked_locked(locked_arena,
- chunk, ptr);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &locked_arena->large_mtx);
+
+ unsigned ndeferred = 0;
+ for (unsigned i = 0; i < nflush; i++) {
+ void *ptr = *(tbin->avail - 1 - i);
+ extent = item_extent[i];
+ assert(ptr != NULL && extent != NULL);
+
+ if (extent_arena_get(extent) == locked_arena) {
+ large_dalloc_finish(tsd_tsdn(tsd), extent);
} else {
/*
* This object was allocated via a different
@@ -220,62 +253,64 @@ tcache_bin_flush_large(tsd_t *tsd, tcache_bin_t *tbin, szind_t binind,
* Stash the object, so that it can be handled
* in a future pass.
*/
- tbin->avail[ndeferred] = ptr;
+ *(tbin->avail - 1 - ndeferred) = ptr;
+ item_extent[ndeferred] = extent;
ndeferred++;
}
}
- malloc_mutex_unlock(&locked_arena->lock);
- if (config_prof && idump)
- prof_idump();
+ if (config_prof && idump) {
+ prof_idump(tsd_tsdn(tsd));
+ }
+ arena_decay_ticks(tsd_tsdn(tsd), locked_arena, nflush -
+ ndeferred);
+ nflush = ndeferred;
}
if (config_stats && !merged_stats) {
/*
* The flush loop didn't happen to flush to this thread's
* arena, so the stats didn't get merged. Manually do so now.
*/
- malloc_mutex_lock(&arena->lock);
- arena->stats.nrequests_large += tbin->tstats.nrequests;
- arena->stats.lstats[binind - NBINS].nrequests +=
- tbin->tstats.nrequests;
+ arena_stats_large_nrequests_add(tsd_tsdn(tsd), &arena->stats,
+ binind, tbin->tstats.nrequests);
tbin->tstats.nrequests = 0;
- malloc_mutex_unlock(&arena->lock);
}
- memmove(tbin->avail, &tbin->avail[tbin->ncached - rem],
- rem * sizeof(void *));
+ memmove(tbin->avail - rem, tbin->avail - tbin->ncached, rem *
+ sizeof(void *));
tbin->ncached = rem;
- if ((int)tbin->ncached < tbin->low_water)
+ if (tbin->ncached < tbin->low_water) {
tbin->low_water = tbin->ncached;
+ }
}
void
-tcache_arena_associate(tcache_t *tcache, arena_t *arena)
-{
+tcache_arena_associate(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
+ assert(tcache->arena == NULL);
+ tcache->arena = arena;
if (config_stats) {
/* Link into list of extant tcaches. */
- malloc_mutex_lock(&arena->lock);
+ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
+
ql_elm_new(tcache, link);
ql_tail_insert(&arena->tcache_ql, tcache, link);
- malloc_mutex_unlock(&arena->lock);
- }
-}
-
-void
-tcache_arena_reassociate(tcache_t *tcache, arena_t *oldarena, arena_t *newarena)
-{
+ cache_bin_array_descriptor_init(
+ &tcache->cache_bin_array_descriptor, tcache->bins_small,
+ tcache->bins_large);
+ ql_tail_insert(&arena->cache_bin_array_descriptor_ql,
+ &tcache->cache_bin_array_descriptor, link);
- tcache_arena_dissociate(tcache, oldarena);
- tcache_arena_associate(tcache, newarena);
+ malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx);
+ }
}
-void
-tcache_arena_dissociate(tcache_t *tcache, arena_t *arena)
-{
-
+static void
+tcache_arena_dissociate(tsdn_t *tsdn, tcache_t *tcache) {
+ arena_t *arena = tcache->arena;
+ assert(arena != NULL);
if (config_stats) {
/* Unlink from list of extant tcaches. */
- malloc_mutex_lock(&arena->lock);
+ malloc_mutex_lock(tsdn, &arena->tcache_ql_mtx);
if (config_debug) {
bool in_ql = false;
tcache_t *iter;
@@ -288,240 +323,364 @@ tcache_arena_dissociate(tcache_t *tcache, arena_t *arena)
assert(in_ql);
}
ql_remove(&arena->tcache_ql, tcache, link);
- tcache_stats_merge(tcache, arena);
- malloc_mutex_unlock(&arena->lock);
+ ql_remove(&arena->cache_bin_array_descriptor_ql,
+ &tcache->cache_bin_array_descriptor, link);
+ tcache_stats_merge(tsdn, tcache, arena);
+ malloc_mutex_unlock(tsdn, &arena->tcache_ql_mtx);
}
+ tcache->arena = NULL;
}
-tcache_t *
-tcache_get_hard(tsd_t *tsd)
-{
- arena_t *arena;
+void
+tcache_arena_reassociate(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
+ tcache_arena_dissociate(tsdn, tcache);
+ tcache_arena_associate(tsdn, tcache, arena);
+}
+
+bool
+tsd_tcache_enabled_data_init(tsd_t *tsd) {
+ /* Called upon tsd initialization. */
+ tsd_tcache_enabled_set(tsd, opt_tcache);
+ tsd_slow_update(tsd);
+
+ if (opt_tcache) {
+ /* Trigger tcache init. */
+ tsd_tcache_data_init(tsd);
+ }
- if (!tcache_enabled_get()) {
- if (tsd_nominal(tsd))
- tcache_enabled_set(false); /* Memoize. */
- return (NULL);
+ return false;
+}
+
+/* Initialize auto tcache (embedded in TSD). */
+static void
+tcache_init(tsd_t *tsd, tcache_t *tcache, void *avail_stack) {
+ memset(&tcache->link, 0, sizeof(ql_elm(tcache_t)));
+ tcache->prof_accumbytes = 0;
+ tcache->next_gc_bin = 0;
+ tcache->arena = NULL;
+
+ ticker_init(&tcache->gc_ticker, TCACHE_GC_INCR);
+
+ size_t stack_offset = 0;
+ assert((TCACHE_NSLOTS_SMALL_MAX & 1U) == 0);
+ memset(tcache->bins_small, 0, sizeof(cache_bin_t) * NBINS);
+ memset(tcache->bins_large, 0, sizeof(cache_bin_t) * (nhbins - NBINS));
+ unsigned i = 0;
+ for (; i < NBINS; i++) {
+ tcache->lg_fill_div[i] = 1;
+ stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
+ /*
+ * avail points past the available space. Allocations will
+ * access the slots toward higher addresses (for the benefit of
+ * prefetch).
+ */
+ tcache_small_bin_get(tcache, i)->avail =
+ (void **)((uintptr_t)avail_stack + (uintptr_t)stack_offset);
+ }
+ for (; i < nhbins; i++) {
+ stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
+ tcache_large_bin_get(tcache, i)->avail =
+ (void **)((uintptr_t)avail_stack + (uintptr_t)stack_offset);
}
- arena = arena_choose(tsd, NULL);
- if (unlikely(arena == NULL))
- return (NULL);
- return (tcache_create(tsd, arena));
+ assert(stack_offset == stack_nelms * sizeof(void *));
}
+/* Initialize auto tcache (embedded in TSD). */
+bool
+tsd_tcache_data_init(tsd_t *tsd) {
+ tcache_t *tcache = tsd_tcachep_get_unsafe(tsd);
+ assert(tcache_small_bin_get(tcache, 0)->avail == NULL);
+ size_t size = stack_nelms * sizeof(void *);
+ /* Avoid false cacheline sharing. */
+ size = sz_sa2u(size, CACHELINE);
+
+ void *avail_array = ipallocztm(tsd_tsdn(tsd), size, CACHELINE, true,
+ NULL, true, arena_get(TSDN_NULL, 0, true));
+ if (avail_array == NULL) {
+ return true;
+ }
+
+ tcache_init(tsd, tcache, avail_array);
+ /*
+ * Initialization is a bit tricky here. After malloc init is done, all
+ * threads can rely on arena_choose and associate tcache accordingly.
+ * However, the thread that does actual malloc bootstrapping relies on
+ * functional tsd, and it can only rely on a0. In that case, we
+ * associate its tcache to a0 temporarily, and later on
+ * arena_choose_hard() will re-associate properly.
+ */
+ tcache->arena = NULL;
+ arena_t *arena;
+ if (!malloc_initialized()) {
+ /* If in initialization, assign to a0. */
+ arena = arena_get(tsd_tsdn(tsd), 0, false);
+ tcache_arena_associate(tsd_tsdn(tsd), tcache, arena);
+ } else {
+ arena = arena_choose(tsd, NULL);
+ /* This may happen if thread.tcache.enabled is used. */
+ if (tcache->arena == NULL) {
+ tcache_arena_associate(tsd_tsdn(tsd), tcache, arena);
+ }
+ }
+ assert(arena == tcache->arena);
+
+ return false;
+}
+
+/* Created manual tcache for tcache.create mallctl. */
tcache_t *
-tcache_create(tsd_t *tsd, arena_t *arena)
-{
+tcache_create_explicit(tsd_t *tsd) {
tcache_t *tcache;
size_t size, stack_offset;
- unsigned i;
- size = offsetof(tcache_t, tbins) + (sizeof(tcache_bin_t) * nhbins);
+ size = sizeof(tcache_t);
/* Naturally align the pointer stacks. */
size = PTR_CEILING(size);
stack_offset = size;
size += stack_nelms * sizeof(void *);
/* Avoid false cacheline sharing. */
- size = sa2u(size, CACHELINE);
-
- tcache = ipallocztm(tsd, size, CACHELINE, true, false, true, a0get());
- if (tcache == NULL)
- return (NULL);
+ size = sz_sa2u(size, CACHELINE);
- tcache_arena_associate(tcache, arena);
-
- assert((TCACHE_NSLOTS_SMALL_MAX & 1U) == 0);
- for (i = 0; i < nhbins; i++) {
- tcache->tbins[i].lg_fill_div = 1;
- tcache->tbins[i].avail = (void **)((uintptr_t)tcache +
- (uintptr_t)stack_offset);
- stack_offset += tcache_bin_info[i].ncached_max * sizeof(void *);
+ tcache = ipallocztm(tsd_tsdn(tsd), size, CACHELINE, true, NULL, true,
+ arena_get(TSDN_NULL, 0, true));
+ if (tcache == NULL) {
+ return NULL;
}
- return (tcache);
+ tcache_init(tsd, tcache,
+ (void *)((uintptr_t)tcache + (uintptr_t)stack_offset));
+ tcache_arena_associate(tsd_tsdn(tsd), tcache, arena_ichoose(tsd, NULL));
+
+ return tcache;
}
static void
-tcache_destroy(tsd_t *tsd, tcache_t *tcache)
-{
- arena_t *arena;
- unsigned i;
+tcache_flush_cache(tsd_t *tsd, tcache_t *tcache) {
+ assert(tcache->arena != NULL);
- arena = arena_choose(tsd, NULL);
- tcache_arena_dissociate(tcache, arena);
-
- for (i = 0; i < NBINS; i++) {
- tcache_bin_t *tbin = &tcache->tbins[i];
+ for (unsigned i = 0; i < NBINS; i++) {
+ cache_bin_t *tbin = tcache_small_bin_get(tcache, i);
tcache_bin_flush_small(tsd, tcache, tbin, i, 0);
- if (config_stats && tbin->tstats.nrequests != 0) {
- arena_bin_t *bin = &arena->bins[i];
- malloc_mutex_lock(&bin->lock);
- bin->stats.nrequests += tbin->tstats.nrequests;
- malloc_mutex_unlock(&bin->lock);
+ if (config_stats) {
+ assert(tbin->tstats.nrequests == 0);
}
}
-
- for (; i < nhbins; i++) {
- tcache_bin_t *tbin = &tcache->tbins[i];
+ for (unsigned i = NBINS; i < nhbins; i++) {
+ cache_bin_t *tbin = tcache_large_bin_get(tcache, i);
tcache_bin_flush_large(tsd, tbin, i, 0, tcache);
- if (config_stats && tbin->tstats.nrequests != 0) {
- malloc_mutex_lock(&arena->lock);
- arena->stats.nrequests_large += tbin->tstats.nrequests;
- arena->stats.lstats[i - NBINS].nrequests +=
- tbin->tstats.nrequests;
- malloc_mutex_unlock(&arena->lock);
+ if (config_stats) {
+ assert(tbin->tstats.nrequests == 0);
}
}
if (config_prof && tcache->prof_accumbytes > 0 &&
- arena_prof_accum(arena, tcache->prof_accumbytes))
- prof_idump();
-
- idalloctm(tsd, tcache, false, true);
+ arena_prof_accum(tsd_tsdn(tsd), tcache->arena,
+ tcache->prof_accumbytes)) {
+ prof_idump(tsd_tsdn(tsd));
+ }
}
void
-tcache_cleanup(tsd_t *tsd)
-{
- tcache_t *tcache;
-
- if (!config_tcache)
- return;
+tcache_flush(tsd_t *tsd) {
+ assert(tcache_available(tsd));
+ tcache_flush_cache(tsd, tsd_tcachep_get(tsd));
+}
- if ((tcache = tsd_tcache_get(tsd)) != NULL) {
- tcache_destroy(tsd, tcache);
- tsd_tcache_set(tsd, NULL);
+static void
+tcache_destroy(tsd_t *tsd, tcache_t *tcache, bool tsd_tcache) {
+ tcache_flush_cache(tsd, tcache);
+ tcache_arena_dissociate(tsd_tsdn(tsd), tcache);
+
+ if (tsd_tcache) {
+ /* Release the avail array for the TSD embedded auto tcache. */
+ void *avail_array =
+ (void *)((uintptr_t)tcache_small_bin_get(tcache, 0)->avail -
+ (uintptr_t)tcache_bin_info[0].ncached_max * sizeof(void *));
+ idalloctm(tsd_tsdn(tsd), avail_array, NULL, NULL, true, true);
+ } else {
+ /* Release both the tcache struct and avail array. */
+ idalloctm(tsd_tsdn(tsd), tcache, NULL, NULL, true, true);
}
}
+/* For auto tcache (embedded in TSD) only. */
void
-tcache_enabled_cleanup(tsd_t *tsd)
-{
+tcache_cleanup(tsd_t *tsd) {
+ tcache_t *tcache = tsd_tcachep_get(tsd);
+ if (!tcache_available(tsd)) {
+ assert(tsd_tcache_enabled_get(tsd) == false);
+ if (config_debug) {
+ assert(tcache_small_bin_get(tcache, 0)->avail == NULL);
+ }
+ return;
+ }
+ assert(tsd_tcache_enabled_get(tsd));
+ assert(tcache_small_bin_get(tcache, 0)->avail != NULL);
- /* Do nothing. */
+ tcache_destroy(tsd, tcache, true);
+ if (config_debug) {
+ tcache_small_bin_get(tcache, 0)->avail = NULL;
+ }
}
-/* Caller must own arena->lock. */
void
-tcache_stats_merge(tcache_t *tcache, arena_t *arena)
-{
+tcache_stats_merge(tsdn_t *tsdn, tcache_t *tcache, arena_t *arena) {
unsigned i;
cassert(config_stats);
/* Merge and reset tcache stats. */
for (i = 0; i < NBINS; i++) {
- arena_bin_t *bin = &arena->bins[i];
- tcache_bin_t *tbin = &tcache->tbins[i];
- malloc_mutex_lock(&bin->lock);
+ bin_t *bin = &arena->bins[i];
+ cache_bin_t *tbin = tcache_small_bin_get(tcache, i);
+ malloc_mutex_lock(tsdn, &bin->lock);
bin->stats.nrequests += tbin->tstats.nrequests;
- malloc_mutex_unlock(&bin->lock);
+ malloc_mutex_unlock(tsdn, &bin->lock);
tbin->tstats.nrequests = 0;
}
for (; i < nhbins; i++) {
- malloc_large_stats_t *lstats = &arena->stats.lstats[i - NBINS];
- tcache_bin_t *tbin = &tcache->tbins[i];
- arena->stats.nrequests_large += tbin->tstats.nrequests;
- lstats->nrequests += tbin->tstats.nrequests;
+ cache_bin_t *tbin = tcache_large_bin_get(tcache, i);
+ arena_stats_large_nrequests_add(tsdn, &arena->stats, i,
+ tbin->tstats.nrequests);
tbin->tstats.nrequests = 0;
}
}
-bool
-tcaches_create(tsd_t *tsd, unsigned *r_ind)
-{
- tcache_t *tcache;
- tcaches_t *elm;
+static bool
+tcaches_create_prep(tsd_t *tsd) {
+ bool err;
+
+ malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
if (tcaches == NULL) {
- tcaches = base_alloc(sizeof(tcache_t *) *
- (MALLOCX_TCACHE_MAX+1));
- if (tcaches == NULL)
- return (true);
+ tcaches = base_alloc(tsd_tsdn(tsd), b0get(), sizeof(tcache_t *)
+ * (MALLOCX_TCACHE_MAX+1), CACHELINE);
+ if (tcaches == NULL) {
+ err = true;
+ goto label_return;
+ }
}
- if (tcaches_avail == NULL && tcaches_past > MALLOCX_TCACHE_MAX)
- return (true);
- tcache = tcache_create(tsd, a0get());
- if (tcache == NULL)
- return (true);
+ if (tcaches_avail == NULL && tcaches_past > MALLOCX_TCACHE_MAX) {
+ err = true;
+ goto label_return;
+ }
+
+ err = false;
+label_return:
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
+ return err;
+}
+bool
+tcaches_create(tsd_t *tsd, unsigned *r_ind) {
+ witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0);
+
+ bool err;
+
+ if (tcaches_create_prep(tsd)) {
+ err = true;
+ goto label_return;
+ }
+
+ tcache_t *tcache = tcache_create_explicit(tsd);
+ if (tcache == NULL) {
+ err = true;
+ goto label_return;
+ }
+
+ tcaches_t *elm;
+ malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
if (tcaches_avail != NULL) {
elm = tcaches_avail;
tcaches_avail = tcaches_avail->next;
elm->tcache = tcache;
- *r_ind = elm - tcaches;
+ *r_ind = (unsigned)(elm - tcaches);
} else {
elm = &tcaches[tcaches_past];
elm->tcache = tcache;
*r_ind = tcaches_past;
tcaches_past++;
}
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
- return (false);
+ err = false;
+label_return:
+ witness_assert_depth(tsdn_witness_tsdp_get(tsd_tsdn(tsd)), 0);
+ return err;
}
-static void
-tcaches_elm_flush(tsd_t *tsd, tcaches_t *elm)
-{
+static tcache_t *
+tcaches_elm_remove(tsd_t *tsd, tcaches_t *elm) {
+ malloc_mutex_assert_owner(tsd_tsdn(tsd), &tcaches_mtx);
- if (elm->tcache == NULL)
- return;
- tcache_destroy(tsd, elm->tcache);
+ if (elm->tcache == NULL) {
+ return NULL;
+ }
+ tcache_t *tcache = elm->tcache;
elm->tcache = NULL;
+ return tcache;
}
void
-tcaches_flush(tsd_t *tsd, unsigned ind)
-{
-
- tcaches_elm_flush(tsd, &tcaches[ind]);
+tcaches_flush(tsd_t *tsd, unsigned ind) {
+ malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
+ tcache_t *tcache = tcaches_elm_remove(tsd, &tcaches[ind]);
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
+ if (tcache != NULL) {
+ tcache_destroy(tsd, tcache, false);
+ }
}
void
-tcaches_destroy(tsd_t *tsd, unsigned ind)
-{
+tcaches_destroy(tsd_t *tsd, unsigned ind) {
+ malloc_mutex_lock(tsd_tsdn(tsd), &tcaches_mtx);
tcaches_t *elm = &tcaches[ind];
- tcaches_elm_flush(tsd, elm);
+ tcache_t *tcache = tcaches_elm_remove(tsd, elm);
elm->next = tcaches_avail;
tcaches_avail = elm;
+ malloc_mutex_unlock(tsd_tsdn(tsd), &tcaches_mtx);
+ if (tcache != NULL) {
+ tcache_destroy(tsd, tcache, false);
+ }
}
bool
-tcache_boot(void)
-{
- unsigned i;
-
- /*
- * If necessary, clamp opt_lg_tcache_max, now that large_maxclass is
- * known.
- */
- if (opt_lg_tcache_max < 0 || (1U << opt_lg_tcache_max) < SMALL_MAXCLASS)
+tcache_boot(tsdn_t *tsdn) {
+ /* If necessary, clamp opt_lg_tcache_max. */
+ if (opt_lg_tcache_max < 0 || (ZU(1) << opt_lg_tcache_max) <
+ SMALL_MAXCLASS) {
tcache_maxclass = SMALL_MAXCLASS;
- else if ((1U << opt_lg_tcache_max) > large_maxclass)
- tcache_maxclass = large_maxclass;
- else
- tcache_maxclass = (1U << opt_lg_tcache_max);
+ } else {
+ tcache_maxclass = (ZU(1) << opt_lg_tcache_max);
+ }
+
+ if (malloc_mutex_init(&tcaches_mtx, "tcaches", WITNESS_RANK_TCACHES,
+ malloc_mutex_rank_exclusive)) {
+ return true;
+ }
- nhbins = size2index(tcache_maxclass) + 1;
+ nhbins = sz_size2index(tcache_maxclass) + 1;
/* Initialize tcache_bin_info. */
- tcache_bin_info = (tcache_bin_info_t *)base_alloc(nhbins *
- sizeof(tcache_bin_info_t));
- if (tcache_bin_info == NULL)
- return (true);
+ tcache_bin_info = (cache_bin_info_t *)base_alloc(tsdn, b0get(), nhbins
+ * sizeof(cache_bin_info_t), CACHELINE);
+ if (tcache_bin_info == NULL) {
+ return true;
+ }
stack_nelms = 0;
+ unsigned i;
for (i = 0; i < NBINS; i++) {
- if ((arena_bin_info[i].nregs << 1) <= TCACHE_NSLOTS_SMALL_MIN) {
+ if ((bin_infos[i].nregs << 1) <= TCACHE_NSLOTS_SMALL_MIN) {
tcache_bin_info[i].ncached_max =
TCACHE_NSLOTS_SMALL_MIN;
- } else if ((arena_bin_info[i].nregs << 1) <=
+ } else if ((bin_infos[i].nregs << 1) <=
TCACHE_NSLOTS_SMALL_MAX) {
tcache_bin_info[i].ncached_max =
- (arena_bin_info[i].nregs << 1);
+ (bin_infos[i].nregs << 1);
} else {
tcache_bin_info[i].ncached_max =
TCACHE_NSLOTS_SMALL_MAX;
@@ -533,5 +692,26 @@ tcache_boot(void)
stack_nelms += tcache_bin_info[i].ncached_max;
}
- return (false);
+ return false;
+}
+
+void
+tcache_prefork(tsdn_t *tsdn) {
+ if (!config_prof && opt_tcache) {
+ malloc_mutex_prefork(tsdn, &tcaches_mtx);
+ }
+}
+
+void
+tcache_postfork_parent(tsdn_t *tsdn) {
+ if (!config_prof && opt_tcache) {
+ malloc_mutex_postfork_parent(tsdn, &tcaches_mtx);
+ }
+}
+
+void
+tcache_postfork_child(tsdn_t *tsdn) {
+ if (!config_prof && opt_tcache) {
+ malloc_mutex_postfork_child(tsdn, &tcaches_mtx);
+ }
}
diff --git a/deps/jemalloc/src/ticker.c b/deps/jemalloc/src/ticker.c
new file mode 100644
index 000000000..d7b8cd26c
--- /dev/null
+++ b/deps/jemalloc/src/ticker.c
@@ -0,0 +1,3 @@
+#define JEMALLOC_TICKER_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
diff --git a/deps/jemalloc/src/tsd.c b/deps/jemalloc/src/tsd.c
index 9ffe9afef..c1430682d 100644
--- a/deps/jemalloc/src/tsd.c
+++ b/deps/jemalloc/src/tsd.c
@@ -1,5 +1,10 @@
-#define JEMALLOC_TSD_C_
-#include "jemalloc/internal/jemalloc_internal.h"
+#define JEMALLOC_TSD_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/mutex.h"
+#include "jemalloc/internal/rtree.h"
/******************************************************************************/
/* Data. */
@@ -7,29 +12,158 @@
static unsigned ncleanups;
static malloc_tsd_cleanup_t cleanups[MALLOC_TSD_CLEANUPS_MAX];
-malloc_tsd_data(, , tsd_t, TSD_INITIALIZER)
+#ifdef JEMALLOC_MALLOC_THREAD_CLEANUP
+__thread tsd_t JEMALLOC_TLS_MODEL tsd_tls = TSD_INITIALIZER;
+__thread bool JEMALLOC_TLS_MODEL tsd_initialized = false;
+bool tsd_booted = false;
+#elif (defined(JEMALLOC_TLS))
+__thread tsd_t JEMALLOC_TLS_MODEL tsd_tls = TSD_INITIALIZER;
+pthread_key_t tsd_tsd;
+bool tsd_booted = false;
+#elif (defined(_WIN32))
+DWORD tsd_tsd;
+tsd_wrapper_t tsd_boot_wrapper = {false, TSD_INITIALIZER};
+bool tsd_booted = false;
+#else
+
+/*
+ * This contains a mutex, but it's pretty convenient to allow the mutex code to
+ * have a dependency on tsd. So we define the struct here, and only refer to it
+ * by pointer in the header.
+ */
+struct tsd_init_head_s {
+ ql_head(tsd_init_block_t) blocks;
+ malloc_mutex_t lock;
+};
+
+pthread_key_t tsd_tsd;
+tsd_init_head_t tsd_init_head = {
+ ql_head_initializer(blocks),
+ MALLOC_MUTEX_INITIALIZER
+};
+tsd_wrapper_t tsd_boot_wrapper = {
+ false,
+ TSD_INITIALIZER
+};
+bool tsd_booted = false;
+#endif
+
/******************************************************************************/
-void *
-malloc_tsd_malloc(size_t size)
-{
+void
+tsd_slow_update(tsd_t *tsd) {
+ if (tsd_nominal(tsd)) {
+ if (malloc_slow || !tsd_tcache_enabled_get(tsd) ||
+ tsd_reentrancy_level_get(tsd) > 0) {
+ tsd->state = tsd_state_nominal_slow;
+ } else {
+ tsd->state = tsd_state_nominal;
+ }
+ }
+}
- return (a0malloc(CACHELINE_CEILING(size)));
+static bool
+tsd_data_init(tsd_t *tsd) {
+ /*
+ * We initialize the rtree context first (before the tcache), since the
+ * tcache initialization depends on it.
+ */
+ rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd));
+
+ /*
+ * A nondeterministic seed based on the address of tsd reduces
+ * the likelihood of lockstep non-uniform cache index
+ * utilization among identical concurrent processes, but at the
+ * cost of test repeatability. For debug builds, instead use a
+ * deterministic seed.
+ */
+ *tsd_offset_statep_get(tsd) = config_debug ? 0 :
+ (uint64_t)(uintptr_t)tsd;
+
+ return tsd_tcache_enabled_data_init(tsd);
}
-void
-malloc_tsd_dalloc(void *wrapper)
-{
+static void
+assert_tsd_data_cleanup_done(tsd_t *tsd) {
+ assert(!tsd_nominal(tsd));
+ assert(*tsd_arenap_get_unsafe(tsd) == NULL);
+ assert(*tsd_iarenap_get_unsafe(tsd) == NULL);
+ assert(*tsd_arenas_tdata_bypassp_get_unsafe(tsd) == true);
+ assert(*tsd_arenas_tdatap_get_unsafe(tsd) == NULL);
+ assert(*tsd_tcache_enabledp_get_unsafe(tsd) == false);
+ assert(*tsd_prof_tdatap_get_unsafe(tsd) == NULL);
+}
- a0dalloc(wrapper);
+static bool
+tsd_data_init_nocleanup(tsd_t *tsd) {
+ assert(tsd->state == tsd_state_reincarnated ||
+ tsd->state == tsd_state_minimal_initialized);
+ /*
+ * During reincarnation, there is no guarantee that the cleanup function
+ * will be called (deallocation may happen after all tsd destructors).
+ * We set up tsd in a way that no cleanup is needed.
+ */
+ rtree_ctx_data_init(tsd_rtree_ctxp_get_unsafe(tsd));
+ *tsd_arenas_tdata_bypassp_get(tsd) = true;
+ *tsd_tcache_enabledp_get_unsafe(tsd) = false;
+ *tsd_reentrancy_levelp_get(tsd) = 1;
+ assert_tsd_data_cleanup_done(tsd);
+
+ return false;
}
-void
-malloc_tsd_no_cleanup(void *arg)
-{
+tsd_t *
+tsd_fetch_slow(tsd_t *tsd, bool minimal) {
+ assert(!tsd_fast(tsd));
+
+ if (tsd->state == tsd_state_nominal_slow) {
+ /* On slow path but no work needed. */
+ assert(malloc_slow || !tsd_tcache_enabled_get(tsd) ||
+ tsd_reentrancy_level_get(tsd) > 0 ||
+ *tsd_arenas_tdata_bypassp_get(tsd));
+ } else if (tsd->state == tsd_state_uninitialized) {
+ if (!minimal) {
+ tsd->state = tsd_state_nominal;
+ tsd_slow_update(tsd);
+ /* Trigger cleanup handler registration. */
+ tsd_set(tsd);
+ tsd_data_init(tsd);
+ } else {
+ tsd->state = tsd_state_minimal_initialized;
+ tsd_set(tsd);
+ tsd_data_init_nocleanup(tsd);
+ }
+ } else if (tsd->state == tsd_state_minimal_initialized) {
+ if (!minimal) {
+ /* Switch to fully initialized. */
+ tsd->state = tsd_state_nominal;
+ assert(*tsd_reentrancy_levelp_get(tsd) >= 1);
+ (*tsd_reentrancy_levelp_get(tsd))--;
+ tsd_slow_update(tsd);
+ tsd_data_init(tsd);
+ } else {
+ assert_tsd_data_cleanup_done(tsd);
+ }
+ } else if (tsd->state == tsd_state_purgatory) {
+ tsd->state = tsd_state_reincarnated;
+ tsd_set(tsd);
+ tsd_data_init_nocleanup(tsd);
+ } else {
+ assert(tsd->state == tsd_state_reincarnated);
+ }
+
+ return tsd;
+}
+
+void *
+malloc_tsd_malloc(size_t size) {
+ return a0malloc(CACHELINE_CEILING(size));
+}
- not_reached();
+void
+malloc_tsd_dalloc(void *wrapper) {
+ a0dalloc(wrapper);
}
#if defined(JEMALLOC_MALLOC_THREAD_CLEANUP) || defined(_WIN32)
@@ -37,21 +171,22 @@ malloc_tsd_no_cleanup(void *arg)
JEMALLOC_EXPORT
#endif
void
-_malloc_thread_cleanup(void)
-{
+_malloc_thread_cleanup(void) {
bool pending[MALLOC_TSD_CLEANUPS_MAX], again;
unsigned i;
- for (i = 0; i < ncleanups; i++)
+ for (i = 0; i < ncleanups; i++) {
pending[i] = true;
+ }
do {
again = false;
for (i = 0; i < ncleanups; i++) {
if (pending[i]) {
pending[i] = cleanups[i]();
- if (pending[i])
+ if (pending[i]) {
again = true;
+ }
}
}
} while (again);
@@ -59,28 +194,44 @@ _malloc_thread_cleanup(void)
#endif
void
-malloc_tsd_cleanup_register(bool (*f)(void))
-{
-
+malloc_tsd_cleanup_register(bool (*f)(void)) {
assert(ncleanups < MALLOC_TSD_CLEANUPS_MAX);
cleanups[ncleanups] = f;
ncleanups++;
}
+static void
+tsd_do_data_cleanup(tsd_t *tsd) {
+ prof_tdata_cleanup(tsd);
+ iarena_cleanup(tsd);
+ arena_cleanup(tsd);
+ arenas_tdata_cleanup(tsd);
+ tcache_cleanup(tsd);
+ witnesses_cleanup(tsd_witness_tsdp_get_unsafe(tsd));
+}
+
void
-tsd_cleanup(void *arg)
-{
+tsd_cleanup(void *arg) {
tsd_t *tsd = (tsd_t *)arg;
switch (tsd->state) {
case tsd_state_uninitialized:
/* Do nothing. */
break;
+ case tsd_state_minimal_initialized:
+ /* This implies the thread only did free() in its life time. */
+ /* Fall through. */
+ case tsd_state_reincarnated:
+ /*
+ * Reincarnated means another destructor deallocated memory
+ * after the destructor was called. Cleanup isn't required but
+ * is still called for testing and completeness.
+ */
+ assert_tsd_data_cleanup_done(tsd);
+ /* Fall through. */
case tsd_state_nominal:
-#define O(n, t) \
- n##_cleanup(tsd);
-MALLOC_TSD
-#undef O
+ case tsd_state_nominal_slow:
+ tsd_do_data_cleanup(tsd);
tsd->state = tsd_state_purgatory;
tsd_set(tsd);
break;
@@ -92,44 +243,43 @@ MALLOC_TSD
* nothing, and do not request another callback.
*/
break;
- case tsd_state_reincarnated:
- /*
- * Another destructor deallocated memory after this destructor
- * was called. Reset state to tsd_state_purgatory and request
- * another callback.
- */
- tsd->state = tsd_state_purgatory;
- tsd_set(tsd);
- break;
default:
not_reached();
}
+#ifdef JEMALLOC_JET
+ test_callback_t test_callback = *tsd_test_callbackp_get_unsafe(tsd);
+ int *data = tsd_test_datap_get_unsafe(tsd);
+ if (test_callback != NULL) {
+ test_callback(data);
+ }
+#endif
}
-bool
-malloc_tsd_boot0(void)
-{
+tsd_t *
+malloc_tsd_boot0(void) {
+ tsd_t *tsd;
ncleanups = 0;
- if (tsd_boot0())
- return (true);
- *tsd_arenas_cache_bypassp_get(tsd_fetch()) = true;
- return (false);
+ if (tsd_boot0()) {
+ return NULL;
+ }
+ tsd = tsd_fetch();
+ *tsd_arenas_tdata_bypassp_get(tsd) = true;
+ return tsd;
}
void
-malloc_tsd_boot1(void)
-{
-
+malloc_tsd_boot1(void) {
tsd_boot1();
- *tsd_arenas_cache_bypassp_get(tsd_fetch()) = false;
+ tsd_t *tsd = tsd_fetch();
+ /* malloc_slow has been set properly. Update tsd_slow. */
+ tsd_slow_update(tsd);
+ *tsd_arenas_tdata_bypassp_get(tsd) = false;
}
#ifdef _WIN32
static BOOL WINAPI
-_tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
-{
-
+_tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) {
#ifdef JEMALLOC_LAZY_LOCK
case DLL_THREAD_ATTACH:
@@ -142,52 +292,60 @@ _tls_callback(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
default:
break;
}
- return (true);
+ return true;
}
+/*
+ * We need to be able to say "read" here (in the "pragma section"), but have
+ * hooked "read". We won't read for the rest of the file, so we can get away
+ * with unhooking.
+ */
+#ifdef read
+# undef read
+#endif
+
#ifdef _MSC_VER
# ifdef _M_IX86
# pragma comment(linker, "/INCLUDE:__tls_used")
+# pragma comment(linker, "/INCLUDE:_tls_callback")
# else
# pragma comment(linker, "/INCLUDE:_tls_used")
+# pragma comment(linker, "/INCLUDE:tls_callback")
# endif
# pragma section(".CRT$XLY",long,read)
#endif
JEMALLOC_SECTION(".CRT$XLY") JEMALLOC_ATTR(used)
-static BOOL (WINAPI *const tls_callback)(HINSTANCE hinstDLL,
+BOOL (WINAPI *const tls_callback)(HINSTANCE hinstDLL,
DWORD fdwReason, LPVOID lpvReserved) = _tls_callback;
#endif
#if (!defined(JEMALLOC_MALLOC_THREAD_CLEANUP) && !defined(JEMALLOC_TLS) && \
!defined(_WIN32))
void *
-tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block)
-{
+tsd_init_check_recursion(tsd_init_head_t *head, tsd_init_block_t *block) {
pthread_t self = pthread_self();
tsd_init_block_t *iter;
/* Check whether this thread has already inserted into the list. */
- malloc_mutex_lock(&head->lock);
+ malloc_mutex_lock(TSDN_NULL, &head->lock);
ql_foreach(iter, &head->blocks, link) {
if (iter->thread == self) {
- malloc_mutex_unlock(&head->lock);
- return (iter->data);
+ malloc_mutex_unlock(TSDN_NULL, &head->lock);
+ return iter->data;
}
}
/* Insert block into list. */
ql_elm_new(block, link);
block->thread = self;
ql_tail_insert(&head->blocks, block, link);
- malloc_mutex_unlock(&head->lock);
- return (NULL);
+ malloc_mutex_unlock(TSDN_NULL, &head->lock);
+ return NULL;
}
void
-tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block)
-{
-
- malloc_mutex_lock(&head->lock);
+tsd_init_finish(tsd_init_head_t *head, tsd_init_block_t *block) {
+ malloc_mutex_lock(TSDN_NULL, &head->lock);
ql_remove(&head->blocks, block, link);
- malloc_mutex_unlock(&head->lock);
+ malloc_mutex_unlock(TSDN_NULL, &head->lock);
}
#endif
diff --git a/deps/jemalloc/src/valgrind.c b/deps/jemalloc/src/valgrind.c
deleted file mode 100644
index 8e7ef3a2e..000000000
--- a/deps/jemalloc/src/valgrind.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include "jemalloc/internal/jemalloc_internal.h"
-#ifndef JEMALLOC_VALGRIND
-# error "This source file is for Valgrind integration."
-#endif
-
-#include <valgrind/memcheck.h>
-
-void
-valgrind_make_mem_noaccess(void *ptr, size_t usize)
-{
-
- VALGRIND_MAKE_MEM_NOACCESS(ptr, usize);
-}
-
-void
-valgrind_make_mem_undefined(void *ptr, size_t usize)
-{
-
- VALGRIND_MAKE_MEM_UNDEFINED(ptr, usize);
-}
-
-void
-valgrind_make_mem_defined(void *ptr, size_t usize)
-{
-
- VALGRIND_MAKE_MEM_DEFINED(ptr, usize);
-}
-
-void
-valgrind_freelike_block(void *ptr, size_t usize)
-{
-
- VALGRIND_FREELIKE_BLOCK(ptr, usize);
-}
diff --git a/deps/jemalloc/src/witness.c b/deps/jemalloc/src/witness.c
new file mode 100644
index 000000000..f42b72ad1
--- /dev/null
+++ b/deps/jemalloc/src/witness.c
@@ -0,0 +1,100 @@
+#define JEMALLOC_WITNESS_C_
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+#include "jemalloc/internal/malloc_io.h"
+
+void
+witness_init(witness_t *witness, const char *name, witness_rank_t rank,
+ witness_comp_t *comp, void *opaque) {
+ witness->name = name;
+ witness->rank = rank;
+ witness->comp = comp;
+ witness->opaque = opaque;
+}
+
+static void
+witness_lock_error_impl(const witness_list_t *witnesses,
+ const witness_t *witness) {
+ witness_t *w;
+
+ malloc_printf("<jemalloc>: Lock rank order reversal:");
+ ql_foreach(w, witnesses, link) {
+ malloc_printf(" %s(%u)", w->name, w->rank);
+ }
+ malloc_printf(" %s(%u)\n", witness->name, witness->rank);
+ abort();
+}
+witness_lock_error_t *JET_MUTABLE witness_lock_error = witness_lock_error_impl;
+
+static void
+witness_owner_error_impl(const witness_t *witness) {
+ malloc_printf("<jemalloc>: Should own %s(%u)\n", witness->name,
+ witness->rank);
+ abort();
+}
+witness_owner_error_t *JET_MUTABLE witness_owner_error =
+ witness_owner_error_impl;
+
+static void
+witness_not_owner_error_impl(const witness_t *witness) {
+ malloc_printf("<jemalloc>: Should not own %s(%u)\n", witness->name,
+ witness->rank);
+ abort();
+}
+witness_not_owner_error_t *JET_MUTABLE witness_not_owner_error =
+ witness_not_owner_error_impl;
+
+static void
+witness_depth_error_impl(const witness_list_t *witnesses,
+ witness_rank_t rank_inclusive, unsigned depth) {
+ witness_t *w;
+
+ malloc_printf("<jemalloc>: Should own %u lock%s of rank >= %u:", depth,
+ (depth != 1) ? "s" : "", rank_inclusive);
+ ql_foreach(w, witnesses, link) {
+ malloc_printf(" %s(%u)", w->name, w->rank);
+ }
+ malloc_printf("\n");
+ abort();
+}
+witness_depth_error_t *JET_MUTABLE witness_depth_error =
+ witness_depth_error_impl;
+
+void
+witnesses_cleanup(witness_tsd_t *witness_tsd) {
+ witness_assert_lockless(witness_tsd_tsdn(witness_tsd));
+
+ /* Do nothing. */
+}
+
+void
+witness_prefork(witness_tsd_t *witness_tsd) {
+ if (!config_debug) {
+ return;
+ }
+ witness_tsd->forking = true;
+}
+
+void
+witness_postfork_parent(witness_tsd_t *witness_tsd) {
+ if (!config_debug) {
+ return;
+ }
+ witness_tsd->forking = false;
+}
+
+void
+witness_postfork_child(witness_tsd_t *witness_tsd) {
+ if (!config_debug) {
+ return;
+ }
+#ifndef JEMALLOC_MUTEX_INIT_CB
+ witness_list_t *witnesses;
+
+ witnesses = &witness_tsd->witnesses;
+ ql_new(witnesses);
+#endif
+ witness_tsd->forking = false;
+}
diff --git a/deps/jemalloc/src/zone.c b/deps/jemalloc/src/zone.c
index 12e1734a9..23dfdd04a 100644
--- a/deps/jemalloc/src/zone.c
+++ b/deps/jemalloc/src/zone.c
@@ -1,10 +1,83 @@
-#include "jemalloc/internal/jemalloc_internal.h"
+#include "jemalloc/internal/jemalloc_preamble.h"
+#include "jemalloc/internal/jemalloc_internal_includes.h"
+
+#include "jemalloc/internal/assert.h"
+
#ifndef JEMALLOC_ZONE
# error "This source file is for zones on Darwin (OS X)."
#endif
+/* Definitions of the following structs in malloc/malloc.h might be too old
+ * for the built binary to run on newer versions of OSX. So use the newest
+ * possible version of those structs.
+ */
+typedef struct _malloc_zone_t {
+ void *reserved1;
+ void *reserved2;
+ size_t (*size)(struct _malloc_zone_t *, const void *);
+ void *(*malloc)(struct _malloc_zone_t *, size_t);
+ void *(*calloc)(struct _malloc_zone_t *, size_t, size_t);
+ void *(*valloc)(struct _malloc_zone_t *, size_t);
+ void (*free)(struct _malloc_zone_t *, void *);
+ void *(*realloc)(struct _malloc_zone_t *, void *, size_t);
+ void (*destroy)(struct _malloc_zone_t *);
+ const char *zone_name;
+ unsigned (*batch_malloc)(struct _malloc_zone_t *, size_t, void **, unsigned);
+ void (*batch_free)(struct _malloc_zone_t *, void **, unsigned);
+ struct malloc_introspection_t *introspect;
+ unsigned version;
+ void *(*memalign)(struct _malloc_zone_t *, size_t, size_t);
+ void (*free_definite_size)(struct _malloc_zone_t *, void *, size_t);
+ size_t (*pressure_relief)(struct _malloc_zone_t *, size_t);
+} malloc_zone_t;
+
+typedef struct {
+ vm_address_t address;
+ vm_size_t size;
+} vm_range_t;
+
+typedef struct malloc_statistics_t {
+ unsigned blocks_in_use;
+ size_t size_in_use;
+ size_t max_size_in_use;
+ size_t size_allocated;
+} malloc_statistics_t;
+
+typedef kern_return_t memory_reader_t(task_t, vm_address_t, vm_size_t, void **);
+
+typedef void vm_range_recorder_t(task_t, void *, unsigned type, vm_range_t *, unsigned);
+
+typedef struct malloc_introspection_t {
+ kern_return_t (*enumerator)(task_t, void *, unsigned, vm_address_t, memory_reader_t, vm_range_recorder_t);
+ size_t (*good_size)(malloc_zone_t *, size_t);
+ boolean_t (*check)(malloc_zone_t *);
+ void (*print)(malloc_zone_t *, boolean_t);
+ void (*log)(malloc_zone_t *, void *);
+ void (*force_lock)(malloc_zone_t *);
+ void (*force_unlock)(malloc_zone_t *);
+ void (*statistics)(malloc_zone_t *, malloc_statistics_t *);
+ boolean_t (*zone_locked)(malloc_zone_t *);
+ boolean_t (*enable_discharge_checking)(malloc_zone_t *);
+ boolean_t (*disable_discharge_checking)(malloc_zone_t *);
+ void (*discharge)(malloc_zone_t *, void *);
+#ifdef __BLOCKS__
+ void (*enumerate_discharged_pointers)(malloc_zone_t *, void (^)(void *, void *));
+#else
+ void *enumerate_unavailable_without_blocks;
+#endif
+ void (*reinit_lock)(malloc_zone_t *);
+} malloc_introspection_t;
+
+extern kern_return_t malloc_get_all_zones(task_t, memory_reader_t, vm_address_t **, unsigned *);
+
+extern malloc_zone_t *malloc_default_zone(void);
+
+extern void malloc_zone_register(malloc_zone_t *zone);
+
+extern void malloc_zone_unregister(malloc_zone_t *zone);
+
/*
- * The malloc_default_purgeable_zone function is only available on >= 10.6.
+ * The malloc_default_purgeable_zone() function is only available on >= 10.6.
* We need to check whether it is present at runtime, thus the weak_import.
*/
extern malloc_zone_t *malloc_default_purgeable_zone(void)
@@ -13,30 +86,43 @@ JEMALLOC_ATTR(weak_import);
/******************************************************************************/
/* Data. */
-static malloc_zone_t zone;
-static struct malloc_introspection_t zone_introspect;
+static malloc_zone_t *default_zone, *purgeable_zone;
+static malloc_zone_t jemalloc_zone;
+static struct malloc_introspection_t jemalloc_zone_introspect;
+static pid_t zone_force_lock_pid = -1;
/******************************************************************************/
/* Function prototypes for non-inline static functions. */
-static size_t zone_size(malloc_zone_t *zone, void *ptr);
+static size_t zone_size(malloc_zone_t *zone, const void *ptr);
static void *zone_malloc(malloc_zone_t *zone, size_t size);
static void *zone_calloc(malloc_zone_t *zone, size_t num, size_t size);
static void *zone_valloc(malloc_zone_t *zone, size_t size);
static void zone_free(malloc_zone_t *zone, void *ptr);
static void *zone_realloc(malloc_zone_t *zone, void *ptr, size_t size);
-#if (JEMALLOC_ZONE_VERSION >= 5)
static void *zone_memalign(malloc_zone_t *zone, size_t alignment,
-#endif
-#if (JEMALLOC_ZONE_VERSION >= 6)
size_t size);
static void zone_free_definite_size(malloc_zone_t *zone, void *ptr,
size_t size);
-#endif
-static void *zone_destroy(malloc_zone_t *zone);
+static void zone_destroy(malloc_zone_t *zone);
+static unsigned zone_batch_malloc(struct _malloc_zone_t *zone, size_t size,
+ void **results, unsigned num_requested);
+static void zone_batch_free(struct _malloc_zone_t *zone,
+ void **to_be_freed, unsigned num_to_be_freed);
+static size_t zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal);
static size_t zone_good_size(malloc_zone_t *zone, size_t size);
+static kern_return_t zone_enumerator(task_t task, void *data, unsigned type_mask,
+ vm_address_t zone_address, memory_reader_t reader,
+ vm_range_recorder_t recorder);
+static boolean_t zone_check(malloc_zone_t *zone);
+static void zone_print(malloc_zone_t *zone, boolean_t verbose);
+static void zone_log(malloc_zone_t *zone, void *address);
static void zone_force_lock(malloc_zone_t *zone);
static void zone_force_unlock(malloc_zone_t *zone);
+static void zone_statistics(malloc_zone_t *zone,
+ malloc_statistics_t *stats);
+static boolean_t zone_locked(malloc_zone_t *zone);
+static void zone_reinit_lock(malloc_zone_t *zone);
/******************************************************************************/
/*
@@ -44,9 +130,7 @@ static void zone_force_unlock(malloc_zone_t *zone);
*/
static size_t
-zone_size(malloc_zone_t *zone, void *ptr)
-{
-
+zone_size(malloc_zone_t *zone, const void *ptr) {
/*
* There appear to be places within Darwin (such as setenv(3)) that
* cause calls to this function with pointers that *no* zone owns. If
@@ -54,40 +138,33 @@ zone_size(malloc_zone_t *zone, void *ptr)
* our zone into two parts, and use one as the default allocator and
* the other as the default deallocator/reallocator. Since that will
* not work in practice, we must check all pointers to assure that they
- * reside within a mapped chunk before determining size.
+ * reside within a mapped extent before determining size.
*/
- return (ivsalloc(ptr, config_prof));
+ return ivsalloc(tsdn_fetch(), ptr);
}
static void *
-zone_malloc(malloc_zone_t *zone, size_t size)
-{
-
- return (je_malloc(size));
+zone_malloc(malloc_zone_t *zone, size_t size) {
+ return je_malloc(size);
}
static void *
-zone_calloc(malloc_zone_t *zone, size_t num, size_t size)
-{
-
- return (je_calloc(num, size));
+zone_calloc(malloc_zone_t *zone, size_t num, size_t size) {
+ return je_calloc(num, size);
}
static void *
-zone_valloc(malloc_zone_t *zone, size_t size)
-{
+zone_valloc(malloc_zone_t *zone, size_t size) {
void *ret = NULL; /* Assignment avoids useless compiler warning. */
je_posix_memalign(&ret, PAGE, size);
- return (ret);
+ return ret;
}
static void
-zone_free(malloc_zone_t *zone, void *ptr)
-{
-
- if (ivsalloc(ptr, config_prof) != 0) {
+zone_free(malloc_zone_t *zone, void *ptr) {
+ if (ivsalloc(tsdn_fetch(), ptr) != 0) {
je_free(ptr);
return;
}
@@ -96,155 +173,235 @@ zone_free(malloc_zone_t *zone, void *ptr)
}
static void *
-zone_realloc(malloc_zone_t *zone, void *ptr, size_t size)
-{
-
- if (ivsalloc(ptr, config_prof) != 0)
- return (je_realloc(ptr, size));
+zone_realloc(malloc_zone_t *zone, void *ptr, size_t size) {
+ if (ivsalloc(tsdn_fetch(), ptr) != 0) {
+ return je_realloc(ptr, size);
+ }
- return (realloc(ptr, size));
+ return realloc(ptr, size);
}
-#if (JEMALLOC_ZONE_VERSION >= 5)
static void *
-zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size)
-{
+zone_memalign(malloc_zone_t *zone, size_t alignment, size_t size) {
void *ret = NULL; /* Assignment avoids useless compiler warning. */
je_posix_memalign(&ret, alignment, size);
- return (ret);
+ return ret;
}
-#endif
-#if (JEMALLOC_ZONE_VERSION >= 6)
static void
-zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size)
-{
+zone_free_definite_size(malloc_zone_t *zone, void *ptr, size_t size) {
+ size_t alloc_size;
- if (ivsalloc(ptr, config_prof) != 0) {
- assert(ivsalloc(ptr, config_prof) == size);
+ alloc_size = ivsalloc(tsdn_fetch(), ptr);
+ if (alloc_size != 0) {
+ assert(alloc_size == size);
je_free(ptr);
return;
}
free(ptr);
}
-#endif
-
-static void *
-zone_destroy(malloc_zone_t *zone)
-{
+static void
+zone_destroy(malloc_zone_t *zone) {
/* This function should never be called. */
not_reached();
- return (NULL);
+}
+
+static unsigned
+zone_batch_malloc(struct _malloc_zone_t *zone, size_t size, void **results,
+ unsigned num_requested) {
+ unsigned i;
+
+ for (i = 0; i < num_requested; i++) {
+ results[i] = je_malloc(size);
+ if (!results[i])
+ break;
+ }
+
+ return i;
+}
+
+static void
+zone_batch_free(struct _malloc_zone_t *zone, void **to_be_freed,
+ unsigned num_to_be_freed) {
+ unsigned i;
+
+ for (i = 0; i < num_to_be_freed; i++) {
+ zone_free(zone, to_be_freed[i]);
+ to_be_freed[i] = NULL;
+ }
}
static size_t
-zone_good_size(malloc_zone_t *zone, size_t size)
-{
+zone_pressure_relief(struct _malloc_zone_t *zone, size_t goal) {
+ return 0;
+}
- if (size == 0)
+static size_t
+zone_good_size(malloc_zone_t *zone, size_t size) {
+ if (size == 0) {
size = 1;
- return (s2u(size));
+ }
+ return sz_s2u(size);
}
-static void
-zone_force_lock(malloc_zone_t *zone)
-{
+static kern_return_t
+zone_enumerator(task_t task, void *data, unsigned type_mask,
+ vm_address_t zone_address, memory_reader_t reader,
+ vm_range_recorder_t recorder) {
+ return KERN_SUCCESS;
+}
- if (isthreaded)
- jemalloc_prefork();
+static boolean_t
+zone_check(malloc_zone_t *zone) {
+ return true;
}
static void
-zone_force_unlock(malloc_zone_t *zone)
-{
+zone_print(malloc_zone_t *zone, boolean_t verbose) {
+}
- if (isthreaded)
- jemalloc_postfork_parent();
+static void
+zone_log(malloc_zone_t *zone, void *address) {
}
-JEMALLOC_ATTR(constructor)
-void
-register_zone(void)
-{
+static void
+zone_force_lock(malloc_zone_t *zone) {
+ if (isthreaded) {
+ /*
+ * See the note in zone_force_unlock, below, to see why we need
+ * this.
+ */
+ assert(zone_force_lock_pid == -1);
+ zone_force_lock_pid = getpid();
+ jemalloc_prefork();
+ }
+}
+static void
+zone_force_unlock(malloc_zone_t *zone) {
/*
- * If something else replaced the system default zone allocator, don't
- * register jemalloc's.
+ * zone_force_lock and zone_force_unlock are the entry points to the
+ * forking machinery on OS X. The tricky thing is, the child is not
+ * allowed to unlock mutexes locked in the parent, even if owned by the
+ * forking thread (and the mutex type we use in OS X will fail an assert
+ * if we try). In the child, we can get away with reinitializing all
+ * the mutexes, which has the effect of unlocking them. In the parent,
+ * doing this would mean we wouldn't wake any waiters blocked on the
+ * mutexes we unlock. So, we record the pid of the current thread in
+ * zone_force_lock, and use that to detect if we're in the parent or
+ * child here, to decide which unlock logic we need.
*/
- malloc_zone_t *default_zone = malloc_default_zone();
- malloc_zone_t *purgeable_zone = NULL;
- if (!default_zone->zone_name ||
- strcmp(default_zone->zone_name, "DefaultMallocZone") != 0) {
- return;
+ if (isthreaded) {
+ assert(zone_force_lock_pid != -1);
+ if (getpid() == zone_force_lock_pid) {
+ jemalloc_postfork_parent();
+ } else {
+ jemalloc_postfork_child();
+ }
+ zone_force_lock_pid = -1;
}
+}
- zone.size = (void *)zone_size;
- zone.malloc = (void *)zone_malloc;
- zone.calloc = (void *)zone_calloc;
- zone.valloc = (void *)zone_valloc;
- zone.free = (void *)zone_free;
- zone.realloc = (void *)zone_realloc;
- zone.destroy = (void *)zone_destroy;
- zone.zone_name = "jemalloc_zone";
- zone.batch_malloc = NULL;
- zone.batch_free = NULL;
- zone.introspect = &zone_introspect;
- zone.version = JEMALLOC_ZONE_VERSION;
-#if (JEMALLOC_ZONE_VERSION >= 5)
- zone.memalign = zone_memalign;
-#endif
-#if (JEMALLOC_ZONE_VERSION >= 6)
- zone.free_definite_size = zone_free_definite_size;
-#endif
-#if (JEMALLOC_ZONE_VERSION >= 8)
- zone.pressure_relief = NULL;
-#endif
+static void
+zone_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) {
+ /* We make no effort to actually fill the values */
+ stats->blocks_in_use = 0;
+ stats->size_in_use = 0;
+ stats->max_size_in_use = 0;
+ stats->size_allocated = 0;
+}
- zone_introspect.enumerator = NULL;
- zone_introspect.good_size = (void *)zone_good_size;
- zone_introspect.check = NULL;
- zone_introspect.print = NULL;
- zone_introspect.log = NULL;
- zone_introspect.force_lock = (void *)zone_force_lock;
- zone_introspect.force_unlock = (void *)zone_force_unlock;
- zone_introspect.statistics = NULL;
-#if (JEMALLOC_ZONE_VERSION >= 6)
- zone_introspect.zone_locked = NULL;
-#endif
-#if (JEMALLOC_ZONE_VERSION >= 7)
- zone_introspect.enable_discharge_checking = NULL;
- zone_introspect.disable_discharge_checking = NULL;
- zone_introspect.discharge = NULL;
+static boolean_t
+zone_locked(malloc_zone_t *zone) {
+ /* Pretend no lock is being held */
+ return false;
+}
+
+static void
+zone_reinit_lock(malloc_zone_t *zone) {
+ /* As of OSX 10.12, this function is only used when force_unlock would
+ * be used if the zone version were < 9. So just use force_unlock. */
+ zone_force_unlock(zone);
+}
+
+static void
+zone_init(void) {
+ jemalloc_zone.size = zone_size;
+ jemalloc_zone.malloc = zone_malloc;
+ jemalloc_zone.calloc = zone_calloc;
+ jemalloc_zone.valloc = zone_valloc;
+ jemalloc_zone.free = zone_free;
+ jemalloc_zone.realloc = zone_realloc;
+ jemalloc_zone.destroy = zone_destroy;
+ jemalloc_zone.zone_name = "jemalloc_zone";
+ jemalloc_zone.batch_malloc = zone_batch_malloc;
+ jemalloc_zone.batch_free = zone_batch_free;
+ jemalloc_zone.introspect = &jemalloc_zone_introspect;
+ jemalloc_zone.version = 9;
+ jemalloc_zone.memalign = zone_memalign;
+ jemalloc_zone.free_definite_size = zone_free_definite_size;
+ jemalloc_zone.pressure_relief = zone_pressure_relief;
+
+ jemalloc_zone_introspect.enumerator = zone_enumerator;
+ jemalloc_zone_introspect.good_size = zone_good_size;
+ jemalloc_zone_introspect.check = zone_check;
+ jemalloc_zone_introspect.print = zone_print;
+ jemalloc_zone_introspect.log = zone_log;
+ jemalloc_zone_introspect.force_lock = zone_force_lock;
+ jemalloc_zone_introspect.force_unlock = zone_force_unlock;
+ jemalloc_zone_introspect.statistics = zone_statistics;
+ jemalloc_zone_introspect.zone_locked = zone_locked;
+ jemalloc_zone_introspect.enable_discharge_checking = NULL;
+ jemalloc_zone_introspect.disable_discharge_checking = NULL;
+ jemalloc_zone_introspect.discharge = NULL;
#ifdef __BLOCKS__
- zone_introspect.enumerate_discharged_pointers = NULL;
+ jemalloc_zone_introspect.enumerate_discharged_pointers = NULL;
#else
- zone_introspect.enumerate_unavailable_without_blocks = NULL;
-#endif
+ jemalloc_zone_introspect.enumerate_unavailable_without_blocks = NULL;
#endif
+ jemalloc_zone_introspect.reinit_lock = zone_reinit_lock;
+}
+
+static malloc_zone_t *
+zone_default_get(void) {
+ malloc_zone_t **zones = NULL;
+ unsigned int num_zones = 0;
/*
- * The default purgeable zone is created lazily by OSX's libc. It uses
- * the default zone when it is created for "small" allocations
- * (< 15 KiB), but assumes the default zone is a scalable_zone. This
- * obviously fails when the default zone is the jemalloc zone, so
- * malloc_default_purgeable_zone is called beforehand so that the
- * default purgeable zone is created when the default zone is still
- * a scalable_zone. As purgeable zones only exist on >= 10.6, we need
- * to check for the existence of malloc_default_purgeable_zone() at
- * run time.
+ * On OSX 10.12, malloc_default_zone returns a special zone that is not
+ * present in the list of registered zones. That zone uses a "lite zone"
+ * if one is present (apparently enabled when malloc stack logging is
+ * enabled), or the first registered zone otherwise. In practice this
+ * means unless malloc stack logging is enabled, the first registered
+ * zone is the default. So get the list of zones to get the first one,
+ * instead of relying on malloc_default_zone.
*/
- if (malloc_default_purgeable_zone != NULL)
- purgeable_zone = malloc_default_purgeable_zone();
+ if (KERN_SUCCESS != malloc_get_all_zones(0, NULL,
+ (vm_address_t**)&zones, &num_zones)) {
+ /*
+ * Reset the value in case the failure happened after it was
+ * set.
+ */
+ num_zones = 0;
+ }
- /* Register the custom zone. At this point it won't be the default. */
- malloc_zone_register(&zone);
+ if (num_zones) {
+ return zones[0];
+ }
+
+ return malloc_default_zone();
+}
+
+/* As written, this function can only promote jemalloc_zone. */
+static void
+zone_promote(void) {
+ malloc_zone_t *zone;
do {
- default_zone = malloc_default_zone();
/*
* Unregister and reregister the default zone. On OSX >= 10.6,
* unregistering takes the last registered zone and places it
@@ -255,6 +412,7 @@ register_zone(void)
*/
malloc_zone_unregister(default_zone);
malloc_zone_register(default_zone);
+
/*
* On OSX 10.6, having the default purgeable zone appear before
* the default zone makes some things crash because it thinks it
@@ -266,9 +424,46 @@ register_zone(void)
* above, i.e. the default zone. Registering it again then puts
* it at the end, obviously after the default zone.
*/
- if (purgeable_zone) {
+ if (purgeable_zone != NULL) {
malloc_zone_unregister(purgeable_zone);
malloc_zone_register(purgeable_zone);
}
- } while (malloc_default_zone() != &zone);
+
+ zone = zone_default_get();
+ } while (zone != &jemalloc_zone);
+}
+
+JEMALLOC_ATTR(constructor)
+void
+zone_register(void) {
+ /*
+ * If something else replaced the system default zone allocator, don't
+ * register jemalloc's.
+ */
+ default_zone = zone_default_get();
+ if (!default_zone->zone_name || strcmp(default_zone->zone_name,
+ "DefaultMallocZone") != 0) {
+ return;
+ }
+
+ /*
+ * The default purgeable zone is created lazily by OSX's libc. It uses
+ * the default zone when it is created for "small" allocations
+ * (< 15 KiB), but assumes the default zone is a scalable_zone. This
+ * obviously fails when the default zone is the jemalloc zone, so
+ * malloc_default_purgeable_zone() is called beforehand so that the
+ * default purgeable zone is created when the default zone is still
+ * a scalable_zone. As purgeable zones only exist on >= 10.6, we need
+ * to check for the existence of malloc_default_purgeable_zone() at
+ * run time.
+ */
+ purgeable_zone = (malloc_default_purgeable_zone == NULL) ? NULL :
+ malloc_default_purgeable_zone();
+
+ /* Register the custom zone. At this point it won't be the default. */
+ zone_init();
+ malloc_zone_register(&jemalloc_zone);
+
+ /* Promote the custom zone to be default. */
+ zone_promote();
}
diff --git a/deps/jemalloc/test/include/test/SFMT-alti.h b/deps/jemalloc/test/include/test/SFMT-alti.h
index 0005df6b4..a1885dbf2 100644
--- a/deps/jemalloc/test/include/test/SFMT-alti.h
+++ b/deps/jemalloc/test/include/test/SFMT-alti.h
@@ -33,8 +33,8 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/**
- * @file SFMT-alti.h
+/**
+ * @file SFMT-alti.h
*
* @brief SIMD oriented Fast Mersenne Twister(SFMT)
* pseudorandom number generator
@@ -95,7 +95,7 @@ vector unsigned int vec_recursion(vector unsigned int a,
* This function fills the internal state array with pseudorandom
* integers.
*/
-JEMALLOC_INLINE void gen_rand_all(sfmt_t *ctx) {
+static inline void gen_rand_all(sfmt_t *ctx) {
int i;
vector unsigned int r, r1, r2;
@@ -119,10 +119,10 @@ JEMALLOC_INLINE void gen_rand_all(sfmt_t *ctx) {
* This function fills the user-specified array with pseudorandom
* integers.
*
- * @param array an 128-bit array to be filled by pseudorandom numbers.
+ * @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pesudorandom numbers to be generated.
*/
-JEMALLOC_INLINE void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
+static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
int i, j;
vector unsigned int r, r1, r2;
@@ -173,7 +173,7 @@ JEMALLOC_INLINE void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
* @param array an 128-bit array to be swaped.
* @param size size of 128-bit array.
*/
-JEMALLOC_INLINE void swap(w128_t *array, int size) {
+static inline void swap(w128_t *array, int size) {
int i;
const vector unsigned char perm = ALTI_SWAP;
diff --git a/deps/jemalloc/test/include/test/SFMT-sse2.h b/deps/jemalloc/test/include/test/SFMT-sse2.h
index 0314a163d..169ad5581 100644
--- a/deps/jemalloc/test/include/test/SFMT-sse2.h
+++ b/deps/jemalloc/test/include/test/SFMT-sse2.h
@@ -33,7 +33,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/**
+/**
* @file SFMT-sse2.h
* @brief SIMD oriented Fast Mersenne Twister(SFMT) for Intel SSE2
*
@@ -60,10 +60,10 @@
* @param mask 128-bit mask
* @return output
*/
-JEMALLOC_ALWAYS_INLINE __m128i mm_recursion(__m128i *a, __m128i *b,
+JEMALLOC_ALWAYS_INLINE __m128i mm_recursion(__m128i *a, __m128i *b,
__m128i c, __m128i d, __m128i mask) {
__m128i v, x, y, z;
-
+
x = _mm_load_si128(a);
y = _mm_srli_epi32(*b, SR1);
z = _mm_srli_si128(c, SR2);
@@ -81,7 +81,7 @@ JEMALLOC_ALWAYS_INLINE __m128i mm_recursion(__m128i *a, __m128i *b,
* This function fills the internal state array with pseudorandom
* integers.
*/
-JEMALLOC_INLINE void gen_rand_all(sfmt_t *ctx) {
+static inline void gen_rand_all(sfmt_t *ctx) {
int i;
__m128i r, r1, r2, mask;
mask = _mm_set_epi32(MSK4, MSK3, MSK2, MSK1);
@@ -108,10 +108,10 @@ JEMALLOC_INLINE void gen_rand_all(sfmt_t *ctx) {
* This function fills the user-specified array with pseudorandom
* integers.
*
- * @param array an 128-bit array to be filled by pseudorandom numbers.
+ * @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pesudorandom numbers to be generated.
*/
-JEMALLOC_INLINE void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
+static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
int i, j;
__m128i r, r1, r2, mask;
mask = _mm_set_epi32(MSK4, MSK3, MSK2, MSK1);
diff --git a/deps/jemalloc/test/include/test/SFMT.h b/deps/jemalloc/test/include/test/SFMT.h
index 09c1607dd..863fc55e8 100644
--- a/deps/jemalloc/test/include/test/SFMT.h
+++ b/deps/jemalloc/test/include/test/SFMT.h
@@ -81,91 +81,66 @@ const char *get_idstring(void);
int get_min_array_size32(void);
int get_min_array_size64(void);
-#ifndef JEMALLOC_ENABLE_INLINE
-double to_real1(uint32_t v);
-double genrand_real1(sfmt_t *ctx);
-double to_real2(uint32_t v);
-double genrand_real2(sfmt_t *ctx);
-double to_real3(uint32_t v);
-double genrand_real3(sfmt_t *ctx);
-double to_res53(uint64_t v);
-double to_res53_mix(uint32_t x, uint32_t y);
-double genrand_res53(sfmt_t *ctx);
-double genrand_res53_mix(sfmt_t *ctx);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(SFMT_C_))
/* These real versions are due to Isaku Wada */
/** generates a random number on [0,1]-real-interval */
-JEMALLOC_INLINE double to_real1(uint32_t v)
-{
+static inline double to_real1(uint32_t v) {
return v * (1.0/4294967295.0);
/* divided by 2^32-1 */
}
/** generates a random number on [0,1]-real-interval */
-JEMALLOC_INLINE double genrand_real1(sfmt_t *ctx)
-{
+static inline double genrand_real1(sfmt_t *ctx) {
return to_real1(gen_rand32(ctx));
}
/** generates a random number on [0,1)-real-interval */
-JEMALLOC_INLINE double to_real2(uint32_t v)
-{
+static inline double to_real2(uint32_t v) {
return v * (1.0/4294967296.0);
/* divided by 2^32 */
}
/** generates a random number on [0,1)-real-interval */
-JEMALLOC_INLINE double genrand_real2(sfmt_t *ctx)
-{
+static inline double genrand_real2(sfmt_t *ctx) {
return to_real2(gen_rand32(ctx));
}
/** generates a random number on (0,1)-real-interval */
-JEMALLOC_INLINE double to_real3(uint32_t v)
-{
+static inline double to_real3(uint32_t v) {
return (((double)v) + 0.5)*(1.0/4294967296.0);
/* divided by 2^32 */
}
/** generates a random number on (0,1)-real-interval */
-JEMALLOC_INLINE double genrand_real3(sfmt_t *ctx)
-{
+static inline double genrand_real3(sfmt_t *ctx) {
return to_real3(gen_rand32(ctx));
}
/** These real versions are due to Isaku Wada */
/** generates a random number on [0,1) with 53-bit resolution*/
-JEMALLOC_INLINE double to_res53(uint64_t v)
-{
+static inline double to_res53(uint64_t v) {
return v * (1.0/18446744073709551616.0L);
}
/** generates a random number on [0,1) with 53-bit resolution from two
* 32 bit integers */
-JEMALLOC_INLINE double to_res53_mix(uint32_t x, uint32_t y)
-{
+static inline double to_res53_mix(uint32_t x, uint32_t y) {
return to_res53(x | ((uint64_t)y << 32));
}
/** generates a random number on [0,1) with 53-bit resolution
*/
-JEMALLOC_INLINE double genrand_res53(sfmt_t *ctx)
-{
+static inline double genrand_res53(sfmt_t *ctx) {
return to_res53(gen_rand64(ctx));
-}
+}
/** generates a random number on [0,1) with 53-bit resolution
using 32bit integer.
*/
-JEMALLOC_INLINE double genrand_res53_mix(sfmt_t *ctx)
-{
+static inline double genrand_res53_mix(sfmt_t *ctx) {
uint32_t x, y;
x = gen_rand32(ctx);
y = gen_rand32(ctx);
return to_res53_mix(x, y);
-}
-#endif
+}
#endif
diff --git a/deps/jemalloc/test/include/test/btalloc.h b/deps/jemalloc/test/include/test/btalloc.h
index c3f9d4df7..5877ea77e 100644
--- a/deps/jemalloc/test/include/test/btalloc.h
+++ b/deps/jemalloc/test/include/test/btalloc.h
@@ -1,20 +1,19 @@
/* btalloc() provides a mechanism for allocating via permuted backtraces. */
void *btalloc(size_t size, unsigned bits);
-#define btalloc_n_proto(n) \
+#define btalloc_n_proto(n) \
void *btalloc_##n(size_t size, unsigned bits);
btalloc_n_proto(0)
btalloc_n_proto(1)
-#define btalloc_n_gen(n) \
+#define btalloc_n_gen(n) \
void * \
-btalloc_##n(size_t size, unsigned bits) \
-{ \
+btalloc_##n(size_t size, unsigned bits) { \
void *p; \
\
- if (bits == 0) \
+ if (bits == 0) { \
p = mallocx(size, 0); \
- else { \
+ } else { \
switch (bits & 0x1U) { \
case 0: \
p = (btalloc_0(size, bits >> 1)); \
@@ -27,5 +26,5 @@ btalloc_##n(size_t size, unsigned bits) \
} \
/* Intentionally sabotage tail call optimization. */ \
assert_ptr_not_null(p, "Unexpected mallocx() failure"); \
- return (p); \
+ return p; \
}
diff --git a/deps/jemalloc/test/include/test/extent_hooks.h b/deps/jemalloc/test/include/test/extent_hooks.h
new file mode 100644
index 000000000..1f0620154
--- /dev/null
+++ b/deps/jemalloc/test/include/test/extent_hooks.h
@@ -0,0 +1,289 @@
+/*
+ * Boilerplate code used for testing extent hooks via interception and
+ * passthrough.
+ */
+
+static void *extent_alloc_hook(extent_hooks_t *extent_hooks, void *new_addr,
+ size_t size, size_t alignment, bool *zero, bool *commit,
+ unsigned arena_ind);
+static bool extent_dalloc_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, bool committed, unsigned arena_ind);
+static void extent_destroy_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, bool committed, unsigned arena_ind);
+static bool extent_commit_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind);
+static bool extent_decommit_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind);
+static bool extent_purge_lazy_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t offset, size_t length, unsigned arena_ind);
+static bool extent_purge_forced_hook(extent_hooks_t *extent_hooks,
+ void *addr, size_t size, size_t offset, size_t length, unsigned arena_ind);
+static bool extent_split_hook(extent_hooks_t *extent_hooks, void *addr,
+ size_t size, size_t size_a, size_t size_b, bool committed,
+ unsigned arena_ind);
+static bool extent_merge_hook(extent_hooks_t *extent_hooks, void *addr_a,
+ size_t size_a, void *addr_b, size_t size_b, bool committed,
+ unsigned arena_ind);
+
+static extent_hooks_t *default_hooks;
+static extent_hooks_t hooks = {
+ extent_alloc_hook,
+ extent_dalloc_hook,
+ extent_destroy_hook,
+ extent_commit_hook,
+ extent_decommit_hook,
+ extent_purge_lazy_hook,
+ extent_purge_forced_hook,
+ extent_split_hook,
+ extent_merge_hook
+};
+
+/* Control whether hook functions pass calls through to default hooks. */
+static bool try_alloc = true;
+static bool try_dalloc = true;
+static bool try_destroy = true;
+static bool try_commit = true;
+static bool try_decommit = true;
+static bool try_purge_lazy = true;
+static bool try_purge_forced = true;
+static bool try_split = true;
+static bool try_merge = true;
+
+/* Set to false prior to operations, then introspect after operations. */
+static bool called_alloc;
+static bool called_dalloc;
+static bool called_destroy;
+static bool called_commit;
+static bool called_decommit;
+static bool called_purge_lazy;
+static bool called_purge_forced;
+static bool called_split;
+static bool called_merge;
+
+/* Set to false prior to operations, then introspect after operations. */
+static bool did_alloc;
+static bool did_dalloc;
+static bool did_destroy;
+static bool did_commit;
+static bool did_decommit;
+static bool did_purge_lazy;
+static bool did_purge_forced;
+static bool did_split;
+static bool did_merge;
+
+#if 0
+# define TRACE_HOOK(fmt, ...) malloc_printf(fmt, __VA_ARGS__)
+#else
+# define TRACE_HOOK(fmt, ...)
+#endif
+
+static void *
+extent_alloc_hook(extent_hooks_t *extent_hooks, void *new_addr, size_t size,
+ size_t alignment, bool *zero, bool *commit, unsigned arena_ind) {
+ void *ret;
+
+ TRACE_HOOK("%s(extent_hooks=%p, new_addr=%p, size=%zu, alignment=%zu, "
+ "*zero=%s, *commit=%s, arena_ind=%u)\n", __func__, extent_hooks,
+ new_addr, size, alignment, *zero ? "true" : "false", *commit ?
+ "true" : "false", arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->alloc, extent_alloc_hook,
+ "Wrong hook function");
+ called_alloc = true;
+ if (!try_alloc) {
+ return NULL;
+ }
+ ret = default_hooks->alloc(default_hooks, new_addr, size, alignment,
+ zero, commit, 0);
+ did_alloc = (ret != NULL);
+ return ret;
+}
+
+static bool
+extent_dalloc_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ bool committed, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, "
+ "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ?
+ "true" : "false", arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->dalloc, extent_dalloc_hook,
+ "Wrong hook function");
+ called_dalloc = true;
+ if (!try_dalloc) {
+ return true;
+ }
+ err = default_hooks->dalloc(default_hooks, addr, size, committed, 0);
+ did_dalloc = !err;
+ return err;
+}
+
+static void
+extent_destroy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ bool committed, unsigned arena_ind) {
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, "
+ "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ?
+ "true" : "false", arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->destroy, extent_destroy_hook,
+ "Wrong hook function");
+ called_destroy = true;
+ if (!try_destroy) {
+ return;
+ }
+ default_hooks->destroy(default_hooks, addr, size, committed, 0);
+ did_destroy = true;
+}
+
+static bool
+extent_commit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, "
+ "length=%zu, arena_ind=%u)\n", __func__, extent_hooks, addr, size,
+ offset, length, arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->commit, extent_commit_hook,
+ "Wrong hook function");
+ called_commit = true;
+ if (!try_commit) {
+ return true;
+ }
+ err = default_hooks->commit(default_hooks, addr, size, offset, length,
+ 0);
+ did_commit = !err;
+ return err;
+}
+
+static bool
+extent_decommit_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, "
+ "length=%zu, arena_ind=%u)\n", __func__, extent_hooks, addr, size,
+ offset, length, arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->decommit, extent_decommit_hook,
+ "Wrong hook function");
+ called_decommit = true;
+ if (!try_decommit) {
+ return true;
+ }
+ err = default_hooks->decommit(default_hooks, addr, size, offset, length,
+ 0);
+ did_decommit = !err;
+ return err;
+}
+
+static bool
+extent_purge_lazy_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, "
+ "length=%zu arena_ind=%u)\n", __func__, extent_hooks, addr, size,
+ offset, length, arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->purge_lazy, extent_purge_lazy_hook,
+ "Wrong hook function");
+ called_purge_lazy = true;
+ if (!try_purge_lazy) {
+ return true;
+ }
+ err = default_hooks->purge_lazy == NULL ||
+ default_hooks->purge_lazy(default_hooks, addr, size, offset, length,
+ 0);
+ did_purge_lazy = !err;
+ return err;
+}
+
+static bool
+extent_purge_forced_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t offset, size_t length, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, offset=%zu, "
+ "length=%zu arena_ind=%u)\n", __func__, extent_hooks, addr, size,
+ offset, length, arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->purge_forced, extent_purge_forced_hook,
+ "Wrong hook function");
+ called_purge_forced = true;
+ if (!try_purge_forced) {
+ return true;
+ }
+ err = default_hooks->purge_forced == NULL ||
+ default_hooks->purge_forced(default_hooks, addr, size, offset,
+ length, 0);
+ did_purge_forced = !err;
+ return err;
+}
+
+static bool
+extent_split_hook(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ size_t size_a, size_t size_b, bool committed, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, size_a=%zu, "
+ "size_b=%zu, committed=%s, arena_ind=%u)\n", __func__, extent_hooks,
+ addr, size, size_a, size_b, committed ? "true" : "false",
+ arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->split, extent_split_hook,
+ "Wrong hook function");
+ called_split = true;
+ if (!try_split) {
+ return true;
+ }
+ err = (default_hooks->split == NULL ||
+ default_hooks->split(default_hooks, addr, size, size_a, size_b,
+ committed, 0));
+ did_split = !err;
+ return err;
+}
+
+static bool
+extent_merge_hook(extent_hooks_t *extent_hooks, void *addr_a, size_t size_a,
+ void *addr_b, size_t size_b, bool committed, unsigned arena_ind) {
+ bool err;
+
+ TRACE_HOOK("%s(extent_hooks=%p, addr_a=%p, size_a=%zu, addr_b=%p "
+ "size_b=%zu, committed=%s, arena_ind=%u)\n", __func__, extent_hooks,
+ addr_a, size_a, addr_b, size_b, committed ? "true" : "false",
+ arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->merge, extent_merge_hook,
+ "Wrong hook function");
+ assert_ptr_eq((void *)((uintptr_t)addr_a + size_a), addr_b,
+ "Extents not mergeable");
+ called_merge = true;
+ if (!try_merge) {
+ return true;
+ }
+ err = (default_hooks->merge == NULL ||
+ default_hooks->merge(default_hooks, addr_a, size_a, addr_b, size_b,
+ committed, 0));
+ did_merge = !err;
+ return err;
+}
+
+static void
+extent_hooks_prep(void) {
+ size_t sz;
+
+ sz = sizeof(default_hooks);
+ assert_d_eq(mallctl("arena.0.extent_hooks", (void *)&default_hooks, &sz,
+ NULL, 0), 0, "Unexpected mallctl() error");
+}
diff --git a/deps/jemalloc/test/include/test/jemalloc_test.h.in b/deps/jemalloc/test/include/test/jemalloc_test.h.in
index 455569da4..67caa86bf 100644
--- a/deps/jemalloc/test/include/test/jemalloc_test.h.in
+++ b/deps/jemalloc/test/include/test/jemalloc_test.h.in
@@ -1,3 +1,7 @@
+#ifdef __cplusplus
+extern "C" {
+#endif
+
#include <limits.h>
#ifndef SIZE_T_MAX
# define SIZE_T_MAX SIZE_MAX
@@ -11,7 +15,6 @@
#ifdef _WIN32
# include "msvc_compat/strings.h"
#endif
-#include <sys/time.h>
#ifdef _WIN32
# include <windows.h>
@@ -20,39 +23,6 @@
# include <pthread.h>
#endif
-/******************************************************************************/
-/*
- * Define always-enabled assertion macros, so that test assertions execute even
- * if assertions are disabled in the library code. These definitions must
- * exist prior to including "jemalloc/internal/util.h".
- */
-#define assert(e) do { \
- if (!(e)) { \
- malloc_printf( \
- "<jemalloc>: %s:%d: Failed assertion: \"%s\"\n", \
- __FILE__, __LINE__, #e); \
- abort(); \
- } \
-} while (0)
-
-#define not_reached() do { \
- malloc_printf( \
- "<jemalloc>: %s:%d: Unreachable code reached\n", \
- __FILE__, __LINE__); \
- abort(); \
-} while (0)
-
-#define not_implemented() do { \
- malloc_printf("<jemalloc>: %s:%d: Not implemented\n", \
- __FILE__, __LINE__); \
- abort(); \
-} while (0)
-
-#define assert_not_implemented(e) do { \
- if (!(e)) \
- not_implemented(); \
-} while (0)
-
#include "test/jemalloc_test_defs.h"
#ifdef JEMALLOC_OSSPIN
@@ -73,7 +43,8 @@
#ifdef JEMALLOC_UNIT_TEST
# define JEMALLOC_JET
# define JEMALLOC_MANGLE
-# include "jemalloc/internal/jemalloc_internal.h"
+# include "jemalloc/internal/jemalloc_preamble.h"
+# include "jemalloc/internal/jemalloc_internal_includes.h"
/******************************************************************************/
/*
@@ -81,26 +52,34 @@
* expose the minimum necessary internal utility code (to avoid re-implementing
* essentially identical code within the test infrastructure).
*/
-#elif defined(JEMALLOC_INTEGRATION_TEST)
+#elif defined(JEMALLOC_INTEGRATION_TEST) || \
+ defined(JEMALLOC_INTEGRATION_CPP_TEST)
# define JEMALLOC_MANGLE
# include "jemalloc/jemalloc@install_suffix@.h"
# include "jemalloc/internal/jemalloc_internal_defs.h"
# include "jemalloc/internal/jemalloc_internal_macros.h"
+static const bool config_debug =
+#ifdef JEMALLOC_DEBUG
+ true
+#else
+ false
+#endif
+ ;
+
# define JEMALLOC_N(n) @private_namespace@##n
# include "jemalloc/internal/private_namespace.h"
+# include "jemalloc/internal/hooks.h"
-# define JEMALLOC_H_TYPES
-# define JEMALLOC_H_STRUCTS
-# define JEMALLOC_H_EXTERNS
-# define JEMALLOC_H_INLINES
+/* Hermetic headers. */
+# include "jemalloc/internal/assert.h"
+# include "jemalloc/internal/malloc_io.h"
+# include "jemalloc/internal/nstime.h"
# include "jemalloc/internal/util.h"
+
+/* Non-hermetic headers. */
# include "jemalloc/internal/qr.h"
# include "jemalloc/internal/ql.h"
-# undef JEMALLOC_H_TYPES
-# undef JEMALLOC_H_STRUCTS
-# undef JEMALLOC_H_EXTERNS
-# undef JEMALLOC_H_INLINES
/******************************************************************************/
/*
@@ -115,7 +94,8 @@
# include "jemalloc/jemalloc_protos_jet.h"
# define JEMALLOC_JET
-# include "jemalloc/internal/jemalloc_internal.h"
+# include "jemalloc/internal/jemalloc_preamble.h"
+# include "jemalloc/internal/jemalloc_internal_includes.h"
# include "jemalloc/internal/public_unnamespace.h"
# undef JEMALLOC_JET
@@ -147,5 +127,47 @@
#include "test/test.h"
#include "test/timer.h"
#include "test/thd.h"
-#define MEXP 19937
+#define MEXP 19937
#include "test/SFMT.h"
+
+/******************************************************************************/
+/*
+ * Define always-enabled assertion macros, so that test assertions execute even
+ * if assertions are disabled in the library code.
+ */
+#undef assert
+#undef not_reached
+#undef not_implemented
+#undef assert_not_implemented
+
+#define assert(e) do { \
+ if (!(e)) { \
+ malloc_printf( \
+ "<jemalloc>: %s:%d: Failed assertion: \"%s\"\n", \
+ __FILE__, __LINE__, #e); \
+ abort(); \
+ } \
+} while (0)
+
+#define not_reached() do { \
+ malloc_printf( \
+ "<jemalloc>: %s:%d: Unreachable code reached\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+} while (0)
+
+#define not_implemented() do { \
+ malloc_printf("<jemalloc>: %s:%d: Not implemented\n", \
+ __FILE__, __LINE__); \
+ abort(); \
+} while (0)
+
+#define assert_not_implemented(e) do { \
+ if (!(e)) { \
+ not_implemented(); \
+ } \
+} while (0)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/deps/jemalloc/test/include/test/math.h b/deps/jemalloc/test/include/test/math.h
index b057b29a1..efba086dd 100644
--- a/deps/jemalloc/test/include/test/math.h
+++ b/deps/jemalloc/test/include/test/math.h
@@ -1,12 +1,3 @@
-#ifndef JEMALLOC_ENABLE_INLINE
-double ln_gamma(double x);
-double i_gamma(double x, double p, double ln_gamma_p);
-double pt_norm(double p);
-double pt_chi2(double p, double df, double ln_gamma_df_2);
-double pt_gamma(double p, double shape, double scale, double ln_gamma_shape);
-#endif
-
-#if (defined(JEMALLOC_ENABLE_INLINE) || defined(MATH_C_))
/*
* Compute the natural log of Gamma(x), accurate to 10 decimal places.
*
@@ -15,9 +6,8 @@ double pt_gamma(double p, double shape, double scale, double ln_gamma_shape);
* Pike, M.C., I.D. Hill (1966) Algorithm 291: Logarithm of Gamma function
* [S14]. Communications of the ACM 9(9):684.
*/
-JEMALLOC_INLINE double
-ln_gamma(double x)
-{
+static inline double
+ln_gamma(double x) {
double f, z;
assert(x > 0.0);
@@ -31,14 +21,15 @@ ln_gamma(double x)
}
x = z;
f = -log(f);
- } else
+ } else {
f = 0.0;
+ }
z = 1.0 / (x * x);
- return (f + (x-0.5) * log(x) - x + 0.918938533204673 +
+ return f + (x-0.5) * log(x) - x + 0.918938533204673 +
(((-0.000595238095238 * z + 0.000793650793651) * z -
- 0.002777777777778) * z + 0.083333333333333) / x);
+ 0.002777777777778) * z + 0.083333333333333) / x;
}
/*
@@ -50,9 +41,8 @@ ln_gamma(double x)
* Bhattacharjee, G.P. (1970) Algorithm AS 32: The incomplete Gamma integral.
* Applied Statistics 19:285-287.
*/
-JEMALLOC_INLINE double
-i_gamma(double x, double p, double ln_gamma_p)
-{
+static inline double
+i_gamma(double x, double p, double ln_gamma_p) {
double acu, factor, oflo, gin, term, rn, a, b, an, dif;
double pn[6];
unsigned i;
@@ -60,8 +50,9 @@ i_gamma(double x, double p, double ln_gamma_p)
assert(p > 0.0);
assert(x >= 0.0);
- if (x == 0.0)
- return (0.0);
+ if (x == 0.0) {
+ return 0.0;
+ }
acu = 1.0e-10;
oflo = 1.0e30;
@@ -80,7 +71,7 @@ i_gamma(double x, double p, double ln_gamma_p)
gin += term;
if (term <= acu) {
gin *= factor / p;
- return (gin);
+ return gin;
}
}
} else {
@@ -99,23 +90,26 @@ i_gamma(double x, double p, double ln_gamma_p)
b += 2.0;
term += 1.0;
an = a * term;
- for (i = 0; i < 2; i++)
+ for (i = 0; i < 2; i++) {
pn[i+4] = b * pn[i+2] - an * pn[i];
+ }
if (pn[5] != 0.0) {
rn = pn[4] / pn[5];
dif = fabs(gin - rn);
if (dif <= acu && dif <= acu * rn) {
gin = 1.0 - factor * gin;
- return (gin);
+ return gin;
}
gin = rn;
}
- for (i = 0; i < 4; i++)
+ for (i = 0; i < 4; i++) {
pn[i] = pn[i+2];
+ }
if (fabs(pn[4]) >= oflo) {
- for (i = 0; i < 4; i++)
+ for (i = 0; i < 4; i++) {
pn[i] /= oflo;
+ }
}
}
}
@@ -131,9 +125,8 @@ i_gamma(double x, double p, double ln_gamma_p)
* Wichura, M.J. (1988) Algorithm AS 241: The percentage points of the normal
* distribution. Applied Statistics 37(3):477-484.
*/
-JEMALLOC_INLINE double
-pt_norm(double p)
-{
+static inline double
+pt_norm(double p) {
double q, r, ret;
assert(p > 0.0 && p < 1.0);
@@ -142,7 +135,7 @@ pt_norm(double p)
if (fabs(q) <= 0.425) {
/* p close to 1/2. */
r = 0.180625 - q * q;
- return (q * (((((((2.5090809287301226727e3 * r +
+ return q * (((((((2.5090809287301226727e3 * r +
3.3430575583588128105e4) * r + 6.7265770927008700853e4) * r
+ 4.5921953931549871457e4) * r + 1.3731693765509461125e4) *
r + 1.9715909503065514427e3) * r + 1.3314166789178437745e2)
@@ -151,12 +144,13 @@ pt_norm(double p)
2.8729085735721942674e4) * r + 3.9307895800092710610e4) * r
+ 2.1213794301586595867e4) * r + 5.3941960214247511077e3) *
r + 6.8718700749205790830e2) * r + 4.2313330701600911252e1)
- * r + 1.0));
+ * r + 1.0);
} else {
- if (q < 0.0)
+ if (q < 0.0) {
r = p;
- else
+ } else {
r = 1.0 - p;
+ }
assert(r > 0.0);
r = sqrt(-log(r));
@@ -198,9 +192,10 @@ pt_norm(double p)
5.99832206555887937690e-1)
* r + 1.0));
}
- if (q < 0.0)
+ if (q < 0.0) {
ret = -ret;
- return (ret);
+ }
+ return ret;
}
}
@@ -218,9 +213,8 @@ pt_norm(double p)
* Shea, B.L. (1991) Algorithm AS R85: A remark on AS 91: The percentage
* points of the Chi^2 distribution. Applied Statistics 40(1):233-235.
*/
-JEMALLOC_INLINE double
-pt_chi2(double p, double df, double ln_gamma_df_2)
-{
+static inline double
+pt_chi2(double p, double df, double ln_gamma_df_2) {
double e, aa, xx, c, ch, a, q, p1, p2, t, x, b, s1, s2, s3, s4, s5, s6;
unsigned i;
@@ -236,8 +230,9 @@ pt_chi2(double p, double df, double ln_gamma_df_2)
if (df < -1.24 * log(p)) {
/* Starting approximation for small Chi^2. */
ch = pow(p * xx * exp(ln_gamma_df_2 + xx * aa), 1.0 / xx);
- if (ch - e < 0.0)
- return (ch);
+ if (ch - e < 0.0) {
+ return ch;
+ }
} else {
if (df > 0.32) {
x = pt_norm(p);
@@ -263,8 +258,9 @@ pt_chi2(double p, double df, double ln_gamma_df_2)
* (13.32 + 3.0 * ch)) / p2;
ch -= (1.0 - exp(a + ln_gamma_df_2 + 0.5 * ch +
c * aa) * p2 / p1) / t;
- if (fabs(q / ch - 1.0) - 0.01 <= 0.0)
+ if (fabs(q / ch - 1.0) - 0.01 <= 0.0) {
break;
+ }
}
}
}
@@ -273,8 +269,9 @@ pt_chi2(double p, double df, double ln_gamma_df_2)
/* Calculation of seven-term Taylor series. */
q = ch;
p1 = 0.5 * ch;
- if (p1 < 0.0)
- return (-1.0);
+ if (p1 < 0.0) {
+ return -1.0;
+ }
p2 = p - i_gamma(p1, xx, ln_gamma_df_2);
t = p2 * exp(xx * aa + ln_gamma_df_2 + p1 - c * log(ch));
b = t / ch;
@@ -290,11 +287,12 @@ pt_chi2(double p, double df, double ln_gamma_df_2)
s6 = (120.0 + c * (346.0 + 127.0 * c)) / 5040.0;
ch += t * (1.0 + 0.5 * t * s1 - b * c * (s1 - b * (s2 - b * (s3
- b * (s4 - b * (s5 - b * s6))))));
- if (fabs(q / ch - 1.0) <= e)
+ if (fabs(q / ch - 1.0) <= e) {
break;
+ }
}
- return (ch);
+ return ch;
}
/*
@@ -302,10 +300,7 @@ pt_chi2(double p, double df, double ln_gamma_df_2)
* compute the upper limit on the definite integral from [0..z] that satisfies
* p.
*/
-JEMALLOC_INLINE double
-pt_gamma(double p, double shape, double scale, double ln_gamma_shape)
-{
-
- return (pt_chi2(p, shape * 2.0, ln_gamma_shape) * 0.5 * scale);
+static inline double
+pt_gamma(double p, double shape, double scale, double ln_gamma_shape) {
+ return pt_chi2(p, shape * 2.0, ln_gamma_shape) * 0.5 * scale;
}
-#endif
diff --git a/deps/jemalloc/test/include/test/mq.h b/deps/jemalloc/test/include/test/mq.h
index 7c4df4931..af2c078da 100644
--- a/deps/jemalloc/test/include/test/mq.h
+++ b/deps/jemalloc/test/include/test/mq.h
@@ -26,9 +26,9 @@ void mq_nanosleep(unsigned ns);
* does not perform any cleanup of messages, since it knows nothing of their
* payloads.
*/
-#define mq_msg(a_mq_msg_type) ql_elm(a_mq_msg_type)
+#define mq_msg(a_mq_msg_type) ql_elm(a_mq_msg_type)
-#define mq_gen(a_attr, a_prefix, a_mq_type, a_mq_msg_type, a_field) \
+#define mq_gen(a_attr, a_prefix, a_mq_type, a_mq_msg_type, a_field) \
typedef struct { \
mtx_t lock; \
ql_head(a_mq_msg_type) msgs; \
@@ -37,31 +37,28 @@ typedef struct { \
a_attr bool \
a_prefix##init(a_mq_type *mq) { \
\
- if (mtx_init(&mq->lock)) \
- return (true); \
+ if (mtx_init(&mq->lock)) { \
+ return true; \
+ } \
ql_new(&mq->msgs); \
mq->count = 0; \
- return (false); \
+ return false; \
} \
a_attr void \
-a_prefix##fini(a_mq_type *mq) \
-{ \
- \
+a_prefix##fini(a_mq_type *mq) { \
mtx_fini(&mq->lock); \
} \
a_attr unsigned \
-a_prefix##count(a_mq_type *mq) \
-{ \
+a_prefix##count(a_mq_type *mq) { \
unsigned count; \
\
mtx_lock(&mq->lock); \
count = mq->count; \
mtx_unlock(&mq->lock); \
- return (count); \
+ return count; \
} \
a_attr a_mq_msg_type * \
-a_prefix##tryget(a_mq_type *mq) \
-{ \
+a_prefix##tryget(a_mq_type *mq) { \
a_mq_msg_type *msg; \
\
mtx_lock(&mq->lock); \
@@ -71,35 +68,36 @@ a_prefix##tryget(a_mq_type *mq) \
mq->count--; \
} \
mtx_unlock(&mq->lock); \
- return (msg); \
+ return msg; \
} \
a_attr a_mq_msg_type * \
-a_prefix##get(a_mq_type *mq) \
-{ \
+a_prefix##get(a_mq_type *mq) { \
a_mq_msg_type *msg; \
unsigned ns; \
\
msg = a_prefix##tryget(mq); \
- if (msg != NULL) \
- return (msg); \
+ if (msg != NULL) { \
+ return msg; \
+ } \
\
ns = 1; \
while (true) { \
mq_nanosleep(ns); \
msg = a_prefix##tryget(mq); \
- if (msg != NULL) \
- return (msg); \
+ if (msg != NULL) { \
+ return msg; \
+ } \
if (ns < 1000*1000*1000) { \
/* Double sleep time, up to max 1 second. */ \
ns <<= 1; \
- if (ns > 1000*1000*1000) \
+ if (ns > 1000*1000*1000) { \
ns = 1000*1000*1000; \
+ } \
} \
} \
} \
a_attr void \
-a_prefix##put(a_mq_type *mq, a_mq_msg_type *msg) \
-{ \
+a_prefix##put(a_mq_type *mq, a_mq_msg_type *msg) { \
\
mtx_lock(&mq->lock); \
ql_elm_new(msg, a_field); \
diff --git a/deps/jemalloc/test/include/test/mtx.h b/deps/jemalloc/test/include/test/mtx.h
index bbe822f54..58afbc3d1 100644
--- a/deps/jemalloc/test/include/test/mtx.h
+++ b/deps/jemalloc/test/include/test/mtx.h
@@ -8,6 +8,8 @@
typedef struct {
#ifdef _WIN32
CRITICAL_SECTION lock;
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ os_unfair_lock lock;
#elif (defined(JEMALLOC_OSSPIN))
OSSpinLock lock;
#else
diff --git a/deps/jemalloc/test/include/test/test.h b/deps/jemalloc/test/include/test/test.h
index 3cf901fc4..fd0e5265d 100644
--- a/deps/jemalloc/test/include/test/test.h
+++ b/deps/jemalloc/test/include/test/test.h
@@ -1,6 +1,6 @@
-#define ASSERT_BUFSIZE 256
+#define ASSERT_BUFSIZE 256
-#define assert_cmp(t, a, b, cmp, neg_cmp, pri, ...) do { \
+#define assert_cmp(t, a, b, cmp, neg_cmp, pri, ...) do { \
t a_ = (a); \
t b_ = (b); \
if (!(a_ cmp b_)) { \
@@ -8,8 +8,8 @@
char message[ASSERT_BUFSIZE]; \
malloc_snprintf(prefix, sizeof(prefix), \
"%s:%s:%d: Failed assertion: " \
- "(%s) "#cmp" (%s) --> " \
- "%"pri" "#neg_cmp" %"pri": ", \
+ "(%s) " #cmp " (%s) --> " \
+ "%" pri " " #neg_cmp " %" pri ": ", \
__func__, __FILE__, __LINE__, \
#a, #b, a_, b_); \
malloc_snprintf(message, sizeof(message), __VA_ARGS__); \
@@ -17,200 +17,200 @@
} \
} while (0)
-#define assert_ptr_eq(a, b, ...) assert_cmp(void *, a, b, ==, \
+#define assert_ptr_eq(a, b, ...) assert_cmp(void *, a, b, ==, \
!=, "p", __VA_ARGS__)
-#define assert_ptr_ne(a, b, ...) assert_cmp(void *, a, b, !=, \
+#define assert_ptr_ne(a, b, ...) assert_cmp(void *, a, b, !=, \
==, "p", __VA_ARGS__)
-#define assert_ptr_null(a, ...) assert_cmp(void *, a, NULL, ==, \
+#define assert_ptr_null(a, ...) assert_cmp(void *, a, NULL, ==, \
!=, "p", __VA_ARGS__)
-#define assert_ptr_not_null(a, ...) assert_cmp(void *, a, NULL, !=, \
+#define assert_ptr_not_null(a, ...) assert_cmp(void *, a, NULL, !=, \
==, "p", __VA_ARGS__)
-#define assert_c_eq(a, b, ...) assert_cmp(char, a, b, ==, !=, "c", __VA_ARGS__)
-#define assert_c_ne(a, b, ...) assert_cmp(char, a, b, !=, ==, "c", __VA_ARGS__)
-#define assert_c_lt(a, b, ...) assert_cmp(char, a, b, <, >=, "c", __VA_ARGS__)
-#define assert_c_le(a, b, ...) assert_cmp(char, a, b, <=, >, "c", __VA_ARGS__)
-#define assert_c_ge(a, b, ...) assert_cmp(char, a, b, >=, <, "c", __VA_ARGS__)
-#define assert_c_gt(a, b, ...) assert_cmp(char, a, b, >, <=, "c", __VA_ARGS__)
-
-#define assert_x_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "#x", __VA_ARGS__)
-#define assert_x_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "#x", __VA_ARGS__)
-#define assert_x_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "#x", __VA_ARGS__)
-#define assert_x_le(a, b, ...) assert_cmp(int, a, b, <=, >, "#x", __VA_ARGS__)
-#define assert_x_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "#x", __VA_ARGS__)
-#define assert_x_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "#x", __VA_ARGS__)
-
-#define assert_d_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "d", __VA_ARGS__)
-#define assert_d_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "d", __VA_ARGS__)
-#define assert_d_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "d", __VA_ARGS__)
-#define assert_d_le(a, b, ...) assert_cmp(int, a, b, <=, >, "d", __VA_ARGS__)
-#define assert_d_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "d", __VA_ARGS__)
-#define assert_d_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "d", __VA_ARGS__)
-
-#define assert_u_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "u", __VA_ARGS__)
-#define assert_u_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "u", __VA_ARGS__)
-#define assert_u_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "u", __VA_ARGS__)
-#define assert_u_le(a, b, ...) assert_cmp(int, a, b, <=, >, "u", __VA_ARGS__)
-#define assert_u_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "u", __VA_ARGS__)
-#define assert_u_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "u", __VA_ARGS__)
-
-#define assert_ld_eq(a, b, ...) assert_cmp(long, a, b, ==, \
+#define assert_c_eq(a, b, ...) assert_cmp(char, a, b, ==, !=, "c", __VA_ARGS__)
+#define assert_c_ne(a, b, ...) assert_cmp(char, a, b, !=, ==, "c", __VA_ARGS__)
+#define assert_c_lt(a, b, ...) assert_cmp(char, a, b, <, >=, "c", __VA_ARGS__)
+#define assert_c_le(a, b, ...) assert_cmp(char, a, b, <=, >, "c", __VA_ARGS__)
+#define assert_c_ge(a, b, ...) assert_cmp(char, a, b, >=, <, "c", __VA_ARGS__)
+#define assert_c_gt(a, b, ...) assert_cmp(char, a, b, >, <=, "c", __VA_ARGS__)
+
+#define assert_x_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "#x", __VA_ARGS__)
+#define assert_x_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "#x", __VA_ARGS__)
+#define assert_x_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "#x", __VA_ARGS__)
+#define assert_x_le(a, b, ...) assert_cmp(int, a, b, <=, >, "#x", __VA_ARGS__)
+#define assert_x_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "#x", __VA_ARGS__)
+#define assert_x_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "#x", __VA_ARGS__)
+
+#define assert_d_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "d", __VA_ARGS__)
+#define assert_d_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "d", __VA_ARGS__)
+#define assert_d_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "d", __VA_ARGS__)
+#define assert_d_le(a, b, ...) assert_cmp(int, a, b, <=, >, "d", __VA_ARGS__)
+#define assert_d_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "d", __VA_ARGS__)
+#define assert_d_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "d", __VA_ARGS__)
+
+#define assert_u_eq(a, b, ...) assert_cmp(int, a, b, ==, !=, "u", __VA_ARGS__)
+#define assert_u_ne(a, b, ...) assert_cmp(int, a, b, !=, ==, "u", __VA_ARGS__)
+#define assert_u_lt(a, b, ...) assert_cmp(int, a, b, <, >=, "u", __VA_ARGS__)
+#define assert_u_le(a, b, ...) assert_cmp(int, a, b, <=, >, "u", __VA_ARGS__)
+#define assert_u_ge(a, b, ...) assert_cmp(int, a, b, >=, <, "u", __VA_ARGS__)
+#define assert_u_gt(a, b, ...) assert_cmp(int, a, b, >, <=, "u", __VA_ARGS__)
+
+#define assert_ld_eq(a, b, ...) assert_cmp(long, a, b, ==, \
!=, "ld", __VA_ARGS__)
-#define assert_ld_ne(a, b, ...) assert_cmp(long, a, b, !=, \
+#define assert_ld_ne(a, b, ...) assert_cmp(long, a, b, !=, \
==, "ld", __VA_ARGS__)
-#define assert_ld_lt(a, b, ...) assert_cmp(long, a, b, <, \
+#define assert_ld_lt(a, b, ...) assert_cmp(long, a, b, <, \
>=, "ld", __VA_ARGS__)
-#define assert_ld_le(a, b, ...) assert_cmp(long, a, b, <=, \
+#define assert_ld_le(a, b, ...) assert_cmp(long, a, b, <=, \
>, "ld", __VA_ARGS__)
-#define assert_ld_ge(a, b, ...) assert_cmp(long, a, b, >=, \
+#define assert_ld_ge(a, b, ...) assert_cmp(long, a, b, >=, \
<, "ld", __VA_ARGS__)
-#define assert_ld_gt(a, b, ...) assert_cmp(long, a, b, >, \
+#define assert_ld_gt(a, b, ...) assert_cmp(long, a, b, >, \
<=, "ld", __VA_ARGS__)
-#define assert_lu_eq(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_eq(a, b, ...) assert_cmp(unsigned long, \
a, b, ==, !=, "lu", __VA_ARGS__)
-#define assert_lu_ne(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_ne(a, b, ...) assert_cmp(unsigned long, \
a, b, !=, ==, "lu", __VA_ARGS__)
-#define assert_lu_lt(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_lt(a, b, ...) assert_cmp(unsigned long, \
a, b, <, >=, "lu", __VA_ARGS__)
-#define assert_lu_le(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_le(a, b, ...) assert_cmp(unsigned long, \
a, b, <=, >, "lu", __VA_ARGS__)
-#define assert_lu_ge(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_ge(a, b, ...) assert_cmp(unsigned long, \
a, b, >=, <, "lu", __VA_ARGS__)
-#define assert_lu_gt(a, b, ...) assert_cmp(unsigned long, \
+#define assert_lu_gt(a, b, ...) assert_cmp(unsigned long, \
a, b, >, <=, "lu", __VA_ARGS__)
-#define assert_qd_eq(a, b, ...) assert_cmp(long long, a, b, ==, \
+#define assert_qd_eq(a, b, ...) assert_cmp(long long, a, b, ==, \
!=, "qd", __VA_ARGS__)
-#define assert_qd_ne(a, b, ...) assert_cmp(long long, a, b, !=, \
+#define assert_qd_ne(a, b, ...) assert_cmp(long long, a, b, !=, \
==, "qd", __VA_ARGS__)
-#define assert_qd_lt(a, b, ...) assert_cmp(long long, a, b, <, \
+#define assert_qd_lt(a, b, ...) assert_cmp(long long, a, b, <, \
>=, "qd", __VA_ARGS__)
-#define assert_qd_le(a, b, ...) assert_cmp(long long, a, b, <=, \
+#define assert_qd_le(a, b, ...) assert_cmp(long long, a, b, <=, \
>, "qd", __VA_ARGS__)
-#define assert_qd_ge(a, b, ...) assert_cmp(long long, a, b, >=, \
+#define assert_qd_ge(a, b, ...) assert_cmp(long long, a, b, >=, \
<, "qd", __VA_ARGS__)
-#define assert_qd_gt(a, b, ...) assert_cmp(long long, a, b, >, \
+#define assert_qd_gt(a, b, ...) assert_cmp(long long, a, b, >, \
<=, "qd", __VA_ARGS__)
-#define assert_qu_eq(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_eq(a, b, ...) assert_cmp(unsigned long long, \
a, b, ==, !=, "qu", __VA_ARGS__)
-#define assert_qu_ne(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_ne(a, b, ...) assert_cmp(unsigned long long, \
a, b, !=, ==, "qu", __VA_ARGS__)
-#define assert_qu_lt(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_lt(a, b, ...) assert_cmp(unsigned long long, \
a, b, <, >=, "qu", __VA_ARGS__)
-#define assert_qu_le(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_le(a, b, ...) assert_cmp(unsigned long long, \
a, b, <=, >, "qu", __VA_ARGS__)
-#define assert_qu_ge(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_ge(a, b, ...) assert_cmp(unsigned long long, \
a, b, >=, <, "qu", __VA_ARGS__)
-#define assert_qu_gt(a, b, ...) assert_cmp(unsigned long long, \
+#define assert_qu_gt(a, b, ...) assert_cmp(unsigned long long, \
a, b, >, <=, "qu", __VA_ARGS__)
-#define assert_jd_eq(a, b, ...) assert_cmp(intmax_t, a, b, ==, \
+#define assert_jd_eq(a, b, ...) assert_cmp(intmax_t, a, b, ==, \
!=, "jd", __VA_ARGS__)
-#define assert_jd_ne(a, b, ...) assert_cmp(intmax_t, a, b, !=, \
+#define assert_jd_ne(a, b, ...) assert_cmp(intmax_t, a, b, !=, \
==, "jd", __VA_ARGS__)
-#define assert_jd_lt(a, b, ...) assert_cmp(intmax_t, a, b, <, \
+#define assert_jd_lt(a, b, ...) assert_cmp(intmax_t, a, b, <, \
>=, "jd", __VA_ARGS__)
-#define assert_jd_le(a, b, ...) assert_cmp(intmax_t, a, b, <=, \
+#define assert_jd_le(a, b, ...) assert_cmp(intmax_t, a, b, <=, \
>, "jd", __VA_ARGS__)
-#define assert_jd_ge(a, b, ...) assert_cmp(intmax_t, a, b, >=, \
+#define assert_jd_ge(a, b, ...) assert_cmp(intmax_t, a, b, >=, \
<, "jd", __VA_ARGS__)
-#define assert_jd_gt(a, b, ...) assert_cmp(intmax_t, a, b, >, \
+#define assert_jd_gt(a, b, ...) assert_cmp(intmax_t, a, b, >, \
<=, "jd", __VA_ARGS__)
-#define assert_ju_eq(a, b, ...) assert_cmp(uintmax_t, a, b, ==, \
+#define assert_ju_eq(a, b, ...) assert_cmp(uintmax_t, a, b, ==, \
!=, "ju", __VA_ARGS__)
-#define assert_ju_ne(a, b, ...) assert_cmp(uintmax_t, a, b, !=, \
+#define assert_ju_ne(a, b, ...) assert_cmp(uintmax_t, a, b, !=, \
==, "ju", __VA_ARGS__)
-#define assert_ju_lt(a, b, ...) assert_cmp(uintmax_t, a, b, <, \
+#define assert_ju_lt(a, b, ...) assert_cmp(uintmax_t, a, b, <, \
>=, "ju", __VA_ARGS__)
-#define assert_ju_le(a, b, ...) assert_cmp(uintmax_t, a, b, <=, \
+#define assert_ju_le(a, b, ...) assert_cmp(uintmax_t, a, b, <=, \
>, "ju", __VA_ARGS__)
-#define assert_ju_ge(a, b, ...) assert_cmp(uintmax_t, a, b, >=, \
+#define assert_ju_ge(a, b, ...) assert_cmp(uintmax_t, a, b, >=, \
<, "ju", __VA_ARGS__)
-#define assert_ju_gt(a, b, ...) assert_cmp(uintmax_t, a, b, >, \
+#define assert_ju_gt(a, b, ...) assert_cmp(uintmax_t, a, b, >, \
<=, "ju", __VA_ARGS__)
-#define assert_zd_eq(a, b, ...) assert_cmp(ssize_t, a, b, ==, \
+#define assert_zd_eq(a, b, ...) assert_cmp(ssize_t, a, b, ==, \
!=, "zd", __VA_ARGS__)
-#define assert_zd_ne(a, b, ...) assert_cmp(ssize_t, a, b, !=, \
+#define assert_zd_ne(a, b, ...) assert_cmp(ssize_t, a, b, !=, \
==, "zd", __VA_ARGS__)
-#define assert_zd_lt(a, b, ...) assert_cmp(ssize_t, a, b, <, \
+#define assert_zd_lt(a, b, ...) assert_cmp(ssize_t, a, b, <, \
>=, "zd", __VA_ARGS__)
-#define assert_zd_le(a, b, ...) assert_cmp(ssize_t, a, b, <=, \
+#define assert_zd_le(a, b, ...) assert_cmp(ssize_t, a, b, <=, \
>, "zd", __VA_ARGS__)
-#define assert_zd_ge(a, b, ...) assert_cmp(ssize_t, a, b, >=, \
+#define assert_zd_ge(a, b, ...) assert_cmp(ssize_t, a, b, >=, \
<, "zd", __VA_ARGS__)
-#define assert_zd_gt(a, b, ...) assert_cmp(ssize_t, a, b, >, \
+#define assert_zd_gt(a, b, ...) assert_cmp(ssize_t, a, b, >, \
<=, "zd", __VA_ARGS__)
-#define assert_zu_eq(a, b, ...) assert_cmp(size_t, a, b, ==, \
+#define assert_zu_eq(a, b, ...) assert_cmp(size_t, a, b, ==, \
!=, "zu", __VA_ARGS__)
-#define assert_zu_ne(a, b, ...) assert_cmp(size_t, a, b, !=, \
+#define assert_zu_ne(a, b, ...) assert_cmp(size_t, a, b, !=, \
==, "zu", __VA_ARGS__)
-#define assert_zu_lt(a, b, ...) assert_cmp(size_t, a, b, <, \
+#define assert_zu_lt(a, b, ...) assert_cmp(size_t, a, b, <, \
>=, "zu", __VA_ARGS__)
-#define assert_zu_le(a, b, ...) assert_cmp(size_t, a, b, <=, \
+#define assert_zu_le(a, b, ...) assert_cmp(size_t, a, b, <=, \
>, "zu", __VA_ARGS__)
-#define assert_zu_ge(a, b, ...) assert_cmp(size_t, a, b, >=, \
+#define assert_zu_ge(a, b, ...) assert_cmp(size_t, a, b, >=, \
<, "zu", __VA_ARGS__)
-#define assert_zu_gt(a, b, ...) assert_cmp(size_t, a, b, >, \
+#define assert_zu_gt(a, b, ...) assert_cmp(size_t, a, b, >, \
<=, "zu", __VA_ARGS__)
-#define assert_d32_eq(a, b, ...) assert_cmp(int32_t, a, b, ==, \
+#define assert_d32_eq(a, b, ...) assert_cmp(int32_t, a, b, ==, \
!=, FMTd32, __VA_ARGS__)
-#define assert_d32_ne(a, b, ...) assert_cmp(int32_t, a, b, !=, \
+#define assert_d32_ne(a, b, ...) assert_cmp(int32_t, a, b, !=, \
==, FMTd32, __VA_ARGS__)
-#define assert_d32_lt(a, b, ...) assert_cmp(int32_t, a, b, <, \
+#define assert_d32_lt(a, b, ...) assert_cmp(int32_t, a, b, <, \
>=, FMTd32, __VA_ARGS__)
-#define assert_d32_le(a, b, ...) assert_cmp(int32_t, a, b, <=, \
+#define assert_d32_le(a, b, ...) assert_cmp(int32_t, a, b, <=, \
>, FMTd32, __VA_ARGS__)
-#define assert_d32_ge(a, b, ...) assert_cmp(int32_t, a, b, >=, \
+#define assert_d32_ge(a, b, ...) assert_cmp(int32_t, a, b, >=, \
<, FMTd32, __VA_ARGS__)
-#define assert_d32_gt(a, b, ...) assert_cmp(int32_t, a, b, >, \
+#define assert_d32_gt(a, b, ...) assert_cmp(int32_t, a, b, >, \
<=, FMTd32, __VA_ARGS__)
-#define assert_u32_eq(a, b, ...) assert_cmp(uint32_t, a, b, ==, \
+#define assert_u32_eq(a, b, ...) assert_cmp(uint32_t, a, b, ==, \
!=, FMTu32, __VA_ARGS__)
-#define assert_u32_ne(a, b, ...) assert_cmp(uint32_t, a, b, !=, \
+#define assert_u32_ne(a, b, ...) assert_cmp(uint32_t, a, b, !=, \
==, FMTu32, __VA_ARGS__)
-#define assert_u32_lt(a, b, ...) assert_cmp(uint32_t, a, b, <, \
+#define assert_u32_lt(a, b, ...) assert_cmp(uint32_t, a, b, <, \
>=, FMTu32, __VA_ARGS__)
-#define assert_u32_le(a, b, ...) assert_cmp(uint32_t, a, b, <=, \
+#define assert_u32_le(a, b, ...) assert_cmp(uint32_t, a, b, <=, \
>, FMTu32, __VA_ARGS__)
-#define assert_u32_ge(a, b, ...) assert_cmp(uint32_t, a, b, >=, \
+#define assert_u32_ge(a, b, ...) assert_cmp(uint32_t, a, b, >=, \
<, FMTu32, __VA_ARGS__)
-#define assert_u32_gt(a, b, ...) assert_cmp(uint32_t, a, b, >, \
+#define assert_u32_gt(a, b, ...) assert_cmp(uint32_t, a, b, >, \
<=, FMTu32, __VA_ARGS__)
-#define assert_d64_eq(a, b, ...) assert_cmp(int64_t, a, b, ==, \
+#define assert_d64_eq(a, b, ...) assert_cmp(int64_t, a, b, ==, \
!=, FMTd64, __VA_ARGS__)
-#define assert_d64_ne(a, b, ...) assert_cmp(int64_t, a, b, !=, \
+#define assert_d64_ne(a, b, ...) assert_cmp(int64_t, a, b, !=, \
==, FMTd64, __VA_ARGS__)
-#define assert_d64_lt(a, b, ...) assert_cmp(int64_t, a, b, <, \
+#define assert_d64_lt(a, b, ...) assert_cmp(int64_t, a, b, <, \
>=, FMTd64, __VA_ARGS__)
-#define assert_d64_le(a, b, ...) assert_cmp(int64_t, a, b, <=, \
+#define assert_d64_le(a, b, ...) assert_cmp(int64_t, a, b, <=, \
>, FMTd64, __VA_ARGS__)
-#define assert_d64_ge(a, b, ...) assert_cmp(int64_t, a, b, >=, \
+#define assert_d64_ge(a, b, ...) assert_cmp(int64_t, a, b, >=, \
<, FMTd64, __VA_ARGS__)
-#define assert_d64_gt(a, b, ...) assert_cmp(int64_t, a, b, >, \
+#define assert_d64_gt(a, b, ...) assert_cmp(int64_t, a, b, >, \
<=, FMTd64, __VA_ARGS__)
-#define assert_u64_eq(a, b, ...) assert_cmp(uint64_t, a, b, ==, \
+#define assert_u64_eq(a, b, ...) assert_cmp(uint64_t, a, b, ==, \
!=, FMTu64, __VA_ARGS__)
-#define assert_u64_ne(a, b, ...) assert_cmp(uint64_t, a, b, !=, \
+#define assert_u64_ne(a, b, ...) assert_cmp(uint64_t, a, b, !=, \
==, FMTu64, __VA_ARGS__)
-#define assert_u64_lt(a, b, ...) assert_cmp(uint64_t, a, b, <, \
+#define assert_u64_lt(a, b, ...) assert_cmp(uint64_t, a, b, <, \
>=, FMTu64, __VA_ARGS__)
-#define assert_u64_le(a, b, ...) assert_cmp(uint64_t, a, b, <=, \
+#define assert_u64_le(a, b, ...) assert_cmp(uint64_t, a, b, <=, \
>, FMTu64, __VA_ARGS__)
-#define assert_u64_ge(a, b, ...) assert_cmp(uint64_t, a, b, >=, \
+#define assert_u64_ge(a, b, ...) assert_cmp(uint64_t, a, b, >=, \
<, FMTu64, __VA_ARGS__)
-#define assert_u64_gt(a, b, ...) assert_cmp(uint64_t, a, b, >, \
+#define assert_u64_gt(a, b, ...) assert_cmp(uint64_t, a, b, >, \
<=, FMTu64, __VA_ARGS__)
-#define assert_b_eq(a, b, ...) do { \
+#define assert_b_eq(a, b, ...) do { \
bool a_ = (a); \
bool b_ = (b); \
if (!(a_ == b_)) { \
@@ -226,7 +226,7 @@
p_test_fail(prefix, message); \
} \
} while (0)
-#define assert_b_ne(a, b, ...) do { \
+#define assert_b_ne(a, b, ...) do { \
bool a_ = (a); \
bool b_ = (b); \
if (!(a_ != b_)) { \
@@ -242,10 +242,10 @@
p_test_fail(prefix, message); \
} \
} while (0)
-#define assert_true(a, ...) assert_b_eq(a, true, __VA_ARGS__)
-#define assert_false(a, ...) assert_b_eq(a, false, __VA_ARGS__)
+#define assert_true(a, ...) assert_b_eq(a, true, __VA_ARGS__)
+#define assert_false(a, ...) assert_b_eq(a, false, __VA_ARGS__)
-#define assert_str_eq(a, b, ...) do { \
+#define assert_str_eq(a, b, ...) do { \
if (strcmp((a), (b))) { \
char prefix[ASSERT_BUFSIZE]; \
char message[ASSERT_BUFSIZE]; \
@@ -258,7 +258,7 @@
p_test_fail(prefix, message); \
} \
} while (0)
-#define assert_str_ne(a, b, ...) do { \
+#define assert_str_ne(a, b, ...) do { \
if (!strcmp((a), (b))) { \
char prefix[ASSERT_BUFSIZE]; \
char message[ASSERT_BUFSIZE]; \
@@ -272,7 +272,7 @@
} \
} while (0)
-#define assert_not_reached(...) do { \
+#define assert_not_reached(...) do { \
char prefix[ASSERT_BUFSIZE]; \
char message[ASSERT_BUFSIZE]; \
malloc_snprintf(prefix, sizeof(prefix), \
@@ -296,22 +296,27 @@ typedef enum {
typedef void (test_t)(void);
-#define TEST_BEGIN(f) \
+#define TEST_BEGIN(f) \
static void \
-f(void) \
-{ \
+f(void) { \
p_test_init(#f);
-#define TEST_END \
+#define TEST_END \
goto label_test_end; \
label_test_end: \
p_test_fini(); \
}
-#define test(...) \
+#define test(...) \
p_test(__VA_ARGS__, NULL)
-#define test_skip_if(e) do { \
+#define test_no_reentrancy(...) \
+ p_test_no_reentrancy(__VA_ARGS__, NULL)
+
+#define test_no_malloc_init(...) \
+ p_test_no_malloc_init(__VA_ARGS__, NULL)
+
+#define test_skip_if(e) do { \
if (e) { \
test_skip("%s:%s:%d: Test skipped: (%s)", \
__func__, __FILE__, __LINE__, #e); \
@@ -319,11 +324,15 @@ label_test_end: \
} \
} while (0)
+bool test_is_reentrant();
+
void test_skip(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
void test_fail(const char *format, ...) JEMALLOC_FORMAT_PRINTF(1, 2);
/* For private use by macros. */
test_status_t p_test(test_t *t, ...);
+test_status_t p_test_no_reentrancy(test_t *t, ...);
+test_status_t p_test_no_malloc_init(test_t *t, ...);
void p_test_init(const char *name);
void p_test_fini(void);
void p_test_fail(const char *prefix, const char *message);
diff --git a/deps/jemalloc/test/include/test/timer.h b/deps/jemalloc/test/include/test/timer.h
index a7fefdfd1..ace6191b8 100644
--- a/deps/jemalloc/test/include/test/timer.h
+++ b/deps/jemalloc/test/include/test/timer.h
@@ -1,23 +1,8 @@
/* Simple timer, for use in benchmark reporting. */
-#include <unistd.h>
-#include <sys/time.h>
-
-#define JEMALLOC_CLOCK_GETTIME defined(_POSIX_MONOTONIC_CLOCK) \
- && _POSIX_MONOTONIC_CLOCK >= 0
-
typedef struct {
-#ifdef _WIN32
- FILETIME ft0;
- FILETIME ft1;
-#elif JEMALLOC_CLOCK_GETTIME
- struct timespec ts0;
- struct timespec ts1;
- int clock_id;
-#else
- struct timeval tv0;
- struct timeval tv1;
-#endif
+ nstime_t t0;
+ nstime_t t1;
} timedelta_t;
void timer_start(timedelta_t *timer);
diff --git a/deps/jemalloc/test/integration/MALLOCX_ARENA.c b/deps/jemalloc/test/integration/MALLOCX_ARENA.c
index 30c203ae6..222164d69 100644
--- a/deps/jemalloc/test/integration/MALLOCX_ARENA.c
+++ b/deps/jemalloc/test/integration/MALLOCX_ARENA.c
@@ -1,6 +1,6 @@
#include "test/jemalloc_test.h"
-#define NTHREADS 10
+#define NTHREADS 10
static bool have_dss =
#ifdef JEMALLOC_DSS
@@ -11,16 +11,15 @@ static bool have_dss =
;
void *
-thd_start(void *arg)
-{
+thd_start(void *arg) {
unsigned thread_ind = (unsigned)(uintptr_t)arg;
unsigned arena_ind;
void *p;
size_t sz;
sz = sizeof(arena_ind);
- assert_d_eq(mallctl("arenas.extend", &arena_ind, &sz, NULL, 0), 0,
- "Error in arenas.extend");
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Error in arenas.create");
if (thread_ind % 4 != 3) {
size_t mib[3];
@@ -42,11 +41,10 @@ thd_start(void *arg)
assert_ptr_not_null(p, "Unexpected mallocx() error");
dallocx(p, 0);
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_MALLOCX_ARENA)
-{
+TEST_BEGIN(test_MALLOCX_ARENA) {
thd_t thds[NTHREADS];
unsigned i;
@@ -55,15 +53,14 @@ TEST_BEGIN(test_MALLOCX_ARENA)
(void *)(uintptr_t)i);
}
- for (i = 0; i < NTHREADS; i++)
+ for (i = 0; i < NTHREADS; i++) {
thd_join(thds[i], NULL);
+ }
}
TEST_END
int
-main(void)
-{
-
- return (test(
- test_MALLOCX_ARENA));
+main(void) {
+ return test(
+ test_MALLOCX_ARENA);
}
diff --git a/deps/jemalloc/test/integration/aligned_alloc.c b/deps/jemalloc/test/integration/aligned_alloc.c
index 609001487..536b67ea8 100644
--- a/deps/jemalloc/test/integration/aligned_alloc.c
+++ b/deps/jemalloc/test/integration/aligned_alloc.c
@@ -1,12 +1,19 @@
#include "test/jemalloc_test.h"
-#define CHUNK 0x400000
-/* #define MAXALIGN ((size_t)UINT64_C(0x80000000000)) */
-#define MAXALIGN ((size_t)0x2000000LU)
-#define NITER 4
+#define MAXALIGN (((size_t)1) << 23)
-TEST_BEGIN(test_alignment_errors)
-{
+/*
+ * On systems which can't merge extents, tests that call this function generate
+ * a lot of dirty memory very quickly. Purging between cycles mitigates
+ * potential OOM on e.g. 32-bit Windows.
+ */
+static void
+purge(void) {
+ assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctl error");
+}
+
+TEST_BEGIN(test_alignment_errors) {
size_t alignment;
void *p;
@@ -27,8 +34,7 @@ TEST_BEGIN(test_alignment_errors)
}
TEST_END
-TEST_BEGIN(test_oom_errors)
-{
+TEST_BEGIN(test_oom_errors) {
size_t alignment, size;
void *p;
@@ -72,14 +78,15 @@ TEST_BEGIN(test_oom_errors)
}
TEST_END
-TEST_BEGIN(test_alignment_and_size)
-{
+TEST_BEGIN(test_alignment_and_size) {
+#define NITER 4
size_t alignment, size, total;
unsigned i;
void *ps[NITER];
- for (i = 0; i < NITER; i++)
+ for (i = 0; i < NITER; i++) {
ps[i] = NULL;
+ }
for (alignment = 8;
alignment <= MAXALIGN;
@@ -100,8 +107,9 @@ TEST_BEGIN(test_alignment_and_size)
alignment, size, size, buf);
}
total += malloc_usable_size(ps[i]);
- if (total >= (MAXALIGN << 1))
+ if (total >= (MAXALIGN << 1)) {
break;
+ }
}
for (i = 0; i < NITER; i++) {
if (ps[i] != NULL) {
@@ -110,16 +118,16 @@ TEST_BEGIN(test_alignment_and_size)
}
}
}
+ purge();
}
+#undef NITER
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_alignment_errors,
test_oom_errors,
- test_alignment_and_size));
+ test_alignment_and_size);
}
diff --git a/deps/jemalloc/test/integration/allocated.c b/deps/jemalloc/test/integration/allocated.c
index 3630e80ce..1425fd0aa 100644
--- a/deps/jemalloc/test/integration/allocated.c
+++ b/deps/jemalloc/test/integration/allocated.c
@@ -9,8 +9,7 @@ static const bool config_stats =
;
void *
-thd_start(void *arg)
-{
+thd_start(void *arg) {
int err;
void *p;
uint64_t a0, a1, d0, d1;
@@ -18,16 +17,18 @@ thd_start(void *arg)
size_t sz, usize;
sz = sizeof(a0);
- if ((err = mallctl("thread.allocated", &a0, &sz, NULL, 0))) {
- if (err == ENOENT)
+ if ((err = mallctl("thread.allocated", (void *)&a0, &sz, NULL, 0))) {
+ if (err == ENOENT) {
goto label_ENOENT;
+ }
test_fail("%s(): Error in mallctl(): %s", __func__,
strerror(err));
}
sz = sizeof(ap0);
- if ((err = mallctl("thread.allocatedp", &ap0, &sz, NULL, 0))) {
- if (err == ENOENT)
+ if ((err = mallctl("thread.allocatedp", (void *)&ap0, &sz, NULL, 0))) {
+ if (err == ENOENT) {
goto label_ENOENT;
+ }
test_fail("%s(): Error in mallctl(): %s", __func__,
strerror(err));
}
@@ -36,16 +37,19 @@ thd_start(void *arg)
"storage");
sz = sizeof(d0);
- if ((err = mallctl("thread.deallocated", &d0, &sz, NULL, 0))) {
- if (err == ENOENT)
+ if ((err = mallctl("thread.deallocated", (void *)&d0, &sz, NULL, 0))) {
+ if (err == ENOENT) {
goto label_ENOENT;
+ }
test_fail("%s(): Error in mallctl(): %s", __func__,
strerror(err));
}
sz = sizeof(dp0);
- if ((err = mallctl("thread.deallocatedp", &dp0, &sz, NULL, 0))) {
- if (err == ENOENT)
+ if ((err = mallctl("thread.deallocatedp", (void *)&dp0, &sz, NULL,
+ 0))) {
+ if (err == ENOENT) {
goto label_ENOENT;
+ }
test_fail("%s(): Error in mallctl(): %s", __func__,
strerror(err));
}
@@ -57,9 +61,9 @@ thd_start(void *arg)
assert_ptr_not_null(p, "Unexpected malloc() error");
sz = sizeof(a1);
- mallctl("thread.allocated", &a1, &sz, NULL, 0);
+ mallctl("thread.allocated", (void *)&a1, &sz, NULL, 0);
sz = sizeof(ap1);
- mallctl("thread.allocatedp", &ap1, &sz, NULL, 0);
+ mallctl("thread.allocatedp", (void *)&ap1, &sz, NULL, 0);
assert_u64_eq(*ap1, a1,
"Dereferenced \"thread.allocatedp\" value should equal "
"\"thread.allocated\" value");
@@ -74,9 +78,9 @@ thd_start(void *arg)
free(p);
sz = sizeof(d1);
- mallctl("thread.deallocated", &d1, &sz, NULL, 0);
+ mallctl("thread.deallocated", (void *)&d1, &sz, NULL, 0);
sz = sizeof(dp1);
- mallctl("thread.deallocatedp", &dp1, &sz, NULL, 0);
+ mallctl("thread.deallocatedp", (void *)&dp1, &sz, NULL, 0);
assert_u64_eq(*dp1, d1,
"Dereferenced \"thread.deallocatedp\" value should equal "
"\"thread.deallocated\" value");
@@ -87,23 +91,20 @@ thd_start(void *arg)
"Deallocated memory counter should increase by at least the amount "
"explicitly deallocated");
- return (NULL);
+ return NULL;
label_ENOENT:
assert_false(config_stats,
"ENOENT should only be returned if stats are disabled");
test_skip("\"thread.allocated\" mallctl not available");
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_main_thread)
-{
-
+TEST_BEGIN(test_main_thread) {
thd_start(NULL);
}
TEST_END
-TEST_BEGIN(test_subthread)
-{
+TEST_BEGIN(test_subthread) {
thd_t thd;
thd_create(&thd, thd_start, NULL);
@@ -112,14 +113,12 @@ TEST_BEGIN(test_subthread)
TEST_END
int
-main(void)
-{
-
+main(void) {
/* Run tests multiple times to check for bad interactions. */
- return (test(
+ return test(
test_main_thread,
test_subthread,
test_main_thread,
test_subthread,
- test_main_thread));
+ test_main_thread);
}
diff --git a/deps/jemalloc/test/integration/chunk.c b/deps/jemalloc/test/integration/chunk.c
deleted file mode 100644
index af1c9a53e..000000000
--- a/deps/jemalloc/test/integration/chunk.c
+++ /dev/null
@@ -1,276 +0,0 @@
-#include "test/jemalloc_test.h"
-
-#ifdef JEMALLOC_FILL
-const char *malloc_conf = "junk:false";
-#endif
-
-static chunk_hooks_t orig_hooks;
-static chunk_hooks_t old_hooks;
-
-static bool do_dalloc = true;
-static bool do_decommit;
-
-static bool did_alloc;
-static bool did_dalloc;
-static bool did_commit;
-static bool did_decommit;
-static bool did_purge;
-static bool did_split;
-static bool did_merge;
-
-#if 0
-# define TRACE_HOOK(fmt, ...) malloc_printf(fmt, __VA_ARGS__)
-#else
-# define TRACE_HOOK(fmt, ...)
-#endif
-
-void *
-chunk_alloc(void *new_addr, size_t size, size_t alignment, bool *zero,
- bool *commit, unsigned arena_ind)
-{
-
- TRACE_HOOK("%s(new_addr=%p, size=%zu, alignment=%zu, *zero=%s, "
- "*commit=%s, arena_ind=%u)\n", __func__, new_addr, size, alignment,
- *zero ? "true" : "false", *commit ? "true" : "false", arena_ind);
- did_alloc = true;
- return (old_hooks.alloc(new_addr, size, alignment, zero, commit,
- arena_ind));
-}
-
-bool
-chunk_dalloc(void *chunk, size_t size, bool committed, unsigned arena_ind)
-{
-
- TRACE_HOOK("%s(chunk=%p, size=%zu, committed=%s, arena_ind=%u)\n",
- __func__, chunk, size, committed ? "true" : "false", arena_ind);
- did_dalloc = true;
- if (!do_dalloc)
- return (true);
- return (old_hooks.dalloc(chunk, size, committed, arena_ind));
-}
-
-bool
-chunk_commit(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
- bool err;
-
- TRACE_HOOK("%s(chunk=%p, size=%zu, offset=%zu, length=%zu, "
- "arena_ind=%u)\n", __func__, chunk, size, offset, length,
- arena_ind);
- err = old_hooks.commit(chunk, size, offset, length, arena_ind);
- did_commit = !err;
- return (err);
-}
-
-bool
-chunk_decommit(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
- bool err;
-
- TRACE_HOOK("%s(chunk=%p, size=%zu, offset=%zu, length=%zu, "
- "arena_ind=%u)\n", __func__, chunk, size, offset, length,
- arena_ind);
- if (!do_decommit)
- return (true);
- err = old_hooks.decommit(chunk, size, offset, length, arena_ind);
- did_decommit = !err;
- return (err);
-}
-
-bool
-chunk_purge(void *chunk, size_t size, size_t offset, size_t length,
- unsigned arena_ind)
-{
-
- TRACE_HOOK("%s(chunk=%p, size=%zu, offset=%zu, length=%zu "
- "arena_ind=%u)\n", __func__, chunk, size, offset, length,
- arena_ind);
- did_purge = true;
- return (old_hooks.purge(chunk, size, offset, length, arena_ind));
-}
-
-bool
-chunk_split(void *chunk, size_t size, size_t size_a, size_t size_b,
- bool committed, unsigned arena_ind)
-{
-
- TRACE_HOOK("%s(chunk=%p, size=%zu, size_a=%zu, size_b=%zu, "
- "committed=%s, arena_ind=%u)\n", __func__, chunk, size, size_a,
- size_b, committed ? "true" : "false", arena_ind);
- did_split = true;
- return (old_hooks.split(chunk, size, size_a, size_b, committed,
- arena_ind));
-}
-
-bool
-chunk_merge(void *chunk_a, size_t size_a, void *chunk_b, size_t size_b,
- bool committed, unsigned arena_ind)
-{
-
- TRACE_HOOK("%s(chunk_a=%p, size_a=%zu, chunk_b=%p size_b=%zu, "
- "committed=%s, arena_ind=%u)\n", __func__, chunk_a, size_a, chunk_b,
- size_b, committed ? "true" : "false", arena_ind);
- did_merge = true;
- return (old_hooks.merge(chunk_a, size_a, chunk_b, size_b,
- committed, arena_ind));
-}
-
-TEST_BEGIN(test_chunk)
-{
- void *p;
- size_t old_size, new_size, large0, large1, huge0, huge1, huge2, sz;
- chunk_hooks_t new_hooks = {
- chunk_alloc,
- chunk_dalloc,
- chunk_commit,
- chunk_decommit,
- chunk_purge,
- chunk_split,
- chunk_merge
- };
- bool xallocx_success_a, xallocx_success_b, xallocx_success_c;
-
- /* Install custom chunk hooks. */
- old_size = sizeof(chunk_hooks_t);
- new_size = sizeof(chunk_hooks_t);
- assert_d_eq(mallctl("arena.0.chunk_hooks", &old_hooks, &old_size,
- &new_hooks, new_size), 0, "Unexpected chunk_hooks error");
- orig_hooks = old_hooks;
- assert_ptr_ne(old_hooks.alloc, chunk_alloc, "Unexpected alloc error");
- assert_ptr_ne(old_hooks.dalloc, chunk_dalloc,
- "Unexpected dalloc error");
- assert_ptr_ne(old_hooks.commit, chunk_commit,
- "Unexpected commit error");
- assert_ptr_ne(old_hooks.decommit, chunk_decommit,
- "Unexpected decommit error");
- assert_ptr_ne(old_hooks.purge, chunk_purge, "Unexpected purge error");
- assert_ptr_ne(old_hooks.split, chunk_split, "Unexpected split error");
- assert_ptr_ne(old_hooks.merge, chunk_merge, "Unexpected merge error");
-
- /* Get large size classes. */
- sz = sizeof(size_t);
- assert_d_eq(mallctl("arenas.lrun.0.size", &large0, &sz, NULL, 0), 0,
- "Unexpected arenas.lrun.0.size failure");
- assert_d_eq(mallctl("arenas.lrun.1.size", &large1, &sz, NULL, 0), 0,
- "Unexpected arenas.lrun.1.size failure");
-
- /* Get huge size classes. */
- assert_d_eq(mallctl("arenas.hchunk.0.size", &huge0, &sz, NULL, 0), 0,
- "Unexpected arenas.hchunk.0.size failure");
- assert_d_eq(mallctl("arenas.hchunk.1.size", &huge1, &sz, NULL, 0), 0,
- "Unexpected arenas.hchunk.1.size failure");
- assert_d_eq(mallctl("arenas.hchunk.2.size", &huge2, &sz, NULL, 0), 0,
- "Unexpected arenas.hchunk.2.size failure");
-
- /* Test dalloc/decommit/purge cascade. */
- do_dalloc = false;
- do_decommit = false;
- p = mallocx(huge0 * 2, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
- did_dalloc = false;
- did_decommit = false;
- did_purge = false;
- did_split = false;
- xallocx_success_a = (xallocx(p, huge0, 0, 0) == huge0);
- assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
- "Unexpected arena.0.purge error");
- if (xallocx_success_a) {
- assert_true(did_dalloc, "Expected dalloc");
- assert_false(did_decommit, "Unexpected decommit");
- assert_true(did_purge, "Expected purge");
- }
- assert_true(did_split, "Expected split");
- dallocx(p, 0);
- do_dalloc = true;
-
- /* Test decommit/commit and observe split/merge. */
- do_dalloc = false;
- do_decommit = true;
- p = mallocx(huge0 * 2, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
- did_decommit = false;
- did_commit = false;
- did_split = false;
- did_merge = false;
- xallocx_success_b = (xallocx(p, huge0, 0, 0) == huge0);
- assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
- "Unexpected arena.0.purge error");
- if (xallocx_success_b)
- assert_true(did_split, "Expected split");
- xallocx_success_c = (xallocx(p, huge0 * 2, 0, 0) == huge0 * 2);
- assert_b_eq(did_decommit, did_commit, "Expected decommit/commit match");
- if (xallocx_success_b && xallocx_success_c)
- assert_true(did_merge, "Expected merge");
- dallocx(p, 0);
- do_dalloc = true;
- do_decommit = false;
-
- /* Test purge for partial-chunk huge allocations. */
- if (huge0 * 2 > huge2) {
- /*
- * There are at least four size classes per doubling, so a
- * successful xallocx() from size=huge2 to size=huge1 is
- * guaranteed to leave trailing purgeable memory.
- */
- p = mallocx(huge2, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
- did_purge = false;
- assert_zu_eq(xallocx(p, huge1, 0, 0), huge1,
- "Unexpected xallocx() failure");
- assert_true(did_purge, "Expected purge");
- dallocx(p, 0);
- }
-
- /* Test decommit for large allocations. */
- do_decommit = true;
- p = mallocx(large1, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
- assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
- "Unexpected arena.0.purge error");
- did_decommit = false;
- assert_zu_eq(xallocx(p, large0, 0, 0), large0,
- "Unexpected xallocx() failure");
- assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
- "Unexpected arena.0.purge error");
- did_commit = false;
- assert_zu_eq(xallocx(p, large1, 0, 0), large1,
- "Unexpected xallocx() failure");
- assert_b_eq(did_decommit, did_commit, "Expected decommit/commit match");
- dallocx(p, 0);
- do_decommit = false;
-
- /* Make sure non-huge allocation succeeds. */
- p = mallocx(42, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
- dallocx(p, 0);
-
- /* Restore chunk hooks. */
- assert_d_eq(mallctl("arena.0.chunk_hooks", NULL, NULL, &old_hooks,
- new_size), 0, "Unexpected chunk_hooks error");
- assert_d_eq(mallctl("arena.0.chunk_hooks", &old_hooks, &old_size,
- NULL, 0), 0, "Unexpected chunk_hooks error");
- assert_ptr_eq(old_hooks.alloc, orig_hooks.alloc,
- "Unexpected alloc error");
- assert_ptr_eq(old_hooks.dalloc, orig_hooks.dalloc,
- "Unexpected dalloc error");
- assert_ptr_eq(old_hooks.commit, orig_hooks.commit,
- "Unexpected commit error");
- assert_ptr_eq(old_hooks.decommit, orig_hooks.decommit,
- "Unexpected decommit error");
- assert_ptr_eq(old_hooks.purge, orig_hooks.purge,
- "Unexpected purge error");
- assert_ptr_eq(old_hooks.split, orig_hooks.split,
- "Unexpected split error");
- assert_ptr_eq(old_hooks.merge, orig_hooks.merge,
- "Unexpected merge error");
-}
-TEST_END
-
-int
-main(void)
-{
-
- return (test(test_chunk));
-}
diff --git a/deps/jemalloc/test/integration/cpp/basic.cpp b/deps/jemalloc/test/integration/cpp/basic.cpp
new file mode 100644
index 000000000..65890ecd5
--- /dev/null
+++ b/deps/jemalloc/test/integration/cpp/basic.cpp
@@ -0,0 +1,25 @@
+#include <memory>
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_basic) {
+ auto foo = new long(4);
+ assert_ptr_not_null(foo, "Unexpected new[] failure");
+ delete foo;
+ // Test nullptr handling.
+ foo = nullptr;
+ delete foo;
+
+ auto bar = new long;
+ assert_ptr_not_null(bar, "Unexpected new failure");
+ delete bar;
+ // Test nullptr handling.
+ bar = nullptr;
+ delete bar;
+}
+TEST_END
+
+int
+main() {
+ return test(
+ test_basic);
+}
diff --git a/deps/jemalloc/test/integration/extent.c b/deps/jemalloc/test/integration/extent.c
new file mode 100644
index 000000000..b5db08766
--- /dev/null
+++ b/deps/jemalloc/test/integration/extent.c
@@ -0,0 +1,248 @@
+#include "test/jemalloc_test.h"
+
+#include "test/extent_hooks.h"
+
+static bool
+check_background_thread_enabled(void) {
+ bool enabled;
+ size_t sz = sizeof(bool);
+ int ret = mallctl("background_thread", (void *)&enabled, &sz, NULL,0);
+ if (ret == ENOENT) {
+ return false;
+ }
+ assert_d_eq(ret, 0, "Unexpected mallctl error");
+ return enabled;
+}
+
+static void
+test_extent_body(unsigned arena_ind) {
+ void *p;
+ size_t large0, large1, large2, sz;
+ size_t purge_mib[3];
+ size_t purge_miblen;
+ int flags;
+ bool xallocx_success_a, xallocx_success_b, xallocx_success_c;
+
+ flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
+
+ /* Get large size classes. */
+ sz = sizeof(size_t);
+ assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
+ 0), 0, "Unexpected arenas.lextent.0.size failure");
+ assert_d_eq(mallctl("arenas.lextent.1.size", (void *)&large1, &sz, NULL,
+ 0), 0, "Unexpected arenas.lextent.1.size failure");
+ assert_d_eq(mallctl("arenas.lextent.2.size", (void *)&large2, &sz, NULL,
+ 0), 0, "Unexpected arenas.lextent.2.size failure");
+
+ /* Test dalloc/decommit/purge cascade. */
+ purge_miblen = sizeof(purge_mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.purge", purge_mib, &purge_miblen),
+ 0, "Unexpected mallctlnametomib() failure");
+ purge_mib[1] = (size_t)arena_ind;
+ called_alloc = false;
+ try_alloc = true;
+ try_dalloc = false;
+ try_decommit = false;
+ p = mallocx(large0 * 2, flags);
+ assert_ptr_not_null(p, "Unexpected mallocx() error");
+ assert_true(called_alloc, "Expected alloc call");
+ called_dalloc = false;
+ called_decommit = false;
+ did_purge_lazy = false;
+ did_purge_forced = false;
+ called_split = false;
+ xallocx_success_a = (xallocx(p, large0, 0, flags) == large0);
+ assert_d_eq(mallctlbymib(purge_mib, purge_miblen, NULL, NULL, NULL, 0),
+ 0, "Unexpected arena.%u.purge error", arena_ind);
+ if (xallocx_success_a) {
+ assert_true(called_dalloc, "Expected dalloc call");
+ assert_true(called_decommit, "Expected decommit call");
+ assert_true(did_purge_lazy || did_purge_forced,
+ "Expected purge");
+ }
+ assert_true(called_split, "Expected split call");
+ dallocx(p, flags);
+ try_dalloc = true;
+
+ /* Test decommit/commit and observe split/merge. */
+ try_dalloc = false;
+ try_decommit = true;
+ p = mallocx(large0 * 2, flags);
+ assert_ptr_not_null(p, "Unexpected mallocx() error");
+ did_decommit = false;
+ did_commit = false;
+ called_split = false;
+ did_split = false;
+ did_merge = false;
+ xallocx_success_b = (xallocx(p, large0, 0, flags) == large0);
+ assert_d_eq(mallctlbymib(purge_mib, purge_miblen, NULL, NULL, NULL, 0),
+ 0, "Unexpected arena.%u.purge error", arena_ind);
+ if (xallocx_success_b) {
+ assert_true(did_split, "Expected split");
+ }
+ xallocx_success_c = (xallocx(p, large0 * 2, 0, flags) == large0 * 2);
+ if (did_split) {
+ assert_b_eq(did_decommit, did_commit,
+ "Expected decommit/commit match");
+ }
+ if (xallocx_success_b && xallocx_success_c) {
+ assert_true(did_merge, "Expected merge");
+ }
+ dallocx(p, flags);
+ try_dalloc = true;
+ try_decommit = false;
+
+ /* Make sure non-large allocation succeeds. */
+ p = mallocx(42, flags);
+ assert_ptr_not_null(p, "Unexpected mallocx() error");
+ dallocx(p, flags);
+}
+
+static void
+test_manual_hook_auto_arena(void) {
+ unsigned narenas;
+ size_t old_size, new_size, sz;
+ size_t hooks_mib[3];
+ size_t hooks_miblen;
+ extent_hooks_t *new_hooks, *old_hooks;
+
+ extent_hooks_prep();
+
+ sz = sizeof(unsigned);
+ /* Get number of auto arenas. */
+ assert_d_eq(mallctl("opt.narenas", (void *)&narenas, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+ if (narenas == 1) {
+ return;
+ }
+
+ /* Install custom extent hooks on arena 1 (might not be initialized). */
+ hooks_miblen = sizeof(hooks_mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.extent_hooks", hooks_mib,
+ &hooks_miblen), 0, "Unexpected mallctlnametomib() failure");
+ hooks_mib[1] = 1;
+ old_size = sizeof(extent_hooks_t *);
+ new_hooks = &hooks;
+ new_size = sizeof(extent_hooks_t *);
+ assert_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks,
+ &old_size, (void *)&new_hooks, new_size), 0,
+ "Unexpected extent_hooks error");
+ static bool auto_arena_created = false;
+ if (old_hooks != &hooks) {
+ assert_b_eq(auto_arena_created, false,
+ "Expected auto arena 1 created only once.");
+ auto_arena_created = true;
+ }
+}
+
+static void
+test_manual_hook_body(void) {
+ unsigned arena_ind;
+ size_t old_size, new_size, sz;
+ size_t hooks_mib[3];
+ size_t hooks_miblen;
+ extent_hooks_t *new_hooks, *old_hooks;
+
+ extent_hooks_prep();
+
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+
+ /* Install custom extent hooks. */
+ hooks_miblen = sizeof(hooks_mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.extent_hooks", hooks_mib,
+ &hooks_miblen), 0, "Unexpected mallctlnametomib() failure");
+ hooks_mib[1] = (size_t)arena_ind;
+ old_size = sizeof(extent_hooks_t *);
+ new_hooks = &hooks;
+ new_size = sizeof(extent_hooks_t *);
+ assert_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks,
+ &old_size, (void *)&new_hooks, new_size), 0,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->alloc, extent_alloc_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->dalloc, extent_dalloc_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->commit, extent_commit_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->decommit, extent_decommit_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->purge_lazy, extent_purge_lazy_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->purge_forced, extent_purge_forced_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->split, extent_split_hook,
+ "Unexpected extent_hooks error");
+ assert_ptr_ne(old_hooks->merge, extent_merge_hook,
+ "Unexpected extent_hooks error");
+
+ if (!check_background_thread_enabled()) {
+ test_extent_body(arena_ind);
+ }
+
+ /* Restore extent hooks. */
+ assert_d_eq(mallctlbymib(hooks_mib, hooks_miblen, NULL, NULL,
+ (void *)&old_hooks, new_size), 0, "Unexpected extent_hooks error");
+ assert_d_eq(mallctlbymib(hooks_mib, hooks_miblen, (void *)&old_hooks,
+ &old_size, NULL, 0), 0, "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks, default_hooks, "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->alloc, default_hooks->alloc,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->dalloc, default_hooks->dalloc,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->commit, default_hooks->commit,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->decommit, default_hooks->decommit,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->purge_lazy, default_hooks->purge_lazy,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->purge_forced, default_hooks->purge_forced,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->split, default_hooks->split,
+ "Unexpected extent_hooks error");
+ assert_ptr_eq(old_hooks->merge, default_hooks->merge,
+ "Unexpected extent_hooks error");
+}
+
+TEST_BEGIN(test_extent_manual_hook) {
+ test_manual_hook_auto_arena();
+ test_manual_hook_body();
+
+ /* Test failure paths. */
+ try_split = false;
+ test_manual_hook_body();
+ try_merge = false;
+ test_manual_hook_body();
+ try_purge_lazy = false;
+ try_purge_forced = false;
+ test_manual_hook_body();
+
+ try_split = try_merge = try_purge_lazy = try_purge_forced = true;
+}
+TEST_END
+
+TEST_BEGIN(test_extent_auto_hook) {
+ unsigned arena_ind;
+ size_t new_size, sz;
+ extent_hooks_t *new_hooks;
+
+ extent_hooks_prep();
+
+ sz = sizeof(unsigned);
+ new_hooks = &hooks;
+ new_size = sizeof(extent_hooks_t *);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz,
+ (void *)&new_hooks, new_size), 0, "Unexpected mallctl() failure");
+
+ test_skip_if(check_background_thread_enabled());
+ test_extent_body(arena_ind);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_extent_manual_hook,
+ test_extent_auto_hook);
+}
diff --git a/deps/jemalloc/test/integration/extent.sh b/deps/jemalloc/test/integration/extent.sh
new file mode 100644
index 000000000..0cc218737
--- /dev/null
+++ b/deps/jemalloc/test/integration/extent.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="junk:false"
+fi
diff --git a/deps/jemalloc/test/integration/mallocx.c b/deps/jemalloc/test/integration/mallocx.c
index 6253175d6..fd960f30c 100644
--- a/deps/jemalloc/test/integration/mallocx.c
+++ b/deps/jemalloc/test/integration/mallocx.c
@@ -1,28 +1,24 @@
#include "test/jemalloc_test.h"
static unsigned
-get_nsizes_impl(const char *cmd)
-{
+get_nsizes_impl(const char *cmd) {
unsigned ret;
size_t z;
z = sizeof(unsigned);
- assert_d_eq(mallctl(cmd, &ret, &z, NULL, 0), 0,
+ assert_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0,
"Unexpected mallctl(\"%s\", ...) failure", cmd);
- return (ret);
+ return ret;
}
static unsigned
-get_nhuge(void)
-{
-
- return (get_nsizes_impl("arenas.nhchunks"));
+get_nlarge(void) {
+ return get_nsizes_impl("arenas.nlextents");
}
static size_t
-get_size_impl(const char *cmd, size_t ind)
-{
+get_size_impl(const char *cmd, size_t ind) {
size_t ret;
size_t z;
size_t mib[4];
@@ -33,56 +29,92 @@ get_size_impl(const char *cmd, size_t ind)
0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
mib[2] = ind;
z = sizeof(size_t);
- assert_d_eq(mallctlbymib(mib, miblen, &ret, &z, NULL, 0),
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0),
0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind);
- return (ret);
+ return ret;
}
static size_t
-get_huge_size(size_t ind)
-{
+get_large_size(size_t ind) {
+ return get_size_impl("arenas.lextent.0.size", ind);
+}
- return (get_size_impl("arenas.hchunk.0.size", ind));
+/*
+ * On systems which can't merge extents, tests that call this function generate
+ * a lot of dirty memory very quickly. Purging between cycles mitigates
+ * potential OOM on e.g. 32-bit Windows.
+ */
+static void
+purge(void) {
+ assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctl error");
}
-TEST_BEGIN(test_oom)
-{
- size_t hugemax, size, alignment;
+TEST_BEGIN(test_overflow) {
+ size_t largemax;
+
+ largemax = get_large_size(get_nlarge()-1);
+
+ assert_ptr_null(mallocx(largemax+1, 0),
+ "Expected OOM for mallocx(size=%#zx, 0)", largemax+1);
+
+ assert_ptr_null(mallocx(ZU(PTRDIFF_MAX)+1, 0),
+ "Expected OOM for mallocx(size=%#zx, 0)", ZU(PTRDIFF_MAX)+1);
- hugemax = get_huge_size(get_nhuge()-1);
+ assert_ptr_null(mallocx(SIZE_T_MAX, 0),
+ "Expected OOM for mallocx(size=%#zx, 0)", SIZE_T_MAX);
+
+ assert_ptr_null(mallocx(1, MALLOCX_ALIGN(ZU(PTRDIFF_MAX)+1)),
+ "Expected OOM for mallocx(size=1, MALLOCX_ALIGN(%#zx))",
+ ZU(PTRDIFF_MAX)+1);
+}
+TEST_END
+
+TEST_BEGIN(test_oom) {
+ size_t largemax;
+ bool oom;
+ void *ptrs[3];
+ unsigned i;
/*
- * It should be impossible to allocate two objects that each consume
- * more than half the virtual address space.
+ * It should be impossible to allocate three objects that each consume
+ * nearly half the virtual address space.
*/
- {
- void *p;
-
- p = mallocx(hugemax, 0);
- if (p != NULL) {
- assert_ptr_null(mallocx(hugemax, 0),
- "Expected OOM for mallocx(size=%#zx, 0)", hugemax);
- dallocx(p, 0);
+ largemax = get_large_size(get_nlarge()-1);
+ oom = false;
+ for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) {
+ ptrs[i] = mallocx(largemax, 0);
+ if (ptrs[i] == NULL) {
+ oom = true;
+ }
+ }
+ assert_true(oom,
+ "Expected OOM during series of calls to mallocx(size=%zu, 0)",
+ largemax);
+ for (i = 0; i < sizeof(ptrs) / sizeof(void *); i++) {
+ if (ptrs[i] != NULL) {
+ dallocx(ptrs[i], 0);
}
}
+ purge();
#if LG_SIZEOF_PTR == 3
- size = ZU(0x8000000000000000);
- alignment = ZU(0x8000000000000000);
+ assert_ptr_null(mallocx(0x8000000000000000ULL,
+ MALLOCX_ALIGN(0x8000000000000000ULL)),
+ "Expected OOM for mallocx()");
+ assert_ptr_null(mallocx(0x8000000000000000ULL,
+ MALLOCX_ALIGN(0x80000000)),
+ "Expected OOM for mallocx()");
#else
- size = ZU(0x80000000);
- alignment = ZU(0x80000000);
+ assert_ptr_null(mallocx(0x80000000UL, MALLOCX_ALIGN(0x80000000UL)),
+ "Expected OOM for mallocx()");
#endif
- assert_ptr_null(mallocx(size, MALLOCX_ALIGN(alignment)),
- "Expected OOM for mallocx(size=%#zx, MALLOCX_ALIGN(%#zx)", size,
- alignment);
}
TEST_END
-TEST_BEGIN(test_basic)
-{
-#define MAXSZ (((size_t)1) << 26)
+TEST_BEGIN(test_basic) {
+#define MAXSZ (((size_t)1) << 23)
size_t sz;
for (sz = 1; sz < MAXSZ; sz = nallocx(sz, 0) + 1) {
@@ -91,38 +123,51 @@ TEST_BEGIN(test_basic)
nsz = nallocx(sz, 0);
assert_zu_ne(nsz, 0, "Unexpected nallocx() error");
p = mallocx(sz, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
+ assert_ptr_not_null(p,
+ "Unexpected mallocx(size=%zx, flags=0) error", sz);
rsz = sallocx(p, 0);
assert_zu_ge(rsz, sz, "Real size smaller than expected");
assert_zu_eq(nsz, rsz, "nallocx()/sallocx() size mismatch");
dallocx(p, 0);
p = mallocx(sz, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
+ assert_ptr_not_null(p,
+ "Unexpected mallocx(size=%zx, flags=0) error", sz);
dallocx(p, 0);
nsz = nallocx(sz, MALLOCX_ZERO);
assert_zu_ne(nsz, 0, "Unexpected nallocx() error");
p = mallocx(sz, MALLOCX_ZERO);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
+ assert_ptr_not_null(p,
+ "Unexpected mallocx(size=%zx, flags=MALLOCX_ZERO) error",
+ nsz);
rsz = sallocx(p, 0);
assert_zu_eq(nsz, rsz, "nallocx()/sallocx() rsize mismatch");
dallocx(p, 0);
+ purge();
}
#undef MAXSZ
}
TEST_END
-TEST_BEGIN(test_alignment_and_size)
-{
-#define MAXALIGN (((size_t)1) << 25)
-#define NITER 4
- size_t nsz, rsz, sz, alignment, total;
+TEST_BEGIN(test_alignment_and_size) {
+ const char *percpu_arena;
+ size_t sz = sizeof(percpu_arena);
+
+ if(mallctl("opt.percpu_arena", (void *)&percpu_arena, &sz, NULL, 0) ||
+ strcmp(percpu_arena, "disabled") != 0) {
+ test_skip("test_alignment_and_size skipped: "
+ "not working with percpu arena.");
+ };
+#define MAXALIGN (((size_t)1) << 23)
+#define NITER 4
+ size_t nsz, rsz, alignment, total;
unsigned i;
void *ps[NITER];
- for (i = 0; i < NITER; i++)
+ for (i = 0; i < NITER; i++) {
ps[i] = NULL;
+ }
for (alignment = 8;
alignment <= MAXALIGN;
@@ -155,8 +200,9 @@ TEST_BEGIN(test_alignment_and_size)
" alignment=%zu, size=%zu", ps[i],
alignment, sz);
total += rsz;
- if (total >= (MAXALIGN << 1))
+ if (total >= (MAXALIGN << 1)) {
break;
+ }
}
for (i = 0; i < NITER; i++) {
if (ps[i] != NULL) {
@@ -165,6 +211,7 @@ TEST_BEGIN(test_alignment_and_size)
}
}
}
+ purge();
}
#undef MAXALIGN
#undef NITER
@@ -172,11 +219,10 @@ TEST_BEGIN(test_alignment_and_size)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
+ test_overflow,
test_oom,
test_basic,
- test_alignment_and_size));
+ test_alignment_and_size);
}
diff --git a/deps/jemalloc/test/integration/mallocx.sh b/deps/jemalloc/test/integration/mallocx.sh
new file mode 100644
index 000000000..0cc218737
--- /dev/null
+++ b/deps/jemalloc/test/integration/mallocx.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="junk:false"
+fi
diff --git a/deps/jemalloc/test/integration/overflow.c b/deps/jemalloc/test/integration/overflow.c
index 303d9b2d3..6a9785b2e 100644
--- a/deps/jemalloc/test/integration/overflow.c
+++ b/deps/jemalloc/test/integration/overflow.c
@@ -1,24 +1,23 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_overflow)
-{
- unsigned nhchunks;
+TEST_BEGIN(test_overflow) {
+ unsigned nlextents;
size_t mib[4];
size_t sz, miblen, max_size_class;
void *p;
sz = sizeof(unsigned);
- assert_d_eq(mallctl("arenas.nhchunks", &nhchunks, &sz, NULL, 0), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL,
+ 0), 0, "Unexpected mallctl() error");
miblen = sizeof(mib) / sizeof(size_t);
- assert_d_eq(mallctlnametomib("arenas.hchunk.0.size", mib, &miblen), 0,
+ assert_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0,
"Unexpected mallctlnametomib() error");
- mib[2] = nhchunks - 1;
+ mib[2] = nlextents - 1;
sz = sizeof(size_t);
- assert_d_eq(mallctlbymib(mib, miblen, &max_size_class, &sz, NULL, 0), 0,
- "Unexpected mallctlbymib() error");
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&max_size_class, &sz,
+ NULL, 0), 0, "Unexpected mallctlbymib() error");
assert_ptr_null(malloc(max_size_class + 1),
"Expected OOM due to over-sized allocation request");
@@ -41,9 +40,7 @@ TEST_BEGIN(test_overflow)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_overflow));
+main(void) {
+ return test(
+ test_overflow);
}
diff --git a/deps/jemalloc/test/integration/posix_memalign.c b/deps/jemalloc/test/integration/posix_memalign.c
index 19741c6cb..2c2726de8 100644
--- a/deps/jemalloc/test/integration/posix_memalign.c
+++ b/deps/jemalloc/test/integration/posix_memalign.c
@@ -1,12 +1,19 @@
#include "test/jemalloc_test.h"
-#define CHUNK 0x400000
-/* #define MAXALIGN ((size_t)UINT64_C(0x80000000000)) */
-#define MAXALIGN ((size_t)0x2000000LU)
-#define NITER 4
+#define MAXALIGN (((size_t)1) << 23)
-TEST_BEGIN(test_alignment_errors)
-{
+/*
+ * On systems which can't merge extents, tests that call this function generate
+ * a lot of dirty memory very quickly. Purging between cycles mitigates
+ * potential OOM on e.g. 32-bit Windows.
+ */
+static void
+purge(void) {
+ assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctl error");
+}
+
+TEST_BEGIN(test_alignment_errors) {
size_t alignment;
void *p;
@@ -25,8 +32,7 @@ TEST_BEGIN(test_alignment_errors)
}
TEST_END
-TEST_BEGIN(test_oom_errors)
-{
+TEST_BEGIN(test_oom_errors) {
size_t alignment, size;
void *p;
@@ -64,15 +70,16 @@ TEST_BEGIN(test_oom_errors)
}
TEST_END
-TEST_BEGIN(test_alignment_and_size)
-{
+TEST_BEGIN(test_alignment_and_size) {
+#define NITER 4
size_t alignment, size, total;
unsigned i;
int err;
void *ps[NITER];
- for (i = 0; i < NITER; i++)
+ for (i = 0; i < NITER; i++) {
ps[i] = NULL;
+ }
for (alignment = 8;
alignment <= MAXALIGN;
@@ -94,8 +101,9 @@ TEST_BEGIN(test_alignment_and_size)
alignment, size, size, buf);
}
total += malloc_usable_size(ps[i]);
- if (total >= (MAXALIGN << 1))
+ if (total >= (MAXALIGN << 1)) {
break;
+ }
}
for (i = 0; i < NITER; i++) {
if (ps[i] != NULL) {
@@ -104,16 +112,16 @@ TEST_BEGIN(test_alignment_and_size)
}
}
}
+ purge();
}
+#undef NITER
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_alignment_errors,
test_oom_errors,
- test_alignment_and_size));
+ test_alignment_and_size);
}
diff --git a/deps/jemalloc/test/integration/rallocx.c b/deps/jemalloc/test/integration/rallocx.c
index be1b27b73..7821ca5f5 100644
--- a/deps/jemalloc/test/integration/rallocx.c
+++ b/deps/jemalloc/test/integration/rallocx.c
@@ -1,14 +1,53 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_grow_and_shrink)
-{
+static unsigned
+get_nsizes_impl(const char *cmd) {
+ unsigned ret;
+ size_t z;
+
+ z = sizeof(unsigned);
+ assert_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0,
+ "Unexpected mallctl(\"%s\", ...) failure", cmd);
+
+ return ret;
+}
+
+static unsigned
+get_nlarge(void) {
+ return get_nsizes_impl("arenas.nlextents");
+}
+
+static size_t
+get_size_impl(const char *cmd, size_t ind) {
+ size_t ret;
+ size_t z;
+ size_t mib[4];
+ size_t miblen = 4;
+
+ z = sizeof(size_t);
+ assert_d_eq(mallctlnametomib(cmd, mib, &miblen),
+ 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
+ mib[2] = ind;
+ z = sizeof(size_t);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0),
+ 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind);
+
+ return ret;
+}
+
+static size_t
+get_large_size(size_t ind) {
+ return get_size_impl("arenas.lextent.0.size", ind);
+}
+
+TEST_BEGIN(test_grow_and_shrink) {
void *p, *q;
size_t tsz;
-#define NCYCLES 3
+#define NCYCLES 3
unsigned i, j;
-#define NSZS 2500
+#define NSZS 1024
size_t szs[NSZS];
-#define MAXSZ ZU(12 * 1024 * 1024)
+#define MAXSZ ZU(12 * 1024 * 1024)
p = mallocx(1, 0);
assert_ptr_not_null(p, "Unexpected mallocx() error");
@@ -46,8 +85,7 @@ TEST_BEGIN(test_grow_and_shrink)
TEST_END
static bool
-validate_fill(const void *p, uint8_t c, size_t offset, size_t len)
-{
+validate_fill(const void *p, uint8_t c, size_t offset, size_t len) {
bool ret = false;
const uint8_t *buf = (const uint8_t *)p;
size_t i;
@@ -62,16 +100,15 @@ validate_fill(const void *p, uint8_t c, size_t offset, size_t len)
}
}
- return (ret);
+ return ret;
}
-TEST_BEGIN(test_zero)
-{
+TEST_BEGIN(test_zero) {
void *p, *q;
size_t psz, qsz, i, j;
size_t start_sizes[] = {1, 3*1024, 63*1024, 4095*1024};
-#define FILL_BYTE 0xaaU
-#define RANGE 2048
+#define FILL_BYTE 0xaaU
+#define RANGE 2048
for (i = 0; i < sizeof(start_sizes)/sizeof(size_t); i++) {
size_t start_size = start_sizes[i];
@@ -110,11 +147,10 @@ TEST_BEGIN(test_zero)
}
TEST_END
-TEST_BEGIN(test_align)
-{
+TEST_BEGIN(test_align) {
void *p, *q;
size_t align;
-#define MAX_ALIGN (ZU(1) << 25)
+#define MAX_ALIGN (ZU(1) << 25)
align = ZU(1);
p = mallocx(1, MALLOCX_ALIGN(align));
@@ -135,25 +171,24 @@ TEST_BEGIN(test_align)
}
TEST_END
-TEST_BEGIN(test_lg_align_and_zero)
-{
+TEST_BEGIN(test_lg_align_and_zero) {
void *p, *q;
- size_t lg_align, sz;
-#define MAX_LG_ALIGN 25
-#define MAX_VALIDATE (ZU(1) << 22)
+ unsigned lg_align;
+ size_t sz;
+#define MAX_LG_ALIGN 25
+#define MAX_VALIDATE (ZU(1) << 22)
- lg_align = ZU(0);
+ lg_align = 0;
p = mallocx(1, MALLOCX_LG_ALIGN(lg_align)|MALLOCX_ZERO);
assert_ptr_not_null(p, "Unexpected mallocx() error");
for (lg_align++; lg_align <= MAX_LG_ALIGN; lg_align++) {
q = rallocx(p, 1, MALLOCX_LG_ALIGN(lg_align)|MALLOCX_ZERO);
assert_ptr_not_null(q,
- "Unexpected rallocx() error for lg_align=%zu", lg_align);
+ "Unexpected rallocx() error for lg_align=%u", lg_align);
assert_ptr_null(
(void *)((uintptr_t)q & ((ZU(1) << lg_align)-1)),
- "%p inadequately aligned for lg_align=%zu",
- q, lg_align);
+ "%p inadequately aligned for lg_align=%u", q, lg_align);
sz = sallocx(q, 0);
if ((sz << 1) <= MAX_VALIDATE) {
assert_false(validate_fill(q, 0, 0, sz),
@@ -173,13 +208,38 @@ TEST_BEGIN(test_lg_align_and_zero)
}
TEST_END
-int
-main(void)
-{
+TEST_BEGIN(test_overflow) {
+ size_t largemax;
+ void *p;
+
+ largemax = get_large_size(get_nlarge()-1);
- return (test(
+ p = mallocx(1, 0);
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+
+ assert_ptr_null(rallocx(p, largemax+1, 0),
+ "Expected OOM for rallocx(p, size=%#zx, 0)", largemax+1);
+
+ assert_ptr_null(rallocx(p, ZU(PTRDIFF_MAX)+1, 0),
+ "Expected OOM for rallocx(p, size=%#zx, 0)", ZU(PTRDIFF_MAX)+1);
+
+ assert_ptr_null(rallocx(p, SIZE_T_MAX, 0),
+ "Expected OOM for rallocx(p, size=%#zx, 0)", SIZE_T_MAX);
+
+ assert_ptr_null(rallocx(p, 1, MALLOCX_ALIGN(ZU(PTRDIFF_MAX)+1)),
+ "Expected OOM for rallocx(p, size=1, MALLOCX_ALIGN(%#zx))",
+ ZU(PTRDIFF_MAX)+1);
+
+ dallocx(p, 0);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
test_grow_and_shrink,
test_zero,
test_align,
- test_lg_align_and_zero));
+ test_lg_align_and_zero,
+ test_overflow);
}
diff --git a/deps/jemalloc/test/integration/sdallocx.c b/deps/jemalloc/test/integration/sdallocx.c
index b84817d76..ca0144855 100644
--- a/deps/jemalloc/test/integration/sdallocx.c
+++ b/deps/jemalloc/test/integration/sdallocx.c
@@ -1,23 +1,22 @@
#include "test/jemalloc_test.h"
-#define MAXALIGN (((size_t)1) << 25)
-#define NITER 4
+#define MAXALIGN (((size_t)1) << 22)
+#define NITER 3
-TEST_BEGIN(test_basic)
-{
+TEST_BEGIN(test_basic) {
void *ptr = mallocx(64, 0);
sdallocx(ptr, 64, 0);
}
TEST_END
-TEST_BEGIN(test_alignment_and_size)
-{
+TEST_BEGIN(test_alignment_and_size) {
size_t nsz, sz, alignment, total;
unsigned i;
void *ps[NITER];
- for (i = 0; i < NITER; i++)
+ for (i = 0; i < NITER; i++) {
ps[i] = NULL;
+ }
for (alignment = 8;
alignment <= MAXALIGN;
@@ -32,8 +31,9 @@ TEST_BEGIN(test_alignment_and_size)
ps[i] = mallocx(sz, MALLOCX_ALIGN(alignment) |
MALLOCX_ZERO);
total += nsz;
- if (total >= (MAXALIGN << 1))
+ if (total >= (MAXALIGN << 1)) {
break;
+ }
}
for (i = 0; i < NITER; i++) {
if (ps[i] != NULL) {
@@ -48,10 +48,8 @@ TEST_BEGIN(test_alignment_and_size)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test_no_reentrancy(
test_basic,
- test_alignment_and_size));
+ test_alignment_and_size);
}
diff --git a/deps/jemalloc/test/integration/thread_arena.c b/deps/jemalloc/test/integration/thread_arena.c
index 67be53513..1e5ec05d8 100644
--- a/deps/jemalloc/test/integration/thread_arena.c
+++ b/deps/jemalloc/test/integration/thread_arena.c
@@ -1,10 +1,9 @@
#include "test/jemalloc_test.h"
-#define NTHREADS 10
+#define NTHREADS 10
void *
-thd_start(void *arg)
-{
+thd_start(void *arg) {
unsigned main_arena_ind = *(unsigned *)arg;
void *p;
unsigned arena_ind;
@@ -16,8 +15,8 @@ thd_start(void *arg)
free(p);
size = sizeof(arena_ind);
- if ((err = mallctl("thread.arena", &arena_ind, &size, &main_arena_ind,
- sizeof(main_arena_ind)))) {
+ if ((err = mallctl("thread.arena", (void *)&arena_ind, &size,
+ (void *)&main_arena_ind, sizeof(main_arena_ind)))) {
char buf[BUFERROR_BUF];
buferror(err, buf, sizeof(buf));
@@ -25,7 +24,8 @@ thd_start(void *arg)
}
size = sizeof(arena_ind);
- if ((err = mallctl("thread.arena", &arena_ind, &size, NULL, 0))) {
+ if ((err = mallctl("thread.arena", (void *)&arena_ind, &size, NULL,
+ 0))) {
char buf[BUFERROR_BUF];
buferror(err, buf, sizeof(buf));
@@ -34,14 +34,19 @@ thd_start(void *arg)
assert_u_eq(arena_ind, main_arena_ind,
"Arena index should be same as for main thread");
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_thread_arena)
-{
+static void
+mallctl_failure(int err) {
+ char buf[BUFERROR_BUF];
+
+ buferror(err, buf, sizeof(buf));
+ test_fail("Error in mallctl(): %s", buf);
+}
+
+TEST_BEGIN(test_thread_arena) {
void *p;
- unsigned arena_ind;
- size_t size;
int err;
thd_t thds[NTHREADS];
unsigned i;
@@ -49,12 +54,15 @@ TEST_BEGIN(test_thread_arena)
p = malloc(1);
assert_ptr_not_null(p, "Error in malloc()");
- size = sizeof(arena_ind);
- if ((err = mallctl("thread.arena", &arena_ind, &size, NULL, 0))) {
- char buf[BUFERROR_BUF];
+ unsigned arena_ind, old_arena_ind;
+ size_t sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Arena creation failure");
- buferror(err, buf, sizeof(buf));
- test_fail("Error in mallctl(): %s", buf);
+ size_t size = sizeof(arena_ind);
+ if ((err = mallctl("thread.arena", (void *)&old_arena_ind, &size,
+ (void *)&arena_ind, sizeof(arena_ind))) != 0) {
+ mallctl_failure(err);
}
for (i = 0; i < NTHREADS; i++) {
@@ -67,13 +75,12 @@ TEST_BEGIN(test_thread_arena)
thd_join(thds[i], (void *)&join_ret);
assert_zd_eq(join_ret, 0, "Unexpected thread join error");
}
+ free(p);
}
TEST_END
int
-main(void)
-{
-
- return (test(
- test_thread_arena));
+main(void) {
+ return test(
+ test_thread_arena);
}
diff --git a/deps/jemalloc/test/integration/thread_tcache_enabled.c b/deps/jemalloc/test/integration/thread_tcache_enabled.c
index f4e89c682..95c9acc13 100644
--- a/deps/jemalloc/test/integration/thread_tcache_enabled.c
+++ b/deps/jemalloc/test/integration/thread_tcache_enabled.c
@@ -1,97 +1,73 @@
#include "test/jemalloc_test.h"
-static const bool config_tcache =
-#ifdef JEMALLOC_TCACHE
- true
-#else
- false
-#endif
- ;
-
void *
-thd_start(void *arg)
-{
- int err;
- size_t sz;
+thd_start(void *arg) {
bool e0, e1;
-
- sz = sizeof(bool);
- if ((err = mallctl("thread.tcache.enabled", &e0, &sz, NULL, 0))) {
- if (err == ENOENT) {
- assert_false(config_tcache,
- "ENOENT should only be returned if tcache is "
- "disabled");
- }
- goto label_ENOENT;
- }
+ size_t sz = sizeof(bool);
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure");
if (e0) {
e1 = false;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz),
- 0, "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_true(e0, "tcache should be enabled");
}
e1 = true;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_false(e0, "tcache should be disabled");
e1 = true;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_true(e0, "tcache should be enabled");
e1 = false;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_true(e0, "tcache should be enabled");
e1 = false;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_false(e0, "tcache should be disabled");
free(malloc(1));
e1 = true;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_false(e0, "tcache should be disabled");
free(malloc(1));
e1 = true;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_true(e0, "tcache should be enabled");
free(malloc(1));
e1 = false;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_true(e0, "tcache should be enabled");
free(malloc(1));
e1 = false;
- assert_d_eq(mallctl("thread.tcache.enabled", &e0, &sz, &e1, sz), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("thread.tcache.enabled", (void *)&e0, &sz,
+ (void *)&e1, sz), 0, "Unexpected mallctl() error");
assert_false(e0, "tcache should be disabled");
free(malloc(1));
- return (NULL);
-label_ENOENT:
- test_skip("\"thread.tcache.enabled\" mallctl not available");
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_main_thread)
-{
-
+TEST_BEGIN(test_main_thread) {
thd_start(NULL);
}
TEST_END
-TEST_BEGIN(test_subthread)
-{
+TEST_BEGIN(test_subthread) {
thd_t thd;
thd_create(&thd, thd_start, NULL);
@@ -100,14 +76,12 @@ TEST_BEGIN(test_subthread)
TEST_END
int
-main(void)
-{
-
+main(void) {
/* Run tests multiple times to check for bad interactions. */
- return (test(
+ return test(
test_main_thread,
test_subthread,
test_main_thread,
test_subthread,
- test_main_thread));
+ test_main_thread);
}
diff --git a/deps/jemalloc/test/integration/xallocx.c b/deps/jemalloc/test/integration/xallocx.c
index 373625219..cd0ca048d 100644
--- a/deps/jemalloc/test/integration/xallocx.c
+++ b/deps/jemalloc/test/integration/xallocx.c
@@ -1,7 +1,24 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_same_size)
-{
+/*
+ * Use a separate arena for xallocx() extension/contraction tests so that
+ * internal allocation e.g. by heap profiling can't interpose allocations where
+ * xallocx() would ordinarily be able to extend.
+ */
+static unsigned
+arena_ind(void) {
+ static unsigned ind = 0;
+
+ if (ind == 0) {
+ size_t sz = sizeof(ind);
+ assert_d_eq(mallctl("arenas.create", (void *)&ind, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure creating arena");
+ }
+
+ return ind;
+}
+
+TEST_BEGIN(test_same_size) {
void *p;
size_t sz, tsz;
@@ -16,8 +33,7 @@ TEST_BEGIN(test_same_size)
}
TEST_END
-TEST_BEGIN(test_extra_no_move)
-{
+TEST_BEGIN(test_extra_no_move) {
void *p;
size_t sz, tsz;
@@ -32,8 +48,7 @@ TEST_BEGIN(test_extra_no_move)
}
TEST_END
-TEST_BEGIN(test_no_move_fail)
-{
+TEST_BEGIN(test_no_move_fail) {
void *p;
size_t sz, tsz;
@@ -49,42 +64,29 @@ TEST_BEGIN(test_no_move_fail)
TEST_END
static unsigned
-get_nsizes_impl(const char *cmd)
-{
+get_nsizes_impl(const char *cmd) {
unsigned ret;
size_t z;
z = sizeof(unsigned);
- assert_d_eq(mallctl(cmd, &ret, &z, NULL, 0), 0,
+ assert_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0,
"Unexpected mallctl(\"%s\", ...) failure", cmd);
- return (ret);
-}
-
-static unsigned
-get_nsmall(void)
-{
-
- return (get_nsizes_impl("arenas.nbins"));
+ return ret;
}
static unsigned
-get_nlarge(void)
-{
-
- return (get_nsizes_impl("arenas.nlruns"));
+get_nsmall(void) {
+ return get_nsizes_impl("arenas.nbins");
}
static unsigned
-get_nhuge(void)
-{
-
- return (get_nsizes_impl("arenas.nhchunks"));
+get_nlarge(void) {
+ return get_nsizes_impl("arenas.nlextents");
}
static size_t
-get_size_impl(const char *cmd, size_t ind)
-{
+get_size_impl(const char *cmd, size_t ind) {
size_t ret;
size_t z;
size_t mib[4];
@@ -95,41 +97,29 @@ get_size_impl(const char *cmd, size_t ind)
0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
mib[2] = ind;
z = sizeof(size_t);
- assert_d_eq(mallctlbymib(mib, miblen, &ret, &z, NULL, 0),
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0),
0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind);
- return (ret);
-}
-
-static size_t
-get_small_size(size_t ind)
-{
-
- return (get_size_impl("arenas.bin.0.size", ind));
+ return ret;
}
static size_t
-get_large_size(size_t ind)
-{
-
- return (get_size_impl("arenas.lrun.0.size", ind));
+get_small_size(size_t ind) {
+ return get_size_impl("arenas.bin.0.size", ind);
}
static size_t
-get_huge_size(size_t ind)
-{
-
- return (get_size_impl("arenas.hchunk.0.size", ind));
+get_large_size(size_t ind) {
+ return get_size_impl("arenas.lextent.0.size", ind);
}
-TEST_BEGIN(test_size)
-{
- size_t small0, hugemax;
+TEST_BEGIN(test_size) {
+ size_t small0, largemax;
void *p;
/* Get size classes. */
small0 = get_small_size(0);
- hugemax = get_huge_size(get_nhuge()-1);
+ largemax = get_large_size(get_nlarge()-1);
p = mallocx(small0, 0);
assert_ptr_not_null(p, "Unexpected mallocx() error");
@@ -139,60 +129,58 @@ TEST_BEGIN(test_size)
"Unexpected xallocx() behavior");
/* Test largest supported size. */
- assert_zu_le(xallocx(p, hugemax, 0, 0), hugemax,
+ assert_zu_le(xallocx(p, largemax, 0, 0), largemax,
"Unexpected xallocx() behavior");
/* Test size overflow. */
- assert_zu_le(xallocx(p, hugemax+1, 0, 0), hugemax,
+ assert_zu_le(xallocx(p, largemax+1, 0, 0), largemax,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, SIZE_T_MAX, 0, 0), hugemax,
+ assert_zu_le(xallocx(p, SIZE_T_MAX, 0, 0), largemax,
"Unexpected xallocx() behavior");
dallocx(p, 0);
}
TEST_END
-TEST_BEGIN(test_size_extra_overflow)
-{
- size_t small0, hugemax;
+TEST_BEGIN(test_size_extra_overflow) {
+ size_t small0, largemax;
void *p;
/* Get size classes. */
small0 = get_small_size(0);
- hugemax = get_huge_size(get_nhuge()-1);
+ largemax = get_large_size(get_nlarge()-1);
p = mallocx(small0, 0);
assert_ptr_not_null(p, "Unexpected mallocx() error");
/* Test overflows that can be resolved by clamping extra. */
- assert_zu_le(xallocx(p, hugemax-1, 2, 0), hugemax,
+ assert_zu_le(xallocx(p, largemax-1, 2, 0), largemax,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, hugemax, 1, 0), hugemax,
+ assert_zu_le(xallocx(p, largemax, 1, 0), largemax,
"Unexpected xallocx() behavior");
- /* Test overflow such that hugemax-size underflows. */
- assert_zu_le(xallocx(p, hugemax+1, 2, 0), hugemax,
+ /* Test overflow such that largemax-size underflows. */
+ assert_zu_le(xallocx(p, largemax+1, 2, 0), largemax,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, hugemax+2, 3, 0), hugemax,
+ assert_zu_le(xallocx(p, largemax+2, 3, 0), largemax,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, SIZE_T_MAX-2, 2, 0), hugemax,
+ assert_zu_le(xallocx(p, SIZE_T_MAX-2, 2, 0), largemax,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, SIZE_T_MAX-1, 1, 0), hugemax,
+ assert_zu_le(xallocx(p, SIZE_T_MAX-1, 1, 0), largemax,
"Unexpected xallocx() behavior");
dallocx(p, 0);
}
TEST_END
-TEST_BEGIN(test_extra_small)
-{
- size_t small0, small1, hugemax;
+TEST_BEGIN(test_extra_small) {
+ size_t small0, small1, largemax;
void *p;
/* Get size classes. */
small0 = get_small_size(0);
small1 = get_small_size(1);
- hugemax = get_huge_size(get_nhuge()-1);
+ largemax = get_large_size(get_nlarge()-1);
p = mallocx(small0, 0);
assert_ptr_not_null(p, "Unexpected mallocx() error");
@@ -207,7 +195,7 @@ TEST_BEGIN(test_extra_small)
"Unexpected xallocx() behavior");
/* Test size+extra overflow. */
- assert_zu_eq(xallocx(p, small0, hugemax - small0 + 1, 0), small0,
+ assert_zu_eq(xallocx(p, small0, largemax - small0 + 1, 0), small0,
"Unexpected xallocx() behavior");
assert_zu_eq(xallocx(p, small0, SIZE_T_MAX - small0, 0), small0,
"Unexpected xallocx() behavior");
@@ -216,140 +204,77 @@ TEST_BEGIN(test_extra_small)
}
TEST_END
-TEST_BEGIN(test_extra_large)
-{
- size_t smallmax, large0, large1, large2, huge0, hugemax;
+TEST_BEGIN(test_extra_large) {
+ int flags = MALLOCX_ARENA(arena_ind());
+ size_t smallmax, large1, large2, large3, largemax;
void *p;
/* Get size classes. */
smallmax = get_small_size(get_nsmall()-1);
- large0 = get_large_size(0);
large1 = get_large_size(1);
large2 = get_large_size(2);
- huge0 = get_huge_size(0);
- hugemax = get_huge_size(get_nhuge()-1);
-
- p = mallocx(large2, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() error");
-
- assert_zu_eq(xallocx(p, large2, 0, 0), large2,
- "Unexpected xallocx() behavior");
- /* Test size decrease with zero extra. */
- assert_zu_eq(xallocx(p, large0, 0, 0), large0,
- "Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, smallmax, 0, 0), large0,
- "Unexpected xallocx() behavior");
-
- assert_zu_eq(xallocx(p, large2, 0, 0), large2,
- "Unexpected xallocx() behavior");
- /* Test size decrease with non-zero extra. */
- assert_zu_eq(xallocx(p, large0, large2 - large0, 0), large2,
- "Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, large1, large2 - large1, 0), large2,
- "Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, large0, large1 - large0, 0), large1,
- "Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, smallmax, large0 - smallmax, 0), large0,
- "Unexpected xallocx() behavior");
-
- assert_zu_eq(xallocx(p, large0, 0, 0), large0,
- "Unexpected xallocx() behavior");
- /* Test size increase with zero extra. */
- assert_zu_eq(xallocx(p, large2, 0, 0), large2,
- "Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, huge0, 0, 0), large2,
- "Unexpected xallocx() behavior");
-
- assert_zu_eq(xallocx(p, large0, 0, 0), large0,
- "Unexpected xallocx() behavior");
- /* Test size increase with non-zero extra. */
- assert_zu_lt(xallocx(p, large0, huge0 - large0, 0), huge0,
- "Unexpected xallocx() behavior");
-
- assert_zu_eq(xallocx(p, large0, 0, 0), large0,
- "Unexpected xallocx() behavior");
- /* Test size increase with non-zero extra. */
- assert_zu_eq(xallocx(p, large0, large2 - large0, 0), large2,
- "Unexpected xallocx() behavior");
-
- assert_zu_eq(xallocx(p, large2, 0, 0), large2,
- "Unexpected xallocx() behavior");
- /* Test size+extra overflow. */
- assert_zu_lt(xallocx(p, large2, hugemax - large2 + 1, 0), huge0,
- "Unexpected xallocx() behavior");
-
- dallocx(p, 0);
-}
-TEST_END
-
-TEST_BEGIN(test_extra_huge)
-{
- size_t largemax, huge0, huge1, huge2, hugemax;
- void *p;
-
- /* Get size classes. */
+ large3 = get_large_size(3);
largemax = get_large_size(get_nlarge()-1);
- huge0 = get_huge_size(0);
- huge1 = get_huge_size(1);
- huge2 = get_huge_size(2);
- hugemax = get_huge_size(get_nhuge()-1);
- p = mallocx(huge2, 0);
+ p = mallocx(large3, flags);
assert_ptr_not_null(p, "Unexpected mallocx() error");
- assert_zu_eq(xallocx(p, huge2, 0, 0), huge2,
+ assert_zu_eq(xallocx(p, large3, 0, flags), large3,
"Unexpected xallocx() behavior");
/* Test size decrease with zero extra. */
- assert_zu_ge(xallocx(p, huge0, 0, 0), huge0,
+ assert_zu_ge(xallocx(p, large1, 0, flags), large1,
"Unexpected xallocx() behavior");
- assert_zu_ge(xallocx(p, largemax, 0, 0), huge0,
+ assert_zu_ge(xallocx(p, smallmax, 0, flags), large1,
"Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, huge2, 0, 0), huge2,
- "Unexpected xallocx() behavior");
+ if (xallocx(p, large3, 0, flags) != large3) {
+ p = rallocx(p, large3, flags);
+ assert_ptr_not_null(p, "Unexpected rallocx() failure");
+ }
/* Test size decrease with non-zero extra. */
- assert_zu_eq(xallocx(p, huge0, huge2 - huge0, 0), huge2,
+ assert_zu_eq(xallocx(p, large1, large3 - large1, flags), large3,
"Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, huge1, huge2 - huge1, 0), huge2,
+ assert_zu_eq(xallocx(p, large2, large3 - large2, flags), large3,
"Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, huge0, huge1 - huge0, 0), huge1,
+ assert_zu_ge(xallocx(p, large1, large2 - large1, flags), large2,
"Unexpected xallocx() behavior");
- assert_zu_ge(xallocx(p, largemax, huge0 - largemax, 0), huge0,
+ assert_zu_ge(xallocx(p, smallmax, large1 - smallmax, flags), large1,
"Unexpected xallocx() behavior");
- assert_zu_ge(xallocx(p, huge0, 0, 0), huge0,
+ assert_zu_ge(xallocx(p, large1, 0, flags), large1,
"Unexpected xallocx() behavior");
/* Test size increase with zero extra. */
- assert_zu_le(xallocx(p, huge2, 0, 0), huge2,
+ assert_zu_le(xallocx(p, large3, 0, flags), large3,
"Unexpected xallocx() behavior");
- assert_zu_le(xallocx(p, hugemax+1, 0, 0), huge2,
+ assert_zu_le(xallocx(p, largemax+1, 0, flags), large3,
"Unexpected xallocx() behavior");
- assert_zu_ge(xallocx(p, huge0, 0, 0), huge0,
+ assert_zu_ge(xallocx(p, large1, 0, flags), large1,
"Unexpected xallocx() behavior");
/* Test size increase with non-zero extra. */
- assert_zu_le(xallocx(p, huge0, SIZE_T_MAX - huge0, 0), hugemax,
+ assert_zu_le(xallocx(p, large1, SIZE_T_MAX - large1, flags), largemax,
"Unexpected xallocx() behavior");
- assert_zu_ge(xallocx(p, huge0, 0, 0), huge0,
+ assert_zu_ge(xallocx(p, large1, 0, flags), large1,
"Unexpected xallocx() behavior");
/* Test size increase with non-zero extra. */
- assert_zu_le(xallocx(p, huge0, huge2 - huge0, 0), huge2,
+ assert_zu_le(xallocx(p, large1, large3 - large1, flags), large3,
"Unexpected xallocx() behavior");
- assert_zu_eq(xallocx(p, huge2, 0, 0), huge2,
- "Unexpected xallocx() behavior");
+ if (xallocx(p, large3, 0, flags) != large3) {
+ p = rallocx(p, large3, flags);
+ assert_ptr_not_null(p, "Unexpected rallocx() failure");
+ }
/* Test size+extra overflow. */
- assert_zu_le(xallocx(p, huge2, hugemax - huge2 + 1, 0), hugemax,
+ assert_zu_le(xallocx(p, large3, largemax - large3 + 1, flags), largemax,
"Unexpected xallocx() behavior");
- dallocx(p, 0);
+ dallocx(p, flags);
}
TEST_END
static void
-print_filled_extents(const void *p, uint8_t c, size_t len)
-{
+print_filled_extents(const void *p, uint8_t c, size_t len) {
const uint8_t *pc = (const uint8_t *)p;
size_t i, range0;
uint8_t c0;
@@ -368,32 +293,33 @@ print_filled_extents(const void *p, uint8_t c, size_t len)
}
static bool
-validate_fill(const void *p, uint8_t c, size_t offset, size_t len)
-{
+validate_fill(const void *p, uint8_t c, size_t offset, size_t len) {
const uint8_t *pc = (const uint8_t *)p;
bool err;
size_t i;
for (i = offset, err = false; i < offset+len; i++) {
- if (pc[i] != c)
+ if (pc[i] != c) {
err = true;
+ }
}
- if (err)
+ if (err) {
print_filled_extents(p, c, offset + len);
+ }
- return (err);
+ return err;
}
static void
-test_zero(size_t szmin, size_t szmax)
-{
+test_zero(size_t szmin, size_t szmax) {
+ int flags = MALLOCX_ARENA(arena_ind()) | MALLOCX_ZERO;
size_t sz, nsz;
void *p;
-#define FILL_BYTE 0x7aU
+#define FILL_BYTE 0x7aU
sz = szmax;
- p = mallocx(sz, MALLOCX_ZERO);
+ p = mallocx(sz, flags);
assert_ptr_not_null(p, "Unexpected mallocx() error");
assert_false(validate_fill(p, 0x00, 0, sz), "Memory not filled: sz=%zu",
sz);
@@ -408,15 +334,19 @@ test_zero(size_t szmin, size_t szmax)
/* Shrink in place so that we can expect growing in place to succeed. */
sz = szmin;
- assert_zu_eq(xallocx(p, sz, 0, MALLOCX_ZERO), sz,
- "Unexpected xallocx() error");
+ if (xallocx(p, sz, 0, flags) != sz) {
+ p = rallocx(p, sz, flags);
+ assert_ptr_not_null(p, "Unexpected rallocx() failure");
+ }
assert_false(validate_fill(p, FILL_BYTE, 0, sz),
"Memory not filled: sz=%zu", sz);
for (sz = szmin; sz < szmax; sz = nsz) {
- nsz = nallocx(sz+1, MALLOCX_ZERO);
- assert_zu_eq(xallocx(p, sz+1, 0, MALLOCX_ZERO), nsz,
- "Unexpected xallocx() failure");
+ nsz = nallocx(sz+1, flags);
+ if (xallocx(p, sz+1, 0, flags) != nsz) {
+ p = rallocx(p, sz+1, flags);
+ assert_ptr_not_null(p, "Unexpected rallocx() failure");
+ }
assert_false(validate_fill(p, FILL_BYTE, 0, sz),
"Memory not filled: sz=%zu", sz);
assert_false(validate_fill(p, 0x00, sz, nsz-sz),
@@ -426,38 +356,23 @@ test_zero(size_t szmin, size_t szmax)
"Memory not filled: nsz=%zu", nsz);
}
- dallocx(p, 0);
+ dallocx(p, flags);
}
-TEST_BEGIN(test_zero_large)
-{
- size_t large0, largemax;
+TEST_BEGIN(test_zero_large) {
+ size_t large0, large1;
/* Get size classes. */
large0 = get_large_size(0);
- largemax = get_large_size(get_nlarge()-1);
-
- test_zero(large0, largemax);
-}
-TEST_END
-
-TEST_BEGIN(test_zero_huge)
-{
- size_t huge0, huge1;
-
- /* Get size classes. */
- huge0 = get_huge_size(0);
- huge1 = get_huge_size(1);
+ large1 = get_large_size(1);
- test_zero(huge1, huge0 * 2);
+ test_zero(large1, large0 * 2);
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_same_size,
test_extra_no_move,
test_no_move_fail,
@@ -465,7 +380,5 @@ main(void)
test_size_extra_overflow,
test_extra_small,
test_extra_large,
- test_extra_huge,
- test_zero_large,
- test_zero_huge));
+ test_zero_large);
}
diff --git a/deps/jemalloc/test/integration/xallocx.sh b/deps/jemalloc/test/integration/xallocx.sh
new file mode 100644
index 000000000..0cc218737
--- /dev/null
+++ b/deps/jemalloc/test/integration/xallocx.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="junk:false"
+fi
diff --git a/deps/jemalloc/test/src/SFMT.c b/deps/jemalloc/test/src/SFMT.c
index 80cabe05e..c05e2183b 100644
--- a/deps/jemalloc/test/src/SFMT.c
+++ b/deps/jemalloc/test/src/SFMT.c
@@ -33,7 +33,7 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-/**
+/**
* @file SFMT.c
* @brief SIMD oriented Fast Mersenne Twister(SFMT)
*
@@ -45,7 +45,7 @@
*
* The new BSD License is applied to this software, see LICENSE.txt
*/
-#define SFMT_C_
+#define SFMT_C_
#include "test/jemalloc_test.h"
#include "test/SFMT-params.h"
@@ -108,7 +108,7 @@ struct sfmt_s {
/*--------------------------------------
FILE GLOBAL VARIABLES
- internal state, index counter and flag
+ internal state, index counter and flag
--------------------------------------*/
/** a parity check vector which certificate the period of 2^{MEXP} */
@@ -117,18 +117,18 @@ static uint32_t parity[4] = {PARITY1, PARITY2, PARITY3, PARITY4};
/*----------------
STATIC FUNCTIONS
----------------*/
-JEMALLOC_INLINE_C int idxof(int i);
+static inline int idxof(int i);
#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2))
-JEMALLOC_INLINE_C void rshift128(w128_t *out, w128_t const *in, int shift);
-JEMALLOC_INLINE_C void lshift128(w128_t *out, w128_t const *in, int shift);
+static inline void rshift128(w128_t *out, w128_t const *in, int shift);
+static inline void lshift128(w128_t *out, w128_t const *in, int shift);
#endif
-JEMALLOC_INLINE_C void gen_rand_all(sfmt_t *ctx);
-JEMALLOC_INLINE_C void gen_rand_array(sfmt_t *ctx, w128_t *array, int size);
-JEMALLOC_INLINE_C uint32_t func1(uint32_t x);
-JEMALLOC_INLINE_C uint32_t func2(uint32_t x);
+static inline void gen_rand_all(sfmt_t *ctx);
+static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size);
+static inline uint32_t func1(uint32_t x);
+static inline uint32_t func2(uint32_t x);
static void period_certification(sfmt_t *ctx);
#if defined(BIG_ENDIAN64) && !defined(ONLY64)
-JEMALLOC_INLINE_C void swap(w128_t *array, int size);
+static inline void swap(w128_t *array, int size);
#endif
#if defined(HAVE_ALTIVEC)
@@ -138,15 +138,15 @@ JEMALLOC_INLINE_C void swap(w128_t *array, int size);
#endif
/**
- * This function simulate a 64-bit index of LITTLE ENDIAN
+ * This function simulate a 64-bit index of LITTLE ENDIAN
* in BIG ENDIAN machine.
*/
#ifdef ONLY64
-JEMALLOC_INLINE_C int idxof(int i) {
+static inline int idxof(int i) {
return i ^ 1;
}
#else
-JEMALLOC_INLINE_C int idxof(int i) {
+static inline int idxof(int i) {
return i;
}
#endif
@@ -160,7 +160,7 @@ JEMALLOC_INLINE_C int idxof(int i) {
*/
#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2))
#ifdef ONLY64
-JEMALLOC_INLINE_C void rshift128(w128_t *out, w128_t const *in, int shift) {
+static inline void rshift128(w128_t *out, w128_t const *in, int shift) {
uint64_t th, tl, oh, ol;
th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]);
@@ -175,7 +175,7 @@ JEMALLOC_INLINE_C void rshift128(w128_t *out, w128_t const *in, int shift) {
out->u[3] = (uint32_t)oh;
}
#else
-JEMALLOC_INLINE_C void rshift128(w128_t *out, w128_t const *in, int shift) {
+static inline void rshift128(w128_t *out, w128_t const *in, int shift) {
uint64_t th, tl, oh, ol;
th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]);
@@ -199,7 +199,7 @@ JEMALLOC_INLINE_C void rshift128(w128_t *out, w128_t const *in, int shift) {
* @param shift the shift value
*/
#ifdef ONLY64
-JEMALLOC_INLINE_C void lshift128(w128_t *out, w128_t const *in, int shift) {
+static inline void lshift128(w128_t *out, w128_t const *in, int shift) {
uint64_t th, tl, oh, ol;
th = ((uint64_t)in->u[2] << 32) | ((uint64_t)in->u[3]);
@@ -214,7 +214,7 @@ JEMALLOC_INLINE_C void lshift128(w128_t *out, w128_t const *in, int shift) {
out->u[3] = (uint32_t)oh;
}
#else
-JEMALLOC_INLINE_C void lshift128(w128_t *out, w128_t const *in, int shift) {
+static inline void lshift128(w128_t *out, w128_t const *in, int shift) {
uint64_t th, tl, oh, ol;
th = ((uint64_t)in->u[3] << 32) | ((uint64_t)in->u[2]);
@@ -241,37 +241,37 @@ JEMALLOC_INLINE_C void lshift128(w128_t *out, w128_t const *in, int shift) {
*/
#if (!defined(HAVE_ALTIVEC)) && (!defined(HAVE_SSE2))
#ifdef ONLY64
-JEMALLOC_INLINE_C void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c,
+static inline void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c,
w128_t *d) {
w128_t x;
w128_t y;
lshift128(&x, a, SL2);
rshift128(&y, c, SR2);
- r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0]
+ r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK2) ^ y.u[0]
^ (d->u[0] << SL1);
- r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1]
+ r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK1) ^ y.u[1]
^ (d->u[1] << SL1);
- r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2]
+ r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK4) ^ y.u[2]
^ (d->u[2] << SL1);
- r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3]
+ r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK3) ^ y.u[3]
^ (d->u[3] << SL1);
}
#else
-JEMALLOC_INLINE_C void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c,
+static inline void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c,
w128_t *d) {
w128_t x;
w128_t y;
lshift128(&x, a, SL2);
rshift128(&y, c, SR2);
- r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0]
+ r->u[0] = a->u[0] ^ x.u[0] ^ ((b->u[0] >> SR1) & MSK1) ^ y.u[0]
^ (d->u[0] << SL1);
- r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1]
+ r->u[1] = a->u[1] ^ x.u[1] ^ ((b->u[1] >> SR1) & MSK2) ^ y.u[1]
^ (d->u[1] << SL1);
- r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2]
+ r->u[2] = a->u[2] ^ x.u[2] ^ ((b->u[2] >> SR1) & MSK3) ^ y.u[2]
^ (d->u[2] << SL1);
- r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3]
+ r->u[3] = a->u[3] ^ x.u[3] ^ ((b->u[3] >> SR1) & MSK4) ^ y.u[3]
^ (d->u[3] << SL1);
}
#endif
@@ -282,7 +282,7 @@ JEMALLOC_INLINE_C void do_recursion(w128_t *r, w128_t *a, w128_t *b, w128_t *c,
* This function fills the internal state array with pseudorandom
* integers.
*/
-JEMALLOC_INLINE_C void gen_rand_all(sfmt_t *ctx) {
+static inline void gen_rand_all(sfmt_t *ctx) {
int i;
w128_t *r1, *r2;
@@ -306,10 +306,10 @@ JEMALLOC_INLINE_C void gen_rand_all(sfmt_t *ctx) {
* This function fills the user-specified array with pseudorandom
* integers.
*
- * @param array an 128-bit array to be filled by pseudorandom numbers.
+ * @param array an 128-bit array to be filled by pseudorandom numbers.
* @param size number of 128-bit pseudorandom numbers to be generated.
*/
-JEMALLOC_INLINE_C void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
+static inline void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
int i, j;
w128_t *r1, *r2;
@@ -343,7 +343,7 @@ JEMALLOC_INLINE_C void gen_rand_array(sfmt_t *ctx, w128_t *array, int size) {
#endif
#if defined(BIG_ENDIAN64) && !defined(ONLY64) && !defined(HAVE_ALTIVEC)
-JEMALLOC_INLINE_C void swap(w128_t *array, int size) {
+static inline void swap(w128_t *array, int size) {
int i;
uint32_t x, y;
@@ -476,7 +476,7 @@ uint32_t gen_rand32_range(sfmt_t *ctx, uint32_t limit) {
* This function generates and returns 64-bit pseudorandom number.
* init_gen_rand or init_by_array must be called before this function.
* The function gen_rand64 should not be called after gen_rand32,
- * unless an initialization is again executed.
+ * unless an initialization is again executed.
* @return 64-bit pseudorandom number
*/
uint64_t gen_rand64(sfmt_t *ctx) {
@@ -618,7 +618,7 @@ sfmt_t *init_gen_rand(uint32_t seed) {
psfmt32[idxof(0)] = seed;
for (i = 1; i < N32; i++) {
- psfmt32[idxof(i)] = 1812433253UL * (psfmt32[idxof(i - 1)]
+ psfmt32[idxof(i)] = 1812433253UL * (psfmt32[idxof(i - 1)]
^ (psfmt32[idxof(i - 1)] >> 30))
+ i;
}
@@ -668,7 +668,7 @@ sfmt_t *init_by_array(uint32_t *init_key, int key_length) {
} else {
count = N32;
}
- r = func1(psfmt32[idxof(0)] ^ psfmt32[idxof(mid)]
+ r = func1(psfmt32[idxof(0)] ^ psfmt32[idxof(mid)]
^ psfmt32[idxof(N32 - 1)]);
psfmt32[idxof(mid)] += r;
r += key_length;
@@ -677,7 +677,7 @@ sfmt_t *init_by_array(uint32_t *init_key, int key_length) {
count--;
for (i = 1, j = 0; (j < count) && (j < key_length); j++) {
- r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)]
+ r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)]
^ psfmt32[idxof((i + N32 - 1) % N32)]);
psfmt32[idxof((i + mid) % N32)] += r;
r += init_key[j] + i;
@@ -686,7 +686,7 @@ sfmt_t *init_by_array(uint32_t *init_key, int key_length) {
i = (i + 1) % N32;
}
for (; j < count; j++) {
- r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)]
+ r = func1(psfmt32[idxof(i)] ^ psfmt32[idxof((i + mid) % N32)]
^ psfmt32[idxof((i + N32 - 1) % N32)]);
psfmt32[idxof((i + mid) % N32)] += r;
r += i;
@@ -695,7 +695,7 @@ sfmt_t *init_by_array(uint32_t *init_key, int key_length) {
i = (i + 1) % N32;
}
for (j = 0; j < N32; j++) {
- r = func2(psfmt32[idxof(i)] + psfmt32[idxof((i + mid) % N32)]
+ r = func2(psfmt32[idxof(i)] + psfmt32[idxof((i + mid) % N32)]
+ psfmt32[idxof((i + N32 - 1) % N32)]);
psfmt32[idxof((i + mid) % N32)] ^= r;
r -= i;
diff --git a/deps/jemalloc/test/src/btalloc.c b/deps/jemalloc/test/src/btalloc.c
index 9a253d978..d570952ce 100644
--- a/deps/jemalloc/test/src/btalloc.c
+++ b/deps/jemalloc/test/src/btalloc.c
@@ -1,8 +1,6 @@
#include "test/jemalloc_test.h"
void *
-btalloc(size_t size, unsigned bits)
-{
-
- return (btalloc_0(size, bits));
+btalloc(size_t size, unsigned bits) {
+ return btalloc_0(size, bits);
}
diff --git a/deps/jemalloc/test/src/math.c b/deps/jemalloc/test/src/math.c
index 887a36390..1758c6778 100644
--- a/deps/jemalloc/test/src/math.c
+++ b/deps/jemalloc/test/src/math.c
@@ -1,2 +1,2 @@
-#define MATH_C_
+#define MATH_C_
#include "test/jemalloc_test.h"
diff --git a/deps/jemalloc/test/src/mq.c b/deps/jemalloc/test/src/mq.c
index 40b31c15c..9b5f672d6 100644
--- a/deps/jemalloc/test/src/mq.c
+++ b/deps/jemalloc/test/src/mq.c
@@ -5,9 +5,7 @@
* time is guaranteed.
*/
void
-mq_nanosleep(unsigned ns)
-{
-
+mq_nanosleep(unsigned ns) {
assert(ns <= 1000*1000*1000);
#ifdef _WIN32
diff --git a/deps/jemalloc/test/src/mtx.c b/deps/jemalloc/test/src/mtx.c
index 73bd02f6d..a393c01fc 100644
--- a/deps/jemalloc/test/src/mtx.c
+++ b/deps/jemalloc/test/src/mtx.c
@@ -1,38 +1,40 @@
#include "test/jemalloc_test.h"
#ifndef _CRT_SPINCOUNT
-#define _CRT_SPINCOUNT 4000
+#define _CRT_SPINCOUNT 4000
#endif
bool
-mtx_init(mtx_t *mtx)
-{
-
+mtx_init(mtx_t *mtx) {
#ifdef _WIN32
- if (!InitializeCriticalSectionAndSpinCount(&mtx->lock, _CRT_SPINCOUNT))
- return (true);
+ if (!InitializeCriticalSectionAndSpinCount(&mtx->lock,
+ _CRT_SPINCOUNT)) {
+ return true;
+ }
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ mtx->lock = OS_UNFAIR_LOCK_INIT;
#elif (defined(JEMALLOC_OSSPIN))
mtx->lock = 0;
#else
pthread_mutexattr_t attr;
- if (pthread_mutexattr_init(&attr) != 0)
- return (true);
+ if (pthread_mutexattr_init(&attr) != 0) {
+ return true;
+ }
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
if (pthread_mutex_init(&mtx->lock, &attr) != 0) {
pthread_mutexattr_destroy(&attr);
- return (true);
+ return true;
}
pthread_mutexattr_destroy(&attr);
#endif
- return (false);
+ return false;
}
void
-mtx_fini(mtx_t *mtx)
-{
-
+mtx_fini(mtx_t *mtx) {
#ifdef _WIN32
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
#elif (defined(JEMALLOC_OSSPIN))
#else
pthread_mutex_destroy(&mtx->lock);
@@ -40,11 +42,11 @@ mtx_fini(mtx_t *mtx)
}
void
-mtx_lock(mtx_t *mtx)
-{
-
+mtx_lock(mtx_t *mtx) {
#ifdef _WIN32
EnterCriticalSection(&mtx->lock);
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ os_unfair_lock_lock(&mtx->lock);
#elif (defined(JEMALLOC_OSSPIN))
OSSpinLockLock(&mtx->lock);
#else
@@ -53,11 +55,11 @@ mtx_lock(mtx_t *mtx)
}
void
-mtx_unlock(mtx_t *mtx)
-{
-
+mtx_unlock(mtx_t *mtx) {
#ifdef _WIN32
LeaveCriticalSection(&mtx->lock);
+#elif (defined(JEMALLOC_OS_UNFAIR_LOCK))
+ os_unfair_lock_unlock(&mtx->lock);
#elif (defined(JEMALLOC_OSSPIN))
OSSpinLockUnlock(&mtx->lock);
#else
diff --git a/deps/jemalloc/test/src/test.c b/deps/jemalloc/test/src/test.c
index 8173614cf..01a4d7380 100644
--- a/deps/jemalloc/test/src/test.c
+++ b/deps/jemalloc/test/src/test.c
@@ -1,14 +1,70 @@
#include "test/jemalloc_test.h"
+/* Test status state. */
+
static unsigned test_count = 0;
static test_status_t test_counts[test_status_count] = {0, 0, 0};
static test_status_t test_status = test_status_pass;
static const char * test_name = "";
+/* Reentrancy testing helpers. */
+
+#define NUM_REENTRANT_ALLOCS 20
+typedef enum {
+ non_reentrant = 0,
+ libc_reentrant = 1,
+ arena_new_reentrant = 2
+} reentrancy_t;
+static reentrancy_t reentrancy;
+
+static bool libc_hook_ran = false;
+static bool arena_new_hook_ran = false;
+
+static const char *
+reentrancy_t_str(reentrancy_t r) {
+ switch (r) {
+ case non_reentrant:
+ return "non-reentrant";
+ case libc_reentrant:
+ return "libc-reentrant";
+ case arena_new_reentrant:
+ return "arena_new-reentrant";
+ default:
+ unreachable();
+ }
+}
+
+static void
+do_hook(bool *hook_ran, void (**hook)()) {
+ *hook_ran = true;
+ *hook = NULL;
+
+ size_t alloc_size = 1;
+ for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) {
+ free(malloc(alloc_size));
+ alloc_size *= 2;
+ }
+}
+
+static void
+libc_reentrancy_hook() {
+ do_hook(&libc_hook_ran, &hooks_libc_hook);
+}
+
+static void
+arena_new_reentrancy_hook() {
+ do_hook(&arena_new_hook_ran, &hooks_arena_new_hook);
+}
+
+/* Actual test infrastructure. */
+bool
+test_is_reentrant() {
+ return reentrancy != non_reentrant;
+}
+
JEMALLOC_FORMAT_PRINTF(1, 2)
void
-test_skip(const char *format, ...)
-{
+test_skip(const char *format, ...) {
va_list ap;
va_start(ap, format);
@@ -20,8 +76,7 @@ test_skip(const char *format, ...)
JEMALLOC_FORMAT_PRINTF(1, 2)
void
-test_fail(const char *format, ...)
-{
+test_fail(const char *format, ...) {
va_list ap;
va_start(ap, format);
@@ -32,9 +87,7 @@ test_fail(const char *format, ...)
}
static const char *
-test_status_string(test_status_t test_status)
-{
-
+test_status_string(test_status_t test_status) {
switch (test_status) {
case test_status_pass: return "pass";
case test_status_skip: return "skip";
@@ -44,48 +97,64 @@ test_status_string(test_status_t test_status)
}
void
-p_test_init(const char *name)
-{
-
+p_test_init(const char *name) {
test_count++;
test_status = test_status_pass;
test_name = name;
}
void
-p_test_fini(void)
-{
-
+p_test_fini(void) {
test_counts[test_status]++;
- malloc_printf("%s: %s\n", test_name, test_status_string(test_status));
+ malloc_printf("%s (%s): %s\n", test_name, reentrancy_t_str(reentrancy),
+ test_status_string(test_status));
}
-test_status_t
-p_test(test_t *t, ...)
-{
+static test_status_t
+p_test_impl(bool do_malloc_init, bool do_reentrant, test_t *t, va_list ap) {
test_status_t ret;
- va_list ap;
- /*
- * Make sure initialization occurs prior to running tests. Tests are
- * special because they may use internal facilities prior to triggering
- * initialization as a side effect of calling into the public API. This
- * is a final safety that works even if jemalloc_constructor() doesn't
- * run, as for MSVC builds.
- */
- if (nallocx(1, 0) == 0) {
- malloc_printf("Initialization error");
- return (test_status_fail);
+ if (do_malloc_init) {
+ /*
+ * Make sure initialization occurs prior to running tests.
+ * Tests are special because they may use internal facilities
+ * prior to triggering initialization as a side effect of
+ * calling into the public API.
+ */
+ if (nallocx(1, 0) == 0) {
+ malloc_printf("Initialization error");
+ return test_status_fail;
+ }
}
ret = test_status_pass;
- va_start(ap, t);
for (; t != NULL; t = va_arg(ap, test_t *)) {
+ /* Non-reentrant run. */
+ reentrancy = non_reentrant;
+ hooks_arena_new_hook = hooks_libc_hook = NULL;
t();
- if (test_status > ret)
+ if (test_status > ret) {
ret = test_status;
+ }
+ /* Reentrant run. */
+ if (do_reentrant) {
+ reentrancy = libc_reentrant;
+ hooks_arena_new_hook = NULL;
+ hooks_libc_hook = &libc_reentrancy_hook;
+ t();
+ if (test_status > ret) {
+ ret = test_status;
+ }
+
+ reentrancy = arena_new_reentrant;
+ hooks_libc_hook = NULL;
+ hooks_arena_new_hook = &arena_new_reentrancy_hook;
+ t();
+ if (test_status > ret) {
+ ret = test_status;
+ }
+ }
}
- va_end(ap);
malloc_printf("--- %s: %u/%u, %s: %u/%u, %s: %u/%u ---\n",
test_status_string(test_status_pass),
@@ -95,13 +164,54 @@ p_test(test_t *t, ...)
test_status_string(test_status_fail),
test_counts[test_status_fail], test_count);
- return (ret);
+ return ret;
}
-void
-p_test_fail(const char *prefix, const char *message)
-{
+test_status_t
+p_test(test_t *t, ...) {
+ test_status_t ret;
+ va_list ap;
+
+ ret = test_status_pass;
+ va_start(ap, t);
+ ret = p_test_impl(true, true, t, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+test_status_t
+p_test_no_reentrancy(test_t *t, ...) {
+ test_status_t ret;
+ va_list ap;
+
+ ret = test_status_pass;
+ va_start(ap, t);
+ ret = p_test_impl(true, false, t, ap);
+ va_end(ap);
+ return ret;
+}
+
+test_status_t
+p_test_no_malloc_init(test_t *t, ...) {
+ test_status_t ret;
+ va_list ap;
+
+ ret = test_status_pass;
+ va_start(ap, t);
+ /*
+ * We also omit reentrancy from bootstrapping tests, since we don't
+ * (yet) care about general reentrancy during bootstrapping.
+ */
+ ret = p_test_impl(false, false, t, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+void
+p_test_fail(const char *prefix, const char *message) {
malloc_cprintf(NULL, NULL, "%s%s\n", prefix, message);
test_status = test_status_fail;
}
diff --git a/deps/jemalloc/test/src/thd.c b/deps/jemalloc/test/src/thd.c
index c9d006586..9a15eabbf 100644
--- a/deps/jemalloc/test/src/thd.c
+++ b/deps/jemalloc/test/src/thd.c
@@ -2,18 +2,16 @@
#ifdef _WIN32
void
-thd_create(thd_t *thd, void *(*proc)(void *), void *arg)
-{
+thd_create(thd_t *thd, void *(*proc)(void *), void *arg) {
LPTHREAD_START_ROUTINE routine = (LPTHREAD_START_ROUTINE)proc;
*thd = CreateThread(NULL, 0, routine, arg, 0, NULL);
- if (*thd == NULL)
+ if (*thd == NULL) {
test_fail("Error in CreateThread()\n");
+ }
}
void
-thd_join(thd_t thd, void **ret)
-{
-
+thd_join(thd_t thd, void **ret) {
if (WaitForSingleObject(thd, INFINITE) == WAIT_OBJECT_0 && ret) {
DWORD exit_code;
GetExitCodeThread(thd, (LPDWORD) &exit_code);
@@ -23,17 +21,14 @@ thd_join(thd_t thd, void **ret)
#else
void
-thd_create(thd_t *thd, void *(*proc)(void *), void *arg)
-{
-
- if (pthread_create(thd, NULL, proc, arg) != 0)
+thd_create(thd_t *thd, void *(*proc)(void *), void *arg) {
+ if (pthread_create(thd, NULL, proc, arg) != 0) {
test_fail("Error in pthread_create()\n");
+ }
}
void
-thd_join(thd_t thd, void **ret)
-{
-
+thd_join(thd_t thd, void **ret) {
pthread_join(thd, ret);
}
#endif
diff --git a/deps/jemalloc/test/src/timer.c b/deps/jemalloc/test/src/timer.c
index 0c93abaf9..c451c6391 100644
--- a/deps/jemalloc/test/src/timer.c
+++ b/deps/jemalloc/test/src/timer.c
@@ -1,73 +1,44 @@
#include "test/jemalloc_test.h"
void
-timer_start(timedelta_t *timer)
-{
-
-#ifdef _WIN32
- GetSystemTimeAsFileTime(&timer->ft0);
-#elif JEMALLOC_CLOCK_GETTIME
- if (sysconf(_SC_MONOTONIC_CLOCK) <= 0)
- timer->clock_id = CLOCK_REALTIME;
- else
- timer->clock_id = CLOCK_MONOTONIC;
- clock_gettime(timer->clock_id, &timer->ts0);
-#else
- gettimeofday(&timer->tv0, NULL);
-#endif
+timer_start(timedelta_t *timer) {
+ nstime_init(&timer->t0, 0);
+ nstime_update(&timer->t0);
}
void
-timer_stop(timedelta_t *timer)
-{
-
-#ifdef _WIN32
- GetSystemTimeAsFileTime(&timer->ft0);
-#elif JEMALLOC_CLOCK_GETTIME
- clock_gettime(timer->clock_id, &timer->ts1);
-#else
- gettimeofday(&timer->tv1, NULL);
-#endif
+timer_stop(timedelta_t *timer) {
+ nstime_copy(&timer->t1, &timer->t0);
+ nstime_update(&timer->t1);
}
uint64_t
-timer_usec(const timedelta_t *timer)
-{
+timer_usec(const timedelta_t *timer) {
+ nstime_t delta;
-#ifdef _WIN32
- uint64_t t0, t1;
- t0 = (((uint64_t)timer->ft0.dwHighDateTime) << 32) |
- timer->ft0.dwLowDateTime;
- t1 = (((uint64_t)timer->ft1.dwHighDateTime) << 32) |
- timer->ft1.dwLowDateTime;
- return ((t1 - t0) / 10);
-#elif JEMALLOC_CLOCK_GETTIME
- return (((timer->ts1.tv_sec - timer->ts0.tv_sec) * 1000000) +
- (timer->ts1.tv_nsec - timer->ts0.tv_nsec) / 1000);
-#else
- return (((timer->tv1.tv_sec - timer->tv0.tv_sec) * 1000000) +
- timer->tv1.tv_usec - timer->tv0.tv_usec);
-#endif
+ nstime_copy(&delta, &timer->t1);
+ nstime_subtract(&delta, &timer->t0);
+ return nstime_ns(&delta) / 1000;
}
void
-timer_ratio(timedelta_t *a, timedelta_t *b, char *buf, size_t buflen)
-{
+timer_ratio(timedelta_t *a, timedelta_t *b, char *buf, size_t buflen) {
uint64_t t0 = timer_usec(a);
uint64_t t1 = timer_usec(b);
uint64_t mult;
- unsigned i = 0;
- unsigned j;
- int n;
+ size_t i = 0;
+ size_t j, n;
/* Whole. */
n = malloc_snprintf(&buf[i], buflen-i, "%"FMTu64, t0 / t1);
i += n;
- if (i >= buflen)
+ if (i >= buflen) {
return;
+ }
mult = 1;
- for (j = 0; j < n; j++)
+ for (j = 0; j < n; j++) {
mult *= 10;
+ }
/* Decimal. */
n = malloc_snprintf(&buf[i], buflen-i, ".");
diff --git a/deps/jemalloc/test/stress/microbench.c b/deps/jemalloc/test/stress/microbench.c
index ee39fea7f..988b7938f 100644
--- a/deps/jemalloc/test/stress/microbench.c
+++ b/deps/jemalloc/test/stress/microbench.c
@@ -1,22 +1,23 @@
#include "test/jemalloc_test.h"
-JEMALLOC_INLINE_C void
-time_func(timedelta_t *timer, uint64_t nwarmup, uint64_t niter, void (*func)(void))
-{
+static inline void
+time_func(timedelta_t *timer, uint64_t nwarmup, uint64_t niter,
+ void (*func)(void)) {
uint64_t i;
- for (i = 0; i < nwarmup; i++)
+ for (i = 0; i < nwarmup; i++) {
func();
+ }
timer_start(timer);
- for (i = 0; i < niter; i++)
+ for (i = 0; i < niter; i++) {
func();
+ }
timer_stop(timer);
}
void
compare_funcs(uint64_t nwarmup, uint64_t niter, const char *name_a,
- void (*func_a), const char *name_b, void (*func_b))
-{
+ void (*func_a), const char *name_b, void (*func_b)) {
timedelta_t timer_a, timer_b;
char ratio_buf[6];
void *p;
@@ -40,8 +41,7 @@ compare_funcs(uint64_t nwarmup, uint64_t niter, const char *name_a,
}
static void
-malloc_free(void)
-{
+malloc_free(void) {
/* The compiler can optimize away free(malloc(1))! */
void *p = malloc(1);
if (p == NULL) {
@@ -52,8 +52,7 @@ malloc_free(void)
}
static void
-mallocx_free(void)
-{
+mallocx_free(void) {
void *p = mallocx(1, 0);
if (p == NULL) {
test_fail("Unexpected mallocx() failure");
@@ -62,17 +61,14 @@ mallocx_free(void)
free(p);
}
-TEST_BEGIN(test_malloc_vs_mallocx)
-{
-
+TEST_BEGIN(test_malloc_vs_mallocx) {
compare_funcs(10*1000*1000, 100*1000*1000, "malloc",
malloc_free, "mallocx", mallocx_free);
}
TEST_END
static void
-malloc_dallocx(void)
-{
+malloc_dallocx(void) {
void *p = malloc(1);
if (p == NULL) {
test_fail("Unexpected malloc() failure");
@@ -82,8 +78,7 @@ malloc_dallocx(void)
}
static void
-malloc_sdallocx(void)
-{
+malloc_sdallocx(void) {
void *p = malloc(1);
if (p == NULL) {
test_fail("Unexpected malloc() failure");
@@ -92,25 +87,20 @@ malloc_sdallocx(void)
sdallocx(p, 1, 0);
}
-TEST_BEGIN(test_free_vs_dallocx)
-{
-
+TEST_BEGIN(test_free_vs_dallocx) {
compare_funcs(10*1000*1000, 100*1000*1000, "free", malloc_free,
"dallocx", malloc_dallocx);
}
TEST_END
-TEST_BEGIN(test_dallocx_vs_sdallocx)
-{
-
+TEST_BEGIN(test_dallocx_vs_sdallocx) {
compare_funcs(10*1000*1000, 100*1000*1000, "dallocx", malloc_dallocx,
"sdallocx", malloc_sdallocx);
}
TEST_END
static void
-malloc_mus_free(void)
-{
+malloc_mus_free(void) {
void *p;
p = malloc(1);
@@ -123,8 +113,7 @@ malloc_mus_free(void)
}
static void
-malloc_sallocx_free(void)
-{
+malloc_sallocx_free(void) {
void *p;
p = malloc(1);
@@ -132,22 +121,20 @@ malloc_sallocx_free(void)
test_fail("Unexpected malloc() failure");
return;
}
- if (sallocx(p, 0) < 1)
+ if (sallocx(p, 0) < 1) {
test_fail("Unexpected sallocx() failure");
+ }
free(p);
}
-TEST_BEGIN(test_mus_vs_sallocx)
-{
-
+TEST_BEGIN(test_mus_vs_sallocx) {
compare_funcs(10*1000*1000, 100*1000*1000, "malloc_usable_size",
malloc_mus_free, "sallocx", malloc_sallocx_free);
}
TEST_END
static void
-malloc_nallocx_free(void)
-{
+malloc_nallocx_free(void) {
void *p;
p = malloc(1);
@@ -155,27 +142,24 @@ malloc_nallocx_free(void)
test_fail("Unexpected malloc() failure");
return;
}
- if (nallocx(1, 0) < 1)
+ if (nallocx(1, 0) < 1) {
test_fail("Unexpected nallocx() failure");
+ }
free(p);
}
-TEST_BEGIN(test_sallocx_vs_nallocx)
-{
-
+TEST_BEGIN(test_sallocx_vs_nallocx) {
compare_funcs(10*1000*1000, 100*1000*1000, "sallocx",
malloc_sallocx_free, "nallocx", malloc_nallocx_free);
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test_no_reentrancy(
test_malloc_vs_mallocx,
test_free_vs_dallocx,
test_dallocx_vs_sdallocx,
test_mus_vs_sallocx,
- test_sallocx_vs_nallocx));
+ test_sallocx_vs_nallocx);
}
diff --git a/deps/jemalloc/test/test.sh.in b/deps/jemalloc/test/test.sh.in
index a39f99f6b..39302fff4 100644
--- a/deps/jemalloc/test/test.sh.in
+++ b/deps/jemalloc/test/test.sh.in
@@ -11,6 +11,18 @@ case @abi@ in
;;
esac
+# Make a copy of the @JEMALLOC_CPREFIX@MALLOC_CONF passed in to this script, so
+# it can be repeatedly concatenated with per test settings.
+export MALLOC_CONF_ALL=${@JEMALLOC_CPREFIX@MALLOC_CONF}
+# Concatenate the individual test's MALLOC_CONF and MALLOC_CONF_ALL.
+export_malloc_conf() {
+ if [ "x${MALLOC_CONF}" != "x" -a "x${MALLOC_CONF_ALL}" != "x" ] ; then
+ export @JEMALLOC_CPREFIX@MALLOC_CONF="${MALLOC_CONF},${MALLOC_CONF_ALL}"
+ else
+ export @JEMALLOC_CPREFIX@MALLOC_CONF="${MALLOC_CONF}${MALLOC_CONF_ALL}"
+ fi
+}
+
# Corresponds to test_status_t.
pass_code=0
skip_code=1
@@ -24,7 +36,21 @@ for t in $@; do
echo
fi
echo "=== ${t} ==="
- ${t}@exe@ @abs_srcroot@ @abs_objroot@
+ if [ -e "@srcroot@${t}.sh" ] ; then
+ # Source the shell script corresponding to the test in a subshell and
+ # execute the test. This allows the shell script to set MALLOC_CONF, which
+ # is then used to set @JEMALLOC_CPREFIX@MALLOC_CONF (thus allowing the
+ # per test shell script to ignore the @JEMALLOC_CPREFIX@ detail).
+ enable_fill=@enable_fill@ \
+ enable_prof=@enable_prof@ \
+ . @srcroot@${t}.sh && \
+ export_malloc_conf && \
+ $JEMALLOC_TEST_PREFIX ${t}@exe@ @abs_srcroot@ @abs_objroot@
+ else
+ export MALLOC_CONF= && \
+ export_malloc_conf && \
+ $JEMALLOC_TEST_PREFIX ${t}@exe@ @abs_srcroot@ @abs_objroot@
+ fi
result_code=$?
case ${result_code} in
${pass_code})
@@ -37,7 +63,8 @@ for t in $@; do
fail_count=$((fail_count+1))
;;
*)
- echo "Test harness error" 1>&2
+ echo "Test harness error: ${t} w/ MALLOC_CONF=\"${MALLOC_CONF}\"" 1>&2
+ echo "Use prefix to debug, e.g. JEMALLOC_TEST_PREFIX=\"gdb --args\" sh test/test.sh ${t}" 1>&2
exit 1
esac
done
diff --git a/deps/jemalloc/test/unit/SFMT.c b/deps/jemalloc/test/unit/SFMT.c
index ba4be8702..1fc8cf1bc 100644
--- a/deps/jemalloc/test/unit/SFMT.c
+++ b/deps/jemalloc/test/unit/SFMT.c
@@ -35,10 +35,10 @@
*/
#include "test/jemalloc_test.h"
-#define BLOCK_SIZE 10000
-#define BLOCK_SIZE64 (BLOCK_SIZE / 2)
-#define COUNT_1 1000
-#define COUNT_2 700
+#define BLOCK_SIZE 10000
+#define BLOCK_SIZE64 (BLOCK_SIZE / 2)
+#define COUNT_1 1000
+#define COUNT_2 700
static const uint32_t init_gen_rand_32_expected[] = {
3440181298U, 1564997079U, 1510669302U, 2930277156U, 1452439940U,
@@ -1449,8 +1449,7 @@ static const uint64_t init_by_array_64_expected[] = {
KQU(15570163926716513029), KQU(13356980519185762498)
};
-TEST_BEGIN(test_gen_rand_32)
-{
+TEST_BEGIN(test_gen_rand_32) {
uint32_t array32[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16));
uint32_t array32_2[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16));
int i;
@@ -1484,8 +1483,7 @@ TEST_BEGIN(test_gen_rand_32)
}
TEST_END
-TEST_BEGIN(test_by_array_32)
-{
+TEST_BEGIN(test_by_array_32) {
uint32_t array32[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16));
uint32_t array32_2[BLOCK_SIZE] JEMALLOC_ATTR(aligned(16));
int i;
@@ -1520,8 +1518,7 @@ TEST_BEGIN(test_by_array_32)
}
TEST_END
-TEST_BEGIN(test_gen_rand_64)
-{
+TEST_BEGIN(test_gen_rand_64) {
uint64_t array64[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16));
uint64_t array64_2[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16));
int i;
@@ -1556,8 +1553,7 @@ TEST_BEGIN(test_gen_rand_64)
}
TEST_END
-TEST_BEGIN(test_by_array_64)
-{
+TEST_BEGIN(test_by_array_64) {
uint64_t array64[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16));
uint64_t array64_2[BLOCK_SIZE64] JEMALLOC_ATTR(aligned(16));
int i;
@@ -1594,12 +1590,10 @@ TEST_BEGIN(test_by_array_64)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_gen_rand_32,
test_by_array_32,
test_gen_rand_64,
- test_by_array_64));
+ test_by_array_64);
}
diff --git a/deps/jemalloc/test/unit/a0.c b/deps/jemalloc/test/unit/a0.c
new file mode 100644
index 000000000..a27ab3f42
--- /dev/null
+++ b/deps/jemalloc/test/unit/a0.c
@@ -0,0 +1,16 @@
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_a0) {
+ void *p;
+
+ p = a0malloc(1);
+ assert_ptr_not_null(p, "Unexpected a0malloc() error");
+ a0dalloc(p);
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_malloc_init(
+ test_a0);
+}
diff --git a/deps/jemalloc/test/unit/arena_reset.c b/deps/jemalloc/test/unit/arena_reset.c
new file mode 100644
index 000000000..f5fb24d1e
--- /dev/null
+++ b/deps/jemalloc/test/unit/arena_reset.c
@@ -0,0 +1,344 @@
+#ifndef ARENA_RESET_PROF_C_
+#include "test/jemalloc_test.h"
+#endif
+
+#include "jemalloc/internal/extent_mmap.h"
+#include "jemalloc/internal/rtree.h"
+
+#include "test/extent_hooks.h"
+
+static unsigned
+get_nsizes_impl(const char *cmd) {
+ unsigned ret;
+ size_t z;
+
+ z = sizeof(unsigned);
+ assert_d_eq(mallctl(cmd, (void *)&ret, &z, NULL, 0), 0,
+ "Unexpected mallctl(\"%s\", ...) failure", cmd);
+
+ return ret;
+}
+
+static unsigned
+get_nsmall(void) {
+ return get_nsizes_impl("arenas.nbins");
+}
+
+static unsigned
+get_nlarge(void) {
+ return get_nsizes_impl("arenas.nlextents");
+}
+
+static size_t
+get_size_impl(const char *cmd, size_t ind) {
+ size_t ret;
+ size_t z;
+ size_t mib[4];
+ size_t miblen = 4;
+
+ z = sizeof(size_t);
+ assert_d_eq(mallctlnametomib(cmd, mib, &miblen),
+ 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
+ mib[2] = ind;
+ z = sizeof(size_t);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&ret, &z, NULL, 0),
+ 0, "Unexpected mallctlbymib([\"%s\", %zu], ...) failure", cmd, ind);
+
+ return ret;
+}
+
+static size_t
+get_small_size(size_t ind) {
+ return get_size_impl("arenas.bin.0.size", ind);
+}
+
+static size_t
+get_large_size(size_t ind) {
+ return get_size_impl("arenas.lextent.0.size", ind);
+}
+
+/* Like ivsalloc(), but safe to call on discarded allocations. */
+static size_t
+vsalloc(tsdn_t *tsdn, const void *ptr) {
+ rtree_ctx_t rtree_ctx_fallback;
+ rtree_ctx_t *rtree_ctx = tsdn_rtree_ctx(tsdn, &rtree_ctx_fallback);
+
+ extent_t *extent;
+ szind_t szind;
+ if (rtree_extent_szind_read(tsdn, &extents_rtree, rtree_ctx,
+ (uintptr_t)ptr, false, &extent, &szind)) {
+ return 0;
+ }
+
+ if (extent == NULL) {
+ return 0;
+ }
+ if (extent_state_get(extent) != extent_state_active) {
+ return 0;
+ }
+
+ if (szind == NSIZES) {
+ return 0;
+ }
+
+ return sz_index2size(szind);
+}
+
+static unsigned
+do_arena_create(extent_hooks_t *h) {
+ unsigned arena_ind;
+ size_t sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz,
+ (void *)(h != NULL ? &h : NULL), (h != NULL ? sizeof(h) : 0)), 0,
+ "Unexpected mallctl() failure");
+ return arena_ind;
+}
+
+static void
+do_arena_reset_pre(unsigned arena_ind, void ***ptrs, unsigned *nptrs) {
+#define NLARGE 32
+ unsigned nsmall, nlarge, i;
+ size_t sz;
+ int flags;
+ tsdn_t *tsdn;
+
+ flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
+
+ nsmall = get_nsmall();
+ nlarge = get_nlarge() > NLARGE ? NLARGE : get_nlarge();
+ *nptrs = nsmall + nlarge;
+ *ptrs = (void **)malloc(*nptrs * sizeof(void *));
+ assert_ptr_not_null(*ptrs, "Unexpected malloc() failure");
+
+ /* Allocate objects with a wide range of sizes. */
+ for (i = 0; i < nsmall; i++) {
+ sz = get_small_size(i);
+ (*ptrs)[i] = mallocx(sz, flags);
+ assert_ptr_not_null((*ptrs)[i],
+ "Unexpected mallocx(%zu, %#x) failure", sz, flags);
+ }
+ for (i = 0; i < nlarge; i++) {
+ sz = get_large_size(i);
+ (*ptrs)[nsmall + i] = mallocx(sz, flags);
+ assert_ptr_not_null((*ptrs)[i],
+ "Unexpected mallocx(%zu, %#x) failure", sz, flags);
+ }
+
+ tsdn = tsdn_fetch();
+
+ /* Verify allocations. */
+ for (i = 0; i < *nptrs; i++) {
+ assert_zu_gt(ivsalloc(tsdn, (*ptrs)[i]), 0,
+ "Allocation should have queryable size");
+ }
+}
+
+static void
+do_arena_reset_post(void **ptrs, unsigned nptrs, unsigned arena_ind) {
+ tsdn_t *tsdn;
+ unsigned i;
+
+ tsdn = tsdn_fetch();
+
+ if (have_background_thread) {
+ malloc_mutex_lock(tsdn,
+ &background_thread_info[arena_ind % ncpus].mtx);
+ }
+ /* Verify allocations no longer exist. */
+ for (i = 0; i < nptrs; i++) {
+ assert_zu_eq(vsalloc(tsdn, ptrs[i]), 0,
+ "Allocation should no longer exist");
+ }
+ if (have_background_thread) {
+ malloc_mutex_unlock(tsdn,
+ &background_thread_info[arena_ind % ncpus].mtx);
+ }
+
+ free(ptrs);
+}
+
+static void
+do_arena_reset_destroy(const char *name, unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen;
+
+ miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib(name, mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+static void
+do_arena_reset(unsigned arena_ind) {
+ do_arena_reset_destroy("arena.0.reset", arena_ind);
+}
+
+static void
+do_arena_destroy(unsigned arena_ind) {
+ do_arena_reset_destroy("arena.0.destroy", arena_ind);
+}
+
+TEST_BEGIN(test_arena_reset) {
+ unsigned arena_ind;
+ void **ptrs;
+ unsigned nptrs;
+
+ arena_ind = do_arena_create(NULL);
+ do_arena_reset_pre(arena_ind, &ptrs, &nptrs);
+ do_arena_reset(arena_ind);
+ do_arena_reset_post(ptrs, nptrs, arena_ind);
+}
+TEST_END
+
+static bool
+arena_i_initialized(unsigned arena_ind, bool refresh) {
+ bool initialized;
+ size_t mib[3];
+ size_t miblen, sz;
+
+ if (refresh) {
+ uint64_t epoch = 1;
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
+ sizeof(epoch)), 0, "Unexpected mallctl() failure");
+ }
+
+ miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.initialized", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ sz = sizeof(initialized);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&initialized, &sz, NULL,
+ 0), 0, "Unexpected mallctlbymib() failure");
+
+ return initialized;
+}
+
+TEST_BEGIN(test_arena_destroy_initial) {
+ assert_false(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false),
+ "Destroyed arena stats should not be initialized");
+}
+TEST_END
+
+TEST_BEGIN(test_arena_destroy_hooks_default) {
+ unsigned arena_ind, arena_ind_another, arena_ind_prev;
+ void **ptrs;
+ unsigned nptrs;
+
+ arena_ind = do_arena_create(NULL);
+ do_arena_reset_pre(arena_ind, &ptrs, &nptrs);
+
+ assert_false(arena_i_initialized(arena_ind, false),
+ "Arena stats should not be initialized");
+ assert_true(arena_i_initialized(arena_ind, true),
+ "Arena stats should be initialized");
+
+ /*
+ * Create another arena before destroying one, to better verify arena
+ * index reuse.
+ */
+ arena_ind_another = do_arena_create(NULL);
+
+ do_arena_destroy(arena_ind);
+
+ assert_false(arena_i_initialized(arena_ind, true),
+ "Arena stats should not be initialized");
+ assert_true(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false),
+ "Destroyed arena stats should be initialized");
+
+ do_arena_reset_post(ptrs, nptrs, arena_ind);
+
+ arena_ind_prev = arena_ind;
+ arena_ind = do_arena_create(NULL);
+ do_arena_reset_pre(arena_ind, &ptrs, &nptrs);
+ assert_u_eq(arena_ind, arena_ind_prev,
+ "Arena index should have been recycled");
+ do_arena_destroy(arena_ind);
+ do_arena_reset_post(ptrs, nptrs, arena_ind);
+
+ do_arena_destroy(arena_ind_another);
+}
+TEST_END
+
+/*
+ * Actually unmap extents, regardless of opt_retain, so that attempts to access
+ * a destroyed arena's memory will segfault.
+ */
+static bool
+extent_dalloc_unmap(extent_hooks_t *extent_hooks, void *addr, size_t size,
+ bool committed, unsigned arena_ind) {
+ TRACE_HOOK("%s(extent_hooks=%p, addr=%p, size=%zu, committed=%s, "
+ "arena_ind=%u)\n", __func__, extent_hooks, addr, size, committed ?
+ "true" : "false", arena_ind);
+ assert_ptr_eq(extent_hooks, &hooks,
+ "extent_hooks should be same as pointer used to set hooks");
+ assert_ptr_eq(extent_hooks->dalloc, extent_dalloc_unmap,
+ "Wrong hook function");
+ called_dalloc = true;
+ if (!try_dalloc) {
+ return true;
+ }
+ pages_unmap(addr, size);
+ did_dalloc = true;
+ return false;
+}
+
+static extent_hooks_t hooks_orig;
+
+static extent_hooks_t hooks_unmap = {
+ extent_alloc_hook,
+ extent_dalloc_unmap, /* dalloc */
+ extent_destroy_hook,
+ extent_commit_hook,
+ extent_decommit_hook,
+ extent_purge_lazy_hook,
+ extent_purge_forced_hook,
+ extent_split_hook,
+ extent_merge_hook
+};
+
+TEST_BEGIN(test_arena_destroy_hooks_unmap) {
+ unsigned arena_ind;
+ void **ptrs;
+ unsigned nptrs;
+
+ extent_hooks_prep();
+ try_decommit = false;
+ memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t));
+ memcpy(&hooks, &hooks_unmap, sizeof(extent_hooks_t));
+
+ did_alloc = false;
+ arena_ind = do_arena_create(&hooks);
+ do_arena_reset_pre(arena_ind, &ptrs, &nptrs);
+
+ assert_true(did_alloc, "Expected alloc");
+
+ assert_false(arena_i_initialized(arena_ind, false),
+ "Arena stats should not be initialized");
+ assert_true(arena_i_initialized(arena_ind, true),
+ "Arena stats should be initialized");
+
+ did_dalloc = false;
+ do_arena_destroy(arena_ind);
+ assert_true(did_dalloc, "Expected dalloc");
+
+ assert_false(arena_i_initialized(arena_ind, true),
+ "Arena stats should not be initialized");
+ assert_true(arena_i_initialized(MALLCTL_ARENAS_DESTROYED, false),
+ "Destroyed arena stats should be initialized");
+
+ do_arena_reset_post(ptrs, nptrs, arena_ind);
+
+ memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t));
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_arena_reset,
+ test_arena_destroy_initial,
+ test_arena_destroy_hooks_default,
+ test_arena_destroy_hooks_unmap);
+}
diff --git a/deps/jemalloc/test/unit/arena_reset_prof.c b/deps/jemalloc/test/unit/arena_reset_prof.c
new file mode 100644
index 000000000..38d801240
--- /dev/null
+++ b/deps/jemalloc/test/unit/arena_reset_prof.c
@@ -0,0 +1,4 @@
+#include "test/jemalloc_test.h"
+#define ARENA_RESET_PROF_C_
+
+#include "arena_reset.c"
diff --git a/deps/jemalloc/test/unit/arena_reset_prof.sh b/deps/jemalloc/test/unit/arena_reset_prof.sh
new file mode 100644
index 000000000..041dc1c35
--- /dev/null
+++ b/deps/jemalloc/test/unit/arena_reset_prof.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+export MALLOC_CONF="prof:true,lg_prof_sample:0"
diff --git a/deps/jemalloc/test/unit/atomic.c b/deps/jemalloc/test/unit/atomic.c
index bdd74f659..572d8d23f 100644
--- a/deps/jemalloc/test/unit/atomic.c
+++ b/deps/jemalloc/test/unit/atomic.c
@@ -1,122 +1,229 @@
#include "test/jemalloc_test.h"
-#define TEST_STRUCT(p, t) \
-struct p##_test_s { \
- t accum0; \
- t x; \
- t s; \
-}; \
-typedef struct p##_test_s p##_test_t;
-
-#define TEST_BODY(p, t, tc, ta, FMT) do { \
- const p##_test_t tests[] = { \
- {(t)-1, (t)-1, (t)-2}, \
- {(t)-1, (t) 0, (t)-2}, \
- {(t)-1, (t) 1, (t)-2}, \
+/*
+ * We *almost* have consistent short names (e.g. "u32" for uint32_t, "b" for
+ * bool, etc. The one exception is that the short name for void * is "p" in
+ * some places and "ptr" in others. In the long run it would be nice to unify
+ * these, but in the short run we'll use this shim.
+ */
+#define assert_p_eq assert_ptr_eq
+
+/*
+ * t: the non-atomic type, like "uint32_t".
+ * ta: the short name for the type, like "u32".
+ * val[1,2,3]: Values of the given type. The CAS tests use val2 for expected,
+ * and val3 for desired.
+ */
+
+#define DO_TESTS(t, ta, val1, val2, val3) do { \
+ t val; \
+ t expected; \
+ bool success; \
+ /* This (along with the load below) also tests ATOMIC_LOAD. */ \
+ atomic_##ta##_t atom = ATOMIC_INIT(val1); \
\
- {(t) 0, (t)-1, (t)-2}, \
- {(t) 0, (t) 0, (t)-2}, \
- {(t) 0, (t) 1, (t)-2}, \
+ /* ATOMIC_INIT and load. */ \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, "Load or init failed"); \
\
- {(t) 1, (t)-1, (t)-2}, \
- {(t) 1, (t) 0, (t)-2}, \
- {(t) 1, (t) 1, (t)-2}, \
+ /* Store. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ atomic_store_##ta(&atom, val2, ATOMIC_RELAXED); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val2, val, "Store failed"); \
\
- {(t)0, (t)-(1 << 22), (t)-2}, \
- {(t)0, (t)(1 << 22), (t)-2}, \
- {(t)(1 << 22), (t)-(1 << 22), (t)-2}, \
- {(t)(1 << 22), (t)(1 << 22), (t)-2} \
- }; \
- unsigned i; \
+ /* Exchange. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_exchange_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, "Exchange returned invalid value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val2, val, "Exchange store invalid value"); \
\
- for (i = 0; i < sizeof(tests)/sizeof(p##_test_t); i++) { \
- bool err; \
- t accum = tests[i].accum0; \
- assert_##ta##_eq(atomic_read_##p(&accum), \
- tests[i].accum0, \
- "Erroneous read, i=%u", i); \
+ /* \
+ * Weak CAS. Spurious failures are allowed, so we loop a few \
+ * times. \
+ */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ success = false; \
+ for (int i = 0; i < 10 && !success; i++) { \
+ expected = val2; \
+ success = atomic_compare_exchange_weak_##ta(&atom, \
+ &expected, val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, expected, \
+ "CAS should update expected"); \
+ } \
+ assert_b_eq(val1 == val2, success, \
+ "Weak CAS did the wrong state update"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ if (success) { \
+ assert_##ta##_eq(val3, val, \
+ "Successful CAS should update atomic"); \
+ } else { \
+ assert_##ta##_eq(val1, val, \
+ "Unsuccessful CAS should not update atomic"); \
+ } \
\
- assert_##ta##_eq(atomic_add_##p(&accum, tests[i].x), \
- (t)((tc)tests[i].accum0 + (tc)tests[i].x), \
- "i=%u, accum=%"FMT", x=%"FMT, \
- i, tests[i].accum0, tests[i].x); \
- assert_##ta##_eq(atomic_read_##p(&accum), accum, \
- "Erroneous add, i=%u", i); \
+ /* Strong CAS. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ expected = val2; \
+ success = atomic_compare_exchange_strong_##ta(&atom, &expected, \
+ val3, ATOMIC_RELAXED, ATOMIC_RELAXED); \
+ assert_b_eq(val1 == val2, success, \
+ "Strong CAS did the wrong state update"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ if (success) { \
+ assert_##ta##_eq(val3, val, \
+ "Successful CAS should update atomic"); \
+ } else { \
+ assert_##ta##_eq(val1, val, \
+ "Unsuccessful CAS should not update atomic"); \
+ } \
\
- accum = tests[i].accum0; \
- assert_##ta##_eq(atomic_sub_##p(&accum, tests[i].x), \
- (t)((tc)tests[i].accum0 - (tc)tests[i].x), \
- "i=%u, accum=%"FMT", x=%"FMT, \
- i, tests[i].accum0, tests[i].x); \
- assert_##ta##_eq(atomic_read_##p(&accum), accum, \
- "Erroneous sub, i=%u", i); \
\
- accum = tests[i].accum0; \
- err = atomic_cas_##p(&accum, tests[i].x, tests[i].s); \
- assert_b_eq(err, tests[i].accum0 != tests[i].x, \
- "Erroneous cas success/failure result"); \
- assert_##ta##_eq(accum, err ? tests[i].accum0 : \
- tests[i].s, "Erroneous cas effect, i=%u", i); \
+} while (0)
+
+#define DO_INTEGER_TESTS(t, ta, val1, val2) do { \
+ atomic_##ta##_t atom; \
+ t val; \
\
- accum = tests[i].accum0; \
- atomic_write_##p(&accum, tests[i].s); \
- assert_##ta##_eq(accum, tests[i].s, \
- "Erroneous write, i=%u", i); \
+ /* Fetch-add. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_fetch_add_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, \
+ "Fetch-add should return previous value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1 + val2, val, \
+ "Fetch-add should update atomic"); \
+ \
+ /* Fetch-sub. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_fetch_sub_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, \
+ "Fetch-sub should return previous value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1 - val2, val, \
+ "Fetch-sub should update atomic"); \
+ \
+ /* Fetch-and. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_fetch_and_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, \
+ "Fetch-and should return previous value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1 & val2, val, \
+ "Fetch-and should update atomic"); \
+ \
+ /* Fetch-or. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_fetch_or_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, \
+ "Fetch-or should return previous value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1 | val2, val, \
+ "Fetch-or should update atomic"); \
+ \
+ /* Fetch-xor. */ \
+ atomic_store_##ta(&atom, val1, ATOMIC_RELAXED); \
+ val = atomic_fetch_xor_##ta(&atom, val2, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1, val, \
+ "Fetch-xor should return previous value"); \
+ val = atomic_load_##ta(&atom, ATOMIC_RELAXED); \
+ assert_##ta##_eq(val1 ^ val2, val, \
+ "Fetch-xor should update atomic"); \
+} while (0)
+
+#define TEST_STRUCT(t, ta) \
+typedef struct { \
+ t val1; \
+ t val2; \
+ t val3; \
+} ta##_test_t;
+
+#define TEST_CASES(t) { \
+ {(t)-1, (t)-1, (t)-2}, \
+ {(t)-1, (t) 0, (t)-2}, \
+ {(t)-1, (t) 1, (t)-2}, \
+ \
+ {(t) 0, (t)-1, (t)-2}, \
+ {(t) 0, (t) 0, (t)-2}, \
+ {(t) 0, (t) 1, (t)-2}, \
+ \
+ {(t) 1, (t)-1, (t)-2}, \
+ {(t) 1, (t) 0, (t)-2}, \
+ {(t) 1, (t) 1, (t)-2}, \
+ \
+ {(t)0, (t)-(1 << 22), (t)-2}, \
+ {(t)0, (t)(1 << 22), (t)-2}, \
+ {(t)(1 << 22), (t)-(1 << 22), (t)-2}, \
+ {(t)(1 << 22), (t)(1 << 22), (t)-2} \
+}
+
+#define TEST_BODY(t, ta) do { \
+ const ta##_test_t tests[] = TEST_CASES(t); \
+ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \
+ ta##_test_t test = tests[i]; \
+ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \
} \
} while (0)
-TEST_STRUCT(uint64, uint64_t)
-TEST_BEGIN(test_atomic_uint64)
-{
+#define INTEGER_TEST_BODY(t, ta) do { \
+ const ta##_test_t tests[] = TEST_CASES(t); \
+ for (unsigned i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { \
+ ta##_test_t test = tests[i]; \
+ DO_TESTS(t, ta, test.val1, test.val2, test.val3); \
+ DO_INTEGER_TESTS(t, ta, test.val1, test.val2); \
+ } \
+} while (0)
+TEST_STRUCT(uint64_t, u64);
+TEST_BEGIN(test_atomic_u64) {
#if !(LG_SIZEOF_PTR == 3 || LG_SIZEOF_INT == 3)
test_skip("64-bit atomic operations not supported");
#else
- TEST_BODY(uint64, uint64_t, uint64_t, u64, FMTx64);
+ INTEGER_TEST_BODY(uint64_t, u64);
#endif
}
TEST_END
-TEST_STRUCT(uint32, uint32_t)
-TEST_BEGIN(test_atomic_uint32)
-{
- TEST_BODY(uint32, uint32_t, uint32_t, u32, "#"FMTx32);
+TEST_STRUCT(uint32_t, u32);
+TEST_BEGIN(test_atomic_u32) {
+ INTEGER_TEST_BODY(uint32_t, u32);
}
TEST_END
-TEST_STRUCT(p, void *)
-TEST_BEGIN(test_atomic_p)
-{
-
- TEST_BODY(p, void *, uintptr_t, ptr, "p");
+TEST_STRUCT(void *, p);
+TEST_BEGIN(test_atomic_p) {
+ TEST_BODY(void *, p);
}
TEST_END
-TEST_STRUCT(z, size_t)
-TEST_BEGIN(test_atomic_z)
-{
+TEST_STRUCT(size_t, zu);
+TEST_BEGIN(test_atomic_zu) {
+ INTEGER_TEST_BODY(size_t, zu);
+}
+TEST_END
- TEST_BODY(z, size_t, size_t, zu, "#zx");
+TEST_STRUCT(ssize_t, zd);
+TEST_BEGIN(test_atomic_zd) {
+ INTEGER_TEST_BODY(ssize_t, zd);
}
TEST_END
-TEST_STRUCT(u, unsigned)
-TEST_BEGIN(test_atomic_u)
-{
- TEST_BODY(u, unsigned, unsigned, u, "#x");
+TEST_STRUCT(unsigned, u);
+TEST_BEGIN(test_atomic_u) {
+ INTEGER_TEST_BODY(unsigned, u);
}
TEST_END
int
-main(void)
-{
-
- return (test(
- test_atomic_uint64,
- test_atomic_uint32,
+main(void) {
+ return test(
+ test_atomic_u64,
+ test_atomic_u32,
test_atomic_p,
- test_atomic_z,
- test_atomic_u));
+ test_atomic_zu,
+ test_atomic_zd,
+ test_atomic_u);
}
diff --git a/deps/jemalloc/test/unit/background_thread.c b/deps/jemalloc/test/unit/background_thread.c
new file mode 100644
index 000000000..f7bd37c42
--- /dev/null
+++ b/deps/jemalloc/test/unit/background_thread.c
@@ -0,0 +1,119 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/util.h"
+
+static void
+test_switch_background_thread_ctl(bool new_val) {
+ bool e0, e1;
+ size_t sz = sizeof(bool);
+
+ e1 = new_val;
+ assert_d_eq(mallctl("background_thread", (void *)&e0, &sz,
+ &e1, sz), 0, "Unexpected mallctl() failure");
+ assert_b_eq(e0, !e1,
+ "background_thread should be %d before.\n", !e1);
+ if (e1) {
+ assert_zu_gt(n_background_threads, 0,
+ "Number of background threads should be non zero.\n");
+ } else {
+ assert_zu_eq(n_background_threads, 0,
+ "Number of background threads should be zero.\n");
+ }
+}
+
+static void
+test_repeat_background_thread_ctl(bool before) {
+ bool e0, e1;
+ size_t sz = sizeof(bool);
+
+ e1 = before;
+ assert_d_eq(mallctl("background_thread", (void *)&e0, &sz,
+ &e1, sz), 0, "Unexpected mallctl() failure");
+ assert_b_eq(e0, before,
+ "background_thread should be %d.\n", before);
+ if (e1) {
+ assert_zu_gt(n_background_threads, 0,
+ "Number of background threads should be non zero.\n");
+ } else {
+ assert_zu_eq(n_background_threads, 0,
+ "Number of background threads should be zero.\n");
+ }
+}
+
+TEST_BEGIN(test_background_thread_ctl) {
+ test_skip_if(!have_background_thread);
+
+ bool e0, e1;
+ size_t sz = sizeof(bool);
+
+ assert_d_eq(mallctl("opt.background_thread", (void *)&e0, &sz,
+ NULL, 0), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("background_thread", (void *)&e1, &sz,
+ NULL, 0), 0, "Unexpected mallctl() failure");
+ assert_b_eq(e0, e1,
+ "Default and opt.background_thread does not match.\n");
+ if (e0) {
+ test_switch_background_thread_ctl(false);
+ }
+ assert_zu_eq(n_background_threads, 0,
+ "Number of background threads should be 0.\n");
+
+ for (unsigned i = 0; i < 4; i++) {
+ test_switch_background_thread_ctl(true);
+ test_repeat_background_thread_ctl(true);
+ test_repeat_background_thread_ctl(true);
+
+ test_switch_background_thread_ctl(false);
+ test_repeat_background_thread_ctl(false);
+ test_repeat_background_thread_ctl(false);
+ }
+}
+TEST_END
+
+TEST_BEGIN(test_background_thread_running) {
+ test_skip_if(!have_background_thread);
+ test_skip_if(!config_stats);
+
+#if defined(JEMALLOC_BACKGROUND_THREAD)
+ tsd_t *tsd = tsd_fetch();
+ background_thread_info_t *info = &background_thread_info[0];
+
+ test_repeat_background_thread_ctl(false);
+ test_switch_background_thread_ctl(true);
+ assert_b_eq(info->state, background_thread_started,
+ "Background_thread did not start.\n");
+
+ nstime_t start, now;
+ nstime_init(&start, 0);
+ nstime_update(&start);
+
+ bool ran = false;
+ while (true) {
+ malloc_mutex_lock(tsd_tsdn(tsd), &info->mtx);
+ if (info->tot_n_runs > 0) {
+ ran = true;
+ }
+ malloc_mutex_unlock(tsd_tsdn(tsd), &info->mtx);
+ if (ran) {
+ break;
+ }
+
+ nstime_init(&now, 0);
+ nstime_update(&now);
+ nstime_subtract(&now, &start);
+ assert_u64_lt(nstime_sec(&now), 1000,
+ "Background threads did not run for 1000 seconds.");
+ sleep(1);
+ }
+ test_switch_background_thread_ctl(false);
+#endif
+}
+TEST_END
+
+int
+main(void) {
+ /* Background_thread creation tests reentrancy naturally. */
+ return test_no_reentrancy(
+ test_background_thread_ctl,
+ test_background_thread_running);
+}
diff --git a/deps/jemalloc/test/unit/background_thread_enable.c b/deps/jemalloc/test/unit/background_thread_enable.c
new file mode 100644
index 000000000..ff95e672c
--- /dev/null
+++ b/deps/jemalloc/test/unit/background_thread_enable.c
@@ -0,0 +1,83 @@
+#include "test/jemalloc_test.h"
+
+const char *malloc_conf = "background_thread:false,narenas:1,max_background_threads:20";
+
+TEST_BEGIN(test_deferred) {
+ test_skip_if(!have_background_thread);
+
+ unsigned id;
+ size_t sz_u = sizeof(unsigned);
+
+ /*
+ * 10 here is somewhat arbitrary, except insofar as we want to ensure
+ * that the number of background threads is smaller than the number of
+ * arenas. I'll ragequit long before we have to spin up 10 threads per
+ * cpu to handle background purging, so this is a conservative
+ * approximation.
+ */
+ for (unsigned i = 0; i < 10 * ncpus; i++) {
+ assert_d_eq(mallctl("arenas.create", &id, &sz_u, NULL, 0), 0,
+ "Failed to create arena");
+ }
+
+ bool enable = true;
+ size_t sz_b = sizeof(bool);
+ assert_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0,
+ "Failed to enable background threads");
+ enable = false;
+ assert_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0,
+ "Failed to disable background threads");
+}
+TEST_END
+
+TEST_BEGIN(test_max_background_threads) {
+ test_skip_if(!have_background_thread);
+
+ size_t maxt;
+ size_t opt_maxt;
+ size_t sz_m = sizeof(maxt);
+ assert_d_eq(mallctl("opt.max_background_threads",
+ &opt_maxt, &sz_m, NULL, 0), 0,
+ "Failed to get opt.max_background_threads");
+ assert_d_eq(mallctl("max_background_threads", &maxt, &sz_m, NULL, 0), 0,
+ "Failed to get max background threads");
+ assert_zu_eq(20, maxt, "should be ncpus");
+ assert_zu_eq(opt_maxt, maxt,
+ "max_background_threads and "
+ "opt.max_background_threads should match");
+ assert_d_eq(mallctl("max_background_threads", NULL, NULL, &maxt, sz_m),
+ 0, "Failed to set max background threads");
+
+ unsigned id;
+ size_t sz_u = sizeof(unsigned);
+
+ for (unsigned i = 0; i < 10 * ncpus; i++) {
+ assert_d_eq(mallctl("arenas.create", &id, &sz_u, NULL, 0), 0,
+ "Failed to create arena");
+ }
+
+ bool enable = true;
+ size_t sz_b = sizeof(bool);
+ assert_d_eq(mallctl("background_thread", NULL, NULL, &enable, sz_b), 0,
+ "Failed to enable background threads");
+ assert_zu_eq(n_background_threads, maxt,
+ "Number of background threads should be 3.\n");
+ maxt = 10;
+ assert_d_eq(mallctl("max_background_threads", NULL, NULL, &maxt, sz_m),
+ 0, "Failed to set max background threads");
+ assert_zu_eq(n_background_threads, maxt,
+ "Number of background threads should be 10.\n");
+ maxt = 3;
+ assert_d_eq(mallctl("max_background_threads", NULL, NULL, &maxt, sz_m),
+ 0, "Failed to set max background threads");
+ assert_zu_eq(n_background_threads, maxt,
+ "Number of background threads should be 3.\n");
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_deferred,
+ test_max_background_threads);
+}
diff --git a/deps/jemalloc/test/unit/base.c b/deps/jemalloc/test/unit/base.c
new file mode 100644
index 000000000..6b792cf21
--- /dev/null
+++ b/deps/jemalloc/test/unit/base.c
@@ -0,0 +1,234 @@
+#include "test/jemalloc_test.h"
+
+#include "test/extent_hooks.h"
+
+static extent_hooks_t hooks_null = {
+ extent_alloc_hook,
+ NULL, /* dalloc */
+ NULL, /* destroy */
+ NULL, /* commit */
+ NULL, /* decommit */
+ NULL, /* purge_lazy */
+ NULL, /* purge_forced */
+ NULL, /* split */
+ NULL /* merge */
+};
+
+static extent_hooks_t hooks_not_null = {
+ extent_alloc_hook,
+ extent_dalloc_hook,
+ extent_destroy_hook,
+ NULL, /* commit */
+ extent_decommit_hook,
+ extent_purge_lazy_hook,
+ extent_purge_forced_hook,
+ NULL, /* split */
+ NULL /* merge */
+};
+
+TEST_BEGIN(test_base_hooks_default) {
+ base_t *base;
+ size_t allocated0, allocated1, resident, mapped, n_thp;
+
+ tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
+ base = base_new(tsdn, 0, (extent_hooks_t *)&extent_hooks_default);
+
+ if (config_stats) {
+ base_stats_get(tsdn, base, &allocated0, &resident, &mapped,
+ &n_thp);
+ assert_zu_ge(allocated0, sizeof(base_t),
+ "Base header should count as allocated");
+ if (opt_metadata_thp == metadata_thp_always) {
+ assert_zu_gt(n_thp, 0,
+ "Base should have 1 THP at least.");
+ }
+ }
+
+ assert_ptr_not_null(base_alloc(tsdn, base, 42, 1),
+ "Unexpected base_alloc() failure");
+
+ if (config_stats) {
+ base_stats_get(tsdn, base, &allocated1, &resident, &mapped,
+ &n_thp);
+ assert_zu_ge(allocated1 - allocated0, 42,
+ "At least 42 bytes were allocated by base_alloc()");
+ }
+
+ base_delete(tsdn, base);
+}
+TEST_END
+
+TEST_BEGIN(test_base_hooks_null) {
+ extent_hooks_t hooks_orig;
+ base_t *base;
+ size_t allocated0, allocated1, resident, mapped, n_thp;
+
+ extent_hooks_prep();
+ try_dalloc = false;
+ try_destroy = true;
+ try_decommit = false;
+ try_purge_lazy = false;
+ try_purge_forced = false;
+ memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t));
+ memcpy(&hooks, &hooks_null, sizeof(extent_hooks_t));
+
+ tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
+ base = base_new(tsdn, 0, &hooks);
+ assert_ptr_not_null(base, "Unexpected base_new() failure");
+
+ if (config_stats) {
+ base_stats_get(tsdn, base, &allocated0, &resident, &mapped,
+ &n_thp);
+ assert_zu_ge(allocated0, sizeof(base_t),
+ "Base header should count as allocated");
+ if (opt_metadata_thp == metadata_thp_always) {
+ assert_zu_gt(n_thp, 0,
+ "Base should have 1 THP at least.");
+ }
+ }
+
+ assert_ptr_not_null(base_alloc(tsdn, base, 42, 1),
+ "Unexpected base_alloc() failure");
+
+ if (config_stats) {
+ base_stats_get(tsdn, base, &allocated1, &resident, &mapped,
+ &n_thp);
+ assert_zu_ge(allocated1 - allocated0, 42,
+ "At least 42 bytes were allocated by base_alloc()");
+ }
+
+ base_delete(tsdn, base);
+
+ memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t));
+}
+TEST_END
+
+TEST_BEGIN(test_base_hooks_not_null) {
+ extent_hooks_t hooks_orig;
+ base_t *base;
+ void *p, *q, *r, *r_exp;
+
+ extent_hooks_prep();
+ try_dalloc = false;
+ try_destroy = true;
+ try_decommit = false;
+ try_purge_lazy = false;
+ try_purge_forced = false;
+ memcpy(&hooks_orig, &hooks, sizeof(extent_hooks_t));
+ memcpy(&hooks, &hooks_not_null, sizeof(extent_hooks_t));
+
+ tsdn_t *tsdn = tsd_tsdn(tsd_fetch());
+ did_alloc = false;
+ base = base_new(tsdn, 0, &hooks);
+ assert_ptr_not_null(base, "Unexpected base_new() failure");
+ assert_true(did_alloc, "Expected alloc");
+
+ /*
+ * Check for tight packing at specified alignment under simple
+ * conditions.
+ */
+ {
+ const size_t alignments[] = {
+ 1,
+ QUANTUM,
+ QUANTUM << 1,
+ CACHELINE,
+ CACHELINE << 1,
+ };
+ unsigned i;
+
+ for (i = 0; i < sizeof(alignments) / sizeof(size_t); i++) {
+ size_t alignment = alignments[i];
+ size_t align_ceil = ALIGNMENT_CEILING(alignment,
+ QUANTUM);
+ p = base_alloc(tsdn, base, 1, alignment);
+ assert_ptr_not_null(p,
+ "Unexpected base_alloc() failure");
+ assert_ptr_eq(p,
+ (void *)(ALIGNMENT_CEILING((uintptr_t)p,
+ alignment)), "Expected quantum alignment");
+ q = base_alloc(tsdn, base, alignment, alignment);
+ assert_ptr_not_null(q,
+ "Unexpected base_alloc() failure");
+ assert_ptr_eq((void *)((uintptr_t)p + align_ceil), q,
+ "Minimal allocation should take up %zu bytes",
+ align_ceil);
+ r = base_alloc(tsdn, base, 1, alignment);
+ assert_ptr_not_null(r,
+ "Unexpected base_alloc() failure");
+ assert_ptr_eq((void *)((uintptr_t)q + align_ceil), r,
+ "Minimal allocation should take up %zu bytes",
+ align_ceil);
+ }
+ }
+
+ /*
+ * Allocate an object that cannot fit in the first block, then verify
+ * that the first block's remaining space is considered for subsequent
+ * allocation.
+ */
+ assert_zu_ge(extent_bsize_get(&base->blocks->extent), QUANTUM,
+ "Remainder insufficient for test");
+ /* Use up all but one quantum of block. */
+ while (extent_bsize_get(&base->blocks->extent) > QUANTUM) {
+ p = base_alloc(tsdn, base, QUANTUM, QUANTUM);
+ assert_ptr_not_null(p, "Unexpected base_alloc() failure");
+ }
+ r_exp = extent_addr_get(&base->blocks->extent);
+ assert_zu_eq(base->extent_sn_next, 1, "One extant block expected");
+ q = base_alloc(tsdn, base, QUANTUM + 1, QUANTUM);
+ assert_ptr_not_null(q, "Unexpected base_alloc() failure");
+ assert_ptr_ne(q, r_exp, "Expected allocation from new block");
+ assert_zu_eq(base->extent_sn_next, 2, "Two extant blocks expected");
+ r = base_alloc(tsdn, base, QUANTUM, QUANTUM);
+ assert_ptr_not_null(r, "Unexpected base_alloc() failure");
+ assert_ptr_eq(r, r_exp, "Expected allocation from first block");
+ assert_zu_eq(base->extent_sn_next, 2, "Two extant blocks expected");
+
+ /*
+ * Check for proper alignment support when normal blocks are too small.
+ */
+ {
+ const size_t alignments[] = {
+ HUGEPAGE,
+ HUGEPAGE << 1
+ };
+ unsigned i;
+
+ for (i = 0; i < sizeof(alignments) / sizeof(size_t); i++) {
+ size_t alignment = alignments[i];
+ p = base_alloc(tsdn, base, QUANTUM, alignment);
+ assert_ptr_not_null(p,
+ "Unexpected base_alloc() failure");
+ assert_ptr_eq(p,
+ (void *)(ALIGNMENT_CEILING((uintptr_t)p,
+ alignment)), "Expected %zu-byte alignment",
+ alignment);
+ }
+ }
+
+ called_dalloc = called_destroy = called_decommit = called_purge_lazy =
+ called_purge_forced = false;
+ base_delete(tsdn, base);
+ assert_true(called_dalloc, "Expected dalloc call");
+ assert_true(!called_destroy, "Unexpected destroy call");
+ assert_true(called_decommit, "Expected decommit call");
+ assert_true(called_purge_lazy, "Expected purge_lazy call");
+ assert_true(called_purge_forced, "Expected purge_forced call");
+
+ try_dalloc = true;
+ try_destroy = true;
+ try_decommit = true;
+ try_purge_lazy = true;
+ try_purge_forced = true;
+ memcpy(&hooks, &hooks_orig, sizeof(extent_hooks_t));
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_base_hooks_default,
+ test_base_hooks_null,
+ test_base_hooks_not_null);
+}
diff --git a/deps/jemalloc/test/unit/bit_util.c b/deps/jemalloc/test/unit/bit_util.c
new file mode 100644
index 000000000..42a97013d
--- /dev/null
+++ b/deps/jemalloc/test/unit/bit_util.c
@@ -0,0 +1,57 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/bit_util.h"
+
+#define TEST_POW2_CEIL(t, suf, pri) do { \
+ unsigned i, pow2; \
+ t x; \
+ \
+ assert_##suf##_eq(pow2_ceil_##suf(0), 0, "Unexpected result"); \
+ \
+ for (i = 0; i < sizeof(t) * 8; i++) { \
+ assert_##suf##_eq(pow2_ceil_##suf(((t)1) << i), ((t)1) \
+ << i, "Unexpected result"); \
+ } \
+ \
+ for (i = 2; i < sizeof(t) * 8; i++) { \
+ assert_##suf##_eq(pow2_ceil_##suf((((t)1) << i) - 1), \
+ ((t)1) << i, "Unexpected result"); \
+ } \
+ \
+ for (i = 0; i < sizeof(t) * 8 - 1; i++) { \
+ assert_##suf##_eq(pow2_ceil_##suf((((t)1) << i) + 1), \
+ ((t)1) << (i+1), "Unexpected result"); \
+ } \
+ \
+ for (pow2 = 1; pow2 < 25; pow2++) { \
+ for (x = (((t)1) << (pow2-1)) + 1; x <= ((t)1) << pow2; \
+ x++) { \
+ assert_##suf##_eq(pow2_ceil_##suf(x), \
+ ((t)1) << pow2, \
+ "Unexpected result, x=%"pri, x); \
+ } \
+ } \
+} while (0)
+
+TEST_BEGIN(test_pow2_ceil_u64) {
+ TEST_POW2_CEIL(uint64_t, u64, FMTu64);
+}
+TEST_END
+
+TEST_BEGIN(test_pow2_ceil_u32) {
+ TEST_POW2_CEIL(uint32_t, u32, FMTu32);
+}
+TEST_END
+
+TEST_BEGIN(test_pow2_ceil_zu) {
+ TEST_POW2_CEIL(size_t, zu, "zu");
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_pow2_ceil_u64,
+ test_pow2_ceil_u32,
+ test_pow2_ceil_zu);
+}
diff --git a/deps/jemalloc/test/unit/bitmap.c b/deps/jemalloc/test/unit/bitmap.c
index 7da583d85..cafb2039e 100644
--- a/deps/jemalloc/test/unit/bitmap.c
+++ b/deps/jemalloc/test/unit/bitmap.c
@@ -1,159 +1,431 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_bitmap_size)
-{
- size_t i, prev_size;
+#define NBITS_TAB \
+ NB( 1) \
+ NB( 2) \
+ NB( 3) \
+ NB( 4) \
+ NB( 5) \
+ NB( 6) \
+ NB( 7) \
+ NB( 8) \
+ NB( 9) \
+ NB(10) \
+ NB(11) \
+ NB(12) \
+ NB(13) \
+ NB(14) \
+ NB(15) \
+ NB(16) \
+ NB(17) \
+ NB(18) \
+ NB(19) \
+ NB(20) \
+ NB(21) \
+ NB(22) \
+ NB(23) \
+ NB(24) \
+ NB(25) \
+ NB(26) \
+ NB(27) \
+ NB(28) \
+ NB(29) \
+ NB(30) \
+ NB(31) \
+ NB(32) \
+ \
+ NB(33) \
+ NB(34) \
+ NB(35) \
+ NB(36) \
+ NB(37) \
+ NB(38) \
+ NB(39) \
+ NB(40) \
+ NB(41) \
+ NB(42) \
+ NB(43) \
+ NB(44) \
+ NB(45) \
+ NB(46) \
+ NB(47) \
+ NB(48) \
+ NB(49) \
+ NB(50) \
+ NB(51) \
+ NB(52) \
+ NB(53) \
+ NB(54) \
+ NB(55) \
+ NB(56) \
+ NB(57) \
+ NB(58) \
+ NB(59) \
+ NB(60) \
+ NB(61) \
+ NB(62) \
+ NB(63) \
+ NB(64) \
+ NB(65) \
+ \
+ NB(126) \
+ NB(127) \
+ NB(128) \
+ NB(129) \
+ NB(130) \
+ \
+ NB(254) \
+ NB(255) \
+ NB(256) \
+ NB(257) \
+ NB(258) \
+ \
+ NB(510) \
+ NB(511) \
+ NB(512) \
+ NB(513) \
+ NB(514) \
+ \
+ NB(1024) \
+ NB(2048) \
+ NB(4096) \
+ NB(8192) \
+ NB(16384) \
- prev_size = 0;
- for (i = 1; i <= BITMAP_MAXBITS; i++) {
- size_t size = bitmap_size(i);
- assert_true(size >= prev_size,
- "Bitmap size is smaller than expected");
- prev_size = size;
+static void
+test_bitmap_initializer_body(const bitmap_info_t *binfo, size_t nbits) {
+ bitmap_info_t binfo_dyn;
+ bitmap_info_init(&binfo_dyn, nbits);
+
+ assert_zu_eq(bitmap_size(binfo), bitmap_size(&binfo_dyn),
+ "Unexpected difference between static and dynamic initialization, "
+ "nbits=%zu", nbits);
+ assert_zu_eq(binfo->nbits, binfo_dyn.nbits,
+ "Unexpected difference between static and dynamic initialization, "
+ "nbits=%zu", nbits);
+#ifdef BITMAP_USE_TREE
+ assert_u_eq(binfo->nlevels, binfo_dyn.nlevels,
+ "Unexpected difference between static and dynamic initialization, "
+ "nbits=%zu", nbits);
+ {
+ unsigned i;
+
+ for (i = 0; i < binfo->nlevels; i++) {
+ assert_zu_eq(binfo->levels[i].group_offset,
+ binfo_dyn.levels[i].group_offset,
+ "Unexpected difference between static and dynamic "
+ "initialization, nbits=%zu, level=%u", nbits, i);
+ }
}
+#else
+ assert_zu_eq(binfo->ngroups, binfo_dyn.ngroups,
+ "Unexpected difference between static and dynamic initialization");
+#endif
+}
+
+TEST_BEGIN(test_bitmap_initializer) {
+#define NB(nbits) { \
+ if (nbits <= BITMAP_MAXBITS) { \
+ bitmap_info_t binfo = \
+ BITMAP_INFO_INITIALIZER(nbits); \
+ test_bitmap_initializer_body(&binfo, nbits); \
+ } \
+ }
+ NBITS_TAB
+#undef NB
}
TEST_END
-TEST_BEGIN(test_bitmap_init)
-{
- size_t i;
+static size_t
+test_bitmap_size_body(const bitmap_info_t *binfo, size_t nbits,
+ size_t prev_size) {
+ size_t size = bitmap_size(binfo);
+ assert_zu_ge(size, (nbits >> 3),
+ "Bitmap size is smaller than expected");
+ assert_zu_ge(size, prev_size, "Bitmap size is smaller than expected");
+ return size;
+}
+
+TEST_BEGIN(test_bitmap_size) {
+ size_t nbits, prev_size;
- for (i = 1; i <= BITMAP_MAXBITS; i++) {
+ prev_size = 0;
+ for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) {
bitmap_info_t binfo;
- bitmap_info_init(&binfo, i);
- {
- size_t j;
- bitmap_t *bitmap = (bitmap_t *)malloc(sizeof(bitmap_t) *
- bitmap_info_ngroups(&binfo));
- bitmap_init(bitmap, &binfo);
-
- for (j = 0; j < i; j++) {
- assert_false(bitmap_get(bitmap, &binfo, j),
- "Bit should be unset");
- }
- free(bitmap);
- }
+ bitmap_info_init(&binfo, nbits);
+ prev_size = test_bitmap_size_body(&binfo, nbits, prev_size);
+ }
+#define NB(nbits) { \
+ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \
+ prev_size = test_bitmap_size_body(&binfo, nbits, \
+ prev_size); \
}
+ prev_size = 0;
+ NBITS_TAB
+#undef NB
}
TEST_END
-TEST_BEGIN(test_bitmap_set)
-{
+static void
+test_bitmap_init_body(const bitmap_info_t *binfo, size_t nbits) {
size_t i;
+ bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo));
+ assert_ptr_not_null(bitmap, "Unexpected malloc() failure");
- for (i = 1; i <= BITMAP_MAXBITS; i++) {
+ bitmap_init(bitmap, binfo, false);
+ for (i = 0; i < nbits; i++) {
+ assert_false(bitmap_get(bitmap, binfo, i),
+ "Bit should be unset");
+ }
+
+ bitmap_init(bitmap, binfo, true);
+ for (i = 0; i < nbits; i++) {
+ assert_true(bitmap_get(bitmap, binfo, i), "Bit should be set");
+ }
+
+ free(bitmap);
+}
+
+TEST_BEGIN(test_bitmap_init) {
+ size_t nbits;
+
+ for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) {
bitmap_info_t binfo;
- bitmap_info_init(&binfo, i);
- {
- size_t j;
- bitmap_t *bitmap = (bitmap_t *)malloc(sizeof(bitmap_t) *
- bitmap_info_ngroups(&binfo));
- bitmap_init(bitmap, &binfo);
-
- for (j = 0; j < i; j++)
- bitmap_set(bitmap, &binfo, j);
- assert_true(bitmap_full(bitmap, &binfo),
- "All bits should be set");
- free(bitmap);
- }
+ bitmap_info_init(&binfo, nbits);
+ test_bitmap_init_body(&binfo, nbits);
}
+#define NB(nbits) { \
+ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \
+ test_bitmap_init_body(&binfo, nbits); \
+ }
+ NBITS_TAB
+#undef NB
}
TEST_END
-TEST_BEGIN(test_bitmap_unset)
-{
+static void
+test_bitmap_set_body(const bitmap_info_t *binfo, size_t nbits) {
size_t i;
+ bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo));
+ assert_ptr_not_null(bitmap, "Unexpected malloc() failure");
+ bitmap_init(bitmap, binfo, false);
+
+ for (i = 0; i < nbits; i++) {
+ bitmap_set(bitmap, binfo, i);
+ }
+ assert_true(bitmap_full(bitmap, binfo), "All bits should be set");
+ free(bitmap);
+}
- for (i = 1; i <= BITMAP_MAXBITS; i++) {
+TEST_BEGIN(test_bitmap_set) {
+ size_t nbits;
+
+ for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) {
bitmap_info_t binfo;
- bitmap_info_init(&binfo, i);
- {
- size_t j;
- bitmap_t *bitmap = (bitmap_t *)malloc(sizeof(bitmap_t) *
- bitmap_info_ngroups(&binfo));
- bitmap_init(bitmap, &binfo);
-
- for (j = 0; j < i; j++)
- bitmap_set(bitmap, &binfo, j);
- assert_true(bitmap_full(bitmap, &binfo),
- "All bits should be set");
- for (j = 0; j < i; j++)
- bitmap_unset(bitmap, &binfo, j);
- for (j = 0; j < i; j++)
- bitmap_set(bitmap, &binfo, j);
- assert_true(bitmap_full(bitmap, &binfo),
- "All bits should be set");
- free(bitmap);
- }
+ bitmap_info_init(&binfo, nbits);
+ test_bitmap_set_body(&binfo, nbits);
+ }
+#define NB(nbits) { \
+ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \
+ test_bitmap_set_body(&binfo, nbits); \
}
+ NBITS_TAB
+#undef NB
}
TEST_END
-TEST_BEGIN(test_bitmap_sfu)
-{
+static void
+test_bitmap_unset_body(const bitmap_info_t *binfo, size_t nbits) {
size_t i;
+ bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo));
+ assert_ptr_not_null(bitmap, "Unexpected malloc() failure");
+ bitmap_init(bitmap, binfo, false);
- for (i = 1; i <= BITMAP_MAXBITS; i++) {
+ for (i = 0; i < nbits; i++) {
+ bitmap_set(bitmap, binfo, i);
+ }
+ assert_true(bitmap_full(bitmap, binfo), "All bits should be set");
+ for (i = 0; i < nbits; i++) {
+ bitmap_unset(bitmap, binfo, i);
+ }
+ for (i = 0; i < nbits; i++) {
+ bitmap_set(bitmap, binfo, i);
+ }
+ assert_true(bitmap_full(bitmap, binfo), "All bits should be set");
+ free(bitmap);
+}
+
+TEST_BEGIN(test_bitmap_unset) {
+ size_t nbits;
+
+ for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) {
bitmap_info_t binfo;
- bitmap_info_init(&binfo, i);
- {
- ssize_t j;
- bitmap_t *bitmap = (bitmap_t *)malloc(sizeof(bitmap_t) *
- bitmap_info_ngroups(&binfo));
- bitmap_init(bitmap, &binfo);
-
- /* Iteratively set bits starting at the beginning. */
- for (j = 0; j < i; j++) {
- assert_zd_eq(bitmap_sfu(bitmap, &binfo), j,
- "First unset bit should be just after "
- "previous first unset bit");
+ bitmap_info_init(&binfo, nbits);
+ test_bitmap_unset_body(&binfo, nbits);
+ }
+#define NB(nbits) { \
+ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \
+ test_bitmap_unset_body(&binfo, nbits); \
+ }
+ NBITS_TAB
+#undef NB
+}
+TEST_END
+
+static void
+test_bitmap_xfu_body(const bitmap_info_t *binfo, size_t nbits) {
+ bitmap_t *bitmap = (bitmap_t *)malloc(bitmap_size(binfo));
+ assert_ptr_not_null(bitmap, "Unexpected malloc() failure");
+ bitmap_init(bitmap, binfo, false);
+
+ /* Iteratively set bits starting at the beginning. */
+ for (size_t i = 0; i < nbits; i++) {
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, 0), i,
+ "First unset bit should be just after previous first unset "
+ "bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i,
+ "First unset bit should be just after previous first unset "
+ "bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i), i,
+ "First unset bit should be just after previous first unset "
+ "bit");
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i,
+ "First unset bit should be just after previous first unset "
+ "bit");
+ }
+ assert_true(bitmap_full(bitmap, binfo), "All bits should be set");
+
+ /*
+ * Iteratively unset bits starting at the end, and verify that
+ * bitmap_sfu() reaches the unset bits.
+ */
+ for (size_t i = nbits - 1; i < nbits; i--) { /* (nbits..0] */
+ bitmap_unset(bitmap, binfo, i);
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, 0), i,
+ "First unset bit should the bit previously unset");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i,
+ "First unset bit should the bit previously unset");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i), i,
+ "First unset bit should the bit previously unset");
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i,
+ "First unset bit should the bit previously unset");
+ bitmap_unset(bitmap, binfo, i);
+ }
+ assert_false(bitmap_get(bitmap, binfo, 0), "Bit should be unset");
+
+ /*
+ * Iteratively set bits starting at the beginning, and verify that
+ * bitmap_sfu() looks past them.
+ */
+ for (size_t i = 1; i < nbits; i++) {
+ bitmap_set(bitmap, binfo, i - 1);
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, 0), i,
+ "First unset bit should be just after the bit previously "
+ "set");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, (i > 0) ? i-1 : i), i,
+ "First unset bit should be just after the bit previously "
+ "set");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i), i,
+ "First unset bit should be just after the bit previously "
+ "set");
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i,
+ "First unset bit should be just after the bit previously "
+ "set");
+ bitmap_unset(bitmap, binfo, i);
+ }
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, 0), nbits - 1,
+ "First unset bit should be the last bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, (nbits > 1) ? nbits-2 : nbits-1),
+ nbits - 1, "First unset bit should be the last bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, nbits - 1), nbits - 1,
+ "First unset bit should be the last bit");
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), nbits - 1,
+ "First unset bit should be the last bit");
+ assert_true(bitmap_full(bitmap, binfo), "All bits should be set");
+
+ /*
+ * Bubble a "usu" pattern through the bitmap and verify that
+ * bitmap_ffu() finds the correct bit for all five min_bit cases.
+ */
+ if (nbits >= 3) {
+ for (size_t i = 0; i < nbits-2; i++) {
+ bitmap_unset(bitmap, binfo, i);
+ bitmap_unset(bitmap, binfo, i+2);
+ if (i > 0) {
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i-1), i,
+ "Unexpected first unset bit");
}
- assert_true(bitmap_full(bitmap, &binfo),
- "All bits should be set");
-
- /*
- * Iteratively unset bits starting at the end, and
- * verify that bitmap_sfu() reaches the unset bits.
- */
- for (j = i - 1; j >= 0; j--) {
- bitmap_unset(bitmap, &binfo, j);
- assert_zd_eq(bitmap_sfu(bitmap, &binfo), j,
- "First unset bit should the bit previously "
- "unset");
- bitmap_unset(bitmap, &binfo, j);
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i), i,
+ "Unexpected first unset bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i+1), i+2,
+ "Unexpected first unset bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i+2), i+2,
+ "Unexpected first unset bit");
+ if (i + 3 < nbits) {
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i+3),
+ nbits, "Unexpected first unset bit");
}
- assert_false(bitmap_get(bitmap, &binfo, 0),
- "Bit should be unset");
-
- /*
- * Iteratively set bits starting at the beginning, and
- * verify that bitmap_sfu() looks past them.
- */
- for (j = 1; j < i; j++) {
- bitmap_set(bitmap, &binfo, j - 1);
- assert_zd_eq(bitmap_sfu(bitmap, &binfo), j,
- "First unset bit should be just after the "
- "bit previously set");
- bitmap_unset(bitmap, &binfo, j);
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i,
+ "Unexpected first unset bit");
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i+2,
+ "Unexpected first unset bit");
+ }
+ }
+
+ /*
+ * Unset the last bit, bubble another unset bit through the bitmap, and
+ * verify that bitmap_ffu() finds the correct bit for all four min_bit
+ * cases.
+ */
+ if (nbits >= 3) {
+ bitmap_unset(bitmap, binfo, nbits-1);
+ for (size_t i = 0; i < nbits-1; i++) {
+ bitmap_unset(bitmap, binfo, i);
+ if (i > 0) {
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i-1), i,
+ "Unexpected first unset bit");
}
- assert_zd_eq(bitmap_sfu(bitmap, &binfo), i - 1,
- "First unset bit should be the last bit");
- assert_true(bitmap_full(bitmap, &binfo),
- "All bits should be set");
- free(bitmap);
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i), i,
+ "Unexpected first unset bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, i+1), nbits-1,
+ "Unexpected first unset bit");
+ assert_zu_eq(bitmap_ffu(bitmap, binfo, nbits-1),
+ nbits-1, "Unexpected first unset bit");
+
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), i,
+ "Unexpected first unset bit");
}
+ assert_zu_eq(bitmap_sfu(bitmap, binfo), nbits-1,
+ "Unexpected first unset bit");
}
+
+ free(bitmap);
+}
+
+TEST_BEGIN(test_bitmap_xfu) {
+ size_t nbits;
+
+ for (nbits = 1; nbits <= BITMAP_MAXBITS; nbits++) {
+ bitmap_info_t binfo;
+ bitmap_info_init(&binfo, nbits);
+ test_bitmap_xfu_body(&binfo, nbits);
+ }
+#define NB(nbits) { \
+ bitmap_info_t binfo = BITMAP_INFO_INITIALIZER(nbits); \
+ test_bitmap_xfu_body(&binfo, nbits); \
+ }
+ NBITS_TAB
+#undef NB
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
+ test_bitmap_initializer,
test_bitmap_size,
test_bitmap_init,
test_bitmap_set,
test_bitmap_unset,
- test_bitmap_sfu));
+ test_bitmap_xfu);
}
diff --git a/deps/jemalloc/test/unit/ckh.c b/deps/jemalloc/test/unit/ckh.c
index b11759599..707ea5f8c 100644
--- a/deps/jemalloc/test/unit/ckh.c
+++ b/deps/jemalloc/test/unit/ckh.c
@@ -1,14 +1,13 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_new_delete)
-{
+TEST_BEGIN(test_new_delete) {
tsd_t *tsd;
ckh_t ckh;
tsd = tsd_fetch();
- assert_false(ckh_new(tsd, &ckh, 2, ckh_string_hash, ckh_string_keycomp),
- "Unexpected ckh_new() error");
+ assert_false(ckh_new(tsd, &ckh, 2, ckh_string_hash,
+ ckh_string_keycomp), "Unexpected ckh_new() error");
ckh_delete(tsd, &ckh);
assert_false(ckh_new(tsd, &ckh, 3, ckh_pointer_hash,
@@ -17,8 +16,7 @@ TEST_BEGIN(test_new_delete)
}
TEST_END
-TEST_BEGIN(test_count_insert_search_remove)
-{
+TEST_BEGIN(test_count_insert_search_remove) {
tsd_t *tsd;
ckh_t ckh;
const char *strs[] = {
@@ -32,8 +30,8 @@ TEST_BEGIN(test_count_insert_search_remove)
tsd = tsd_fetch();
- assert_false(ckh_new(tsd, &ckh, 2, ckh_string_hash, ckh_string_keycomp),
- "Unexpected ckh_new() error");
+ assert_false(ckh_new(tsd, &ckh, 2, ckh_string_hash,
+ ckh_string_keycomp), "Unexpected ckh_new() error");
assert_zu_eq(ckh_count(&ckh), 0,
"ckh_count() should return %zu, but it returned %zu", ZU(0),
ckh_count(&ckh));
@@ -105,9 +103,8 @@ TEST_BEGIN(test_count_insert_search_remove)
}
TEST_END
-TEST_BEGIN(test_insert_iter_remove)
-{
-#define NITEMS ZU(1000)
+TEST_BEGIN(test_insert_iter_remove) {
+#define NITEMS ZU(1000)
tsd_t *tsd;
ckh_t ckh;
void **p[NITEMS];
@@ -174,10 +171,12 @@ TEST_BEGIN(test_insert_iter_remove)
}
}
- for (j = 0; j < i + 1; j++)
+ for (j = 0; j < i + 1; j++) {
assert_true(seen[j], "Item %zu not seen", j);
- for (; j < NITEMS; j++)
+ }
+ for (; j < NITEMS; j++) {
assert_false(seen[j], "Item %zu seen", j);
+ }
}
}
@@ -204,11 +203,9 @@ TEST_BEGIN(test_insert_iter_remove)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_new_delete,
test_count_insert_search_remove,
- test_insert_iter_remove));
+ test_insert_iter_remove);
}
diff --git a/deps/jemalloc/test/unit/decay.c b/deps/jemalloc/test/unit/decay.c
new file mode 100644
index 000000000..f727bf931
--- /dev/null
+++ b/deps/jemalloc/test/unit/decay.c
@@ -0,0 +1,599 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/ticker.h"
+
+static nstime_monotonic_t *nstime_monotonic_orig;
+static nstime_update_t *nstime_update_orig;
+
+static unsigned nupdates_mock;
+static nstime_t time_mock;
+static bool monotonic_mock;
+
+static bool
+check_background_thread_enabled(void) {
+ bool enabled;
+ size_t sz = sizeof(bool);
+ int ret = mallctl("background_thread", (void *)&enabled, &sz, NULL,0);
+ if (ret == ENOENT) {
+ return false;
+ }
+ assert_d_eq(ret, 0, "Unexpected mallctl error");
+ return enabled;
+}
+
+static bool
+nstime_monotonic_mock(void) {
+ return monotonic_mock;
+}
+
+static bool
+nstime_update_mock(nstime_t *time) {
+ nupdates_mock++;
+ if (monotonic_mock) {
+ nstime_copy(time, &time_mock);
+ }
+ return !monotonic_mock;
+}
+
+static unsigned
+do_arena_create(ssize_t dirty_decay_ms, ssize_t muzzy_decay_ms) {
+ unsigned arena_ind;
+ size_t sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+
+ assert_d_eq(mallctlnametomib("arena.0.dirty_decay_ms", mib, &miblen),
+ 0, "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
+ (void *)&dirty_decay_ms, sizeof(dirty_decay_ms)), 0,
+ "Unexpected mallctlbymib() failure");
+
+ assert_d_eq(mallctlnametomib("arena.0.muzzy_decay_ms", mib, &miblen),
+ 0, "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL,
+ (void *)&muzzy_decay_ms, sizeof(muzzy_decay_ms)), 0,
+ "Unexpected mallctlbymib() failure");
+
+ return arena_ind;
+}
+
+static void
+do_arena_destroy(unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+void
+do_epoch(void) {
+ uint64_t epoch = 1;
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
+}
+
+void
+do_purge(unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+void
+do_decay(unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+static uint64_t
+get_arena_npurge_impl(const char *mibname, unsigned arena_ind) {
+ size_t mib[4];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib(mibname, mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[2] = (size_t)arena_ind;
+ uint64_t npurge = 0;
+ size_t sz = sizeof(npurge);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&npurge, &sz, NULL, 0),
+ config_stats ? 0 : ENOENT, "Unexpected mallctlbymib() failure");
+ return npurge;
+}
+
+static uint64_t
+get_arena_dirty_npurge(unsigned arena_ind) {
+ do_epoch();
+ return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind);
+}
+
+static uint64_t
+get_arena_muzzy_npurge(unsigned arena_ind) {
+ do_epoch();
+ return get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
+}
+
+static uint64_t
+get_arena_npurge(unsigned arena_ind) {
+ do_epoch();
+ return get_arena_npurge_impl("stats.arenas.0.dirty_npurge", arena_ind) +
+ get_arena_npurge_impl("stats.arenas.0.muzzy_npurge", arena_ind);
+}
+
+static size_t
+get_arena_pdirty(unsigned arena_ind) {
+ do_epoch();
+ size_t mib[4];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("stats.arenas.0.pdirty", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[2] = (size_t)arena_ind;
+ size_t pdirty;
+ size_t sz = sizeof(pdirty);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&pdirty, &sz, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+ return pdirty;
+}
+
+static size_t
+get_arena_pmuzzy(unsigned arena_ind) {
+ do_epoch();
+ size_t mib[4];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("stats.arenas.0.pmuzzy", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[2] = (size_t)arena_ind;
+ size_t pmuzzy;
+ size_t sz = sizeof(pmuzzy);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&pmuzzy, &sz, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+ return pmuzzy;
+}
+
+static void *
+do_mallocx(size_t size, int flags) {
+ void *p = mallocx(size, flags);
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ return p;
+}
+
+static void
+generate_dirty(unsigned arena_ind, size_t size) {
+ int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
+ void *p = do_mallocx(size, flags);
+ dallocx(p, flags);
+}
+
+TEST_BEGIN(test_decay_ticks) {
+ test_skip_if(check_background_thread_enabled());
+
+ ticker_t *decay_ticker;
+ unsigned tick0, tick1, arena_ind;
+ size_t sz, large0;
+ void *p;
+
+ sz = sizeof(size_t);
+ assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure");
+
+ /* Set up a manually managed arena for test. */
+ arena_ind = do_arena_create(0, 0);
+
+ /* Migrate to the new arena, and get the ticker. */
+ unsigned old_arena_ind;
+ size_t sz_arena_ind = sizeof(old_arena_ind);
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind,
+ &sz_arena_ind, (void *)&arena_ind, sizeof(arena_ind)), 0,
+ "Unexpected mallctl() failure");
+ decay_ticker = decay_ticker_get(tsd_fetch(), arena_ind);
+ assert_ptr_not_null(decay_ticker,
+ "Unexpected failure getting decay ticker");
+
+ /*
+ * Test the standard APIs using a large size class, since we can't
+ * control tcache interactions for small size classes (except by
+ * completely disabling tcache for the entire test program).
+ */
+
+ /* malloc(). */
+ tick0 = ticker_read(decay_ticker);
+ p = malloc(large0);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during malloc()");
+ /* free(). */
+ tick0 = ticker_read(decay_ticker);
+ free(p);
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during free()");
+
+ /* calloc(). */
+ tick0 = ticker_read(decay_ticker);
+ p = calloc(1, large0);
+ assert_ptr_not_null(p, "Unexpected calloc() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during calloc()");
+ free(p);
+
+ /* posix_memalign(). */
+ tick0 = ticker_read(decay_ticker);
+ assert_d_eq(posix_memalign(&p, sizeof(size_t), large0), 0,
+ "Unexpected posix_memalign() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during posix_memalign()");
+ free(p);
+
+ /* aligned_alloc(). */
+ tick0 = ticker_read(decay_ticker);
+ p = aligned_alloc(sizeof(size_t), large0);
+ assert_ptr_not_null(p, "Unexpected aligned_alloc() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during aligned_alloc()");
+ free(p);
+
+ /* realloc(). */
+ /* Allocate. */
+ tick0 = ticker_read(decay_ticker);
+ p = realloc(NULL, large0);
+ assert_ptr_not_null(p, "Unexpected realloc() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
+ /* Reallocate. */
+ tick0 = ticker_read(decay_ticker);
+ p = realloc(p, large0);
+ assert_ptr_not_null(p, "Unexpected realloc() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
+ /* Deallocate. */
+ tick0 = ticker_read(decay_ticker);
+ realloc(p, 0);
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0, "Expected ticker to tick during realloc()");
+
+ /*
+ * Test the *allocx() APIs using large and small size classes, with
+ * tcache explicitly disabled.
+ */
+ {
+ unsigned i;
+ size_t allocx_sizes[2];
+ allocx_sizes[0] = large0;
+ allocx_sizes[1] = 1;
+
+ for (i = 0; i < sizeof(allocx_sizes) / sizeof(size_t); i++) {
+ sz = allocx_sizes[i];
+
+ /* mallocx(). */
+ tick0 = ticker_read(decay_ticker);
+ p = mallocx(sz, MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during mallocx() (sz=%zu)",
+ sz);
+ /* rallocx(). */
+ tick0 = ticker_read(decay_ticker);
+ p = rallocx(p, sz, MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(p, "Unexpected rallocx() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during rallocx() (sz=%zu)",
+ sz);
+ /* xallocx(). */
+ tick0 = ticker_read(decay_ticker);
+ xallocx(p, sz, 0, MALLOCX_TCACHE_NONE);
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during xallocx() (sz=%zu)",
+ sz);
+ /* dallocx(). */
+ tick0 = ticker_read(decay_ticker);
+ dallocx(p, MALLOCX_TCACHE_NONE);
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during dallocx() (sz=%zu)",
+ sz);
+ /* sdallocx(). */
+ p = mallocx(sz, MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ tick0 = ticker_read(decay_ticker);
+ sdallocx(p, sz, MALLOCX_TCACHE_NONE);
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during sdallocx() "
+ "(sz=%zu)", sz);
+ }
+ }
+
+ /*
+ * Test tcache fill/flush interactions for large and small size classes,
+ * using an explicit tcache.
+ */
+ unsigned tcache_ind, i;
+ size_t tcache_sizes[2];
+ tcache_sizes[0] = large0;
+ tcache_sizes[1] = 1;
+
+ size_t tcache_max, sz_tcache_max;
+ sz_tcache_max = sizeof(tcache_max);
+ assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max,
+ &sz_tcache_max, NULL, 0), 0, "Unexpected mallctl() failure");
+
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("tcache.create", (void *)&tcache_ind, &sz,
+ NULL, 0), 0, "Unexpected mallctl failure");
+
+ for (i = 0; i < sizeof(tcache_sizes) / sizeof(size_t); i++) {
+ sz = tcache_sizes[i];
+
+ /* tcache fill. */
+ tick0 = ticker_read(decay_ticker);
+ p = mallocx(sz, MALLOCX_TCACHE(tcache_ind));
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ tick1 = ticker_read(decay_ticker);
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during tcache fill "
+ "(sz=%zu)", sz);
+ /* tcache flush. */
+ dallocx(p, MALLOCX_TCACHE(tcache_ind));
+ tick0 = ticker_read(decay_ticker);
+ assert_d_eq(mallctl("tcache.flush", NULL, NULL,
+ (void *)&tcache_ind, sizeof(unsigned)), 0,
+ "Unexpected mallctl failure");
+ tick1 = ticker_read(decay_ticker);
+
+ /* Will only tick if it's in tcache. */
+ if (sz <= tcache_max) {
+ assert_u32_ne(tick1, tick0,
+ "Expected ticker to tick during tcache "
+ "flush (sz=%zu)", sz);
+ } else {
+ assert_u32_eq(tick1, tick0,
+ "Unexpected ticker tick during tcache "
+ "flush (sz=%zu)", sz);
+ }
+ }
+}
+TEST_END
+
+static void
+decay_ticker_helper(unsigned arena_ind, int flags, bool dirty, ssize_t dt,
+ uint64_t dirty_npurge0, uint64_t muzzy_npurge0, bool terminate_asap) {
+#define NINTERVALS 101
+ nstime_t time, update_interval, decay_ms, deadline;
+
+ nstime_init(&time, 0);
+ nstime_update(&time);
+
+ nstime_init2(&decay_ms, dt, 0);
+ nstime_copy(&deadline, &time);
+ nstime_add(&deadline, &decay_ms);
+
+ nstime_init2(&update_interval, dt, 0);
+ nstime_idivide(&update_interval, NINTERVALS);
+
+ /*
+ * Keep q's slab from being deallocated during the looping below. If a
+ * cached slab were to repeatedly come and go during looping, it could
+ * prevent the decay backlog ever becoming empty.
+ */
+ void *p = do_mallocx(1, flags);
+ uint64_t dirty_npurge1, muzzy_npurge1;
+ do {
+ for (unsigned i = 0; i < DECAY_NTICKS_PER_UPDATE / 2;
+ i++) {
+ void *q = do_mallocx(1, flags);
+ dallocx(q, flags);
+ }
+ dirty_npurge1 = get_arena_dirty_npurge(arena_ind);
+ muzzy_npurge1 = get_arena_muzzy_npurge(arena_ind);
+
+ nstime_add(&time_mock, &update_interval);
+ nstime_update(&time);
+ } while (nstime_compare(&time, &deadline) <= 0 && ((dirty_npurge1 ==
+ dirty_npurge0 && muzzy_npurge1 == muzzy_npurge0) ||
+ !terminate_asap));
+ dallocx(p, flags);
+
+ if (config_stats) {
+ assert_u64_gt(dirty_npurge1 + muzzy_npurge1, dirty_npurge0 +
+ muzzy_npurge0, "Expected purging to occur");
+ }
+#undef NINTERVALS
+}
+
+TEST_BEGIN(test_decay_ticker) {
+ test_skip_if(check_background_thread_enabled());
+#define NPS 2048
+ ssize_t ddt = opt_dirty_decay_ms;
+ ssize_t mdt = opt_muzzy_decay_ms;
+ unsigned arena_ind = do_arena_create(ddt, mdt);
+ int flags = (MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE);
+ void *ps[NPS];
+ size_t large;
+
+ /*
+ * Allocate a bunch of large objects, pause the clock, deallocate every
+ * other object (to fragment virtual memory), restore the clock, then
+ * [md]allocx() in a tight loop while advancing time rapidly to verify
+ * the ticker triggers purging.
+ */
+
+ size_t tcache_max;
+ size_t sz = sizeof(size_t);
+ assert_d_eq(mallctl("arenas.tcache_max", (void *)&tcache_max, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure");
+ large = nallocx(tcache_max + 1, flags);
+
+ do_purge(arena_ind);
+ uint64_t dirty_npurge0 = get_arena_dirty_npurge(arena_ind);
+ uint64_t muzzy_npurge0 = get_arena_muzzy_npurge(arena_ind);
+
+ for (unsigned i = 0; i < NPS; i++) {
+ ps[i] = do_mallocx(large, flags);
+ }
+
+ nupdates_mock = 0;
+ nstime_init(&time_mock, 0);
+ nstime_update(&time_mock);
+ monotonic_mock = true;
+
+ nstime_monotonic_orig = nstime_monotonic;
+ nstime_update_orig = nstime_update;
+ nstime_monotonic = nstime_monotonic_mock;
+ nstime_update = nstime_update_mock;
+
+ for (unsigned i = 0; i < NPS; i += 2) {
+ dallocx(ps[i], flags);
+ unsigned nupdates0 = nupdates_mock;
+ do_decay(arena_ind);
+ assert_u_gt(nupdates_mock, nupdates0,
+ "Expected nstime_update() to be called");
+ }
+
+ decay_ticker_helper(arena_ind, flags, true, ddt, dirty_npurge0,
+ muzzy_npurge0, true);
+ decay_ticker_helper(arena_ind, flags, false, ddt+mdt, dirty_npurge0,
+ muzzy_npurge0, false);
+
+ do_arena_destroy(arena_ind);
+
+ nstime_monotonic = nstime_monotonic_orig;
+ nstime_update = nstime_update_orig;
+#undef NPS
+}
+TEST_END
+
+TEST_BEGIN(test_decay_nonmonotonic) {
+ test_skip_if(check_background_thread_enabled());
+#define NPS (SMOOTHSTEP_NSTEPS + 1)
+ int flags = (MALLOCX_ARENA(0) | MALLOCX_TCACHE_NONE);
+ void *ps[NPS];
+ uint64_t npurge0 = 0;
+ uint64_t npurge1 = 0;
+ size_t sz, large0;
+ unsigned i, nupdates0;
+
+ sz = sizeof(size_t);
+ assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&large0, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure");
+
+ assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctl failure");
+ do_epoch();
+ sz = sizeof(uint64_t);
+ npurge0 = get_arena_npurge(0);
+
+ nupdates_mock = 0;
+ nstime_init(&time_mock, 0);
+ nstime_update(&time_mock);
+ monotonic_mock = false;
+
+ nstime_monotonic_orig = nstime_monotonic;
+ nstime_update_orig = nstime_update;
+ nstime_monotonic = nstime_monotonic_mock;
+ nstime_update = nstime_update_mock;
+
+ for (i = 0; i < NPS; i++) {
+ ps[i] = mallocx(large0, flags);
+ assert_ptr_not_null(ps[i], "Unexpected mallocx() failure");
+ }
+
+ for (i = 0; i < NPS; i++) {
+ dallocx(ps[i], flags);
+ nupdates0 = nupdates_mock;
+ assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
+ "Unexpected arena.0.decay failure");
+ assert_u_gt(nupdates_mock, nupdates0,
+ "Expected nstime_update() to be called");
+ }
+
+ do_epoch();
+ sz = sizeof(uint64_t);
+ npurge1 = get_arena_npurge(0);
+
+ if (config_stats) {
+ assert_u64_eq(npurge0, npurge1, "Unexpected purging occurred");
+ }
+
+ nstime_monotonic = nstime_monotonic_orig;
+ nstime_update = nstime_update_orig;
+#undef NPS
+}
+TEST_END
+
+TEST_BEGIN(test_decay_now) {
+ test_skip_if(check_background_thread_enabled());
+
+ unsigned arena_ind = do_arena_create(0, 0);
+ assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
+ assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
+ size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
+ /* Verify that dirty/muzzy pages never linger after deallocation. */
+ for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
+ size_t size = sizes[i];
+ generate_dirty(arena_ind, size);
+ assert_zu_eq(get_arena_pdirty(arena_ind), 0,
+ "Unexpected dirty pages");
+ assert_zu_eq(get_arena_pmuzzy(arena_ind), 0,
+ "Unexpected muzzy pages");
+ }
+ do_arena_destroy(arena_ind);
+}
+TEST_END
+
+TEST_BEGIN(test_decay_never) {
+ test_skip_if(check_background_thread_enabled());
+
+ unsigned arena_ind = do_arena_create(-1, -1);
+ int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
+ assert_zu_eq(get_arena_pdirty(arena_ind), 0, "Unexpected dirty pages");
+ assert_zu_eq(get_arena_pmuzzy(arena_ind), 0, "Unexpected muzzy pages");
+ size_t sizes[] = {16, PAGE<<2, HUGEPAGE<<2};
+ void *ptrs[sizeof(sizes)/sizeof(size_t)];
+ for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
+ ptrs[i] = do_mallocx(sizes[i], flags);
+ }
+ /* Verify that each deallocation generates additional dirty pages. */
+ size_t pdirty_prev = get_arena_pdirty(arena_ind);
+ size_t pmuzzy_prev = get_arena_pmuzzy(arena_ind);
+ assert_zu_eq(pdirty_prev, 0, "Unexpected dirty pages");
+ assert_zu_eq(pmuzzy_prev, 0, "Unexpected muzzy pages");
+ for (unsigned i = 0; i < sizeof(sizes)/sizeof(size_t); i++) {
+ dallocx(ptrs[i], flags);
+ size_t pdirty = get_arena_pdirty(arena_ind);
+ size_t pmuzzy = get_arena_pmuzzy(arena_ind);
+ assert_zu_gt(pdirty, pdirty_prev,
+ "Expected dirty pages to increase.");
+ assert_zu_eq(pmuzzy, 0, "Unexpected muzzy pages");
+ pdirty_prev = pdirty;
+ }
+ do_arena_destroy(arena_ind);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_decay_ticks,
+ test_decay_ticker,
+ test_decay_nonmonotonic,
+ test_decay_now,
+ test_decay_never);
+}
diff --git a/deps/jemalloc/test/unit/decay.sh b/deps/jemalloc/test/unit/decay.sh
new file mode 100644
index 000000000..45aeccf42
--- /dev/null
+++ b/deps/jemalloc/test/unit/decay.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+export MALLOC_CONF="dirty_decay_ms:1000,muzzy_decay_ms:1000,lg_tcache_max:0"
diff --git a/deps/jemalloc/test/unit/div.c b/deps/jemalloc/test/unit/div.c
new file mode 100644
index 000000000..b47f10b2b
--- /dev/null
+++ b/deps/jemalloc/test/unit/div.c
@@ -0,0 +1,29 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/div.h"
+
+TEST_BEGIN(test_div_exhaustive) {
+ for (size_t divisor = 2; divisor < 1000 * 1000; ++divisor) {
+ div_info_t div_info;
+ div_init(&div_info, divisor);
+ size_t max = 1000 * divisor;
+ if (max < 1000 * 1000) {
+ max = 1000 * 1000;
+ }
+ for (size_t dividend = 0; dividend < 1000 * divisor;
+ dividend += divisor) {
+ size_t quotient = div_compute(
+ &div_info, dividend);
+ assert_zu_eq(dividend, quotient * divisor,
+ "With divisor = %zu, dividend = %zu, "
+ "got quotient %zu", divisor, dividend, quotient);
+ }
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_div_exhaustive);
+}
diff --git a/deps/jemalloc/test/unit/emitter.c b/deps/jemalloc/test/unit/emitter.c
new file mode 100644
index 000000000..535c7cf1d
--- /dev/null
+++ b/deps/jemalloc/test/unit/emitter.c
@@ -0,0 +1,413 @@
+#include "test/jemalloc_test.h"
+#include "jemalloc/internal/emitter.h"
+
+/*
+ * This is so useful for debugging and feature work, we'll leave printing
+ * functionality committed but disabled by default.
+ */
+/* Print the text as it will appear. */
+static bool print_raw = false;
+/* Print the text escaped, so it can be copied back into the test case. */
+static bool print_escaped = false;
+
+typedef struct buf_descriptor_s buf_descriptor_t;
+struct buf_descriptor_s {
+ char *buf;
+ size_t len;
+ bool mid_quote;
+};
+
+/*
+ * Forwards all writes to the passed-in buf_v (which should be cast from a
+ * buf_descriptor_t *).
+ */
+static void
+forwarding_cb(void *buf_descriptor_v, const char *str) {
+ buf_descriptor_t *buf_descriptor = (buf_descriptor_t *)buf_descriptor_v;
+
+ if (print_raw) {
+ malloc_printf("%s", str);
+ }
+ if (print_escaped) {
+ const char *it = str;
+ while (*it != '\0') {
+ if (!buf_descriptor->mid_quote) {
+ malloc_printf("\"");
+ buf_descriptor->mid_quote = true;
+ }
+ switch (*it) {
+ case '\\':
+ malloc_printf("\\");
+ break;
+ case '\"':
+ malloc_printf("\\\"");
+ break;
+ case '\t':
+ malloc_printf("\\t");
+ break;
+ case '\n':
+ malloc_printf("\\n\"\n");
+ buf_descriptor->mid_quote = false;
+ break;
+ default:
+ malloc_printf("%c", *it);
+ }
+ it++;
+ }
+ }
+
+ size_t written = malloc_snprintf(buf_descriptor->buf,
+ buf_descriptor->len, "%s", str);
+ assert_zu_eq(written, strlen(str), "Buffer overflow!");
+ buf_descriptor->buf += written;
+ buf_descriptor->len -= written;
+ assert_zu_gt(buf_descriptor->len, 0, "Buffer out of space!");
+}
+
+static void
+assert_emit_output(void (*emit_fn)(emitter_t *),
+ const char *expected_json_output, const char *expected_table_output) {
+ emitter_t emitter;
+ char buf[MALLOC_PRINTF_BUFSIZE];
+ buf_descriptor_t buf_descriptor;
+
+ buf_descriptor.buf = buf;
+ buf_descriptor.len = MALLOC_PRINTF_BUFSIZE;
+ buf_descriptor.mid_quote = false;
+
+ emitter_init(&emitter, emitter_output_json, &forwarding_cb,
+ &buf_descriptor);
+ (*emit_fn)(&emitter);
+ assert_str_eq(expected_json_output, buf, "json output failure");
+
+ buf_descriptor.buf = buf;
+ buf_descriptor.len = MALLOC_PRINTF_BUFSIZE;
+ buf_descriptor.mid_quote = false;
+
+ emitter_init(&emitter, emitter_output_table, &forwarding_cb,
+ &buf_descriptor);
+ (*emit_fn)(&emitter);
+ assert_str_eq(expected_table_output, buf, "table output failure");
+}
+
+static void
+emit_dict(emitter_t *emitter) {
+ bool b_false = false;
+ bool b_true = true;
+ int i_123 = 123;
+ const char *str = "a string";
+
+ emitter_begin(emitter);
+ emitter_dict_begin(emitter, "foo", "This is the foo table:");
+ emitter_kv(emitter, "abc", "ABC", emitter_type_bool, &b_false);
+ emitter_kv(emitter, "def", "DEF", emitter_type_bool, &b_true);
+ emitter_kv_note(emitter, "ghi", "GHI", emitter_type_int, &i_123,
+ "note_key1", emitter_type_string, &str);
+ emitter_kv_note(emitter, "jkl", "JKL", emitter_type_string, &str,
+ "note_key2", emitter_type_bool, &b_false);
+ emitter_dict_end(emitter);
+ emitter_end(emitter);
+}
+static const char *dict_json =
+"{\n"
+"\t\"foo\": {\n"
+"\t\t\"abc\": false,\n"
+"\t\t\"def\": true,\n"
+"\t\t\"ghi\": 123,\n"
+"\t\t\"jkl\": \"a string\"\n"
+"\t}\n"
+"}\n";
+static const char *dict_table =
+"This is the foo table:\n"
+" ABC: false\n"
+" DEF: true\n"
+" GHI: 123 (note_key1: \"a string\")\n"
+" JKL: \"a string\" (note_key2: false)\n";
+
+TEST_BEGIN(test_dict) {
+ assert_emit_output(&emit_dict, dict_json, dict_table);
+}
+TEST_END
+
+static void
+emit_table_printf(emitter_t *emitter) {
+ emitter_begin(emitter);
+ emitter_table_printf(emitter, "Table note 1\n");
+ emitter_table_printf(emitter, "Table note 2 %s\n",
+ "with format string");
+ emitter_end(emitter);
+}
+
+static const char *table_printf_json =
+"{\n"
+"}\n";
+
+static const char *table_printf_table =
+"Table note 1\n"
+"Table note 2 with format string\n";
+
+TEST_BEGIN(test_table_printf) {
+ assert_emit_output(&emit_table_printf, table_printf_json,
+ table_printf_table);
+}
+TEST_END
+
+static void emit_nested_dict(emitter_t *emitter) {
+ int val = 123;
+ emitter_begin(emitter);
+ emitter_dict_begin(emitter, "json1", "Dict 1");
+ emitter_dict_begin(emitter, "json2", "Dict 2");
+ emitter_kv(emitter, "primitive", "A primitive", emitter_type_int, &val);
+ emitter_dict_end(emitter); /* Close 2 */
+ emitter_dict_begin(emitter, "json3", "Dict 3");
+ emitter_dict_end(emitter); /* Close 3 */
+ emitter_dict_end(emitter); /* Close 1 */
+ emitter_dict_begin(emitter, "json4", "Dict 4");
+ emitter_kv(emitter, "primitive", "Another primitive",
+ emitter_type_int, &val);
+ emitter_dict_end(emitter); /* Close 4 */
+ emitter_end(emitter);
+}
+
+static const char *nested_dict_json =
+"{\n"
+"\t\"json1\": {\n"
+"\t\t\"json2\": {\n"
+"\t\t\t\"primitive\": 123\n"
+"\t\t},\n"
+"\t\t\"json3\": {\n"
+"\t\t}\n"
+"\t},\n"
+"\t\"json4\": {\n"
+"\t\t\"primitive\": 123\n"
+"\t}\n"
+"}\n";
+
+static const char *nested_dict_table =
+"Dict 1\n"
+" Dict 2\n"
+" A primitive: 123\n"
+" Dict 3\n"
+"Dict 4\n"
+" Another primitive: 123\n";
+
+TEST_BEGIN(test_nested_dict) {
+ assert_emit_output(&emit_nested_dict, nested_dict_json,
+ nested_dict_table);
+}
+TEST_END
+
+static void
+emit_types(emitter_t *emitter) {
+ bool b = false;
+ int i = -123;
+ unsigned u = 123;
+ ssize_t zd = -456;
+ size_t zu = 456;
+ const char *str = "string";
+ uint32_t u32 = 789;
+ uint64_t u64 = 10000000000ULL;
+
+ emitter_begin(emitter);
+ emitter_kv(emitter, "k1", "K1", emitter_type_bool, &b);
+ emitter_kv(emitter, "k2", "K2", emitter_type_int, &i);
+ emitter_kv(emitter, "k3", "K3", emitter_type_unsigned, &u);
+ emitter_kv(emitter, "k4", "K4", emitter_type_ssize, &zd);
+ emitter_kv(emitter, "k5", "K5", emitter_type_size, &zu);
+ emitter_kv(emitter, "k6", "K6", emitter_type_string, &str);
+ emitter_kv(emitter, "k7", "K7", emitter_type_uint32, &u32);
+ emitter_kv(emitter, "k8", "K8", emitter_type_uint64, &u64);
+ /*
+ * We don't test the title type, since it's only used for tables. It's
+ * tested in the emitter_table_row tests.
+ */
+ emitter_end(emitter);
+}
+
+static const char *types_json =
+"{\n"
+"\t\"k1\": false,\n"
+"\t\"k2\": -123,\n"
+"\t\"k3\": 123,\n"
+"\t\"k4\": -456,\n"
+"\t\"k5\": 456,\n"
+"\t\"k6\": \"string\",\n"
+"\t\"k7\": 789,\n"
+"\t\"k8\": 10000000000\n"
+"}\n";
+
+static const char *types_table =
+"K1: false\n"
+"K2: -123\n"
+"K3: 123\n"
+"K4: -456\n"
+"K5: 456\n"
+"K6: \"string\"\n"
+"K7: 789\n"
+"K8: 10000000000\n";
+
+TEST_BEGIN(test_types) {
+ assert_emit_output(&emit_types, types_json, types_table);
+}
+TEST_END
+
+static void
+emit_modal(emitter_t *emitter) {
+ int val = 123;
+ emitter_begin(emitter);
+ emitter_dict_begin(emitter, "j0", "T0");
+ emitter_json_dict_begin(emitter, "j1");
+ emitter_kv(emitter, "i1", "I1", emitter_type_int, &val);
+ emitter_json_kv(emitter, "i2", emitter_type_int, &val);
+ emitter_table_kv(emitter, "I3", emitter_type_int, &val);
+ emitter_table_dict_begin(emitter, "T1");
+ emitter_kv(emitter, "i4", "I4", emitter_type_int, &val);
+ emitter_json_dict_end(emitter); /* Close j1 */
+ emitter_kv(emitter, "i5", "I5", emitter_type_int, &val);
+ emitter_table_dict_end(emitter); /* Close T1 */
+ emitter_kv(emitter, "i6", "I6", emitter_type_int, &val);
+ emitter_dict_end(emitter); /* Close j0 / T0 */
+ emitter_end(emitter);
+}
+
+const char *modal_json =
+"{\n"
+"\t\"j0\": {\n"
+"\t\t\"j1\": {\n"
+"\t\t\t\"i1\": 123,\n"
+"\t\t\t\"i2\": 123,\n"
+"\t\t\t\"i4\": 123\n"
+"\t\t},\n"
+"\t\t\"i5\": 123,\n"
+"\t\t\"i6\": 123\n"
+"\t}\n"
+"}\n";
+
+const char *modal_table =
+"T0\n"
+" I1: 123\n"
+" I3: 123\n"
+" T1\n"
+" I4: 123\n"
+" I5: 123\n"
+" I6: 123\n";
+
+TEST_BEGIN(test_modal) {
+ assert_emit_output(&emit_modal, modal_json, modal_table);
+}
+TEST_END
+
+static void
+emit_json_arr(emitter_t *emitter) {
+ int ival = 123;
+
+ emitter_begin(emitter);
+ emitter_json_dict_begin(emitter, "dict");
+ emitter_json_arr_begin(emitter, "arr");
+ emitter_json_arr_obj_begin(emitter);
+ emitter_json_kv(emitter, "foo", emitter_type_int, &ival);
+ emitter_json_arr_obj_end(emitter); /* Close arr[0] */
+ /* arr[1] and arr[2] are primitives. */
+ emitter_json_arr_value(emitter, emitter_type_int, &ival);
+ emitter_json_arr_value(emitter, emitter_type_int, &ival);
+ emitter_json_arr_obj_begin(emitter);
+ emitter_json_kv(emitter, "bar", emitter_type_int, &ival);
+ emitter_json_kv(emitter, "baz", emitter_type_int, &ival);
+ emitter_json_arr_obj_end(emitter); /* Close arr[3]. */
+ emitter_json_arr_end(emitter); /* Close arr. */
+ emitter_json_dict_end(emitter); /* Close dict. */
+ emitter_end(emitter);
+}
+
+static const char *json_arr_json =
+"{\n"
+"\t\"dict\": {\n"
+"\t\t\"arr\": [\n"
+"\t\t\t{\n"
+"\t\t\t\t\"foo\": 123\n"
+"\t\t\t},\n"
+"\t\t\t123,\n"
+"\t\t\t123,\n"
+"\t\t\t{\n"
+"\t\t\t\t\"bar\": 123,\n"
+"\t\t\t\t\"baz\": 123\n"
+"\t\t\t}\n"
+"\t\t]\n"
+"\t}\n"
+"}\n";
+
+static const char *json_arr_table = "";
+
+TEST_BEGIN(test_json_arr) {
+ assert_emit_output(&emit_json_arr, json_arr_json, json_arr_table);
+}
+TEST_END
+
+static void
+emit_table_row(emitter_t *emitter) {
+ emitter_begin(emitter);
+ emitter_row_t row;
+ emitter_col_t abc = {emitter_justify_left, 10, emitter_type_title};
+ abc.str_val = "ABC title";
+ emitter_col_t def = {emitter_justify_right, 15, emitter_type_title};
+ def.str_val = "DEF title";
+ emitter_col_t ghi = {emitter_justify_right, 5, emitter_type_title};
+ ghi.str_val = "GHI";
+
+ emitter_row_init(&row);
+ emitter_col_init(&abc, &row);
+ emitter_col_init(&def, &row);
+ emitter_col_init(&ghi, &row);
+
+ emitter_table_row(emitter, &row);
+
+ abc.type = emitter_type_int;
+ def.type = emitter_type_bool;
+ ghi.type = emitter_type_int;
+
+ abc.int_val = 123;
+ def.bool_val = true;
+ ghi.int_val = 456;
+ emitter_table_row(emitter, &row);
+
+ abc.int_val = 789;
+ def.bool_val = false;
+ ghi.int_val = 1011;
+ emitter_table_row(emitter, &row);
+
+ abc.type = emitter_type_string;
+ abc.str_val = "a string";
+ def.bool_val = false;
+ ghi.type = emitter_type_title;
+ ghi.str_val = "ghi";
+ emitter_table_row(emitter, &row);
+
+ emitter_end(emitter);
+}
+
+static const char *table_row_json =
+"{\n"
+"}\n";
+
+static const char *table_row_table =
+"ABC title DEF title GHI\n"
+"123 true 456\n"
+"789 false 1011\n"
+"\"a string\" false ghi\n";
+
+TEST_BEGIN(test_table_row) {
+ assert_emit_output(&emit_table_row, table_row_json, table_row_table);
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_dict,
+ test_table_printf,
+ test_nested_dict,
+ test_types,
+ test_modal,
+ test_json_arr,
+ test_table_row);
+}
diff --git a/deps/jemalloc/test/unit/extent_quantize.c b/deps/jemalloc/test/unit/extent_quantize.c
new file mode 100644
index 000000000..0ca7a75d9
--- /dev/null
+++ b/deps/jemalloc/test/unit/extent_quantize.c
@@ -0,0 +1,141 @@
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_small_extent_size) {
+ unsigned nbins, i;
+ size_t sz, extent_size;
+ size_t mib[4];
+ size_t miblen = sizeof(mib) / sizeof(size_t);
+
+ /*
+ * Iterate over all small size classes, get their extent sizes, and
+ * verify that the quantized size is the same as the extent size.
+ */
+
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.nbins", (void *)&nbins, &sz, NULL, 0), 0,
+ "Unexpected mallctl failure");
+
+ assert_d_eq(mallctlnametomib("arenas.bin.0.slab_size", mib, &miblen), 0,
+ "Unexpected mallctlnametomib failure");
+ for (i = 0; i < nbins; i++) {
+ mib[2] = i;
+ sz = sizeof(size_t);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&extent_size, &sz,
+ NULL, 0), 0, "Unexpected mallctlbymib failure");
+ assert_zu_eq(extent_size,
+ extent_size_quantize_floor(extent_size),
+ "Small extent quantization should be a no-op "
+ "(extent_size=%zu)", extent_size);
+ assert_zu_eq(extent_size,
+ extent_size_quantize_ceil(extent_size),
+ "Small extent quantization should be a no-op "
+ "(extent_size=%zu)", extent_size);
+ }
+}
+TEST_END
+
+TEST_BEGIN(test_large_extent_size) {
+ bool cache_oblivious;
+ unsigned nlextents, i;
+ size_t sz, extent_size_prev, ceil_prev;
+ size_t mib[4];
+ size_t miblen = sizeof(mib) / sizeof(size_t);
+
+ /*
+ * Iterate over all large size classes, get their extent sizes, and
+ * verify that the quantized size is the same as the extent size.
+ */
+
+ sz = sizeof(bool);
+ assert_d_eq(mallctl("config.cache_oblivious", (void *)&cache_oblivious,
+ &sz, NULL, 0), 0, "Unexpected mallctl failure");
+
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL,
+ 0), 0, "Unexpected mallctl failure");
+
+ assert_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0,
+ "Unexpected mallctlnametomib failure");
+ for (i = 0; i < nlextents; i++) {
+ size_t lextent_size, extent_size, floor, ceil;
+
+ mib[2] = i;
+ sz = sizeof(size_t);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&lextent_size,
+ &sz, NULL, 0), 0, "Unexpected mallctlbymib failure");
+ extent_size = cache_oblivious ? lextent_size + PAGE :
+ lextent_size;
+ floor = extent_size_quantize_floor(extent_size);
+ ceil = extent_size_quantize_ceil(extent_size);
+
+ assert_zu_eq(extent_size, floor,
+ "Extent quantization should be a no-op for precise size "
+ "(lextent_size=%zu, extent_size=%zu)", lextent_size,
+ extent_size);
+ assert_zu_eq(extent_size, ceil,
+ "Extent quantization should be a no-op for precise size "
+ "(lextent_size=%zu, extent_size=%zu)", lextent_size,
+ extent_size);
+
+ if (i > 0) {
+ assert_zu_eq(extent_size_prev,
+ extent_size_quantize_floor(extent_size - PAGE),
+ "Floor should be a precise size");
+ if (extent_size_prev < ceil_prev) {
+ assert_zu_eq(ceil_prev, extent_size,
+ "Ceiling should be a precise size "
+ "(extent_size_prev=%zu, ceil_prev=%zu, "
+ "extent_size=%zu)", extent_size_prev,
+ ceil_prev, extent_size);
+ }
+ }
+ if (i + 1 < nlextents) {
+ extent_size_prev = floor;
+ ceil_prev = extent_size_quantize_ceil(extent_size +
+ PAGE);
+ }
+ }
+}
+TEST_END
+
+TEST_BEGIN(test_monotonic) {
+#define SZ_MAX ZU(4 * 1024 * 1024)
+ unsigned i;
+ size_t floor_prev, ceil_prev;
+
+ floor_prev = 0;
+ ceil_prev = 0;
+ for (i = 1; i <= SZ_MAX >> LG_PAGE; i++) {
+ size_t extent_size, floor, ceil;
+
+ extent_size = i << LG_PAGE;
+ floor = extent_size_quantize_floor(extent_size);
+ ceil = extent_size_quantize_ceil(extent_size);
+
+ assert_zu_le(floor, extent_size,
+ "Floor should be <= (floor=%zu, extent_size=%zu, ceil=%zu)",
+ floor, extent_size, ceil);
+ assert_zu_ge(ceil, extent_size,
+ "Ceiling should be >= (floor=%zu, extent_size=%zu, "
+ "ceil=%zu)", floor, extent_size, ceil);
+
+ assert_zu_le(floor_prev, floor, "Floor should be monotonic "
+ "(floor_prev=%zu, floor=%zu, extent_size=%zu, ceil=%zu)",
+ floor_prev, floor, extent_size, ceil);
+ assert_zu_le(ceil_prev, ceil, "Ceiling should be monotonic "
+ "(floor=%zu, extent_size=%zu, ceil_prev=%zu, ceil=%zu)",
+ floor, extent_size, ceil_prev, ceil);
+
+ floor_prev = floor;
+ ceil_prev = ceil;
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_small_extent_size,
+ test_large_extent_size,
+ test_monotonic);
+}
diff --git a/deps/jemalloc/test/unit/fork.c b/deps/jemalloc/test/unit/fork.c
new file mode 100644
index 000000000..b1690750a
--- /dev/null
+++ b/deps/jemalloc/test/unit/fork.c
@@ -0,0 +1,141 @@
+#include "test/jemalloc_test.h"
+
+#ifndef _WIN32
+#include <sys/wait.h>
+#endif
+
+#ifndef _WIN32
+static void
+wait_for_child_exit(int pid) {
+ int status;
+ while (true) {
+ if (waitpid(pid, &status, 0) == -1) {
+ test_fail("Unexpected waitpid() failure.");
+ }
+ if (WIFSIGNALED(status)) {
+ test_fail("Unexpected child termination due to "
+ "signal %d", WTERMSIG(status));
+ break;
+ }
+ if (WIFEXITED(status)) {
+ if (WEXITSTATUS(status) != 0) {
+ test_fail("Unexpected child exit value %d",
+ WEXITSTATUS(status));
+ }
+ break;
+ }
+ }
+}
+#endif
+
+TEST_BEGIN(test_fork) {
+#ifndef _WIN32
+ void *p;
+ pid_t pid;
+
+ /* Set up a manually managed arena for test. */
+ unsigned arena_ind;
+ size_t sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+
+ /* Migrate to the new arena. */
+ unsigned old_arena_ind;
+ sz = sizeof(old_arena_ind);
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz,
+ (void *)&arena_ind, sizeof(arena_ind)), 0,
+ "Unexpected mallctl() failure");
+
+ p = malloc(1);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
+
+ pid = fork();
+
+ free(p);
+
+ p = malloc(64);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
+ free(p);
+
+ if (pid == -1) {
+ /* Error. */
+ test_fail("Unexpected fork() failure");
+ } else if (pid == 0) {
+ /* Child. */
+ _exit(0);
+ } else {
+ wait_for_child_exit(pid);
+ }
+#else
+ test_skip("fork(2) is irrelevant to Windows");
+#endif
+}
+TEST_END
+
+#ifndef _WIN32
+static void *
+do_fork_thd(void *arg) {
+ malloc(1);
+ int pid = fork();
+ if (pid == -1) {
+ /* Error. */
+ test_fail("Unexpected fork() failure");
+ } else if (pid == 0) {
+ /* Child. */
+ char *args[] = {"true", NULL};
+ execvp(args[0], args);
+ test_fail("Exec failed");
+ } else {
+ /* Parent */
+ wait_for_child_exit(pid);
+ }
+ return NULL;
+}
+#endif
+
+#ifndef _WIN32
+static void
+do_test_fork_multithreaded() {
+ thd_t child;
+ thd_create(&child, do_fork_thd, NULL);
+ do_fork_thd(NULL);
+ thd_join(child, NULL);
+}
+#endif
+
+TEST_BEGIN(test_fork_multithreaded) {
+#ifndef _WIN32
+ /*
+ * We've seen bugs involving hanging on arenas_lock (though the same
+ * class of bugs can happen on any mutex). The bugs are intermittent
+ * though, so we want to run the test multiple times. Since we hold the
+ * arenas lock only early in the process lifetime, we can't just run
+ * this test in a loop (since, after all the arenas are initialized, we
+ * won't acquire arenas_lock any further). We therefore repeat the test
+ * with multiple processes.
+ */
+ for (int i = 0; i < 100; i++) {
+ int pid = fork();
+ if (pid == -1) {
+ /* Error. */
+ test_fail("Unexpected fork() failure,");
+ } else if (pid == 0) {
+ /* Child. */
+ do_test_fork_multithreaded();
+ _exit(0);
+ } else {
+ wait_for_child_exit(pid);
+ }
+ }
+#else
+ test_skip("fork(2) is irrelevant to Windows");
+#endif
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_fork,
+ test_fork_multithreaded);
+}
diff --git a/deps/jemalloc/test/unit/hash.c b/deps/jemalloc/test/unit/hash.c
index 77a8cede9..7cc034f8d 100644
--- a/deps/jemalloc/test/unit/hash.c
+++ b/deps/jemalloc/test/unit/hash.c
@@ -28,6 +28,7 @@
*/
#include "test/jemalloc_test.h"
+#include "jemalloc/internal/hash.h"
typedef enum {
hash_variant_x86_32,
@@ -35,43 +36,39 @@ typedef enum {
hash_variant_x64_128
} hash_variant_t;
-static size_t
-hash_variant_bits(hash_variant_t variant)
-{
-
+static int
+hash_variant_bits(hash_variant_t variant) {
switch (variant) {
- case hash_variant_x86_32: return (32);
- case hash_variant_x86_128: return (128);
- case hash_variant_x64_128: return (128);
+ case hash_variant_x86_32: return 32;
+ case hash_variant_x86_128: return 128;
+ case hash_variant_x64_128: return 128;
default: not_reached();
}
}
static const char *
-hash_variant_string(hash_variant_t variant)
-{
-
+hash_variant_string(hash_variant_t variant) {
switch (variant) {
- case hash_variant_x86_32: return ("hash_x86_32");
- case hash_variant_x86_128: return ("hash_x86_128");
- case hash_variant_x64_128: return ("hash_x64_128");
+ case hash_variant_x86_32: return "hash_x86_32";
+ case hash_variant_x86_128: return "hash_x86_128";
+ case hash_variant_x64_128: return "hash_x64_128";
default: not_reached();
}
}
+#define KEY_SIZE 256
static void
-hash_variant_verify(hash_variant_t variant)
-{
- const size_t hashbytes = hash_variant_bits(variant) / 8;
- uint8_t key[256];
- VARIABLE_ARRAY(uint8_t, hashes, hashbytes * 256);
+hash_variant_verify_key(hash_variant_t variant, uint8_t *key) {
+ const int hashbytes = hash_variant_bits(variant) / 8;
+ const int hashes_size = hashbytes * 256;
+ VARIABLE_ARRAY(uint8_t, hashes, hashes_size);
VARIABLE_ARRAY(uint8_t, final, hashbytes);
unsigned i;
uint32_t computed, expected;
- memset(key, 0, sizeof(key));
- memset(hashes, 0, sizeof(hashes));
- memset(final, 0, sizeof(final));
+ memset(key, 0, KEY_SIZE);
+ memset(hashes, 0, hashes_size);
+ memset(final, 0, hashbytes);
/*
* Hash keys of the form {0}, {0,1}, {0,1,2}, ..., {0,1,...,255} as the
@@ -102,17 +99,17 @@ hash_variant_verify(hash_variant_t variant)
/* Hash the result array. */
switch (variant) {
case hash_variant_x86_32: {
- uint32_t out = hash_x86_32(hashes, hashbytes*256, 0);
+ uint32_t out = hash_x86_32(hashes, hashes_size, 0);
memcpy(final, &out, sizeof(out));
break;
} case hash_variant_x86_128: {
uint64_t out[2];
- hash_x86_128(hashes, hashbytes*256, 0, out);
+ hash_x86_128(hashes, hashes_size, 0, out);
memcpy(final, out, sizeof(out));
break;
} case hash_variant_x64_128: {
uint64_t out[2];
- hash_x64_128(hashes, hashbytes*256, 0, out);
+ hash_x64_128(hashes, hashes_size, 0, out);
memcpy(final, out, sizeof(out));
break;
} default: not_reached();
@@ -139,33 +136,38 @@ hash_variant_verify(hash_variant_t variant)
hash_variant_string(variant), expected, computed);
}
-TEST_BEGIN(test_hash_x86_32)
-{
+static void
+hash_variant_verify(hash_variant_t variant) {
+#define MAX_ALIGN 16
+ uint8_t key[KEY_SIZE + (MAX_ALIGN - 1)];
+ unsigned i;
+
+ for (i = 0; i < MAX_ALIGN; i++) {
+ hash_variant_verify_key(variant, &key[i]);
+ }
+#undef MAX_ALIGN
+}
+#undef KEY_SIZE
+TEST_BEGIN(test_hash_x86_32) {
hash_variant_verify(hash_variant_x86_32);
}
TEST_END
-TEST_BEGIN(test_hash_x86_128)
-{
-
+TEST_BEGIN(test_hash_x86_128) {
hash_variant_verify(hash_variant_x86_128);
}
TEST_END
-TEST_BEGIN(test_hash_x64_128)
-{
-
+TEST_BEGIN(test_hash_x64_128) {
hash_variant_verify(hash_variant_x64_128);
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_hash_x86_32,
test_hash_x86_128,
- test_hash_x64_128));
+ test_hash_x64_128);
}
diff --git a/deps/jemalloc/test/unit/hooks.c b/deps/jemalloc/test/unit/hooks.c
new file mode 100644
index 000000000..b70172e13
--- /dev/null
+++ b/deps/jemalloc/test/unit/hooks.c
@@ -0,0 +1,38 @@
+#include "test/jemalloc_test.h"
+
+static bool hook_called = false;
+
+static void
+hook() {
+ hook_called = true;
+}
+
+static int
+func_to_hook(int arg1, int arg2) {
+ return arg1 + arg2;
+}
+
+#define func_to_hook JEMALLOC_HOOK(func_to_hook, hooks_libc_hook)
+
+TEST_BEGIN(unhooked_call) {
+ hooks_libc_hook = NULL;
+ hook_called = false;
+ assert_d_eq(3, func_to_hook(1, 2), "Hooking changed return value.");
+ assert_false(hook_called, "Nulling out hook didn't take.");
+}
+TEST_END
+
+TEST_BEGIN(hooked_call) {
+ hooks_libc_hook = &hook;
+ hook_called = false;
+ assert_d_eq(3, func_to_hook(1, 2), "Hooking changed return value.");
+ assert_true(hook_called, "Hook should have executed.");
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ unhooked_call,
+ hooked_call);
+}
diff --git a/deps/jemalloc/test/unit/junk.c b/deps/jemalloc/test/unit/junk.c
index b23dd1e95..243ced41e 100644
--- a/deps/jemalloc/test/unit/junk.c
+++ b/deps/jemalloc/test/unit/junk.c
@@ -1,104 +1,89 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_FILL
-# ifndef JEMALLOC_TEST_JUNK_OPT
-# define JEMALLOC_TEST_JUNK_OPT "junk:true"
-# endif
-const char *malloc_conf =
- "abort:false,zero:false,redzone:true,quarantine:0," JEMALLOC_TEST_JUNK_OPT;
-#endif
+#include "jemalloc/internal/util.h"
static arena_dalloc_junk_small_t *arena_dalloc_junk_small_orig;
-static arena_dalloc_junk_large_t *arena_dalloc_junk_large_orig;
-static huge_dalloc_junk_t *huge_dalloc_junk_orig;
+static large_dalloc_junk_t *large_dalloc_junk_orig;
+static large_dalloc_maybe_junk_t *large_dalloc_maybe_junk_orig;
static void *watch_for_junking;
static bool saw_junking;
static void
-watch_junking(void *p)
-{
-
+watch_junking(void *p) {
watch_for_junking = p;
saw_junking = false;
}
static void
-arena_dalloc_junk_small_intercept(void *ptr, arena_bin_info_t *bin_info)
-{
+arena_dalloc_junk_small_intercept(void *ptr, const bin_info_t *bin_info) {
size_t i;
arena_dalloc_junk_small_orig(ptr, bin_info);
for (i = 0; i < bin_info->reg_size; i++) {
- assert_c_eq(((char *)ptr)[i], 0x5a,
+ assert_u_eq(((uint8_t *)ptr)[i], JEMALLOC_FREE_JUNK,
"Missing junk fill for byte %zu/%zu of deallocated region",
i, bin_info->reg_size);
}
- if (ptr == watch_for_junking)
+ if (ptr == watch_for_junking) {
saw_junking = true;
+ }
}
static void
-arena_dalloc_junk_large_intercept(void *ptr, size_t usize)
-{
+large_dalloc_junk_intercept(void *ptr, size_t usize) {
size_t i;
- arena_dalloc_junk_large_orig(ptr, usize);
+ large_dalloc_junk_orig(ptr, usize);
for (i = 0; i < usize; i++) {
- assert_c_eq(((char *)ptr)[i], 0x5a,
+ assert_u_eq(((uint8_t *)ptr)[i], JEMALLOC_FREE_JUNK,
"Missing junk fill for byte %zu/%zu of deallocated region",
i, usize);
}
- if (ptr == watch_for_junking)
+ if (ptr == watch_for_junking) {
saw_junking = true;
+ }
}
static void
-huge_dalloc_junk_intercept(void *ptr, size_t usize)
-{
-
- huge_dalloc_junk_orig(ptr, usize);
- /*
- * The conditions under which junk filling actually occurs are nuanced
- * enough that it doesn't make sense to duplicate the decision logic in
- * test code, so don't actually check that the region is junk-filled.
- */
- if (ptr == watch_for_junking)
+large_dalloc_maybe_junk_intercept(void *ptr, size_t usize) {
+ large_dalloc_maybe_junk_orig(ptr, usize);
+ if (ptr == watch_for_junking) {
saw_junking = true;
+ }
}
static void
-test_junk(size_t sz_min, size_t sz_max)
-{
- char *s;
+test_junk(size_t sz_min, size_t sz_max) {
+ uint8_t *s;
size_t sz_prev, sz, i;
if (opt_junk_free) {
arena_dalloc_junk_small_orig = arena_dalloc_junk_small;
arena_dalloc_junk_small = arena_dalloc_junk_small_intercept;
- arena_dalloc_junk_large_orig = arena_dalloc_junk_large;
- arena_dalloc_junk_large = arena_dalloc_junk_large_intercept;
- huge_dalloc_junk_orig = huge_dalloc_junk;
- huge_dalloc_junk = huge_dalloc_junk_intercept;
+ large_dalloc_junk_orig = large_dalloc_junk;
+ large_dalloc_junk = large_dalloc_junk_intercept;
+ large_dalloc_maybe_junk_orig = large_dalloc_maybe_junk;
+ large_dalloc_maybe_junk = large_dalloc_maybe_junk_intercept;
}
sz_prev = 0;
- s = (char *)mallocx(sz_min, 0);
+ s = (uint8_t *)mallocx(sz_min, 0);
assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
for (sz = sallocx(s, 0); sz <= sz_max;
sz_prev = sz, sz = sallocx(s, 0)) {
if (sz_prev > 0) {
- assert_c_eq(s[0], 'a',
+ assert_u_eq(s[0], 'a',
"Previously allocated byte %zu/%zu is corrupted",
ZU(0), sz_prev);
- assert_c_eq(s[sz_prev-1], 'a',
+ assert_u_eq(s[sz_prev-1], 'a',
"Previously allocated byte %zu/%zu is corrupted",
sz_prev-1, sz_prev);
}
for (i = sz_prev; i < sz; i++) {
if (opt_junk_alloc) {
- assert_c_eq(s[i], 0xa5,
+ assert_u_eq(s[i], JEMALLOC_ALLOC_JUNK,
"Newly allocated byte %zu/%zu isn't "
"junk-filled", i, sz);
}
@@ -106,13 +91,21 @@ test_junk(size_t sz_min, size_t sz_max)
}
if (xallocx(s, sz+1, 0, 0) == sz) {
+ uint8_t *t;
watch_junking(s);
- s = (char *)rallocx(s, sz+1, 0);
- assert_ptr_not_null((void *)s,
+ t = (uint8_t *)rallocx(s, sz+1, 0);
+ assert_ptr_not_null((void *)t,
"Unexpected rallocx() failure");
- assert_true(!opt_junk_free || saw_junking,
- "Expected region of size %zu to be junk-filled",
- sz);
+ assert_zu_ge(sallocx(t, 0), sz+1,
+ "Unexpectedly small rallocx() result");
+ if (!background_thread_enabled()) {
+ assert_ptr_ne(s, t,
+ "Unexpected in-place rallocx()");
+ assert_true(!opt_junk_free || saw_junking,
+ "Expected region of size %zu to be "
+ "junk-filled", sz);
+ }
+ s = t;
}
}
@@ -123,132 +116,26 @@ test_junk(size_t sz_min, size_t sz_max)
if (opt_junk_free) {
arena_dalloc_junk_small = arena_dalloc_junk_small_orig;
- arena_dalloc_junk_large = arena_dalloc_junk_large_orig;
- huge_dalloc_junk = huge_dalloc_junk_orig;
+ large_dalloc_junk = large_dalloc_junk_orig;
+ large_dalloc_maybe_junk = large_dalloc_maybe_junk_orig;
}
}
-TEST_BEGIN(test_junk_small)
-{
-
+TEST_BEGIN(test_junk_small) {
test_skip_if(!config_fill);
test_junk(1, SMALL_MAXCLASS-1);
}
TEST_END
-TEST_BEGIN(test_junk_large)
-{
-
- test_skip_if(!config_fill);
- test_junk(SMALL_MAXCLASS+1, large_maxclass);
-}
-TEST_END
-
-TEST_BEGIN(test_junk_huge)
-{
-
- test_skip_if(!config_fill);
- test_junk(large_maxclass+1, chunksize*2);
-}
-TEST_END
-
-arena_ralloc_junk_large_t *arena_ralloc_junk_large_orig;
-static void *most_recently_trimmed;
-
-static size_t
-shrink_size(size_t size)
-{
- size_t shrink_size;
-
- for (shrink_size = size - 1; nallocx(shrink_size, 0) == size;
- shrink_size--)
- ; /* Do nothing. */
-
- return (shrink_size);
-}
-
-static void
-arena_ralloc_junk_large_intercept(void *ptr, size_t old_usize, size_t usize)
-{
-
- arena_ralloc_junk_large_orig(ptr, old_usize, usize);
- assert_zu_eq(old_usize, large_maxclass, "Unexpected old_usize");
- assert_zu_eq(usize, shrink_size(large_maxclass), "Unexpected usize");
- most_recently_trimmed = ptr;
-}
-
-TEST_BEGIN(test_junk_large_ralloc_shrink)
-{
- void *p1, *p2;
-
- p1 = mallocx(large_maxclass, 0);
- assert_ptr_not_null(p1, "Unexpected mallocx() failure");
-
- arena_ralloc_junk_large_orig = arena_ralloc_junk_large;
- arena_ralloc_junk_large = arena_ralloc_junk_large_intercept;
-
- p2 = rallocx(p1, shrink_size(large_maxclass), 0);
- assert_ptr_eq(p1, p2, "Unexpected move during shrink");
-
- arena_ralloc_junk_large = arena_ralloc_junk_large_orig;
-
- assert_ptr_eq(most_recently_trimmed, p1,
- "Expected trimmed portion of region to be junk-filled");
-}
-TEST_END
-
-static bool detected_redzone_corruption;
-
-static void
-arena_redzone_corruption_replacement(void *ptr, size_t usize, bool after,
- size_t offset, uint8_t byte)
-{
-
- detected_redzone_corruption = true;
-}
-
-TEST_BEGIN(test_junk_redzone)
-{
- char *s;
- arena_redzone_corruption_t *arena_redzone_corruption_orig;
-
+TEST_BEGIN(test_junk_large) {
test_skip_if(!config_fill);
- test_skip_if(!opt_junk_alloc || !opt_junk_free);
-
- arena_redzone_corruption_orig = arena_redzone_corruption;
- arena_redzone_corruption = arena_redzone_corruption_replacement;
-
- /* Test underflow. */
- detected_redzone_corruption = false;
- s = (char *)mallocx(1, 0);
- assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
- s[-1] = 0xbb;
- dallocx(s, 0);
- assert_true(detected_redzone_corruption,
- "Did not detect redzone corruption");
-
- /* Test overflow. */
- detected_redzone_corruption = false;
- s = (char *)mallocx(1, 0);
- assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
- s[sallocx(s, 0)] = 0xbb;
- dallocx(s, 0);
- assert_true(detected_redzone_corruption,
- "Did not detect redzone corruption");
-
- arena_redzone_corruption = arena_redzone_corruption_orig;
+ test_junk(SMALL_MAXCLASS+1, (1U << (LG_LARGE_MINCLASS+1)));
}
TEST_END
int
-main(void)
-{
-
- assert(!config_fill || opt_junk_alloc || opt_junk_free);
- return (test(
+main(void) {
+ return test(
test_junk_small,
- test_junk_large,
- test_junk_huge,
- test_junk_large_ralloc_shrink,
- test_junk_redzone));
+ test_junk_large);
}
diff --git a/deps/jemalloc/test/unit/junk.sh b/deps/jemalloc/test/unit/junk.sh
new file mode 100644
index 000000000..97cd8ca5e
--- /dev/null
+++ b/deps/jemalloc/test/unit/junk.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="abort:false,zero:false,junk:true"
+fi
diff --git a/deps/jemalloc/test/unit/junk_alloc.c b/deps/jemalloc/test/unit/junk_alloc.c
index 8db3331d2..a442a0ca5 100644
--- a/deps/jemalloc/test/unit/junk_alloc.c
+++ b/deps/jemalloc/test/unit/junk_alloc.c
@@ -1,3 +1 @@
-#define JEMALLOC_TEST_JUNK_OPT "junk:alloc"
#include "junk.c"
-#undef JEMALLOC_TEST_JUNK_OPT
diff --git a/deps/jemalloc/test/unit/junk_alloc.sh b/deps/jemalloc/test/unit/junk_alloc.sh
new file mode 100644
index 000000000..e1008c2e1
--- /dev/null
+++ b/deps/jemalloc/test/unit/junk_alloc.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="abort:false,zero:false,junk:alloc"
+fi
diff --git a/deps/jemalloc/test/unit/junk_free.c b/deps/jemalloc/test/unit/junk_free.c
index 482a61d07..a442a0ca5 100644
--- a/deps/jemalloc/test/unit/junk_free.c
+++ b/deps/jemalloc/test/unit/junk_free.c
@@ -1,3 +1 @@
-#define JEMALLOC_TEST_JUNK_OPT "junk:free"
#include "junk.c"
-#undef JEMALLOC_TEST_JUNK_OPT
diff --git a/deps/jemalloc/test/unit/junk_free.sh b/deps/jemalloc/test/unit/junk_free.sh
new file mode 100644
index 000000000..402196ca6
--- /dev/null
+++ b/deps/jemalloc/test/unit/junk_free.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="abort:false,zero:false,junk:free"
+fi
diff --git a/deps/jemalloc/test/unit/lg_chunk.c b/deps/jemalloc/test/unit/lg_chunk.c
deleted file mode 100644
index 7e5df3814..000000000
--- a/deps/jemalloc/test/unit/lg_chunk.c
+++ /dev/null
@@ -1,26 +0,0 @@
-#include "test/jemalloc_test.h"
-
-/*
- * Make sure that opt.lg_chunk clamping is sufficient. In practice, this test
- * program will fail a debug assertion during initialization and abort (rather
- * than the test soft-failing) if clamping is insufficient.
- */
-const char *malloc_conf = "lg_chunk:0";
-
-TEST_BEGIN(test_lg_chunk_clamp)
-{
- void *p;
-
- p = mallocx(1, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
- dallocx(p, 0);
-}
-TEST_END
-
-int
-main(void)
-{
-
- return (test(
- test_lg_chunk_clamp));
-}
diff --git a/deps/jemalloc/test/unit/log.c b/deps/jemalloc/test/unit/log.c
new file mode 100644
index 000000000..a52bd737d
--- /dev/null
+++ b/deps/jemalloc/test/unit/log.c
@@ -0,0 +1,193 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/log.h"
+
+static void
+expect_no_logging(const char *names) {
+ log_var_t log_l1 = LOG_VAR_INIT("l1");
+ log_var_t log_l2 = LOG_VAR_INIT("l2");
+ log_var_t log_l2_a = LOG_VAR_INIT("l2.a");
+
+ strcpy(log_var_names, names);
+
+ int count = 0;
+
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1)
+ count++;
+ log_do_end(log_l1)
+
+ log_do_begin(log_l2)
+ count++;
+ log_do_end(log_l2)
+
+ log_do_begin(log_l2_a)
+ count++;
+ log_do_end(log_l2_a)
+ }
+ assert_d_eq(count, 0, "Disabled logging not ignored!");
+}
+
+TEST_BEGIN(test_log_disabled) {
+ test_skip_if(!config_log);
+ atomic_store_b(&log_init_done, true, ATOMIC_RELAXED);
+ expect_no_logging("");
+ expect_no_logging("abc");
+ expect_no_logging("a.b.c");
+ expect_no_logging("l12");
+ expect_no_logging("l123|a456|b789");
+ expect_no_logging("|||");
+}
+TEST_END
+
+TEST_BEGIN(test_log_enabled_direct) {
+ test_skip_if(!config_log);
+ atomic_store_b(&log_init_done, true, ATOMIC_RELAXED);
+ log_var_t log_l1 = LOG_VAR_INIT("l1");
+ log_var_t log_l1_a = LOG_VAR_INIT("l1.a");
+ log_var_t log_l2 = LOG_VAR_INIT("l2");
+
+ int count;
+
+ count = 0;
+ strcpy(log_var_names, "l1");
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1)
+ count++;
+ log_do_end(log_l1)
+ }
+ assert_d_eq(count, 10, "Mis-logged!");
+
+ count = 0;
+ strcpy(log_var_names, "l1.a");
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1_a)
+ count++;
+ log_do_end(log_l1_a)
+ }
+ assert_d_eq(count, 10, "Mis-logged!");
+
+ count = 0;
+ strcpy(log_var_names, "l1.a|abc|l2|def");
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1_a)
+ count++;
+ log_do_end(log_l1_a)
+
+ log_do_begin(log_l2)
+ count++;
+ log_do_end(log_l2)
+ }
+ assert_d_eq(count, 20, "Mis-logged!");
+}
+TEST_END
+
+TEST_BEGIN(test_log_enabled_indirect) {
+ test_skip_if(!config_log);
+ atomic_store_b(&log_init_done, true, ATOMIC_RELAXED);
+ strcpy(log_var_names, "l0|l1|abc|l2.b|def");
+
+ /* On. */
+ log_var_t log_l1 = LOG_VAR_INIT("l1");
+ /* Off. */
+ log_var_t log_l1a = LOG_VAR_INIT("l1a");
+ /* On. */
+ log_var_t log_l1_a = LOG_VAR_INIT("l1.a");
+ /* Off. */
+ log_var_t log_l2_a = LOG_VAR_INIT("l2.a");
+ /* On. */
+ log_var_t log_l2_b_a = LOG_VAR_INIT("l2.b.a");
+ /* On. */
+ log_var_t log_l2_b_b = LOG_VAR_INIT("l2.b.b");
+
+ /* 4 are on total, so should sum to 40. */
+ int count = 0;
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1)
+ count++;
+ log_do_end(log_l1)
+
+ log_do_begin(log_l1a)
+ count++;
+ log_do_end(log_l1a)
+
+ log_do_begin(log_l1_a)
+ count++;
+ log_do_end(log_l1_a)
+
+ log_do_begin(log_l2_a)
+ count++;
+ log_do_end(log_l2_a)
+
+ log_do_begin(log_l2_b_a)
+ count++;
+ log_do_end(log_l2_b_a)
+
+ log_do_begin(log_l2_b_b)
+ count++;
+ log_do_end(log_l2_b_b)
+ }
+
+ assert_d_eq(count, 40, "Mis-logged!");
+}
+TEST_END
+
+TEST_BEGIN(test_log_enabled_global) {
+ test_skip_if(!config_log);
+ atomic_store_b(&log_init_done, true, ATOMIC_RELAXED);
+ strcpy(log_var_names, "abc|.|def");
+
+ log_var_t log_l1 = LOG_VAR_INIT("l1");
+ log_var_t log_l2_a_a = LOG_VAR_INIT("l2.a.a");
+
+ int count = 0;
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(log_l1)
+ count++;
+ log_do_end(log_l1)
+
+ log_do_begin(log_l2_a_a)
+ count++;
+ log_do_end(log_l2_a_a)
+ }
+ assert_d_eq(count, 20, "Mis-logged!");
+}
+TEST_END
+
+TEST_BEGIN(test_logs_if_no_init) {
+ test_skip_if(!config_log);
+ atomic_store_b(&log_init_done, false, ATOMIC_RELAXED);
+
+ log_var_t l = LOG_VAR_INIT("definitely.not.enabled");
+
+ int count = 0;
+ for (int i = 0; i < 10; i++) {
+ log_do_begin(l)
+ count++;
+ log_do_end(l)
+ }
+ assert_d_eq(count, 0, "Logging shouldn't happen if not initialized.");
+}
+TEST_END
+
+/*
+ * This really just checks to make sure that this usage compiles; we don't have
+ * any test code to run.
+ */
+TEST_BEGIN(test_log_only_format_string) {
+ if (false) {
+ LOG("log_str", "No arguments follow this format string.");
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_log_disabled,
+ test_log_enabled_direct,
+ test_log_enabled_indirect,
+ test_log_enabled_global,
+ test_logs_if_no_init,
+ test_log_only_format_string);
+}
diff --git a/deps/jemalloc/test/unit/mallctl.c b/deps/jemalloc/test/unit/mallctl.c
index 31e354ca7..1ecbab08e 100644
--- a/deps/jemalloc/test/unit/mallctl.c
+++ b/deps/jemalloc/test/unit/mallctl.c
@@ -1,7 +1,8 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_mallctl_errors)
-{
+#include "jemalloc/internal/util.h"
+
+TEST_BEGIN(test_mallctl_errors) {
uint64_t epoch;
size_t sz;
@@ -12,22 +13,23 @@ TEST_BEGIN(test_mallctl_errors)
EPERM, "mallctl() should return EPERM on attempt to write "
"read-only value");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)-1),
- EINVAL, "mallctl() should return EINVAL for input size mismatch");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)+1),
- EINVAL, "mallctl() should return EINVAL for input size mismatch");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
+ sizeof(epoch)-1), EINVAL,
+ "mallctl() should return EINVAL for input size mismatch");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
+ sizeof(epoch)+1), EINVAL,
+ "mallctl() should return EINVAL for input size mismatch");
sz = sizeof(epoch)-1;
- assert_d_eq(mallctl("epoch", &epoch, &sz, NULL, 0), EINVAL,
+ assert_d_eq(mallctl("epoch", (void *)&epoch, &sz, NULL, 0), EINVAL,
"mallctl() should return EINVAL for output size mismatch");
sz = sizeof(epoch)+1;
- assert_d_eq(mallctl("epoch", &epoch, &sz, NULL, 0), EINVAL,
+ assert_d_eq(mallctl("epoch", (void *)&epoch, &sz, NULL, 0), EINVAL,
"mallctl() should return EINVAL for output size mismatch");
}
TEST_END
-TEST_BEGIN(test_mallctlnametomib_errors)
-{
+TEST_BEGIN(test_mallctlnametomib_errors) {
size_t mib[1];
size_t miblen;
@@ -37,8 +39,7 @@ TEST_BEGIN(test_mallctlnametomib_errors)
}
TEST_END
-TEST_BEGIN(test_mallctlbymib_errors)
-{
+TEST_BEGIN(test_mallctlbymib_errors) {
uint64_t epoch;
size_t sz;
size_t mib[1];
@@ -56,24 +57,25 @@ TEST_BEGIN(test_mallctlbymib_errors)
assert_d_eq(mallctlnametomib("epoch", mib, &miblen), 0,
"Unexpected mallctlnametomib() failure");
- assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &epoch,
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&epoch,
sizeof(epoch)-1), EINVAL,
"mallctlbymib() should return EINVAL for input size mismatch");
- assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &epoch,
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, (void *)&epoch,
sizeof(epoch)+1), EINVAL,
"mallctlbymib() should return EINVAL for input size mismatch");
sz = sizeof(epoch)-1;
- assert_d_eq(mallctlbymib(mib, miblen, &epoch, &sz, NULL, 0), EINVAL,
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&epoch, &sz, NULL, 0),
+ EINVAL,
"mallctlbymib() should return EINVAL for output size mismatch");
sz = sizeof(epoch)+1;
- assert_d_eq(mallctlbymib(mib, miblen, &epoch, &sz, NULL, 0), EINVAL,
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&epoch, &sz, NULL, 0),
+ EINVAL,
"mallctlbymib() should return EINVAL for output size mismatch");
}
TEST_END
-TEST_BEGIN(test_mallctl_read_write)
-{
+TEST_BEGIN(test_mallctl_read_write) {
uint64_t old_epoch, new_epoch;
size_t sz = sizeof(old_epoch);
@@ -83,24 +85,24 @@ TEST_BEGIN(test_mallctl_read_write)
assert_zu_eq(sz, sizeof(old_epoch), "Unexpected output size");
/* Read. */
- assert_d_eq(mallctl("epoch", &old_epoch, &sz, NULL, 0), 0,
+ assert_d_eq(mallctl("epoch", (void *)&old_epoch, &sz, NULL, 0), 0,
"Unexpected mallctl() failure");
assert_zu_eq(sz, sizeof(old_epoch), "Unexpected output size");
/* Write. */
- assert_d_eq(mallctl("epoch", NULL, NULL, &new_epoch, sizeof(new_epoch)),
- 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&new_epoch,
+ sizeof(new_epoch)), 0, "Unexpected mallctl() failure");
assert_zu_eq(sz, sizeof(old_epoch), "Unexpected output size");
/* Read+write. */
- assert_d_eq(mallctl("epoch", &old_epoch, &sz, &new_epoch,
- sizeof(new_epoch)), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", (void *)&old_epoch, &sz,
+ (void *)&new_epoch, sizeof(new_epoch)), 0,
+ "Unexpected mallctl() failure");
assert_zu_eq(sz, sizeof(old_epoch), "Unexpected output size");
}
TEST_END
-TEST_BEGIN(test_mallctlnametomib_short_mib)
-{
+TEST_BEGIN(test_mallctlnametomib_short_mib) {
size_t mib[4];
size_t miblen;
@@ -114,65 +116,65 @@ TEST_BEGIN(test_mallctlnametomib_short_mib)
}
TEST_END
-TEST_BEGIN(test_mallctl_config)
-{
-
-#define TEST_MALLCTL_CONFIG(config) do { \
- bool oldval; \
+TEST_BEGIN(test_mallctl_config) {
+#define TEST_MALLCTL_CONFIG(config, t) do { \
+ t oldval; \
size_t sz = sizeof(oldval); \
- assert_d_eq(mallctl("config."#config, &oldval, &sz, NULL, 0), \
- 0, "Unexpected mallctl() failure"); \
+ assert_d_eq(mallctl("config."#config, (void *)&oldval, &sz, \
+ NULL, 0), 0, "Unexpected mallctl() failure"); \
assert_b_eq(oldval, config_##config, "Incorrect config value"); \
assert_zu_eq(sz, sizeof(oldval), "Unexpected output size"); \
} while (0)
- TEST_MALLCTL_CONFIG(cache_oblivious);
- TEST_MALLCTL_CONFIG(debug);
- TEST_MALLCTL_CONFIG(fill);
- TEST_MALLCTL_CONFIG(lazy_lock);
- TEST_MALLCTL_CONFIG(munmap);
- TEST_MALLCTL_CONFIG(prof);
- TEST_MALLCTL_CONFIG(prof_libgcc);
- TEST_MALLCTL_CONFIG(prof_libunwind);
- TEST_MALLCTL_CONFIG(stats);
- TEST_MALLCTL_CONFIG(tcache);
- TEST_MALLCTL_CONFIG(tls);
- TEST_MALLCTL_CONFIG(utrace);
- TEST_MALLCTL_CONFIG(valgrind);
- TEST_MALLCTL_CONFIG(xmalloc);
+ TEST_MALLCTL_CONFIG(cache_oblivious, bool);
+ TEST_MALLCTL_CONFIG(debug, bool);
+ TEST_MALLCTL_CONFIG(fill, bool);
+ TEST_MALLCTL_CONFIG(lazy_lock, bool);
+ TEST_MALLCTL_CONFIG(malloc_conf, const char *);
+ TEST_MALLCTL_CONFIG(prof, bool);
+ TEST_MALLCTL_CONFIG(prof_libgcc, bool);
+ TEST_MALLCTL_CONFIG(prof_libunwind, bool);
+ TEST_MALLCTL_CONFIG(stats, bool);
+ TEST_MALLCTL_CONFIG(utrace, bool);
+ TEST_MALLCTL_CONFIG(xmalloc, bool);
#undef TEST_MALLCTL_CONFIG
}
TEST_END
-TEST_BEGIN(test_mallctl_opt)
-{
+TEST_BEGIN(test_mallctl_opt) {
bool config_always = true;
-#define TEST_MALLCTL_OPT(t, opt, config) do { \
+#define TEST_MALLCTL_OPT(t, opt, config) do { \
t oldval; \
size_t sz = sizeof(oldval); \
int expected = config_##config ? 0 : ENOENT; \
- int result = mallctl("opt."#opt, &oldval, &sz, NULL, 0); \
+ int result = mallctl("opt."#opt, (void *)&oldval, &sz, NULL, \
+ 0); \
assert_d_eq(result, expected, \
"Unexpected mallctl() result for opt."#opt); \
assert_zu_eq(sz, sizeof(oldval), "Unexpected output size"); \
} while (0)
TEST_MALLCTL_OPT(bool, abort, always);
- TEST_MALLCTL_OPT(size_t, lg_chunk, always);
+ TEST_MALLCTL_OPT(bool, abort_conf, always);
+ TEST_MALLCTL_OPT(const char *, metadata_thp, always);
+ TEST_MALLCTL_OPT(bool, retain, always);
TEST_MALLCTL_OPT(const char *, dss, always);
- TEST_MALLCTL_OPT(size_t, narenas, always);
- TEST_MALLCTL_OPT(ssize_t, lg_dirty_mult, always);
+ TEST_MALLCTL_OPT(unsigned, narenas, always);
+ TEST_MALLCTL_OPT(const char *, percpu_arena, always);
+ TEST_MALLCTL_OPT(bool, background_thread, always);
+ TEST_MALLCTL_OPT(ssize_t, dirty_decay_ms, always);
+ TEST_MALLCTL_OPT(ssize_t, muzzy_decay_ms, always);
TEST_MALLCTL_OPT(bool, stats_print, always);
TEST_MALLCTL_OPT(const char *, junk, fill);
- TEST_MALLCTL_OPT(size_t, quarantine, fill);
- TEST_MALLCTL_OPT(bool, redzone, fill);
TEST_MALLCTL_OPT(bool, zero, fill);
TEST_MALLCTL_OPT(bool, utrace, utrace);
TEST_MALLCTL_OPT(bool, xmalloc, xmalloc);
- TEST_MALLCTL_OPT(bool, tcache, tcache);
- TEST_MALLCTL_OPT(size_t, lg_tcache_max, tcache);
+ TEST_MALLCTL_OPT(bool, tcache, always);
+ TEST_MALLCTL_OPT(size_t, lg_extent_max_active_fit, always);
+ TEST_MALLCTL_OPT(size_t, lg_tcache_max, always);
+ TEST_MALLCTL_OPT(const char *, thp, always);
TEST_MALLCTL_OPT(bool, prof, prof);
TEST_MALLCTL_OPT(const char *, prof_prefix, prof);
TEST_MALLCTL_OPT(bool, prof_active, prof);
@@ -187,14 +189,13 @@ TEST_BEGIN(test_mallctl_opt)
}
TEST_END
-TEST_BEGIN(test_manpage_example)
-{
+TEST_BEGIN(test_manpage_example) {
unsigned nbins, i;
size_t mib[4];
size_t len, miblen;
len = sizeof(nbins);
- assert_d_eq(mallctl("arenas.nbins", &nbins, &len, NULL, 0), 0,
+ assert_d_eq(mallctl("arenas.nbins", (void *)&nbins, &len, NULL, 0), 0,
"Unexpected mallctl() failure");
miblen = 4;
@@ -205,23 +206,20 @@ TEST_BEGIN(test_manpage_example)
mib[2] = i;
len = sizeof(bin_size);
- assert_d_eq(mallctlbymib(mib, miblen, &bin_size, &len, NULL, 0),
- 0, "Unexpected mallctlbymib() failure");
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&bin_size, &len,
+ NULL, 0), 0, "Unexpected mallctlbymib() failure");
/* Do something with bin_size... */
}
}
TEST_END
-TEST_BEGIN(test_tcache_none)
-{
- void *p0, *q, *p1;
-
- test_skip_if(!config_tcache);
+TEST_BEGIN(test_tcache_none) {
+ test_skip_if(!opt_tcache);
/* Allocate p and q. */
- p0 = mallocx(42, 0);
+ void *p0 = mallocx(42, 0);
assert_ptr_not_null(p0, "Unexpected mallocx() failure");
- q = mallocx(42, 0);
+ void *q = mallocx(42, 0);
assert_ptr_not_null(q, "Unexpected mallocx() failure");
/* Deallocate p and q, but bypass the tcache for q. */
@@ -229,7 +227,7 @@ TEST_BEGIN(test_tcache_none)
dallocx(q, MALLOCX_TCACHE_NONE);
/* Make sure that tcache-based allocation returns p, not q. */
- p1 = mallocx(42, 0);
+ void *p1 = mallocx(42, 0);
assert_ptr_not_null(p1, "Unexpected mallocx() failure");
assert_ptr_eq(p0, p1, "Expected tcache to allocate cached region");
@@ -238,42 +236,39 @@ TEST_BEGIN(test_tcache_none)
}
TEST_END
-TEST_BEGIN(test_tcache)
-{
-#define NTCACHES 10
+TEST_BEGIN(test_tcache) {
+#define NTCACHES 10
unsigned tis[NTCACHES];
void *ps[NTCACHES];
void *qs[NTCACHES];
unsigned i;
size_t sz, psz, qsz;
- test_skip_if(!config_tcache);
-
psz = 42;
qsz = nallocx(psz, 0) + 1;
/* Create tcaches. */
for (i = 0; i < NTCACHES; i++) {
sz = sizeof(unsigned);
- assert_d_eq(mallctl("tcache.create", &tis[i], &sz, NULL, 0), 0,
- "Unexpected mallctl() failure, i=%u", i);
+ assert_d_eq(mallctl("tcache.create", (void *)&tis[i], &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure, i=%u", i);
}
/* Exercise tcache ID recycling. */
for (i = 0; i < NTCACHES; i++) {
- assert_d_eq(mallctl("tcache.destroy", NULL, NULL, &tis[i],
- sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u",
- i);
+ assert_d_eq(mallctl("tcache.destroy", NULL, NULL,
+ (void *)&tis[i], sizeof(unsigned)), 0,
+ "Unexpected mallctl() failure, i=%u", i);
}
for (i = 0; i < NTCACHES; i++) {
sz = sizeof(unsigned);
- assert_d_eq(mallctl("tcache.create", &tis[i], &sz, NULL, 0), 0,
- "Unexpected mallctl() failure, i=%u", i);
+ assert_d_eq(mallctl("tcache.create", (void *)&tis[i], &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure, i=%u", i);
}
/* Flush empty tcaches. */
for (i = 0; i < NTCACHES; i++) {
- assert_d_eq(mallctl("tcache.flush", NULL, NULL, &tis[i],
+ assert_d_eq(mallctl("tcache.flush", NULL, NULL, (void *)&tis[i],
sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u",
i);
}
@@ -310,79 +305,169 @@ TEST_BEGIN(test_tcache)
assert_ptr_eq(qs[i], q0,
"Expected rallocx() to allocate cached region, i=%u", i);
/* Avoid undefined behavior in case of test failure. */
- if (qs[i] == NULL)
+ if (qs[i] == NULL) {
qs[i] = ps[i];
+ }
}
- for (i = 0; i < NTCACHES; i++)
+ for (i = 0; i < NTCACHES; i++) {
dallocx(qs[i], MALLOCX_TCACHE(tis[i]));
+ }
/* Flush some non-empty tcaches. */
for (i = 0; i < NTCACHES/2; i++) {
- assert_d_eq(mallctl("tcache.flush", NULL, NULL, &tis[i],
+ assert_d_eq(mallctl("tcache.flush", NULL, NULL, (void *)&tis[i],
sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u",
i);
}
/* Destroy tcaches. */
for (i = 0; i < NTCACHES; i++) {
- assert_d_eq(mallctl("tcache.destroy", NULL, NULL, &tis[i],
- sizeof(unsigned)), 0, "Unexpected mallctl() failure, i=%u",
- i);
+ assert_d_eq(mallctl("tcache.destroy", NULL, NULL,
+ (void *)&tis[i], sizeof(unsigned)), 0,
+ "Unexpected mallctl() failure, i=%u", i);
}
}
TEST_END
-TEST_BEGIN(test_thread_arena)
-{
- unsigned arena_old, arena_new, narenas;
- size_t sz = sizeof(unsigned);
+TEST_BEGIN(test_thread_arena) {
+ unsigned old_arena_ind, new_arena_ind, narenas;
- assert_d_eq(mallctl("arenas.narenas", &narenas, &sz, NULL, 0), 0,
+ const char *opa;
+ size_t sz = sizeof(opa);
+ assert_d_eq(mallctl("opt.percpu_arena", (void *)&opa, &sz, NULL, 0), 0,
"Unexpected mallctl() failure");
+
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
assert_u_eq(narenas, opt_narenas, "Number of arenas incorrect");
- arena_new = narenas - 1;
- assert_d_eq(mallctl("thread.arena", &arena_old, &sz, &arena_new,
- sizeof(unsigned)), 0, "Unexpected mallctl() failure");
- arena_new = 0;
- assert_d_eq(mallctl("thread.arena", &arena_old, &sz, &arena_new,
- sizeof(unsigned)), 0, "Unexpected mallctl() failure");
+
+ if (strcmp(opa, "disabled") == 0) {
+ new_arena_ind = narenas - 1;
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz,
+ (void *)&new_arena_ind, sizeof(unsigned)), 0,
+ "Unexpected mallctl() failure");
+ new_arena_ind = 0;
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz,
+ (void *)&new_arena_ind, sizeof(unsigned)), 0,
+ "Unexpected mallctl() failure");
+ } else {
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz,
+ NULL, 0), 0, "Unexpected mallctl() failure");
+ new_arena_ind = percpu_arena_ind_limit(opt_percpu_arena) - 1;
+ if (old_arena_ind != new_arena_ind) {
+ assert_d_eq(mallctl("thread.arena",
+ (void *)&old_arena_ind, &sz, (void *)&new_arena_ind,
+ sizeof(unsigned)), EPERM, "thread.arena ctl "
+ "should not be allowed with percpu arena");
+ }
+ }
}
TEST_END
-TEST_BEGIN(test_arena_i_lg_dirty_mult)
-{
- ssize_t lg_dirty_mult, orig_lg_dirty_mult, prev_lg_dirty_mult;
+TEST_BEGIN(test_arena_i_initialized) {
+ unsigned narenas, i;
+ size_t sz;
+ size_t mib[3];
+ size_t miblen = sizeof(mib) / sizeof(size_t);
+ bool initialized;
+
+ sz = sizeof(narenas);
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+
+ assert_d_eq(mallctlnametomib("arena.0.initialized", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ for (i = 0; i < narenas; i++) {
+ mib[1] = i;
+ sz = sizeof(initialized);
+ assert_d_eq(mallctlbymib(mib, miblen, &initialized, &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure");
+ }
+
+ mib[1] = MALLCTL_ARENAS_ALL;
+ sz = sizeof(initialized);
+ assert_d_eq(mallctlbymib(mib, miblen, &initialized, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+ assert_true(initialized,
+ "Merged arena statistics should always be initialized");
+
+ /* Equivalent to the above but using mallctl() directly. */
+ sz = sizeof(initialized);
+ assert_d_eq(mallctl(
+ "arena." STRINGIFY(MALLCTL_ARENAS_ALL) ".initialized",
+ (void *)&initialized, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+ assert_true(initialized,
+ "Merged arena statistics should always be initialized");
+}
+TEST_END
+
+TEST_BEGIN(test_arena_i_dirty_decay_ms) {
+ ssize_t dirty_decay_ms, orig_dirty_decay_ms, prev_dirty_decay_ms;
size_t sz = sizeof(ssize_t);
- assert_d_eq(mallctl("arena.0.lg_dirty_mult", &orig_lg_dirty_mult, &sz,
- NULL, 0), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("arena.0.dirty_decay_ms",
+ (void *)&orig_dirty_decay_ms, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
- lg_dirty_mult = -2;
- assert_d_eq(mallctl("arena.0.lg_dirty_mult", NULL, NULL,
- &lg_dirty_mult, sizeof(ssize_t)), EFAULT,
+ dirty_decay_ms = -2;
+ assert_d_eq(mallctl("arena.0.dirty_decay_ms", NULL, NULL,
+ (void *)&dirty_decay_ms, sizeof(ssize_t)), EFAULT,
"Unexpected mallctl() success");
- lg_dirty_mult = (sizeof(size_t) << 3);
- assert_d_eq(mallctl("arena.0.lg_dirty_mult", NULL, NULL,
- &lg_dirty_mult, sizeof(ssize_t)), EFAULT,
+ dirty_decay_ms = 0x7fffffff;
+ assert_d_eq(mallctl("arena.0.dirty_decay_ms", NULL, NULL,
+ (void *)&dirty_decay_ms, sizeof(ssize_t)), 0,
+ "Unexpected mallctl() failure");
+
+ for (prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms = -1;
+ dirty_decay_ms < 20; prev_dirty_decay_ms = dirty_decay_ms,
+ dirty_decay_ms++) {
+ ssize_t old_dirty_decay_ms;
+
+ assert_d_eq(mallctl("arena.0.dirty_decay_ms",
+ (void *)&old_dirty_decay_ms, &sz, (void *)&dirty_decay_ms,
+ sizeof(ssize_t)), 0, "Unexpected mallctl() failure");
+ assert_zd_eq(old_dirty_decay_ms, prev_dirty_decay_ms,
+ "Unexpected old arena.0.dirty_decay_ms");
+ }
+}
+TEST_END
+
+TEST_BEGIN(test_arena_i_muzzy_decay_ms) {
+ ssize_t muzzy_decay_ms, orig_muzzy_decay_ms, prev_muzzy_decay_ms;
+ size_t sz = sizeof(ssize_t);
+
+ assert_d_eq(mallctl("arena.0.muzzy_decay_ms",
+ (void *)&orig_muzzy_decay_ms, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+
+ muzzy_decay_ms = -2;
+ assert_d_eq(mallctl("arena.0.muzzy_decay_ms", NULL, NULL,
+ (void *)&muzzy_decay_ms, sizeof(ssize_t)), EFAULT,
"Unexpected mallctl() success");
- for (prev_lg_dirty_mult = orig_lg_dirty_mult, lg_dirty_mult = -1;
- lg_dirty_mult < (ssize_t)(sizeof(size_t) << 3); prev_lg_dirty_mult
- = lg_dirty_mult, lg_dirty_mult++) {
- ssize_t old_lg_dirty_mult;
+ muzzy_decay_ms = 0x7fffffff;
+ assert_d_eq(mallctl("arena.0.muzzy_decay_ms", NULL, NULL,
+ (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0,
+ "Unexpected mallctl() failure");
- assert_d_eq(mallctl("arena.0.lg_dirty_mult", &old_lg_dirty_mult,
- &sz, &lg_dirty_mult, sizeof(ssize_t)), 0,
- "Unexpected mallctl() failure");
- assert_zd_eq(old_lg_dirty_mult, prev_lg_dirty_mult,
- "Unexpected old arena.0.lg_dirty_mult");
+ for (prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms = -1;
+ muzzy_decay_ms < 20; prev_muzzy_decay_ms = muzzy_decay_ms,
+ muzzy_decay_ms++) {
+ ssize_t old_muzzy_decay_ms;
+
+ assert_d_eq(mallctl("arena.0.muzzy_decay_ms",
+ (void *)&old_muzzy_decay_ms, &sz, (void *)&muzzy_decay_ms,
+ sizeof(ssize_t)), 0, "Unexpected mallctl() failure");
+ assert_zd_eq(old_muzzy_decay_ms, prev_muzzy_decay_ms,
+ "Unexpected old arena.0.muzzy_decay_ms");
}
}
TEST_END
-TEST_BEGIN(test_arena_i_purge)
-{
+TEST_BEGIN(test_arena_i_purge) {
unsigned narenas;
size_t sz = sizeof(unsigned);
size_t mib[3];
@@ -391,18 +476,44 @@ TEST_BEGIN(test_arena_i_purge)
assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
"Unexpected mallctl() failure");
- assert_d_eq(mallctl("arenas.narenas", &narenas, &sz, NULL, 0), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
assert_d_eq(mallctlnametomib("arena.0.purge", mib, &miblen), 0,
"Unexpected mallctlnametomib() failure");
mib[1] = narenas;
assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
"Unexpected mallctlbymib() failure");
+
+ mib[1] = MALLCTL_ARENAS_ALL;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
}
TEST_END
-TEST_BEGIN(test_arena_i_dss)
-{
+TEST_BEGIN(test_arena_i_decay) {
+ unsigned narenas;
+ size_t sz = sizeof(unsigned);
+ size_t mib[3];
+ size_t miblen = 3;
+
+ assert_d_eq(mallctl("arena.0.decay", NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctlnametomib("arena.0.decay", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = narenas;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+
+ mib[1] = MALLCTL_ARENAS_ALL;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+TEST_END
+
+TEST_BEGIN(test_arena_i_dss) {
const char *dss_prec_old, *dss_prec_new;
size_t sz = sizeof(dss_prec_old);
size_t mib[3];
@@ -413,170 +524,213 @@ TEST_BEGIN(test_arena_i_dss)
"Unexpected mallctlnametomib() error");
dss_prec_new = "disabled";
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_old, &sz, &dss_prec_new,
- sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz,
+ (void *)&dss_prec_new, sizeof(dss_prec_new)), 0,
+ "Unexpected mallctl() failure");
assert_str_ne(dss_prec_old, "primary",
"Unexpected default for dss precedence");
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_new, &sz, &dss_prec_old,
- sizeof(dss_prec_old)), 0, "Unexpected mallctl() failure");
-
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_old, &sz, NULL, 0), 0,
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_new, &sz,
+ (void *)&dss_prec_old, sizeof(dss_prec_old)), 0,
"Unexpected mallctl() failure");
+
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure");
assert_str_ne(dss_prec_old, "primary",
"Unexpected value for dss precedence");
mib[1] = narenas_total_get();
dss_prec_new = "disabled";
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_old, &sz, &dss_prec_new,
- sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz,
+ (void *)&dss_prec_new, sizeof(dss_prec_new)), 0,
+ "Unexpected mallctl() failure");
assert_str_ne(dss_prec_old, "primary",
"Unexpected default for dss precedence");
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_new, &sz, &dss_prec_old,
- sizeof(dss_prec_new)), 0, "Unexpected mallctl() failure");
-
- assert_d_eq(mallctlbymib(mib, miblen, &dss_prec_old, &sz, NULL, 0), 0,
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_new, &sz,
+ (void *)&dss_prec_old, sizeof(dss_prec_new)), 0,
"Unexpected mallctl() failure");
+
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&dss_prec_old, &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure");
assert_str_ne(dss_prec_old, "primary",
"Unexpected value for dss precedence");
}
TEST_END
-TEST_BEGIN(test_arenas_initialized)
-{
- unsigned narenas;
- size_t sz = sizeof(narenas);
+TEST_BEGIN(test_arena_i_retain_grow_limit) {
+ size_t old_limit, new_limit, default_limit;
+ size_t mib[3];
+ size_t miblen;
+
+ bool retain_enabled;
+ size_t sz = sizeof(retain_enabled);
+ assert_d_eq(mallctl("opt.retain", &retain_enabled, &sz, NULL, 0),
+ 0, "Unexpected mallctl() failure");
+ test_skip_if(!retain_enabled);
+
+ sz = sizeof(default_limit);
+ miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.retain_grow_limit", mib, &miblen),
+ 0, "Unexpected mallctlnametomib() error");
- assert_d_eq(mallctl("arenas.narenas", &narenas, &sz, NULL, 0), 0,
+ assert_d_eq(mallctlbymib(mib, miblen, &default_limit, &sz, NULL, 0), 0,
"Unexpected mallctl() failure");
- {
- VARIABLE_ARRAY(bool, initialized, narenas);
+ assert_zu_eq(default_limit, sz_pind2sz(EXTENT_GROW_MAX_PIND),
+ "Unexpected default for retain_grow_limit");
- sz = narenas * sizeof(bool);
- assert_d_eq(mallctl("arenas.initialized", initialized, &sz,
- NULL, 0), 0, "Unexpected mallctl() failure");
- }
+ new_limit = PAGE - 1;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit,
+ sizeof(new_limit)), EFAULT, "Unexpected mallctl() success");
+
+ new_limit = PAGE + 1;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit,
+ sizeof(new_limit)), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctlbymib(mib, miblen, &old_limit, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+ assert_zu_eq(old_limit, PAGE,
+ "Unexpected value for retain_grow_limit");
+
+ /* Expect grow less than psize class 10. */
+ new_limit = sz_pind2sz(10) - 1;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &new_limit,
+ sizeof(new_limit)), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctlbymib(mib, miblen, &old_limit, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+ assert_zu_eq(old_limit, sz_pind2sz(9),
+ "Unexpected value for retain_grow_limit");
+
+ /* Restore to default. */
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, &default_limit,
+ sizeof(default_limit)), 0, "Unexpected mallctl() failure");
}
TEST_END
-TEST_BEGIN(test_arenas_lg_dirty_mult)
-{
- ssize_t lg_dirty_mult, orig_lg_dirty_mult, prev_lg_dirty_mult;
+TEST_BEGIN(test_arenas_dirty_decay_ms) {
+ ssize_t dirty_decay_ms, orig_dirty_decay_ms, prev_dirty_decay_ms;
size_t sz = sizeof(ssize_t);
- assert_d_eq(mallctl("arenas.lg_dirty_mult", &orig_lg_dirty_mult, &sz,
- NULL, 0), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("arenas.dirty_decay_ms",
+ (void *)&orig_dirty_decay_ms, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
- lg_dirty_mult = -2;
- assert_d_eq(mallctl("arenas.lg_dirty_mult", NULL, NULL,
- &lg_dirty_mult, sizeof(ssize_t)), EFAULT,
+ dirty_decay_ms = -2;
+ assert_d_eq(mallctl("arenas.dirty_decay_ms", NULL, NULL,
+ (void *)&dirty_decay_ms, sizeof(ssize_t)), EFAULT,
"Unexpected mallctl() success");
- lg_dirty_mult = (sizeof(size_t) << 3);
- assert_d_eq(mallctl("arenas.lg_dirty_mult", NULL, NULL,
- &lg_dirty_mult, sizeof(ssize_t)), EFAULT,
- "Unexpected mallctl() success");
+ dirty_decay_ms = 0x7fffffff;
+ assert_d_eq(mallctl("arenas.dirty_decay_ms", NULL, NULL,
+ (void *)&dirty_decay_ms, sizeof(ssize_t)), 0,
+ "Expected mallctl() failure");
+
+ for (prev_dirty_decay_ms = dirty_decay_ms, dirty_decay_ms = -1;
+ dirty_decay_ms < 20; prev_dirty_decay_ms = dirty_decay_ms,
+ dirty_decay_ms++) {
+ ssize_t old_dirty_decay_ms;
+
+ assert_d_eq(mallctl("arenas.dirty_decay_ms",
+ (void *)&old_dirty_decay_ms, &sz, (void *)&dirty_decay_ms,
+ sizeof(ssize_t)), 0, "Unexpected mallctl() failure");
+ assert_zd_eq(old_dirty_decay_ms, prev_dirty_decay_ms,
+ "Unexpected old arenas.dirty_decay_ms");
+ }
+}
+TEST_END
- for (prev_lg_dirty_mult = orig_lg_dirty_mult, lg_dirty_mult = -1;
- lg_dirty_mult < (ssize_t)(sizeof(size_t) << 3); prev_lg_dirty_mult =
- lg_dirty_mult, lg_dirty_mult++) {
- ssize_t old_lg_dirty_mult;
+TEST_BEGIN(test_arenas_muzzy_decay_ms) {
+ ssize_t muzzy_decay_ms, orig_muzzy_decay_ms, prev_muzzy_decay_ms;
+ size_t sz = sizeof(ssize_t);
- assert_d_eq(mallctl("arenas.lg_dirty_mult", &old_lg_dirty_mult,
- &sz, &lg_dirty_mult, sizeof(ssize_t)), 0,
- "Unexpected mallctl() failure");
- assert_zd_eq(old_lg_dirty_mult, prev_lg_dirty_mult,
- "Unexpected old arenas.lg_dirty_mult");
+ assert_d_eq(mallctl("arenas.muzzy_decay_ms",
+ (void *)&orig_muzzy_decay_ms, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+
+ muzzy_decay_ms = -2;
+ assert_d_eq(mallctl("arenas.muzzy_decay_ms", NULL, NULL,
+ (void *)&muzzy_decay_ms, sizeof(ssize_t)), EFAULT,
+ "Unexpected mallctl() success");
+
+ muzzy_decay_ms = 0x7fffffff;
+ assert_d_eq(mallctl("arenas.muzzy_decay_ms", NULL, NULL,
+ (void *)&muzzy_decay_ms, sizeof(ssize_t)), 0,
+ "Expected mallctl() failure");
+
+ for (prev_muzzy_decay_ms = muzzy_decay_ms, muzzy_decay_ms = -1;
+ muzzy_decay_ms < 20; prev_muzzy_decay_ms = muzzy_decay_ms,
+ muzzy_decay_ms++) {
+ ssize_t old_muzzy_decay_ms;
+
+ assert_d_eq(mallctl("arenas.muzzy_decay_ms",
+ (void *)&old_muzzy_decay_ms, &sz, (void *)&muzzy_decay_ms,
+ sizeof(ssize_t)), 0, "Unexpected mallctl() failure");
+ assert_zd_eq(old_muzzy_decay_ms, prev_muzzy_decay_ms,
+ "Unexpected old arenas.muzzy_decay_ms");
}
}
TEST_END
-TEST_BEGIN(test_arenas_constants)
-{
-
-#define TEST_ARENAS_CONSTANT(t, name, expected) do { \
+TEST_BEGIN(test_arenas_constants) {
+#define TEST_ARENAS_CONSTANT(t, name, expected) do { \
t name; \
size_t sz = sizeof(t); \
- assert_d_eq(mallctl("arenas."#name, &name, &sz, NULL, 0), 0, \
- "Unexpected mallctl() failure"); \
+ assert_d_eq(mallctl("arenas."#name, (void *)&name, &sz, NULL, \
+ 0), 0, "Unexpected mallctl() failure"); \
assert_zu_eq(name, expected, "Incorrect "#name" size"); \
} while (0)
TEST_ARENAS_CONSTANT(size_t, quantum, QUANTUM);
TEST_ARENAS_CONSTANT(size_t, page, PAGE);
TEST_ARENAS_CONSTANT(unsigned, nbins, NBINS);
- TEST_ARENAS_CONSTANT(unsigned, nlruns, nlclasses);
- TEST_ARENAS_CONSTANT(unsigned, nhchunks, nhclasses);
+ TEST_ARENAS_CONSTANT(unsigned, nlextents, NSIZES - NBINS);
#undef TEST_ARENAS_CONSTANT
}
TEST_END
-TEST_BEGIN(test_arenas_bin_constants)
-{
-
-#define TEST_ARENAS_BIN_CONSTANT(t, name, expected) do { \
+TEST_BEGIN(test_arenas_bin_constants) {
+#define TEST_ARENAS_BIN_CONSTANT(t, name, expected) do { \
t name; \
size_t sz = sizeof(t); \
- assert_d_eq(mallctl("arenas.bin.0."#name, &name, &sz, NULL, 0), \
- 0, "Unexpected mallctl() failure"); \
+ assert_d_eq(mallctl("arenas.bin.0."#name, (void *)&name, &sz, \
+ NULL, 0), 0, "Unexpected mallctl() failure"); \
assert_zu_eq(name, expected, "Incorrect "#name" size"); \
} while (0)
- TEST_ARENAS_BIN_CONSTANT(size_t, size, arena_bin_info[0].reg_size);
- TEST_ARENAS_BIN_CONSTANT(uint32_t, nregs, arena_bin_info[0].nregs);
- TEST_ARENAS_BIN_CONSTANT(size_t, run_size, arena_bin_info[0].run_size);
+ TEST_ARENAS_BIN_CONSTANT(size_t, size, bin_infos[0].reg_size);
+ TEST_ARENAS_BIN_CONSTANT(uint32_t, nregs, bin_infos[0].nregs);
+ TEST_ARENAS_BIN_CONSTANT(size_t, slab_size,
+ bin_infos[0].slab_size);
#undef TEST_ARENAS_BIN_CONSTANT
}
TEST_END
-TEST_BEGIN(test_arenas_lrun_constants)
-{
-
-#define TEST_ARENAS_LRUN_CONSTANT(t, name, expected) do { \
+TEST_BEGIN(test_arenas_lextent_constants) {
+#define TEST_ARENAS_LEXTENT_CONSTANT(t, name, expected) do { \
t name; \
size_t sz = sizeof(t); \
- assert_d_eq(mallctl("arenas.lrun.0."#name, &name, &sz, NULL, \
- 0), 0, "Unexpected mallctl() failure"); \
+ assert_d_eq(mallctl("arenas.lextent.0."#name, (void *)&name, \
+ &sz, NULL, 0), 0, "Unexpected mallctl() failure"); \
assert_zu_eq(name, expected, "Incorrect "#name" size"); \
} while (0)
- TEST_ARENAS_LRUN_CONSTANT(size_t, size, LARGE_MINCLASS);
+ TEST_ARENAS_LEXTENT_CONSTANT(size_t, size, LARGE_MINCLASS);
-#undef TEST_ARENAS_LRUN_CONSTANT
+#undef TEST_ARENAS_LEXTENT_CONSTANT
}
TEST_END
-TEST_BEGIN(test_arenas_hchunk_constants)
-{
-
-#define TEST_ARENAS_HCHUNK_CONSTANT(t, name, expected) do { \
- t name; \
- size_t sz = sizeof(t); \
- assert_d_eq(mallctl("arenas.hchunk.0."#name, &name, &sz, NULL, \
- 0), 0, "Unexpected mallctl() failure"); \
- assert_zu_eq(name, expected, "Incorrect "#name" size"); \
-} while (0)
-
- TEST_ARENAS_HCHUNK_CONSTANT(size_t, size, chunksize);
-
-#undef TEST_ARENAS_HCHUNK_CONSTANT
-}
-TEST_END
-
-TEST_BEGIN(test_arenas_extend)
-{
+TEST_BEGIN(test_arenas_create) {
unsigned narenas_before, arena, narenas_after;
size_t sz = sizeof(unsigned);
- assert_d_eq(mallctl("arenas.narenas", &narenas_before, &sz, NULL, 0), 0,
- "Unexpected mallctl() failure");
- assert_d_eq(mallctl("arenas.extend", &arena, &sz, NULL, 0), 0,
- "Unexpected mallctl() failure");
- assert_d_eq(mallctl("arenas.narenas", &narenas_after, &sz, NULL, 0), 0,
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas_before, &sz,
+ NULL, 0), 0, "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0,
"Unexpected mallctl() failure");
+ assert_d_eq(mallctl("arenas.narenas", (void *)&narenas_after, &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure");
assert_u_eq(narenas_before+1, narenas_after,
"Unexpected number of arenas before versus after extension");
@@ -584,18 +738,34 @@ TEST_BEGIN(test_arenas_extend)
}
TEST_END
-TEST_BEGIN(test_stats_arenas)
-{
+TEST_BEGIN(test_arenas_lookup) {
+ unsigned arena, arena1;
+ void *ptr;
+ size_t sz = sizeof(unsigned);
-#define TEST_STATS_ARENAS(t, name) do { \
+ assert_d_eq(mallctl("arenas.create", (void *)&arena, &sz, NULL, 0), 0,
+ "Unexpected mallctl() failure");
+ ptr = mallocx(42, MALLOCX_ARENA(arena) | MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(ptr, "Unexpected mallocx() failure");
+ assert_d_eq(mallctl("arenas.lookup", &arena1, &sz, &ptr, sizeof(ptr)),
+ 0, "Unexpected mallctl() failure");
+ assert_u_eq(arena, arena1, "Unexpected arena index");
+ dallocx(ptr, 0);
+}
+TEST_END
+
+TEST_BEGIN(test_stats_arenas) {
+#define TEST_STATS_ARENAS(t, name) do { \
t name; \
size_t sz = sizeof(t); \
- assert_d_eq(mallctl("stats.arenas.0."#name, &name, &sz, NULL, \
- 0), 0, "Unexpected mallctl() failure"); \
+ assert_d_eq(mallctl("stats.arenas.0."#name, (void *)&name, &sz, \
+ NULL, 0), 0, "Unexpected mallctl() failure"); \
} while (0)
- TEST_STATS_ARENAS(const char *, dss);
TEST_STATS_ARENAS(unsigned, nthreads);
+ TEST_STATS_ARENAS(const char *, dss);
+ TEST_STATS_ARENAS(ssize_t, dirty_decay_ms);
+ TEST_STATS_ARENAS(ssize_t, muzzy_decay_ms);
TEST_STATS_ARENAS(size_t, pactive);
TEST_STATS_ARENAS(size_t, pdirty);
@@ -604,10 +774,8 @@ TEST_BEGIN(test_stats_arenas)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_mallctl_errors,
test_mallctlnametomib_errors,
test_mallctlbymib_errors,
@@ -619,15 +787,19 @@ main(void)
test_tcache_none,
test_tcache,
test_thread_arena,
- test_arena_i_lg_dirty_mult,
+ test_arena_i_initialized,
+ test_arena_i_dirty_decay_ms,
+ test_arena_i_muzzy_decay_ms,
test_arena_i_purge,
+ test_arena_i_decay,
test_arena_i_dss,
- test_arenas_initialized,
- test_arenas_lg_dirty_mult,
+ test_arena_i_retain_grow_limit,
+ test_arenas_dirty_decay_ms,
+ test_arenas_muzzy_decay_ms,
test_arenas_constants,
test_arenas_bin_constants,
- test_arenas_lrun_constants,
- test_arenas_hchunk_constants,
- test_arenas_extend,
- test_stats_arenas));
+ test_arenas_lextent_constants,
+ test_arenas_create,
+ test_arenas_lookup,
+ test_stats_arenas);
}
diff --git a/deps/jemalloc/test/unit/util.c b/deps/jemalloc/test/unit/malloc_io.c
index 8ab39a458..79ba7fc53 100644
--- a/deps/jemalloc/test/unit/util.c
+++ b/deps/jemalloc/test/unit/malloc_io.c
@@ -1,38 +1,6 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_pow2_ceil)
-{
- unsigned i, pow2;
- size_t x;
-
- assert_zu_eq(pow2_ceil(0), 0, "Unexpected result");
-
- for (i = 0; i < sizeof(size_t) * 8; i++) {
- assert_zu_eq(pow2_ceil(ZU(1) << i), ZU(1) << i,
- "Unexpected result");
- }
-
- for (i = 2; i < sizeof(size_t) * 8; i++) {
- assert_zu_eq(pow2_ceil((ZU(1) << i) - 1), ZU(1) << i,
- "Unexpected result");
- }
-
- for (i = 0; i < sizeof(size_t) * 8 - 1; i++) {
- assert_zu_eq(pow2_ceil((ZU(1) << i) + 1), ZU(1) << (i+1),
- "Unexpected result");
- }
-
- for (pow2 = 1; pow2 < 25; pow2++) {
- for (x = (ZU(1) << (pow2-1)) + 1; x <= ZU(1) << pow2; x++) {
- assert_zu_eq(pow2_ceil(x), ZU(1) << pow2,
- "Unexpected result, x=%zu", x);
- }
- }
-}
-TEST_END
-
-TEST_BEGIN(test_malloc_strtoumax_no_endptr)
-{
+TEST_BEGIN(test_malloc_strtoumax_no_endptr) {
int err;
set_errno(0);
@@ -42,8 +10,7 @@ TEST_BEGIN(test_malloc_strtoumax_no_endptr)
}
TEST_END
-TEST_BEGIN(test_malloc_strtoumax)
-{
+TEST_BEGIN(test_malloc_strtoumax) {
struct test_s {
const char *input;
const char *expected_remainder;
@@ -52,8 +19,9 @@ TEST_BEGIN(test_malloc_strtoumax)
const char *expected_errno_name;
uintmax_t expected_x;
};
-#define ERR(e) e, #e
-#define KUMAX(x) ((uintmax_t)x##ULL)
+#define ERR(e) e, #e
+#define KUMAX(x) ((uintmax_t)x##ULL)
+#define KSMAX(x) ((uintmax_t)(intmax_t)x##LL)
struct test_s tests[] = {
{"0", "0", -1, ERR(EINVAL), UINTMAX_MAX},
{"0", "0", 1, ERR(EINVAL), UINTMAX_MAX},
@@ -66,13 +34,13 @@ TEST_BEGIN(test_malloc_strtoumax)
{"42", "", 0, ERR(0), KUMAX(42)},
{"+42", "", 0, ERR(0), KUMAX(42)},
- {"-42", "", 0, ERR(0), KUMAX(-42)},
+ {"-42", "", 0, ERR(0), KSMAX(-42)},
{"042", "", 0, ERR(0), KUMAX(042)},
{"+042", "", 0, ERR(0), KUMAX(042)},
- {"-042", "", 0, ERR(0), KUMAX(-042)},
+ {"-042", "", 0, ERR(0), KSMAX(-042)},
{"0x42", "", 0, ERR(0), KUMAX(0x42)},
{"+0x42", "", 0, ERR(0), KUMAX(0x42)},
- {"-0x42", "", 0, ERR(0), KUMAX(-0x42)},
+ {"-0x42", "", 0, ERR(0), KSMAX(-0x42)},
{"0", "", 0, ERR(0), KUMAX(0)},
{"1", "", 0, ERR(0), KUMAX(1)},
@@ -109,6 +77,7 @@ TEST_BEGIN(test_malloc_strtoumax)
};
#undef ERR
#undef KUMAX
+#undef KSMAX
unsigned i;
for (i = 0; i < sizeof(tests)/sizeof(struct test_s); i++) {
@@ -135,18 +104,17 @@ TEST_BEGIN(test_malloc_strtoumax)
}
TEST_END
-TEST_BEGIN(test_malloc_snprintf_truncated)
-{
-#define BUFLEN 15
+TEST_BEGIN(test_malloc_snprintf_truncated) {
+#define BUFLEN 15
char buf[BUFLEN];
- int result;
+ size_t result;
size_t len;
#define TEST(expected_str_untruncated, ...) do { \
result = malloc_snprintf(buf, len, __VA_ARGS__); \
assert_d_eq(strncmp(buf, expected_str_untruncated, len-1), 0, \
"Unexpected string inequality (\"%s\" vs \"%s\")", \
- buf, expected_str_untruncated); \
- assert_d_eq(result, strlen(expected_str_untruncated), \
+ buf, expected_str_untruncated); \
+ assert_zu_eq(result, strlen(expected_str_untruncated), \
"Unexpected result"); \
} while (0)
@@ -168,15 +136,14 @@ TEST_BEGIN(test_malloc_snprintf_truncated)
}
TEST_END
-TEST_BEGIN(test_malloc_snprintf)
-{
-#define BUFLEN 128
+TEST_BEGIN(test_malloc_snprintf) {
+#define BUFLEN 128
char buf[BUFLEN];
- int result;
-#define TEST(expected_str, ...) do { \
+ size_t result;
+#define TEST(expected_str, ...) do { \
result = malloc_snprintf(buf, sizeof(buf), __VA_ARGS__); \
assert_str_eq(buf, expected_str, "Unexpected output"); \
- assert_d_eq(result, strlen(expected_str), "Unexpected result"); \
+ assert_zu_eq(result, strlen(expected_str), "Unexpected result");\
} while (0)
TEST("hello", "hello");
@@ -282,13 +249,10 @@ TEST_BEGIN(test_malloc_snprintf)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_pow2_ceil,
+main(void) {
+ return test(
test_malloc_strtoumax_no_endptr,
test_malloc_strtoumax,
test_malloc_snprintf_truncated,
- test_malloc_snprintf));
+ test_malloc_snprintf);
}
diff --git a/deps/jemalloc/test/unit/math.c b/deps/jemalloc/test/unit/math.c
index ebec77a62..09ef20c7b 100644
--- a/deps/jemalloc/test/unit/math.c
+++ b/deps/jemalloc/test/unit/math.c
@@ -1,39 +1,42 @@
#include "test/jemalloc_test.h"
-#define MAX_REL_ERR 1.0e-9
-#define MAX_ABS_ERR 1.0e-9
+#define MAX_REL_ERR 1.0e-9
+#define MAX_ABS_ERR 1.0e-9
#include <float.h>
+#ifdef __PGI
+#undef INFINITY
+#endif
+
#ifndef INFINITY
-#define INFINITY (DBL_MAX + DBL_MAX)
+#define INFINITY (DBL_MAX + DBL_MAX)
#endif
static bool
-double_eq_rel(double a, double b, double max_rel_err, double max_abs_err)
-{
+double_eq_rel(double a, double b, double max_rel_err, double max_abs_err) {
double rel_err;
- if (fabs(a - b) < max_abs_err)
- return (true);
+ if (fabs(a - b) < max_abs_err) {
+ return true;
+ }
rel_err = (fabs(b) > fabs(a)) ? fabs((a-b)/b) : fabs((a-b)/a);
return (rel_err < max_rel_err);
}
static uint64_t
-factorial(unsigned x)
-{
+factorial(unsigned x) {
uint64_t ret = 1;
unsigned i;
- for (i = 2; i <= x; i++)
+ for (i = 2; i <= x; i++) {
ret *= (uint64_t)i;
+ }
- return (ret);
+ return ret;
}
-TEST_BEGIN(test_ln_gamma_factorial)
-{
+TEST_BEGIN(test_ln_gamma_factorial) {
unsigned x;
/* exp(ln_gamma(x)) == (x-1)! for integer x. */
@@ -184,8 +187,7 @@ static const double ln_gamma_misc_expected[] = {
359.13420536957539753
};
-TEST_BEGIN(test_ln_gamma_misc)
-{
+TEST_BEGIN(test_ln_gamma_misc) {
unsigned i;
for (i = 1; i < sizeof(ln_gamma_misc_expected)/sizeof(double); i++) {
@@ -235,8 +237,7 @@ static const double pt_norm_expected[] = {
1.88079360815125041, 2.05374891063182208, 2.32634787404084076
};
-TEST_BEGIN(test_pt_norm)
-{
+TEST_BEGIN(test_pt_norm) {
unsigned i;
for (i = 1; i < sizeof(pt_norm_expected)/sizeof(double); i++) {
@@ -285,8 +286,7 @@ static const double pt_chi2_expected[] = {
1046.4872561869577, 1063.5717461999654, 1107.0741966053859
};
-TEST_BEGIN(test_pt_chi2)
-{
+TEST_BEGIN(test_pt_chi2) {
unsigned i, j;
unsigned e = 0;
@@ -347,8 +347,7 @@ static const double pt_gamma_expected[] = {
4.7230515633946677, 5.6417477865306020, 8.4059469148854635
};
-TEST_BEGIN(test_pt_gamma_shape)
-{
+TEST_BEGIN(test_pt_gamma_shape) {
unsigned i, j;
unsigned e = 0;
@@ -367,8 +366,7 @@ TEST_BEGIN(test_pt_gamma_shape)
}
TEST_END
-TEST_BEGIN(test_pt_gamma_scale)
-{
+TEST_BEGIN(test_pt_gamma_scale) {
double shape = 1.0;
double ln_gamma_shape = ln_gamma(shape);
@@ -381,14 +379,12 @@ TEST_BEGIN(test_pt_gamma_scale)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_ln_gamma_factorial,
test_ln_gamma_misc,
test_pt_norm,
test_pt_chi2,
test_pt_gamma_shape,
- test_pt_gamma_scale));
+ test_pt_gamma_scale);
}
diff --git a/deps/jemalloc/test/unit/mq.c b/deps/jemalloc/test/unit/mq.c
index bde2a480b..57a4d54e4 100644
--- a/deps/jemalloc/test/unit/mq.c
+++ b/deps/jemalloc/test/unit/mq.c
@@ -1,7 +1,7 @@
#include "test/jemalloc_test.h"
-#define NSENDERS 3
-#define NMSGS 100000
+#define NSENDERS 3
+#define NMSGS 100000
typedef struct mq_msg_s mq_msg_t;
struct mq_msg_s {
@@ -9,8 +9,7 @@ struct mq_msg_s {
};
mq_gen(static, mq_, mq_t, mq_msg_t, link)
-TEST_BEGIN(test_mq_basic)
-{
+TEST_BEGIN(test_mq_basic) {
mq_t mq;
mq_msg_t msg;
@@ -31,8 +30,7 @@ TEST_BEGIN(test_mq_basic)
TEST_END
static void *
-thd_receiver_start(void *arg)
-{
+thd_receiver_start(void *arg) {
mq_t *mq = (mq_t *)arg;
unsigned i;
@@ -41,12 +39,11 @@ thd_receiver_start(void *arg)
assert_ptr_not_null(msg, "mq_get() should never return NULL");
dallocx(msg, 0);
}
- return (NULL);
+ return NULL;
}
static void *
-thd_sender_start(void *arg)
-{
+thd_sender_start(void *arg) {
mq_t *mq = (mq_t *)arg;
unsigned i;
@@ -58,11 +55,10 @@ thd_sender_start(void *arg)
msg = (mq_msg_t *)p;
mq_put(mq, msg);
}
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_mq_threaded)
-{
+TEST_BEGIN(test_mq_threaded) {
mq_t mq;
thd_t receiver;
thd_t senders[NSENDERS];
@@ -71,23 +67,23 @@ TEST_BEGIN(test_mq_threaded)
assert_false(mq_init(&mq), "Unexpected mq_init() failure");
thd_create(&receiver, thd_receiver_start, (void *)&mq);
- for (i = 0; i < NSENDERS; i++)
+ for (i = 0; i < NSENDERS; i++) {
thd_create(&senders[i], thd_sender_start, (void *)&mq);
+ }
thd_join(receiver, NULL);
- for (i = 0; i < NSENDERS; i++)
+ for (i = 0; i < NSENDERS; i++) {
thd_join(senders[i], NULL);
+ }
mq_fini(&mq);
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_mq_basic,
- test_mq_threaded));
+ test_mq_threaded);
}
diff --git a/deps/jemalloc/test/unit/mtx.c b/deps/jemalloc/test/unit/mtx.c
index 96ff69486..424587b03 100644
--- a/deps/jemalloc/test/unit/mtx.c
+++ b/deps/jemalloc/test/unit/mtx.c
@@ -1,10 +1,9 @@
#include "test/jemalloc_test.h"
-#define NTHREADS 2
-#define NINCRS 2000000
+#define NTHREADS 2
+#define NINCRS 2000000
-TEST_BEGIN(test_mtx_basic)
-{
+TEST_BEGIN(test_mtx_basic) {
mtx_t mtx;
assert_false(mtx_init(&mtx), "Unexpected mtx_init() failure");
@@ -20,8 +19,7 @@ typedef struct {
} thd_start_arg_t;
static void *
-thd_start(void *varg)
-{
+thd_start(void *varg) {
thd_start_arg_t *arg = (thd_start_arg_t *)varg;
unsigned i;
@@ -30,31 +28,30 @@ thd_start(void *varg)
arg->x++;
mtx_unlock(&arg->mtx);
}
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_mtx_race)
-{
+TEST_BEGIN(test_mtx_race) {
thd_start_arg_t arg;
thd_t thds[NTHREADS];
unsigned i;
assert_false(mtx_init(&arg.mtx), "Unexpected mtx_init() failure");
arg.x = 0;
- for (i = 0; i < NTHREADS; i++)
+ for (i = 0; i < NTHREADS; i++) {
thd_create(&thds[i], thd_start, (void *)&arg);
- for (i = 0; i < NTHREADS; i++)
+ }
+ for (i = 0; i < NTHREADS; i++) {
thd_join(thds[i], NULL);
+ }
assert_u_eq(arg.x, NTHREADS * NINCRS,
"Race-related counter corruption");
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_mtx_basic,
- test_mtx_race));
+ test_mtx_race);
}
diff --git a/deps/jemalloc/test/unit/nstime.c b/deps/jemalloc/test/unit/nstime.c
new file mode 100644
index 000000000..f31378058
--- /dev/null
+++ b/deps/jemalloc/test/unit/nstime.c
@@ -0,0 +1,249 @@
+#include "test/jemalloc_test.h"
+
+#define BILLION UINT64_C(1000000000)
+
+TEST_BEGIN(test_nstime_init) {
+ nstime_t nst;
+
+ nstime_init(&nst, 42000000043);
+ assert_u64_eq(nstime_ns(&nst), 42000000043, "ns incorrectly read");
+ assert_u64_eq(nstime_sec(&nst), 42, "sec incorrectly read");
+ assert_u64_eq(nstime_nsec(&nst), 43, "nsec incorrectly read");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_init2) {
+ nstime_t nst;
+
+ nstime_init2(&nst, 42, 43);
+ assert_u64_eq(nstime_sec(&nst), 42, "sec incorrectly read");
+ assert_u64_eq(nstime_nsec(&nst), 43, "nsec incorrectly read");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_copy) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_init(&nstb, 0);
+ nstime_copy(&nstb, &nsta);
+ assert_u64_eq(nstime_sec(&nstb), 42, "sec incorrectly copied");
+ assert_u64_eq(nstime_nsec(&nstb), 43, "nsec incorrectly copied");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_compare) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0, "Times should be equal");
+ assert_d_eq(nstime_compare(&nstb, &nsta), 0, "Times should be equal");
+
+ nstime_init2(&nstb, 42, 42);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 1,
+ "nsta should be greater than nstb");
+ assert_d_eq(nstime_compare(&nstb, &nsta), -1,
+ "nstb should be less than nsta");
+
+ nstime_init2(&nstb, 42, 44);
+ assert_d_eq(nstime_compare(&nsta, &nstb), -1,
+ "nsta should be less than nstb");
+ assert_d_eq(nstime_compare(&nstb, &nsta), 1,
+ "nstb should be greater than nsta");
+
+ nstime_init2(&nstb, 41, BILLION - 1);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 1,
+ "nsta should be greater than nstb");
+ assert_d_eq(nstime_compare(&nstb, &nsta), -1,
+ "nstb should be less than nsta");
+
+ nstime_init2(&nstb, 43, 0);
+ assert_d_eq(nstime_compare(&nsta, &nstb), -1,
+ "nsta should be less than nstb");
+ assert_d_eq(nstime_compare(&nstb, &nsta), 1,
+ "nstb should be greater than nsta");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_add) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_add(&nsta, &nstb);
+ nstime_init2(&nstb, 84, 86);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect addition result");
+
+ nstime_init2(&nsta, 42, BILLION - 1);
+ nstime_copy(&nstb, &nsta);
+ nstime_add(&nsta, &nstb);
+ nstime_init2(&nstb, 85, BILLION - 2);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect addition result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_iadd) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, BILLION - 1);
+ nstime_iadd(&nsta, 1);
+ nstime_init2(&nstb, 43, 0);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect addition result");
+
+ nstime_init2(&nsta, 42, 1);
+ nstime_iadd(&nsta, BILLION + 1);
+ nstime_init2(&nstb, 43, 2);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect addition result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_subtract) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_subtract(&nsta, &nstb);
+ nstime_init(&nstb, 0);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect subtraction result");
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_init2(&nstb, 41, 44);
+ nstime_subtract(&nsta, &nstb);
+ nstime_init2(&nstb, 0, BILLION - 1);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect subtraction result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_isubtract) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_isubtract(&nsta, 42*BILLION + 43);
+ nstime_init(&nstb, 0);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect subtraction result");
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_isubtract(&nsta, 41*BILLION + 44);
+ nstime_init2(&nstb, 0, BILLION - 1);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect subtraction result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_imultiply) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_imultiply(&nsta, 10);
+ nstime_init2(&nstb, 420, 430);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect multiplication result");
+
+ nstime_init2(&nsta, 42, 666666666);
+ nstime_imultiply(&nsta, 3);
+ nstime_init2(&nstb, 127, 999999998);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect multiplication result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_idivide) {
+ nstime_t nsta, nstb;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_imultiply(&nsta, 10);
+ nstime_idivide(&nsta, 10);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect division result");
+
+ nstime_init2(&nsta, 42, 666666666);
+ nstime_copy(&nstb, &nsta);
+ nstime_imultiply(&nsta, 3);
+ nstime_idivide(&nsta, 3);
+ assert_d_eq(nstime_compare(&nsta, &nstb), 0,
+ "Incorrect division result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_divide) {
+ nstime_t nsta, nstb, nstc;
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_imultiply(&nsta, 10);
+ assert_u64_eq(nstime_divide(&nsta, &nstb), 10,
+ "Incorrect division result");
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_imultiply(&nsta, 10);
+ nstime_init(&nstc, 1);
+ nstime_add(&nsta, &nstc);
+ assert_u64_eq(nstime_divide(&nsta, &nstb), 10,
+ "Incorrect division result");
+
+ nstime_init2(&nsta, 42, 43);
+ nstime_copy(&nstb, &nsta);
+ nstime_imultiply(&nsta, 10);
+ nstime_init(&nstc, 1);
+ nstime_subtract(&nsta, &nstc);
+ assert_u64_eq(nstime_divide(&nsta, &nstb), 9,
+ "Incorrect division result");
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_monotonic) {
+ nstime_monotonic();
+}
+TEST_END
+
+TEST_BEGIN(test_nstime_update) {
+ nstime_t nst;
+
+ nstime_init(&nst, 0);
+
+ assert_false(nstime_update(&nst), "Basic time update failed.");
+
+ /* Only Rip Van Winkle sleeps this long. */
+ {
+ nstime_t addend;
+ nstime_init2(&addend, 631152000, 0);
+ nstime_add(&nst, &addend);
+ }
+ {
+ nstime_t nst0;
+ nstime_copy(&nst0, &nst);
+ assert_true(nstime_update(&nst),
+ "Update should detect time roll-back.");
+ assert_d_eq(nstime_compare(&nst, &nst0), 0,
+ "Time should not have been modified");
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_nstime_init,
+ test_nstime_init2,
+ test_nstime_copy,
+ test_nstime_compare,
+ test_nstime_add,
+ test_nstime_iadd,
+ test_nstime_subtract,
+ test_nstime_isubtract,
+ test_nstime_imultiply,
+ test_nstime_idivide,
+ test_nstime_divide,
+ test_nstime_monotonic,
+ test_nstime_update);
+}
diff --git a/deps/jemalloc/test/unit/pack.c b/deps/jemalloc/test/unit/pack.c
new file mode 100644
index 000000000..fc188b003
--- /dev/null
+++ b/deps/jemalloc/test/unit/pack.c
@@ -0,0 +1,166 @@
+#include "test/jemalloc_test.h"
+
+/*
+ * Size class that is a divisor of the page size, ideally 4+ regions per run.
+ */
+#if LG_PAGE <= 14
+#define SZ (ZU(1) << (LG_PAGE - 2))
+#else
+#define SZ ZU(4096)
+#endif
+
+/*
+ * Number of slabs to consume at high water mark. Should be at least 2 so that
+ * if mmap()ed memory grows downward, downward growth of mmap()ed memory is
+ * tested.
+ */
+#define NSLABS 8
+
+static unsigned
+binind_compute(void) {
+ size_t sz;
+ unsigned nbins, i;
+
+ sz = sizeof(nbins);
+ assert_d_eq(mallctl("arenas.nbins", (void *)&nbins, &sz, NULL, 0), 0,
+ "Unexpected mallctl failure");
+
+ for (i = 0; i < nbins; i++) {
+ size_t mib[4];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ size_t size;
+
+ assert_d_eq(mallctlnametomib("arenas.bin.0.size", mib,
+ &miblen), 0, "Unexpected mallctlnametomb failure");
+ mib[2] = (size_t)i;
+
+ sz = sizeof(size);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&size, &sz, NULL,
+ 0), 0, "Unexpected mallctlbymib failure");
+ if (size == SZ) {
+ return i;
+ }
+ }
+
+ test_fail("Unable to compute nregs_per_run");
+ return 0;
+}
+
+static size_t
+nregs_per_run_compute(void) {
+ uint32_t nregs;
+ size_t sz;
+ unsigned binind = binind_compute();
+ size_t mib[4];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+
+ assert_d_eq(mallctlnametomib("arenas.bin.0.nregs", mib, &miblen), 0,
+ "Unexpected mallctlnametomb failure");
+ mib[2] = (size_t)binind;
+ sz = sizeof(nregs);
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&nregs, &sz, NULL,
+ 0), 0, "Unexpected mallctlbymib failure");
+ return nregs;
+}
+
+static unsigned
+arenas_create_mallctl(void) {
+ unsigned arena_ind;
+ size_t sz;
+
+ sz = sizeof(arena_ind);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Error in arenas.create");
+
+ return arena_ind;
+}
+
+static void
+arena_reset_mallctl(unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+
+ assert_d_eq(mallctlnametomib("arena.0.reset", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+TEST_BEGIN(test_pack) {
+ bool prof_enabled;
+ size_t sz = sizeof(prof_enabled);
+ if (mallctl("opt.prof", (void *)&prof_enabled, &sz, NULL, 0) == 0) {
+ test_skip_if(prof_enabled);
+ }
+
+ unsigned arena_ind = arenas_create_mallctl();
+ size_t nregs_per_run = nregs_per_run_compute();
+ size_t nregs = nregs_per_run * NSLABS;
+ VARIABLE_ARRAY(void *, ptrs, nregs);
+ size_t i, j, offset;
+
+ /* Fill matrix. */
+ for (i = offset = 0; i < NSLABS; i++) {
+ for (j = 0; j < nregs_per_run; j++) {
+ void *p = mallocx(SZ, MALLOCX_ARENA(arena_ind) |
+ MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(p,
+ "Unexpected mallocx(%zu, MALLOCX_ARENA(%u) |"
+ " MALLOCX_TCACHE_NONE) failure, run=%zu, reg=%zu",
+ SZ, arena_ind, i, j);
+ ptrs[(i * nregs_per_run) + j] = p;
+ }
+ }
+
+ /*
+ * Free all but one region of each run, but rotate which region is
+ * preserved, so that subsequent allocations exercise the within-run
+ * layout policy.
+ */
+ offset = 0;
+ for (i = offset = 0;
+ i < NSLABS;
+ i++, offset = (offset + 1) % nregs_per_run) {
+ for (j = 0; j < nregs_per_run; j++) {
+ void *p = ptrs[(i * nregs_per_run) + j];
+ if (offset == j) {
+ continue;
+ }
+ dallocx(p, MALLOCX_ARENA(arena_ind) |
+ MALLOCX_TCACHE_NONE);
+ }
+ }
+
+ /*
+ * Logically refill matrix, skipping preserved regions and verifying
+ * that the matrix is unmodified.
+ */
+ offset = 0;
+ for (i = offset = 0;
+ i < NSLABS;
+ i++, offset = (offset + 1) % nregs_per_run) {
+ for (j = 0; j < nregs_per_run; j++) {
+ void *p;
+
+ if (offset == j) {
+ continue;
+ }
+ p = mallocx(SZ, MALLOCX_ARENA(arena_ind) |
+ MALLOCX_TCACHE_NONE);
+ assert_ptr_eq(p, ptrs[(i * nregs_per_run) + j],
+ "Unexpected refill discrepancy, run=%zu, reg=%zu\n",
+ i, j);
+ }
+ }
+
+ /* Clean up. */
+ arena_reset_mallctl(arena_ind);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_pack);
+}
diff --git a/deps/jemalloc/test/unit/pack.sh b/deps/jemalloc/test/unit/pack.sh
new file mode 100644
index 000000000..6f451480b
--- /dev/null
+++ b/deps/jemalloc/test/unit/pack.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+# Immediately purge to minimize fragmentation.
+export MALLOC_CONF="dirty_decay_ms:0,muzzy_decay_ms:0"
diff --git a/deps/jemalloc/test/unit/pages.c b/deps/jemalloc/test/unit/pages.c
new file mode 100644
index 000000000..ee729eece
--- /dev/null
+++ b/deps/jemalloc/test/unit/pages.c
@@ -0,0 +1,29 @@
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_pages_huge) {
+ size_t alloc_size;
+ bool commit;
+ void *pages, *hugepage;
+
+ alloc_size = HUGEPAGE * 2 - PAGE;
+ commit = true;
+ pages = pages_map(NULL, alloc_size, PAGE, &commit);
+ assert_ptr_not_null(pages, "Unexpected pages_map() error");
+
+ if (init_system_thp_mode == thp_mode_default) {
+ hugepage = (void *)(ALIGNMENT_CEILING((uintptr_t)pages, HUGEPAGE));
+ assert_b_ne(pages_huge(hugepage, HUGEPAGE), have_madvise_huge,
+ "Unexpected pages_huge() result");
+ assert_false(pages_nohuge(hugepage, HUGEPAGE),
+ "Unexpected pages_nohuge() result");
+ }
+
+ pages_unmap(pages, alloc_size);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_pages_huge);
+}
diff --git a/deps/jemalloc/test/unit/ph.c b/deps/jemalloc/test/unit/ph.c
new file mode 100644
index 000000000..88bf56f88
--- /dev/null
+++ b/deps/jemalloc/test/unit/ph.c
@@ -0,0 +1,318 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/ph.h"
+
+typedef struct node_s node_t;
+
+struct node_s {
+#define NODE_MAGIC 0x9823af7e
+ uint32_t magic;
+ phn(node_t) link;
+ uint64_t key;
+};
+
+static int
+node_cmp(const node_t *a, const node_t *b) {
+ int ret;
+
+ ret = (a->key > b->key) - (a->key < b->key);
+ if (ret == 0) {
+ /*
+ * Duplicates are not allowed in the heap, so force an
+ * arbitrary ordering for non-identical items with equal keys.
+ */
+ ret = (((uintptr_t)a) > ((uintptr_t)b))
+ - (((uintptr_t)a) < ((uintptr_t)b));
+ }
+ return ret;
+}
+
+static int
+node_cmp_magic(const node_t *a, const node_t *b) {
+
+ assert_u32_eq(a->magic, NODE_MAGIC, "Bad magic");
+ assert_u32_eq(b->magic, NODE_MAGIC, "Bad magic");
+
+ return node_cmp(a, b);
+}
+
+typedef ph(node_t) heap_t;
+ph_gen(static, heap_, heap_t, node_t, link, node_cmp_magic);
+
+static void
+node_print(const node_t *node, unsigned depth) {
+ unsigned i;
+ node_t *leftmost_child, *sibling;
+
+ for (i = 0; i < depth; i++) {
+ malloc_printf("\t");
+ }
+ malloc_printf("%2"FMTu64"\n", node->key);
+
+ leftmost_child = phn_lchild_get(node_t, link, node);
+ if (leftmost_child == NULL) {
+ return;
+ }
+ node_print(leftmost_child, depth + 1);
+
+ for (sibling = phn_next_get(node_t, link, leftmost_child); sibling !=
+ NULL; sibling = phn_next_get(node_t, link, sibling)) {
+ node_print(sibling, depth + 1);
+ }
+}
+
+static void
+heap_print(const heap_t *heap) {
+ node_t *auxelm;
+
+ malloc_printf("vvv heap %p vvv\n", heap);
+ if (heap->ph_root == NULL) {
+ goto label_return;
+ }
+
+ node_print(heap->ph_root, 0);
+
+ for (auxelm = phn_next_get(node_t, link, heap->ph_root); auxelm != NULL;
+ auxelm = phn_next_get(node_t, link, auxelm)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, auxelm)), auxelm,
+ "auxelm's prev doesn't link to auxelm");
+ node_print(auxelm, 0);
+ }
+
+label_return:
+ malloc_printf("^^^ heap %p ^^^\n", heap);
+}
+
+static unsigned
+node_validate(const node_t *node, const node_t *parent) {
+ unsigned nnodes = 1;
+ node_t *leftmost_child, *sibling;
+
+ if (parent != NULL) {
+ assert_d_ge(node_cmp_magic(node, parent), 0,
+ "Child is less than parent");
+ }
+
+ leftmost_child = phn_lchild_get(node_t, link, node);
+ if (leftmost_child == NULL) {
+ return nnodes;
+ }
+ assert_ptr_eq((void *)phn_prev_get(node_t, link, leftmost_child),
+ (void *)node, "Leftmost child does not link to node");
+ nnodes += node_validate(leftmost_child, node);
+
+ for (sibling = phn_next_get(node_t, link, leftmost_child); sibling !=
+ NULL; sibling = phn_next_get(node_t, link, sibling)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, sibling)), sibling,
+ "sibling's prev doesn't link to sibling");
+ nnodes += node_validate(sibling, node);
+ }
+ return nnodes;
+}
+
+static unsigned
+heap_validate(const heap_t *heap) {
+ unsigned nnodes = 0;
+ node_t *auxelm;
+
+ if (heap->ph_root == NULL) {
+ goto label_return;
+ }
+
+ nnodes += node_validate(heap->ph_root, NULL);
+
+ for (auxelm = phn_next_get(node_t, link, heap->ph_root); auxelm != NULL;
+ auxelm = phn_next_get(node_t, link, auxelm)) {
+ assert_ptr_eq(phn_next_get(node_t, link, phn_prev_get(node_t,
+ link, auxelm)), auxelm,
+ "auxelm's prev doesn't link to auxelm");
+ nnodes += node_validate(auxelm, NULL);
+ }
+
+label_return:
+ if (false) {
+ heap_print(heap);
+ }
+ return nnodes;
+}
+
+TEST_BEGIN(test_ph_empty) {
+ heap_t heap;
+
+ heap_new(&heap);
+ assert_true(heap_empty(&heap), "Heap should be empty");
+ assert_ptr_null(heap_first(&heap), "Unexpected node");
+ assert_ptr_null(heap_any(&heap), "Unexpected node");
+}
+TEST_END
+
+static void
+node_remove(heap_t *heap, node_t *node) {
+ heap_remove(heap, node);
+
+ node->magic = 0;
+}
+
+static node_t *
+node_remove_first(heap_t *heap) {
+ node_t *node = heap_remove_first(heap);
+ node->magic = 0;
+ return node;
+}
+
+static node_t *
+node_remove_any(heap_t *heap) {
+ node_t *node = heap_remove_any(heap);
+ node->magic = 0;
+ return node;
+}
+
+TEST_BEGIN(test_ph_random) {
+#define NNODES 25
+#define NBAGS 250
+#define SEED 42
+ sfmt_t *sfmt;
+ uint64_t bag[NNODES];
+ heap_t heap;
+ node_t nodes[NNODES];
+ unsigned i, j, k;
+
+ sfmt = init_gen_rand(SEED);
+ for (i = 0; i < NBAGS; i++) {
+ switch (i) {
+ case 0:
+ /* Insert in order. */
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = j;
+ }
+ break;
+ case 1:
+ /* Insert in reverse order. */
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = NNODES - j - 1;
+ }
+ break;
+ default:
+ for (j = 0; j < NNODES; j++) {
+ bag[j] = gen_rand64_range(sfmt, NNODES);
+ }
+ }
+
+ for (j = 1; j <= NNODES; j++) {
+ /* Initialize heap and nodes. */
+ heap_new(&heap);
+ assert_u_eq(heap_validate(&heap), 0,
+ "Incorrect node count");
+ for (k = 0; k < j; k++) {
+ nodes[k].magic = NODE_MAGIC;
+ nodes[k].key = bag[k];
+ }
+
+ /* Insert nodes. */
+ for (k = 0; k < j; k++) {
+ heap_insert(&heap, &nodes[k]);
+ if (i % 13 == 12) {
+ assert_ptr_not_null(heap_any(&heap),
+ "Heap should not be empty");
+ /* Trigger merging. */
+ assert_ptr_not_null(heap_first(&heap),
+ "Heap should not be empty");
+ }
+ assert_u_eq(heap_validate(&heap), k + 1,
+ "Incorrect node count");
+ }
+
+ assert_false(heap_empty(&heap),
+ "Heap should not be empty");
+
+ /* Remove nodes. */
+ switch (i % 6) {
+ case 0:
+ for (k = 0; k < j; k++) {
+ assert_u_eq(heap_validate(&heap), j - k,
+ "Incorrect node count");
+ node_remove(&heap, &nodes[k]);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ }
+ break;
+ case 1:
+ for (k = j; k > 0; k--) {
+ node_remove(&heap, &nodes[k-1]);
+ assert_u_eq(heap_validate(&heap), k - 1,
+ "Incorrect node count");
+ }
+ break;
+ case 2: {
+ node_t *prev = NULL;
+ for (k = 0; k < j; k++) {
+ node_t *node = node_remove_first(&heap);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ if (prev != NULL) {
+ assert_d_ge(node_cmp(node,
+ prev), 0,
+ "Bad removal order");
+ }
+ prev = node;
+ }
+ break;
+ } case 3: {
+ node_t *prev = NULL;
+ for (k = 0; k < j; k++) {
+ node_t *node = heap_first(&heap);
+ assert_u_eq(heap_validate(&heap), j - k,
+ "Incorrect node count");
+ if (prev != NULL) {
+ assert_d_ge(node_cmp(node,
+ prev), 0,
+ "Bad removal order");
+ }
+ node_remove(&heap, node);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ prev = node;
+ }
+ break;
+ } case 4: {
+ for (k = 0; k < j; k++) {
+ node_remove_any(&heap);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ }
+ break;
+ } case 5: {
+ for (k = 0; k < j; k++) {
+ node_t *node = heap_any(&heap);
+ assert_u_eq(heap_validate(&heap), j - k,
+ "Incorrect node count");
+ node_remove(&heap, node);
+ assert_u_eq(heap_validate(&heap), j - k
+ - 1, "Incorrect node count");
+ }
+ break;
+ } default:
+ not_reached();
+ }
+
+ assert_ptr_null(heap_first(&heap),
+ "Heap should be empty");
+ assert_ptr_null(heap_any(&heap),
+ "Heap should be empty");
+ assert_true(heap_empty(&heap), "Heap should be empty");
+ }
+ }
+ fini_gen_rand(sfmt);
+#undef NNODES
+#undef SEED
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_ph_empty,
+ test_ph_random);
+}
diff --git a/deps/jemalloc/test/unit/prng.c b/deps/jemalloc/test/unit/prng.c
new file mode 100644
index 000000000..b5795c2f4
--- /dev/null
+++ b/deps/jemalloc/test/unit/prng.c
@@ -0,0 +1,237 @@
+#include "test/jemalloc_test.h"
+
+static void
+test_prng_lg_range_u32(bool atomic) {
+ atomic_u32_t sa, sb;
+ uint32_t ra, rb;
+ unsigned lg_range;
+
+ atomic_store_u32(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_u32(&sa, 32, atomic);
+ atomic_store_u32(&sa, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_u32(&sa, 32, atomic);
+ assert_u32_eq(ra, rb,
+ "Repeated generation should produce repeated results");
+
+ atomic_store_u32(&sb, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_u32(&sb, 32, atomic);
+ assert_u32_eq(ra, rb,
+ "Equivalent generation should produce equivalent results");
+
+ atomic_store_u32(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_u32(&sa, 32, atomic);
+ rb = prng_lg_range_u32(&sa, 32, atomic);
+ assert_u32_ne(ra, rb,
+ "Full-width results must not immediately repeat");
+
+ atomic_store_u32(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_u32(&sa, 32, atomic);
+ for (lg_range = 31; lg_range > 0; lg_range--) {
+ atomic_store_u32(&sb, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_u32(&sb, lg_range, atomic);
+ assert_u32_eq((rb & (UINT32_C(0xffffffff) << lg_range)),
+ 0, "High order bits should be 0, lg_range=%u", lg_range);
+ assert_u32_eq(rb, (ra >> (32 - lg_range)),
+ "Expected high order bits of full-width result, "
+ "lg_range=%u", lg_range);
+ }
+}
+
+static void
+test_prng_lg_range_u64(void) {
+ uint64_t sa, sb, ra, rb;
+ unsigned lg_range;
+
+ sa = 42;
+ ra = prng_lg_range_u64(&sa, 64);
+ sa = 42;
+ rb = prng_lg_range_u64(&sa, 64);
+ assert_u64_eq(ra, rb,
+ "Repeated generation should produce repeated results");
+
+ sb = 42;
+ rb = prng_lg_range_u64(&sb, 64);
+ assert_u64_eq(ra, rb,
+ "Equivalent generation should produce equivalent results");
+
+ sa = 42;
+ ra = prng_lg_range_u64(&sa, 64);
+ rb = prng_lg_range_u64(&sa, 64);
+ assert_u64_ne(ra, rb,
+ "Full-width results must not immediately repeat");
+
+ sa = 42;
+ ra = prng_lg_range_u64(&sa, 64);
+ for (lg_range = 63; lg_range > 0; lg_range--) {
+ sb = 42;
+ rb = prng_lg_range_u64(&sb, lg_range);
+ assert_u64_eq((rb & (UINT64_C(0xffffffffffffffff) << lg_range)),
+ 0, "High order bits should be 0, lg_range=%u", lg_range);
+ assert_u64_eq(rb, (ra >> (64 - lg_range)),
+ "Expected high order bits of full-width result, "
+ "lg_range=%u", lg_range);
+ }
+}
+
+static void
+test_prng_lg_range_zu(bool atomic) {
+ atomic_zu_t sa, sb;
+ size_t ra, rb;
+ unsigned lg_range;
+
+ atomic_store_zu(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ atomic_store_zu(&sa, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ assert_zu_eq(ra, rb,
+ "Repeated generation should produce repeated results");
+
+ atomic_store_zu(&sb, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_zu(&sb, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ assert_zu_eq(ra, rb,
+ "Equivalent generation should produce equivalent results");
+
+ atomic_store_zu(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ rb = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ assert_zu_ne(ra, rb,
+ "Full-width results must not immediately repeat");
+
+ atomic_store_zu(&sa, 42, ATOMIC_RELAXED);
+ ra = prng_lg_range_zu(&sa, ZU(1) << (3 + LG_SIZEOF_PTR), atomic);
+ for (lg_range = (ZU(1) << (3 + LG_SIZEOF_PTR)) - 1; lg_range > 0;
+ lg_range--) {
+ atomic_store_zu(&sb, 42, ATOMIC_RELAXED);
+ rb = prng_lg_range_zu(&sb, lg_range, atomic);
+ assert_zu_eq((rb & (SIZE_T_MAX << lg_range)),
+ 0, "High order bits should be 0, lg_range=%u", lg_range);
+ assert_zu_eq(rb, (ra >> ((ZU(1) << (3 + LG_SIZEOF_PTR)) -
+ lg_range)), "Expected high order bits of full-width "
+ "result, lg_range=%u", lg_range);
+ }
+}
+
+TEST_BEGIN(test_prng_lg_range_u32_nonatomic) {
+ test_prng_lg_range_u32(false);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_lg_range_u32_atomic) {
+ test_prng_lg_range_u32(true);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_lg_range_u64_nonatomic) {
+ test_prng_lg_range_u64();
+}
+TEST_END
+
+TEST_BEGIN(test_prng_lg_range_zu_nonatomic) {
+ test_prng_lg_range_zu(false);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_lg_range_zu_atomic) {
+ test_prng_lg_range_zu(true);
+}
+TEST_END
+
+static void
+test_prng_range_u32(bool atomic) {
+ uint32_t range;
+#define MAX_RANGE 10000000
+#define RANGE_STEP 97
+#define NREPS 10
+
+ for (range = 2; range < MAX_RANGE; range += RANGE_STEP) {
+ atomic_u32_t s;
+ unsigned rep;
+
+ atomic_store_u32(&s, range, ATOMIC_RELAXED);
+ for (rep = 0; rep < NREPS; rep++) {
+ uint32_t r = prng_range_u32(&s, range, atomic);
+
+ assert_u32_lt(r, range, "Out of range");
+ }
+ }
+}
+
+static void
+test_prng_range_u64(void) {
+ uint64_t range;
+#define MAX_RANGE 10000000
+#define RANGE_STEP 97
+#define NREPS 10
+
+ for (range = 2; range < MAX_RANGE; range += RANGE_STEP) {
+ uint64_t s;
+ unsigned rep;
+
+ s = range;
+ for (rep = 0; rep < NREPS; rep++) {
+ uint64_t r = prng_range_u64(&s, range);
+
+ assert_u64_lt(r, range, "Out of range");
+ }
+ }
+}
+
+static void
+test_prng_range_zu(bool atomic) {
+ size_t range;
+#define MAX_RANGE 10000000
+#define RANGE_STEP 97
+#define NREPS 10
+
+ for (range = 2; range < MAX_RANGE; range += RANGE_STEP) {
+ atomic_zu_t s;
+ unsigned rep;
+
+ atomic_store_zu(&s, range, ATOMIC_RELAXED);
+ for (rep = 0; rep < NREPS; rep++) {
+ size_t r = prng_range_zu(&s, range, atomic);
+
+ assert_zu_lt(r, range, "Out of range");
+ }
+ }
+}
+
+TEST_BEGIN(test_prng_range_u32_nonatomic) {
+ test_prng_range_u32(false);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_range_u32_atomic) {
+ test_prng_range_u32(true);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_range_u64_nonatomic) {
+ test_prng_range_u64();
+}
+TEST_END
+
+TEST_BEGIN(test_prng_range_zu_nonatomic) {
+ test_prng_range_zu(false);
+}
+TEST_END
+
+TEST_BEGIN(test_prng_range_zu_atomic) {
+ test_prng_range_zu(true);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_prng_lg_range_u32_nonatomic,
+ test_prng_lg_range_u32_atomic,
+ test_prng_lg_range_u64_nonatomic,
+ test_prng_lg_range_zu_nonatomic,
+ test_prng_lg_range_zu_atomic,
+ test_prng_range_u32_nonatomic,
+ test_prng_range_u32_atomic,
+ test_prng_range_u64_nonatomic,
+ test_prng_range_zu_nonatomic,
+ test_prng_range_zu_atomic);
+}
diff --git a/deps/jemalloc/test/unit/prof_accum.c b/deps/jemalloc/test/unit/prof_accum.c
index fd229e0fd..252200635 100644
--- a/deps/jemalloc/test/unit/prof_accum.c
+++ b/deps/jemalloc/test/unit/prof_accum.c
@@ -1,36 +1,27 @@
#include "test/jemalloc_test.h"
-#define NTHREADS 4
-#define NALLOCS_PER_THREAD 50
-#define DUMP_INTERVAL 1
-#define BT_COUNT_CHECK_INTERVAL 5
-
-#ifdef JEMALLOC_PROF
-const char *malloc_conf =
- "prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0";
-#endif
+#define NTHREADS 4
+#define NALLOCS_PER_THREAD 50
+#define DUMP_INTERVAL 1
+#define BT_COUNT_CHECK_INTERVAL 5
static int
-prof_dump_open_intercept(bool propagate_err, const char *filename)
-{
+prof_dump_open_intercept(bool propagate_err, const char *filename) {
int fd;
fd = open("/dev/null", O_WRONLY);
assert_d_ne(fd, -1, "Unexpected open() failure");
- return (fd);
+ return fd;
}
static void *
-alloc_from_permuted_backtrace(unsigned thd_ind, unsigned iteration)
-{
-
- return (btalloc(1, thd_ind*NALLOCS_PER_THREAD + iteration));
+alloc_from_permuted_backtrace(unsigned thd_ind, unsigned iteration) {
+ return btalloc(1, thd_ind*NALLOCS_PER_THREAD + iteration);
}
static void *
-thd_start(void *varg)
-{
+thd_start(void *varg) {
unsigned thd_ind = *(unsigned *)varg;
size_t bt_count_prev, bt_count;
unsigned i_prev, i;
@@ -55,11 +46,10 @@ thd_start(void *varg)
}
}
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_idump)
-{
+TEST_BEGIN(test_idump) {
bool active;
thd_t thds[NTHREADS];
unsigned thd_args[NTHREADS];
@@ -68,8 +58,9 @@ TEST_BEGIN(test_idump)
test_skip_if(!config_prof);
active = true;
- assert_d_eq(mallctl("prof.active", NULL, NULL, &active, sizeof(active)),
- 0, "Unexpected mallctl failure while activating profiling");
+ assert_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active,
+ sizeof(active)), 0,
+ "Unexpected mallctl failure while activating profiling");
prof_dump_open = prof_dump_open_intercept;
@@ -77,15 +68,14 @@ TEST_BEGIN(test_idump)
thd_args[i] = i;
thd_create(&thds[i], thd_start, (void *)&thd_args[i]);
}
- for (i = 0; i < NTHREADS; i++)
+ for (i = 0; i < NTHREADS; i++) {
thd_join(thds[i], NULL);
+ }
}
TEST_END
int
-main(void)
-{
-
- return (test(
- test_idump));
+main(void) {
+ return test_no_reentrancy(
+ test_idump);
}
diff --git a/deps/jemalloc/test/unit/prof_accum.sh b/deps/jemalloc/test/unit/prof_accum.sh
new file mode 100644
index 000000000..b3e13fc54
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_accum.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0"
+fi
diff --git a/deps/jemalloc/test/unit/prof_active.c b/deps/jemalloc/test/unit/prof_active.c
index 814909572..850a24a77 100644
--- a/deps/jemalloc/test/unit/prof_active.c
+++ b/deps/jemalloc/test/unit/prof_active.c
@@ -1,18 +1,12 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_PROF
-const char *malloc_conf =
- "prof:true,prof_thread_active_init:false,lg_prof_sample:0";
-#endif
-
static void
-mallctl_bool_get(const char *name, bool expected, const char *func, int line)
-{
+mallctl_bool_get(const char *name, bool expected, const char *func, int line) {
bool old;
size_t sz;
sz = sizeof(old);
- assert_d_eq(mallctl(name, &old, &sz, NULL, 0), 0,
+ assert_d_eq(mallctl(name, (void *)&old, &sz, NULL, 0), 0,
"%s():%d: Unexpected mallctl failure reading %s", func, line, name);
assert_b_eq(old, expected, "%s():%d: Unexpected %s value", func, line,
name);
@@ -20,13 +14,13 @@ mallctl_bool_get(const char *name, bool expected, const char *func, int line)
static void
mallctl_bool_set(const char *name, bool old_expected, bool val_new,
- const char *func, int line)
-{
+ const char *func, int line) {
bool old;
size_t sz;
sz = sizeof(old);
- assert_d_eq(mallctl(name, &old, &sz, &val_new, sizeof(val_new)), 0,
+ assert_d_eq(mallctl(name, (void *)&old, &sz, (void *)&val_new,
+ sizeof(val_new)), 0,
"%s():%d: Unexpected mallctl failure reading/writing %s", func,
line, name);
assert_b_eq(old, old_expected, "%s():%d: Unexpected %s value", func,
@@ -35,50 +29,41 @@ mallctl_bool_set(const char *name, bool old_expected, bool val_new,
static void
mallctl_prof_active_get_impl(bool prof_active_old_expected, const char *func,
- int line)
-{
-
+ int line) {
mallctl_bool_get("prof.active", prof_active_old_expected, func, line);
}
-#define mallctl_prof_active_get(a) \
+#define mallctl_prof_active_get(a) \
mallctl_prof_active_get_impl(a, __func__, __LINE__)
static void
mallctl_prof_active_set_impl(bool prof_active_old_expected,
- bool prof_active_new, const char *func, int line)
-{
-
+ bool prof_active_new, const char *func, int line) {
mallctl_bool_set("prof.active", prof_active_old_expected,
prof_active_new, func, line);
}
-#define mallctl_prof_active_set(a, b) \
+#define mallctl_prof_active_set(a, b) \
mallctl_prof_active_set_impl(a, b, __func__, __LINE__)
static void
mallctl_thread_prof_active_get_impl(bool thread_prof_active_old_expected,
- const char *func, int line)
-{
-
+ const char *func, int line) {
mallctl_bool_get("thread.prof.active", thread_prof_active_old_expected,
func, line);
}
-#define mallctl_thread_prof_active_get(a) \
+#define mallctl_thread_prof_active_get(a) \
mallctl_thread_prof_active_get_impl(a, __func__, __LINE__)
static void
mallctl_thread_prof_active_set_impl(bool thread_prof_active_old_expected,
- bool thread_prof_active_new, const char *func, int line)
-{
-
+ bool thread_prof_active_new, const char *func, int line) {
mallctl_bool_set("thread.prof.active", thread_prof_active_old_expected,
thread_prof_active_new, func, line);
}
-#define mallctl_thread_prof_active_set(a, b) \
+#define mallctl_thread_prof_active_set(a, b) \
mallctl_thread_prof_active_set_impl(a, b, __func__, __LINE__)
static void
-prof_sampling_probe_impl(bool expect_sample, const char *func, int line)
-{
+prof_sampling_probe_impl(bool expect_sample, const char *func, int line) {
void *p;
size_t expected_backtraces = expect_sample ? 1 : 0;
@@ -90,12 +75,10 @@ prof_sampling_probe_impl(bool expect_sample, const char *func, int line)
"%s():%d: Unexpected backtrace count", func, line);
dallocx(p, 0);
}
-#define prof_sampling_probe(a) \
+#define prof_sampling_probe(a) \
prof_sampling_probe_impl(a, __func__, __LINE__)
-TEST_BEGIN(test_prof_active)
-{
-
+TEST_BEGIN(test_prof_active) {
test_skip_if(!config_prof);
mallctl_prof_active_get(true);
@@ -128,9 +111,7 @@ TEST_BEGIN(test_prof_active)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_prof_active));
+main(void) {
+ return test_no_reentrancy(
+ test_prof_active);
}
diff --git a/deps/jemalloc/test/unit/prof_active.sh b/deps/jemalloc/test/unit/prof_active.sh
new file mode 100644
index 000000000..0167cb10b
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_active.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,prof_thread_active_init:false,lg_prof_sample:0"
+fi
diff --git a/deps/jemalloc/test/unit/prof_gdump.c b/deps/jemalloc/test/unit/prof_gdump.c
index a0e6ee921..fcb434cb9 100644
--- a/deps/jemalloc/test/unit/prof_gdump.c
+++ b/deps/jemalloc/test/unit/prof_gdump.c
@@ -1,14 +1,9 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_PROF
-const char *malloc_conf = "prof:true,prof_active:false,prof_gdump:true";
-#endif
-
static bool did_prof_dump_open;
static int
-prof_dump_open_intercept(bool propagate_err, const char *filename)
-{
+prof_dump_open_intercept(bool propagate_err, const char *filename) {
int fd;
did_prof_dump_open = true;
@@ -16,11 +11,10 @@ prof_dump_open_intercept(bool propagate_err, const char *filename)
fd = open("/dev/null", O_WRONLY);
assert_d_ne(fd, -1, "Unexpected open() failure");
- return (fd);
+ return fd;
}
-TEST_BEGIN(test_gdump)
-{
+TEST_BEGIN(test_gdump) {
bool active, gdump, gdump_old;
void *p, *q, *r, *s;
size_t sz;
@@ -28,40 +22,41 @@ TEST_BEGIN(test_gdump)
test_skip_if(!config_prof);
active = true;
- assert_d_eq(mallctl("prof.active", NULL, NULL, &active, sizeof(active)),
- 0, "Unexpected mallctl failure while activating profiling");
+ assert_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active,
+ sizeof(active)), 0,
+ "Unexpected mallctl failure while activating profiling");
prof_dump_open = prof_dump_open_intercept;
did_prof_dump_open = false;
- p = mallocx(chunksize, 0);
+ p = mallocx((1U << LG_LARGE_MINCLASS), 0);
assert_ptr_not_null(p, "Unexpected mallocx() failure");
assert_true(did_prof_dump_open, "Expected a profile dump");
did_prof_dump_open = false;
- q = mallocx(chunksize, 0);
+ q = mallocx((1U << LG_LARGE_MINCLASS), 0);
assert_ptr_not_null(q, "Unexpected mallocx() failure");
assert_true(did_prof_dump_open, "Expected a profile dump");
gdump = false;
sz = sizeof(gdump_old);
- assert_d_eq(mallctl("prof.gdump", &gdump_old, &sz, &gdump,
- sizeof(gdump)), 0,
+ assert_d_eq(mallctl("prof.gdump", (void *)&gdump_old, &sz,
+ (void *)&gdump, sizeof(gdump)), 0,
"Unexpected mallctl failure while disabling prof.gdump");
assert(gdump_old);
did_prof_dump_open = false;
- r = mallocx(chunksize, 0);
+ r = mallocx((1U << LG_LARGE_MINCLASS), 0);
assert_ptr_not_null(q, "Unexpected mallocx() failure");
assert_false(did_prof_dump_open, "Unexpected profile dump");
gdump = true;
sz = sizeof(gdump_old);
- assert_d_eq(mallctl("prof.gdump", &gdump_old, &sz, &gdump,
- sizeof(gdump)), 0,
+ assert_d_eq(mallctl("prof.gdump", (void *)&gdump_old, &sz,
+ (void *)&gdump, sizeof(gdump)), 0,
"Unexpected mallctl failure while enabling prof.gdump");
assert(!gdump_old);
did_prof_dump_open = false;
- s = mallocx(chunksize, 0);
+ s = mallocx((1U << LG_LARGE_MINCLASS), 0);
assert_ptr_not_null(q, "Unexpected mallocx() failure");
assert_true(did_prof_dump_open, "Expected a profile dump");
@@ -73,9 +68,7 @@ TEST_BEGIN(test_gdump)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_gdump));
+main(void) {
+ return test_no_reentrancy(
+ test_gdump);
}
diff --git a/deps/jemalloc/test/unit/prof_gdump.sh b/deps/jemalloc/test/unit/prof_gdump.sh
new file mode 100644
index 000000000..3f600d200
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_gdump.sh
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,prof_active:false,prof_gdump:true"
+fi
+
diff --git a/deps/jemalloc/test/unit/prof_idump.c b/deps/jemalloc/test/unit/prof_idump.c
index bdea53ecd..1cc6c98cd 100644
--- a/deps/jemalloc/test/unit/prof_idump.c
+++ b/deps/jemalloc/test/unit/prof_idump.c
@@ -1,16 +1,9 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_PROF
-const char *malloc_conf =
- "prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0,"
- "lg_prof_interval:0";
-#endif
-
static bool did_prof_dump_open;
static int
-prof_dump_open_intercept(bool propagate_err, const char *filename)
-{
+prof_dump_open_intercept(bool propagate_err, const char *filename) {
int fd;
did_prof_dump_open = true;
@@ -18,19 +11,19 @@ prof_dump_open_intercept(bool propagate_err, const char *filename)
fd = open("/dev/null", O_WRONLY);
assert_d_ne(fd, -1, "Unexpected open() failure");
- return (fd);
+ return fd;
}
-TEST_BEGIN(test_idump)
-{
+TEST_BEGIN(test_idump) {
bool active;
void *p;
test_skip_if(!config_prof);
active = true;
- assert_d_eq(mallctl("prof.active", NULL, NULL, &active, sizeof(active)),
- 0, "Unexpected mallctl failure while activating profiling");
+ assert_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active,
+ sizeof(active)), 0,
+ "Unexpected mallctl failure while activating profiling");
prof_dump_open = prof_dump_open_intercept;
@@ -43,9 +36,7 @@ TEST_BEGIN(test_idump)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_idump));
+main(void) {
+ return test(
+ test_idump);
}
diff --git a/deps/jemalloc/test/unit/prof_idump.sh b/deps/jemalloc/test/unit/prof_idump.sh
new file mode 100644
index 000000000..4dc599a30
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_idump.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+export MALLOC_CONF="tcache:false"
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="${MALLOC_CONF},prof:true,prof_accum:true,prof_active:false,lg_prof_sample:0,lg_prof_interval:0"
+fi
+
+
diff --git a/deps/jemalloc/test/unit/prof_reset.c b/deps/jemalloc/test/unit/prof_reset.c
index 69983e5e5..7cce42d27 100644
--- a/deps/jemalloc/test/unit/prof_reset.c
+++ b/deps/jemalloc/test/unit/prof_reset.c
@@ -1,52 +1,42 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_PROF
-const char *malloc_conf =
- "prof:true,prof_active:false,lg_prof_sample:0";
-#endif
-
static int
-prof_dump_open_intercept(bool propagate_err, const char *filename)
-{
+prof_dump_open_intercept(bool propagate_err, const char *filename) {
int fd;
fd = open("/dev/null", O_WRONLY);
assert_d_ne(fd, -1, "Unexpected open() failure");
- return (fd);
+ return fd;
}
static void
-set_prof_active(bool active)
-{
-
- assert_d_eq(mallctl("prof.active", NULL, NULL, &active, sizeof(active)),
- 0, "Unexpected mallctl failure");
+set_prof_active(bool active) {
+ assert_d_eq(mallctl("prof.active", NULL, NULL, (void *)&active,
+ sizeof(active)), 0, "Unexpected mallctl failure");
}
static size_t
-get_lg_prof_sample(void)
-{
+get_lg_prof_sample(void) {
size_t lg_prof_sample;
size_t sz = sizeof(size_t);
- assert_d_eq(mallctl("prof.lg_sample", &lg_prof_sample, &sz, NULL, 0), 0,
+ assert_d_eq(mallctl("prof.lg_sample", (void *)&lg_prof_sample, &sz,
+ NULL, 0), 0,
"Unexpected mallctl failure while reading profiling sample rate");
- return (lg_prof_sample);
+ return lg_prof_sample;
}
static void
-do_prof_reset(size_t lg_prof_sample)
-{
+do_prof_reset(size_t lg_prof_sample) {
assert_d_eq(mallctl("prof.reset", NULL, NULL,
- &lg_prof_sample, sizeof(size_t)), 0,
+ (void *)&lg_prof_sample, sizeof(size_t)), 0,
"Unexpected mallctl failure while resetting profile data");
assert_zu_eq(lg_prof_sample, get_lg_prof_sample(),
"Expected profile sample rate change");
}
-TEST_BEGIN(test_prof_reset_basic)
-{
+TEST_BEGIN(test_prof_reset_basic) {
size_t lg_prof_sample_orig, lg_prof_sample, lg_prof_sample_next;
size_t sz;
unsigned i;
@@ -54,8 +44,8 @@ TEST_BEGIN(test_prof_reset_basic)
test_skip_if(!config_prof);
sz = sizeof(size_t);
- assert_d_eq(mallctl("opt.lg_prof_sample", &lg_prof_sample_orig, &sz,
- NULL, 0), 0,
+ assert_d_eq(mallctl("opt.lg_prof_sample", (void *)&lg_prof_sample_orig,
+ &sz, NULL, 0), 0,
"Unexpected mallctl failure while reading profiling sample rate");
assert_zu_eq(lg_prof_sample_orig, 0,
"Unexpected profiling sample rate");
@@ -94,17 +84,15 @@ TEST_END
bool prof_dump_header_intercepted = false;
prof_cnt_t cnt_all_copy = {0, 0, 0, 0};
static bool
-prof_dump_header_intercept(bool propagate_err, const prof_cnt_t *cnt_all)
-{
-
+prof_dump_header_intercept(tsdn_t *tsdn, bool propagate_err,
+ const prof_cnt_t *cnt_all) {
prof_dump_header_intercepted = true;
memcpy(&cnt_all_copy, cnt_all, sizeof(prof_cnt_t));
- return (false);
+ return false;
}
-TEST_BEGIN(test_prof_reset_cleanup)
-{
+TEST_BEGIN(test_prof_reset_cleanup) {
void *p;
prof_dump_header_t *prof_dump_header_orig;
@@ -142,14 +130,13 @@ TEST_BEGIN(test_prof_reset_cleanup)
}
TEST_END
-#define NTHREADS 4
-#define NALLOCS_PER_THREAD (1U << 13)
-#define OBJ_RING_BUF_COUNT 1531
-#define RESET_INTERVAL (1U << 10)
-#define DUMP_INTERVAL 3677
+#define NTHREADS 4
+#define NALLOCS_PER_THREAD (1U << 13)
+#define OBJ_RING_BUF_COUNT 1531
+#define RESET_INTERVAL (1U << 10)
+#define DUMP_INTERVAL 3677
static void *
-thd_start(void *varg)
-{
+thd_start(void *varg) {
unsigned thd_ind = *(unsigned *)varg;
unsigned i;
void *objs[OBJ_RING_BUF_COUNT];
@@ -189,11 +176,10 @@ thd_start(void *varg)
}
}
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_prof_reset)
-{
+TEST_BEGIN(test_prof_reset) {
size_t lg_prof_sample_orig;
thd_t thds[NTHREADS];
unsigned thd_args[NTHREADS];
@@ -216,8 +202,9 @@ TEST_BEGIN(test_prof_reset)
thd_args[i] = i;
thd_create(&thds[i], thd_start, (void *)&thd_args[i]);
}
- for (i = 0; i < NTHREADS; i++)
+ for (i = 0; i < NTHREADS; i++) {
thd_join(thds[i], NULL);
+ }
assert_zu_eq(prof_bt_count(), bt_count,
"Unexpected bactrace count change");
@@ -236,9 +223,8 @@ TEST_END
#undef DUMP_INTERVAL
/* Test sampling at the same allocation site across resets. */
-#define NITER 10
-TEST_BEGIN(test_xallocx)
-{
+#define NITER 10
+TEST_BEGIN(test_xallocx) {
size_t lg_prof_sample_orig;
unsigned i;
void *ptrs[NITER];
@@ -288,15 +274,13 @@ TEST_END
#undef NITER
int
-main(void)
-{
-
+main(void) {
/* Intercept dumping prior to running any tests. */
prof_dump_open = prof_dump_open_intercept;
- return (test(
+ return test_no_reentrancy(
test_prof_reset_basic,
test_prof_reset_cleanup,
test_prof_reset,
- test_xallocx));
+ test_xallocx);
}
diff --git a/deps/jemalloc/test/unit/prof_reset.sh b/deps/jemalloc/test/unit/prof_reset.sh
new file mode 100644
index 000000000..43c516a08
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_reset.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,prof_active:false,lg_prof_sample:0"
+fi
diff --git a/deps/jemalloc/test/unit/prof_tctx.c b/deps/jemalloc/test/unit/prof_tctx.c
new file mode 100644
index 000000000..ff3b2b0ca
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_tctx.c
@@ -0,0 +1,46 @@
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_prof_realloc) {
+ tsdn_t *tsdn;
+ int flags;
+ void *p, *q;
+ prof_tctx_t *tctx_p, *tctx_q;
+ uint64_t curobjs_0, curobjs_1, curobjs_2, curobjs_3;
+
+ test_skip_if(!config_prof);
+
+ tsdn = tsdn_fetch();
+ flags = MALLOCX_TCACHE_NONE;
+
+ prof_cnt_all(&curobjs_0, NULL, NULL, NULL);
+ p = mallocx(1024, flags);
+ assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ tctx_p = prof_tctx_get(tsdn, p, NULL);
+ assert_ptr_ne(tctx_p, (prof_tctx_t *)(uintptr_t)1U,
+ "Expected valid tctx");
+ prof_cnt_all(&curobjs_1, NULL, NULL, NULL);
+ assert_u64_eq(curobjs_0 + 1, curobjs_1,
+ "Allocation should have increased sample size");
+
+ q = rallocx(p, 2048, flags);
+ assert_ptr_ne(p, q, "Expected move");
+ assert_ptr_not_null(p, "Unexpected rmallocx() failure");
+ tctx_q = prof_tctx_get(tsdn, q, NULL);
+ assert_ptr_ne(tctx_q, (prof_tctx_t *)(uintptr_t)1U,
+ "Expected valid tctx");
+ prof_cnt_all(&curobjs_2, NULL, NULL, NULL);
+ assert_u64_eq(curobjs_1, curobjs_2,
+ "Reallocation should not have changed sample size");
+
+ dallocx(q, flags);
+ prof_cnt_all(&curobjs_3, NULL, NULL, NULL);
+ assert_u64_eq(curobjs_0, curobjs_3,
+ "Sample size should have returned to base level");
+}
+TEST_END
+
+int
+main(void) {
+ return test_no_reentrancy(
+ test_prof_realloc);
+}
diff --git a/deps/jemalloc/test/unit/prof_tctx.sh b/deps/jemalloc/test/unit/prof_tctx.sh
new file mode 100644
index 000000000..8fcc7d8a7
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_tctx.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,lg_prof_sample:0"
+fi
diff --git a/deps/jemalloc/test/unit/prof_thread_name.c b/deps/jemalloc/test/unit/prof_thread_name.c
index f501158d7..c9c2a2b76 100644
--- a/deps/jemalloc/test/unit/prof_thread_name.c
+++ b/deps/jemalloc/test/unit/prof_thread_name.c
@@ -1,42 +1,35 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_PROF
-const char *malloc_conf = "prof:true,prof_active:false";
-#endif
-
static void
mallctl_thread_name_get_impl(const char *thread_name_expected, const char *func,
- int line)
-{
+ int line) {
const char *thread_name_old;
size_t sz;
sz = sizeof(thread_name_old);
- assert_d_eq(mallctl("thread.prof.name", &thread_name_old, &sz, NULL, 0),
- 0, "%s():%d: Unexpected mallctl failure reading thread.prof.name",
+ assert_d_eq(mallctl("thread.prof.name", (void *)&thread_name_old, &sz,
+ NULL, 0), 0,
+ "%s():%d: Unexpected mallctl failure reading thread.prof.name",
func, line);
assert_str_eq(thread_name_old, thread_name_expected,
"%s():%d: Unexpected thread.prof.name value", func, line);
}
-#define mallctl_thread_name_get(a) \
+#define mallctl_thread_name_get(a) \
mallctl_thread_name_get_impl(a, __func__, __LINE__)
static void
mallctl_thread_name_set_impl(const char *thread_name, const char *func,
- int line)
-{
-
- assert_d_eq(mallctl("thread.prof.name", NULL, NULL, &thread_name,
- sizeof(thread_name)), 0,
+ int line) {
+ assert_d_eq(mallctl("thread.prof.name", NULL, NULL,
+ (void *)&thread_name, sizeof(thread_name)), 0,
"%s():%d: Unexpected mallctl failure reading thread.prof.name",
func, line);
mallctl_thread_name_get_impl(thread_name, func, line);
}
-#define mallctl_thread_name_set(a) \
+#define mallctl_thread_name_set(a) \
mallctl_thread_name_set_impl(a, __func__, __LINE__)
-TEST_BEGIN(test_prof_thread_name_validation)
-{
+TEST_BEGIN(test_prof_thread_name_validation) {
const char *thread_name;
test_skip_if(!config_prof);
@@ -46,15 +39,15 @@ TEST_BEGIN(test_prof_thread_name_validation)
/* NULL input shouldn't be allowed. */
thread_name = NULL;
- assert_d_eq(mallctl("thread.prof.name", NULL, NULL, &thread_name,
- sizeof(thread_name)), EFAULT,
+ assert_d_eq(mallctl("thread.prof.name", NULL, NULL,
+ (void *)&thread_name, sizeof(thread_name)), EFAULT,
"Unexpected mallctl result writing \"%s\" to thread.prof.name",
thread_name);
/* '\n' shouldn't be allowed. */
thread_name = "hi\nthere";
- assert_d_eq(mallctl("thread.prof.name", NULL, NULL, &thread_name,
- sizeof(thread_name)), EFAULT,
+ assert_d_eq(mallctl("thread.prof.name", NULL, NULL,
+ (void *)&thread_name, sizeof(thread_name)), EFAULT,
"Unexpected mallctl result writing \"%s\" to thread.prof.name",
thread_name);
@@ -64,8 +57,9 @@ TEST_BEGIN(test_prof_thread_name_validation)
size_t sz;
sz = sizeof(thread_name_old);
- assert_d_eq(mallctl("thread.prof.name", &thread_name_old, &sz,
- &thread_name, sizeof(thread_name)), EPERM,
+ assert_d_eq(mallctl("thread.prof.name",
+ (void *)&thread_name_old, &sz, (void *)&thread_name,
+ sizeof(thread_name)), EPERM,
"Unexpected mallctl result writing \"%s\" to "
"thread.prof.name", thread_name);
}
@@ -74,11 +68,10 @@ TEST_BEGIN(test_prof_thread_name_validation)
}
TEST_END
-#define NTHREADS 4
-#define NRESET 25
+#define NTHREADS 4
+#define NRESET 25
static void *
-thd_start(void *varg)
-{
+thd_start(void *varg) {
unsigned thd_ind = *(unsigned *)varg;
char thread_name[16] = "";
unsigned i;
@@ -97,11 +90,10 @@ thd_start(void *varg)
mallctl_thread_name_set(thread_name);
mallctl_thread_name_set("");
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_prof_thread_name_threaded)
-{
+TEST_BEGIN(test_prof_thread_name_threaded) {
thd_t thds[NTHREADS];
unsigned thd_args[NTHREADS];
unsigned i;
@@ -112,18 +104,17 @@ TEST_BEGIN(test_prof_thread_name_threaded)
thd_args[i] = i;
thd_create(&thds[i], thd_start, (void *)&thd_args[i]);
}
- for (i = 0; i < NTHREADS; i++)
+ for (i = 0; i < NTHREADS; i++) {
thd_join(thds[i], NULL);
+ }
}
TEST_END
#undef NTHREADS
#undef NRESET
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_prof_thread_name_validation,
- test_prof_thread_name_threaded));
+ test_prof_thread_name_threaded);
}
diff --git a/deps/jemalloc/test/unit/prof_thread_name.sh b/deps/jemalloc/test/unit/prof_thread_name.sh
new file mode 100644
index 000000000..298c1058e
--- /dev/null
+++ b/deps/jemalloc/test/unit/prof_thread_name.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_prof}" = "x1" ] ; then
+ export MALLOC_CONF="prof:true,prof_active:false"
+fi
diff --git a/deps/jemalloc/test/unit/ql.c b/deps/jemalloc/test/unit/ql.c
index 05fad450f..b76c24c41 100644
--- a/deps/jemalloc/test/unit/ql.c
+++ b/deps/jemalloc/test/unit/ql.c
@@ -1,7 +1,9 @@
#include "test/jemalloc_test.h"
+#include "jemalloc/internal/ql.h"
+
/* Number of ring entries, in [2..26]. */
-#define NENTRIES 9
+#define NENTRIES 9
typedef struct list_s list_t;
typedef ql_head(list_t) list_head_t;
@@ -12,8 +14,7 @@ struct list_s {
};
static void
-test_empty_list(list_head_t *head)
-{
+test_empty_list(list_head_t *head) {
list_t *t;
unsigned i;
@@ -34,8 +35,7 @@ test_empty_list(list_head_t *head)
assert_u_eq(i, 0, "Unexpected element for empty list");
}
-TEST_BEGIN(test_ql_empty)
-{
+TEST_BEGIN(test_ql_empty) {
list_head_t head;
ql_new(&head);
@@ -44,8 +44,7 @@ TEST_BEGIN(test_ql_empty)
TEST_END
static void
-init_entries(list_t *entries, unsigned nentries)
-{
+init_entries(list_t *entries, unsigned nentries) {
unsigned i;
for (i = 0; i < nentries; i++) {
@@ -55,8 +54,7 @@ init_entries(list_t *entries, unsigned nentries)
}
static void
-test_entries_list(list_head_t *head, list_t *entries, unsigned nentries)
-{
+test_entries_list(list_head_t *head, list_t *entries, unsigned nentries) {
list_t *t;
unsigned i;
@@ -91,31 +89,31 @@ test_entries_list(list_head_t *head, list_t *entries, unsigned nentries)
}
}
-TEST_BEGIN(test_ql_tail_insert)
-{
+TEST_BEGIN(test_ql_tail_insert) {
list_head_t head;
list_t entries[NENTRIES];
unsigned i;
ql_new(&head);
init_entries(entries, sizeof(entries)/sizeof(list_t));
- for (i = 0; i < NENTRIES; i++)
+ for (i = 0; i < NENTRIES; i++) {
ql_tail_insert(&head, &entries[i], link);
+ }
test_entries_list(&head, entries, NENTRIES);
}
TEST_END
-TEST_BEGIN(test_ql_tail_remove)
-{
+TEST_BEGIN(test_ql_tail_remove) {
list_head_t head;
list_t entries[NENTRIES];
unsigned i;
ql_new(&head);
init_entries(entries, sizeof(entries)/sizeof(list_t));
- for (i = 0; i < NENTRIES; i++)
+ for (i = 0; i < NENTRIES; i++) {
ql_tail_insert(&head, &entries[i], link);
+ }
for (i = 0; i < NENTRIES; i++) {
test_entries_list(&head, entries, NENTRIES-i);
@@ -125,31 +123,31 @@ TEST_BEGIN(test_ql_tail_remove)
}
TEST_END
-TEST_BEGIN(test_ql_head_insert)
-{
+TEST_BEGIN(test_ql_head_insert) {
list_head_t head;
list_t entries[NENTRIES];
unsigned i;
ql_new(&head);
init_entries(entries, sizeof(entries)/sizeof(list_t));
- for (i = 0; i < NENTRIES; i++)
+ for (i = 0; i < NENTRIES; i++) {
ql_head_insert(&head, &entries[NENTRIES-i-1], link);
+ }
test_entries_list(&head, entries, NENTRIES);
}
TEST_END
-TEST_BEGIN(test_ql_head_remove)
-{
+TEST_BEGIN(test_ql_head_remove) {
list_head_t head;
list_t entries[NENTRIES];
unsigned i;
ql_new(&head);
init_entries(entries, sizeof(entries)/sizeof(list_t));
- for (i = 0; i < NENTRIES; i++)
+ for (i = 0; i < NENTRIES; i++) {
ql_head_insert(&head, &entries[NENTRIES-i-1], link);
+ }
for (i = 0; i < NENTRIES; i++) {
test_entries_list(&head, &entries[i], NENTRIES-i);
@@ -159,8 +157,7 @@ TEST_BEGIN(test_ql_head_remove)
}
TEST_END
-TEST_BEGIN(test_ql_insert)
-{
+TEST_BEGIN(test_ql_insert) {
list_head_t head;
list_t entries[8];
list_t *a, *b, *c, *d, *e, *f, *g, *h;
@@ -196,14 +193,12 @@ TEST_BEGIN(test_ql_insert)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_ql_empty,
test_ql_tail_insert,
test_ql_tail_remove,
test_ql_head_insert,
test_ql_head_remove,
- test_ql_insert));
+ test_ql_insert);
}
diff --git a/deps/jemalloc/test/unit/qr.c b/deps/jemalloc/test/unit/qr.c
index a2a2d902b..271a10953 100644
--- a/deps/jemalloc/test/unit/qr.c
+++ b/deps/jemalloc/test/unit/qr.c
@@ -1,9 +1,11 @@
#include "test/jemalloc_test.h"
+#include "jemalloc/internal/qr.h"
+
/* Number of ring entries, in [2..26]. */
-#define NENTRIES 9
+#define NENTRIES 9
/* Split index, in [1..NENTRIES). */
-#define SPLIT_INDEX 5
+#define SPLIT_INDEX 5
typedef struct ring_s ring_t;
@@ -13,8 +15,7 @@ struct ring_s {
};
static void
-init_entries(ring_t *entries)
-{
+init_entries(ring_t *entries) {
unsigned i;
for (i = 0; i < NENTRIES; i++) {
@@ -24,8 +25,7 @@ init_entries(ring_t *entries)
}
static void
-test_independent_entries(ring_t *entries)
-{
+test_independent_entries(ring_t *entries) {
ring_t *t;
unsigned i, j;
@@ -61,8 +61,7 @@ test_independent_entries(ring_t *entries)
}
}
-TEST_BEGIN(test_qr_one)
-{
+TEST_BEGIN(test_qr_one) {
ring_t entries[NENTRIES];
init_entries(entries);
@@ -71,8 +70,7 @@ TEST_BEGIN(test_qr_one)
TEST_END
static void
-test_entries_ring(ring_t *entries)
-{
+test_entries_ring(ring_t *entries) {
ring_t *t;
unsigned i, j;
@@ -104,27 +102,27 @@ test_entries_ring(ring_t *entries)
}
}
-TEST_BEGIN(test_qr_after_insert)
-{
+TEST_BEGIN(test_qr_after_insert) {
ring_t entries[NENTRIES];
unsigned i;
init_entries(entries);
- for (i = 1; i < NENTRIES; i++)
+ for (i = 1; i < NENTRIES; i++) {
qr_after_insert(&entries[i - 1], &entries[i], link);
+ }
test_entries_ring(entries);
}
TEST_END
-TEST_BEGIN(test_qr_remove)
-{
+TEST_BEGIN(test_qr_remove) {
ring_t entries[NENTRIES];
ring_t *t;
unsigned i, j;
init_entries(entries);
- for (i = 1; i < NENTRIES; i++)
+ for (i = 1; i < NENTRIES; i++) {
qr_after_insert(&entries[i - 1], &entries[i], link);
+ }
for (i = 0; i < NENTRIES; i++) {
j = 0;
@@ -145,15 +143,15 @@ TEST_BEGIN(test_qr_remove)
}
TEST_END
-TEST_BEGIN(test_qr_before_insert)
-{
+TEST_BEGIN(test_qr_before_insert) {
ring_t entries[NENTRIES];
ring_t *t;
unsigned i, j;
init_entries(entries);
- for (i = 1; i < NENTRIES; i++)
+ for (i = 1; i < NENTRIES; i++) {
qr_before_insert(&entries[i - 1], &entries[i], link);
+ }
for (i = 0; i < NENTRIES; i++) {
j = 0;
qr_foreach(t, &entries[i], link) {
@@ -184,8 +182,7 @@ TEST_BEGIN(test_qr_before_insert)
TEST_END
static void
-test_split_entries(ring_t *entries)
-{
+test_split_entries(ring_t *entries) {
ring_t *t;
unsigned i, j;
@@ -206,43 +203,41 @@ test_split_entries(ring_t *entries)
}
}
-TEST_BEGIN(test_qr_meld_split)
-{
+TEST_BEGIN(test_qr_meld_split) {
ring_t entries[NENTRIES];
unsigned i;
init_entries(entries);
- for (i = 1; i < NENTRIES; i++)
+ for (i = 1; i < NENTRIES; i++) {
qr_after_insert(&entries[i - 1], &entries[i], link);
+ }
- qr_split(&entries[0], &entries[SPLIT_INDEX], link);
+ qr_split(&entries[0], &entries[SPLIT_INDEX], ring_t, link);
test_split_entries(entries);
- qr_meld(&entries[0], &entries[SPLIT_INDEX], link);
+ qr_meld(&entries[0], &entries[SPLIT_INDEX], ring_t, link);
test_entries_ring(entries);
- qr_meld(&entries[0], &entries[SPLIT_INDEX], link);
+ qr_meld(&entries[0], &entries[SPLIT_INDEX], ring_t, link);
test_split_entries(entries);
- qr_split(&entries[0], &entries[SPLIT_INDEX], link);
+ qr_split(&entries[0], &entries[SPLIT_INDEX], ring_t, link);
test_entries_ring(entries);
- qr_split(&entries[0], &entries[0], link);
+ qr_split(&entries[0], &entries[0], ring_t, link);
test_entries_ring(entries);
- qr_meld(&entries[0], &entries[0], link);
+ qr_meld(&entries[0], &entries[0], ring_t, link);
test_entries_ring(entries);
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_qr_one,
test_qr_after_insert,
test_qr_remove,
test_qr_before_insert,
- test_qr_meld_split));
+ test_qr_meld_split);
}
diff --git a/deps/jemalloc/test/unit/quarantine.c b/deps/jemalloc/test/unit/quarantine.c
deleted file mode 100644
index bbd48a51d..000000000
--- a/deps/jemalloc/test/unit/quarantine.c
+++ /dev/null
@@ -1,108 +0,0 @@
-#include "test/jemalloc_test.h"
-
-#define QUARANTINE_SIZE 8192
-#define STRINGIFY_HELPER(x) #x
-#define STRINGIFY(x) STRINGIFY_HELPER(x)
-
-#ifdef JEMALLOC_FILL
-const char *malloc_conf = "abort:false,junk:true,redzone:true,quarantine:"
- STRINGIFY(QUARANTINE_SIZE);
-#endif
-
-void
-quarantine_clear(void)
-{
- void *p;
-
- p = mallocx(QUARANTINE_SIZE*2, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
- dallocx(p, 0);
-}
-
-TEST_BEGIN(test_quarantine)
-{
-#define SZ ZU(256)
-#define NQUARANTINED (QUARANTINE_SIZE/SZ)
- void *quarantined[NQUARANTINED+1];
- size_t i, j;
-
- test_skip_if(!config_fill);
-
- assert_zu_eq(nallocx(SZ, 0), SZ,
- "SZ=%zu does not precisely equal a size class", SZ);
-
- quarantine_clear();
-
- /*
- * Allocate enough regions to completely fill the quarantine, plus one
- * more. The last iteration occurs with a completely full quarantine,
- * but no regions should be drained from the quarantine until the last
- * deallocation occurs. Therefore no region recycling should occur
- * until after this loop completes.
- */
- for (i = 0; i < NQUARANTINED+1; i++) {
- void *p = mallocx(SZ, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
- quarantined[i] = p;
- dallocx(p, 0);
- for (j = 0; j < i; j++) {
- assert_ptr_ne(p, quarantined[j],
- "Quarantined region recycled too early; "
- "i=%zu, j=%zu", i, j);
- }
- }
-#undef NQUARANTINED
-#undef SZ
-}
-TEST_END
-
-static bool detected_redzone_corruption;
-
-static void
-arena_redzone_corruption_replacement(void *ptr, size_t usize, bool after,
- size_t offset, uint8_t byte)
-{
-
- detected_redzone_corruption = true;
-}
-
-TEST_BEGIN(test_quarantine_redzone)
-{
- char *s;
- arena_redzone_corruption_t *arena_redzone_corruption_orig;
-
- test_skip_if(!config_fill);
-
- arena_redzone_corruption_orig = arena_redzone_corruption;
- arena_redzone_corruption = arena_redzone_corruption_replacement;
-
- /* Test underflow. */
- detected_redzone_corruption = false;
- s = (char *)mallocx(1, 0);
- assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
- s[-1] = 0xbb;
- dallocx(s, 0);
- assert_true(detected_redzone_corruption,
- "Did not detect redzone corruption");
-
- /* Test overflow. */
- detected_redzone_corruption = false;
- s = (char *)mallocx(1, 0);
- assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
- s[sallocx(s, 0)] = 0xbb;
- dallocx(s, 0);
- assert_true(detected_redzone_corruption,
- "Did not detect redzone corruption");
-
- arena_redzone_corruption = arena_redzone_corruption_orig;
-}
-TEST_END
-
-int
-main(void)
-{
-
- return (test(
- test_quarantine,
- test_quarantine_redzone));
-}
diff --git a/deps/jemalloc/test/unit/rb.c b/deps/jemalloc/test/unit/rb.c
index b38eb0e33..65c049207 100644
--- a/deps/jemalloc/test/unit/rb.c
+++ b/deps/jemalloc/test/unit/rb.c
@@ -1,27 +1,29 @@
#include "test/jemalloc_test.h"
-#define rbtn_black_height(a_type, a_field, a_rbt, r_height) do { \
- a_type *rbp_bh_t; \
- for (rbp_bh_t = (a_rbt)->rbt_root, (r_height) = 0; \
- rbp_bh_t != &(a_rbt)->rbt_nil; \
- rbp_bh_t = rbtn_left_get(a_type, a_field, rbp_bh_t)) { \
- if (!rbtn_red_get(a_type, a_field, rbp_bh_t)) { \
- (r_height)++; \
+#include "jemalloc/internal/rb.h"
+
+#define rbtn_black_height(a_type, a_field, a_rbt, r_height) do { \
+ a_type *rbp_bh_t; \
+ for (rbp_bh_t = (a_rbt)->rbt_root, (r_height) = 0; rbp_bh_t != \
+ NULL; rbp_bh_t = rbtn_left_get(a_type, a_field, \
+ rbp_bh_t)) { \
+ if (!rbtn_red_get(a_type, a_field, rbp_bh_t)) { \
+ (r_height)++; \
+ } \
} \
- } \
} while (0)
typedef struct node_s node_t;
struct node_s {
-#define NODE_MAGIC 0x9823af7e
+#define NODE_MAGIC 0x9823af7e
uint32_t magic;
rb_node(node_t) link;
uint64_t key;
};
static int
-node_cmp(node_t *a, node_t *b) {
+node_cmp(const node_t *a, const node_t *b) {
int ret;
assert_u32_eq(a->magic, NODE_MAGIC, "Bad magic");
@@ -36,14 +38,13 @@ node_cmp(node_t *a, node_t *b) {
ret = (((uintptr_t)a) > ((uintptr_t)b))
- (((uintptr_t)a) < ((uintptr_t)b));
}
- return (ret);
+ return ret;
}
typedef rb_tree(node_t) tree_t;
rb_gen(static, tree_, tree_t, node_t, link, node_cmp);
-TEST_BEGIN(test_rb_empty)
-{
+TEST_BEGIN(test_rb_empty) {
tree_t tree;
node_t key;
@@ -68,47 +69,56 @@ TEST_BEGIN(test_rb_empty)
TEST_END
static unsigned
-tree_recurse(node_t *node, unsigned black_height, unsigned black_depth,
- node_t *nil)
-{
+tree_recurse(node_t *node, unsigned black_height, unsigned black_depth) {
unsigned ret = 0;
- node_t *left_node = rbtn_left_get(node_t, link, node);
- node_t *right_node = rbtn_right_get(node_t, link, node);
+ node_t *left_node;
+ node_t *right_node;
- if (!rbtn_red_get(node_t, link, node))
+ if (node == NULL) {
+ return ret;
+ }
+
+ left_node = rbtn_left_get(node_t, link, node);
+ right_node = rbtn_right_get(node_t, link, node);
+
+ if (!rbtn_red_get(node_t, link, node)) {
black_depth++;
+ }
/* Red nodes must be interleaved with black nodes. */
if (rbtn_red_get(node_t, link, node)) {
- assert_false(rbtn_red_get(node_t, link, left_node),
- "Node should be black");
- assert_false(rbtn_red_get(node_t, link, right_node),
- "Node should be black");
+ if (left_node != NULL) {
+ assert_false(rbtn_red_get(node_t, link, left_node),
+ "Node should be black");
+ }
+ if (right_node != NULL) {
+ assert_false(rbtn_red_get(node_t, link, right_node),
+ "Node should be black");
+ }
}
- if (node == nil)
- return (ret);
/* Self. */
assert_u32_eq(node->magic, NODE_MAGIC, "Bad magic");
/* Left subtree. */
- if (left_node != nil)
- ret += tree_recurse(left_node, black_height, black_depth, nil);
- else
+ if (left_node != NULL) {
+ ret += tree_recurse(left_node, black_height, black_depth);
+ } else {
ret += (black_depth != black_height);
+ }
/* Right subtree. */
- if (right_node != nil)
- ret += tree_recurse(right_node, black_height, black_depth, nil);
- else
+ if (right_node != NULL) {
+ ret += tree_recurse(right_node, black_height, black_depth);
+ } else {
ret += (black_depth != black_height);
+ }
- return (ret);
+ return ret;
}
static node_t *
-tree_iterate_cb(tree_t *tree, node_t *node, void *data)
-{
+tree_iterate_cb(tree_t *tree, node_t *node, void *data) {
unsigned *i = (unsigned *)data;
node_t *search_node;
@@ -131,34 +141,31 @@ tree_iterate_cb(tree_t *tree, node_t *node, void *data)
(*i)++;
- return (NULL);
+ return NULL;
}
static unsigned
-tree_iterate(tree_t *tree)
-{
+tree_iterate(tree_t *tree) {
unsigned i;
i = 0;
tree_iter(tree, NULL, tree_iterate_cb, (void *)&i);
- return (i);
+ return i;
}
static unsigned
-tree_iterate_reverse(tree_t *tree)
-{
+tree_iterate_reverse(tree_t *tree) {
unsigned i;
i = 0;
tree_reverse_iter(tree, NULL, tree_iterate_cb, (void *)&i);
- return (i);
+ return i;
}
static void
-node_remove(tree_t *tree, node_t *node, unsigned nnodes)
-{
+node_remove(tree_t *tree, node_t *node, unsigned nnodes) {
node_t *search_node;
unsigned black_height, imbalances;
@@ -181,8 +188,7 @@ node_remove(tree_t *tree, node_t *node, unsigned nnodes)
node->magic = 0;
rbtn_black_height(node_t, link, tree, black_height);
- imbalances = tree_recurse(tree->rbt_root, black_height, 0,
- &(tree->rbt_nil));
+ imbalances = tree_recurse(tree->rbt_root, black_height, 0);
assert_u_eq(imbalances, 0, "Tree is unbalanced");
assert_u_eq(tree_iterate(tree), nnodes-1,
"Unexpected node iteration count");
@@ -191,32 +197,37 @@ node_remove(tree_t *tree, node_t *node, unsigned nnodes)
}
static node_t *
-remove_iterate_cb(tree_t *tree, node_t *node, void *data)
-{
+remove_iterate_cb(tree_t *tree, node_t *node, void *data) {
unsigned *nnodes = (unsigned *)data;
node_t *ret = tree_next(tree, node);
node_remove(tree, node, *nnodes);
- return (ret);
+ return ret;
}
static node_t *
-remove_reverse_iterate_cb(tree_t *tree, node_t *node, void *data)
-{
+remove_reverse_iterate_cb(tree_t *tree, node_t *node, void *data) {
unsigned *nnodes = (unsigned *)data;
node_t *ret = tree_prev(tree, node);
node_remove(tree, node, *nnodes);
- return (ret);
+ return ret;
+}
+
+static void
+destroy_cb(node_t *node, void *data) {
+ unsigned *nnodes = (unsigned *)data;
+
+ assert_u_gt(*nnodes, 0, "Destruction removed too many nodes");
+ (*nnodes)--;
}
-TEST_BEGIN(test_rb_random)
-{
-#define NNODES 25
-#define NBAGS 250
-#define SEED 42
+TEST_BEGIN(test_rb_random) {
+#define NNODES 25
+#define NBAGS 250
+#define SEED 42
sfmt_t *sfmt;
uint64_t bag[NNODES];
tree_t tree;
@@ -228,23 +239,25 @@ TEST_BEGIN(test_rb_random)
switch (i) {
case 0:
/* Insert in order. */
- for (j = 0; j < NNODES; j++)
+ for (j = 0; j < NNODES; j++) {
bag[j] = j;
+ }
break;
case 1:
/* Insert in reverse order. */
- for (j = 0; j < NNODES; j++)
+ for (j = 0; j < NNODES; j++) {
bag[j] = NNODES - j - 1;
+ }
break;
default:
- for (j = 0; j < NNODES; j++)
+ for (j = 0; j < NNODES; j++) {
bag[j] = gen_rand64_range(sfmt, NNODES);
+ }
}
for (j = 1; j <= NNODES; j++) {
/* Initialize tree and nodes. */
tree_new(&tree);
- tree.rbt_nil.magic = 0;
for (k = 0; k < j; k++) {
nodes[k].magic = NODE_MAGIC;
nodes[k].key = bag[k];
@@ -257,7 +270,7 @@ TEST_BEGIN(test_rb_random)
rbtn_black_height(node_t, link, &tree,
black_height);
imbalances = tree_recurse(tree.rbt_root,
- black_height, 0, &(tree.rbt_nil));
+ black_height, 0);
assert_u_eq(imbalances, 0,
"Tree is unbalanced");
@@ -278,14 +291,16 @@ TEST_BEGIN(test_rb_random)
}
/* Remove nodes. */
- switch (i % 4) {
+ switch (i % 5) {
case 0:
- for (k = 0; k < j; k++)
+ for (k = 0; k < j; k++) {
node_remove(&tree, &nodes[k], j - k);
+ }
break;
case 1:
- for (k = j; k > 0; k--)
+ for (k = j; k > 0; k--) {
node_remove(&tree, &nodes[k-1], k);
+ }
break;
case 2: {
node_t *start;
@@ -314,6 +329,12 @@ TEST_BEGIN(test_rb_random)
assert_u_eq(nnodes, 0,
"Removal terminated early");
break;
+ } case 4: {
+ unsigned nnodes = j;
+ tree_destroy(&tree, destroy_cb, &nnodes);
+ assert_u_eq(nnodes, 0,
+ "Destruction terminated early");
+ break;
} default:
not_reached();
}
@@ -327,10 +348,8 @@ TEST_BEGIN(test_rb_random)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_rb_empty,
- test_rb_random));
+ test_rb_random);
}
diff --git a/deps/jemalloc/test/unit/retained.c b/deps/jemalloc/test/unit/retained.c
new file mode 100644
index 000000000..d51a59811
--- /dev/null
+++ b/deps/jemalloc/test/unit/retained.c
@@ -0,0 +1,181 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/spin.h"
+
+static unsigned arena_ind;
+static size_t sz;
+static size_t esz;
+#define NEPOCHS 8
+#define PER_THD_NALLOCS 1
+static atomic_u_t epoch;
+static atomic_u_t nfinished;
+
+static unsigned
+do_arena_create(extent_hooks_t *h) {
+ unsigned arena_ind;
+ size_t sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz,
+ (void *)(h != NULL ? &h : NULL), (h != NULL ? sizeof(h) : 0)), 0,
+ "Unexpected mallctl() failure");
+ return arena_ind;
+}
+
+static void
+do_arena_destroy(unsigned arena_ind) {
+ size_t mib[3];
+ size_t miblen;
+
+ miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.destroy", mib, &miblen), 0,
+ "Unexpected mallctlnametomib() failure");
+ mib[1] = (size_t)arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL, 0), 0,
+ "Unexpected mallctlbymib() failure");
+}
+
+static void
+do_refresh(void) {
+ uint64_t epoch = 1;
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch,
+ sizeof(epoch)), 0, "Unexpected mallctl() failure");
+}
+
+static size_t
+do_get_size_impl(const char *cmd, unsigned arena_ind) {
+ size_t mib[4];
+ size_t miblen = sizeof(mib) / sizeof(size_t);
+ size_t z = sizeof(size_t);
+
+ assert_d_eq(mallctlnametomib(cmd, mib, &miblen),
+ 0, "Unexpected mallctlnametomib(\"%s\", ...) failure", cmd);
+ mib[2] = arena_ind;
+ size_t size;
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&size, &z, NULL, 0),
+ 0, "Unexpected mallctlbymib([\"%s\"], ...) failure", cmd);
+
+ return size;
+}
+
+static size_t
+do_get_active(unsigned arena_ind) {
+ return do_get_size_impl("stats.arenas.0.pactive", arena_ind) * PAGE;
+}
+
+static size_t
+do_get_mapped(unsigned arena_ind) {
+ return do_get_size_impl("stats.arenas.0.mapped", arena_ind);
+}
+
+static void *
+thd_start(void *arg) {
+ for (unsigned next_epoch = 1; next_epoch < NEPOCHS; next_epoch++) {
+ /* Busy-wait for next epoch. */
+ unsigned cur_epoch;
+ spin_t spinner = SPIN_INITIALIZER;
+ while ((cur_epoch = atomic_load_u(&epoch, ATOMIC_ACQUIRE)) !=
+ next_epoch) {
+ spin_adaptive(&spinner);
+ }
+ assert_u_eq(cur_epoch, next_epoch, "Unexpected epoch");
+
+ /*
+ * Allocate. The main thread will reset the arena, so there's
+ * no need to deallocate.
+ */
+ for (unsigned i = 0; i < PER_THD_NALLOCS; i++) {
+ void *p = mallocx(sz, MALLOCX_ARENA(arena_ind) |
+ MALLOCX_TCACHE_NONE
+ );
+ assert_ptr_not_null(p,
+ "Unexpected mallocx() failure\n");
+ }
+
+ /* Let the main thread know we've finished this iteration. */
+ atomic_fetch_add_u(&nfinished, 1, ATOMIC_RELEASE);
+ }
+
+ return NULL;
+}
+
+TEST_BEGIN(test_retained) {
+ test_skip_if(!config_stats);
+
+ arena_ind = do_arena_create(NULL);
+ sz = nallocx(HUGEPAGE, 0);
+ esz = sz + sz_large_pad;
+
+ atomic_store_u(&epoch, 0, ATOMIC_RELAXED);
+
+ unsigned nthreads = ncpus * 2;
+ VARIABLE_ARRAY(thd_t, threads, nthreads);
+ for (unsigned i = 0; i < nthreads; i++) {
+ thd_create(&threads[i], thd_start, NULL);
+ }
+
+ for (unsigned e = 1; e < NEPOCHS; e++) {
+ atomic_store_u(&nfinished, 0, ATOMIC_RELEASE);
+ atomic_store_u(&epoch, e, ATOMIC_RELEASE);
+
+ /* Wait for threads to finish allocating. */
+ spin_t spinner = SPIN_INITIALIZER;
+ while (atomic_load_u(&nfinished, ATOMIC_ACQUIRE) < nthreads) {
+ spin_adaptive(&spinner);
+ }
+
+ /*
+ * Assert that retained is no more than the sum of size classes
+ * that should have been used to satisfy the worker threads'
+ * requests, discounting per growth fragmentation.
+ */
+ do_refresh();
+
+ size_t allocated = esz * nthreads * PER_THD_NALLOCS;
+ size_t active = do_get_active(arena_ind);
+ assert_zu_le(allocated, active, "Unexpected active memory");
+ size_t mapped = do_get_mapped(arena_ind);
+ assert_zu_le(active, mapped, "Unexpected mapped memory");
+
+ arena_t *arena = arena_get(tsdn_fetch(), arena_ind, false);
+ size_t usable = 0;
+ size_t fragmented = 0;
+ for (pszind_t pind = sz_psz2ind(HUGEPAGE); pind <
+ arena->extent_grow_next; pind++) {
+ size_t psz = sz_pind2sz(pind);
+ size_t psz_fragmented = psz % esz;
+ size_t psz_usable = psz - psz_fragmented;
+ /*
+ * Only consider size classes that wouldn't be skipped.
+ */
+ if (psz_usable > 0) {
+ assert_zu_lt(usable, allocated,
+ "Excessive retained memory "
+ "(%#zx[+%#zx] > %#zx)", usable, psz_usable,
+ allocated);
+ fragmented += psz_fragmented;
+ usable += psz_usable;
+ }
+ }
+
+ /*
+ * Clean up arena. Destroying and recreating the arena
+ * is simpler that specifying extent hooks that deallocate
+ * (rather than retaining) during reset.
+ */
+ do_arena_destroy(arena_ind);
+ assert_u_eq(do_arena_create(NULL), arena_ind,
+ "Unexpected arena index");
+ }
+
+ for (unsigned i = 0; i < nthreads; i++) {
+ thd_join(threads[i], NULL);
+ }
+
+ do_arena_destroy(arena_ind);
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_retained);
+}
diff --git a/deps/jemalloc/test/unit/rtree.c b/deps/jemalloc/test/unit/rtree.c
index b54b3e86f..908100fac 100644
--- a/deps/jemalloc/test/unit/rtree.c
+++ b/deps/jemalloc/test/unit/rtree.c
@@ -1,138 +1,207 @@
#include "test/jemalloc_test.h"
+#include "jemalloc/internal/rtree.h"
+
+rtree_node_alloc_t *rtree_node_alloc_orig;
+rtree_node_dalloc_t *rtree_node_dalloc_orig;
+rtree_leaf_alloc_t *rtree_leaf_alloc_orig;
+rtree_leaf_dalloc_t *rtree_leaf_dalloc_orig;
+
+/* Potentially too large to safely place on the stack. */
+rtree_t test_rtree;
+
static rtree_node_elm_t *
-node_alloc(size_t nelms)
-{
+rtree_node_alloc_intercept(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) {
+ rtree_node_elm_t *node;
+
+ if (rtree != &test_rtree) {
+ return rtree_node_alloc_orig(tsdn, rtree, nelms);
+ }
+
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+ node = (rtree_node_elm_t *)calloc(nelms, sizeof(rtree_node_elm_t));
+ assert_ptr_not_null(node, "Unexpected calloc() failure");
+ malloc_mutex_lock(tsdn, &rtree->init_lock);
- return ((rtree_node_elm_t *)calloc(nelms, sizeof(rtree_node_elm_t)));
+ return node;
}
static void
-node_dalloc(rtree_node_elm_t *node)
-{
+rtree_node_dalloc_intercept(tsdn_t *tsdn, rtree_t *rtree,
+ rtree_node_elm_t *node) {
+ if (rtree != &test_rtree) {
+ rtree_node_dalloc_orig(tsdn, rtree, node);
+ return;
+ }
free(node);
}
-TEST_BEGIN(test_rtree_get_empty)
-{
- unsigned i;
-
- for (i = 1; i <= (sizeof(uintptr_t) << 3); i++) {
- rtree_t rtree;
- assert_false(rtree_new(&rtree, i, node_alloc, node_dalloc),
- "Unexpected rtree_new() failure");
- assert_ptr_null(rtree_get(&rtree, 0, false),
- "rtree_get() should return NULL for empty tree");
- rtree_delete(&rtree);
+static rtree_leaf_elm_t *
+rtree_leaf_alloc_intercept(tsdn_t *tsdn, rtree_t *rtree, size_t nelms) {
+ rtree_leaf_elm_t *leaf;
+
+ if (rtree != &test_rtree) {
+ return rtree_leaf_alloc_orig(tsdn, rtree, nelms);
}
+
+ malloc_mutex_unlock(tsdn, &rtree->init_lock);
+ leaf = (rtree_leaf_elm_t *)calloc(nelms, sizeof(rtree_leaf_elm_t));
+ assert_ptr_not_null(leaf, "Unexpected calloc() failure");
+ malloc_mutex_lock(tsdn, &rtree->init_lock);
+
+ return leaf;
}
-TEST_END
-TEST_BEGIN(test_rtree_extrema)
-{
- unsigned i;
- extent_node_t node_a, node_b;
+static void
+rtree_leaf_dalloc_intercept(tsdn_t *tsdn, rtree_t *rtree,
+ rtree_leaf_elm_t *leaf) {
+ if (rtree != &test_rtree) {
+ rtree_leaf_dalloc_orig(tsdn, rtree, leaf);
+ return;
+ }
- for (i = 1; i <= (sizeof(uintptr_t) << 3); i++) {
- rtree_t rtree;
- assert_false(rtree_new(&rtree, i, node_alloc, node_dalloc),
- "Unexpected rtree_new() failure");
+ free(leaf);
+}
- assert_false(rtree_set(&rtree, 0, &node_a),
- "Unexpected rtree_set() failure");
- assert_ptr_eq(rtree_get(&rtree, 0, true), &node_a,
- "rtree_get() should return previously set value");
+TEST_BEGIN(test_rtree_read_empty) {
+ tsdn_t *tsdn;
- assert_false(rtree_set(&rtree, ~((uintptr_t)0), &node_b),
- "Unexpected rtree_set() failure");
- assert_ptr_eq(rtree_get(&rtree, ~((uintptr_t)0), true), &node_b,
- "rtree_get() should return previously set value");
+ tsdn = tsdn_fetch();
- rtree_delete(&rtree);
- }
+ rtree_t *rtree = &test_rtree;
+ rtree_ctx_t rtree_ctx;
+ rtree_ctx_data_init(&rtree_ctx);
+ assert_false(rtree_new(rtree, false), "Unexpected rtree_new() failure");
+ assert_ptr_null(rtree_extent_read(tsdn, rtree, &rtree_ctx, PAGE,
+ false), "rtree_extent_read() should return NULL for empty tree");
+ rtree_delete(tsdn, rtree);
}
TEST_END
-TEST_BEGIN(test_rtree_bits)
-{
- unsigned i, j, k;
-
- for (i = 1; i < (sizeof(uintptr_t) << 3); i++) {
- uintptr_t keys[] = {0, 1,
- (((uintptr_t)1) << (sizeof(uintptr_t)*8-i)) - 1};
- extent_node_t node;
- rtree_t rtree;
-
- assert_false(rtree_new(&rtree, i, node_alloc, node_dalloc),
- "Unexpected rtree_new() failure");
-
- for (j = 0; j < sizeof(keys)/sizeof(uintptr_t); j++) {
- assert_false(rtree_set(&rtree, keys[j], &node),
- "Unexpected rtree_set() failure");
- for (k = 0; k < sizeof(keys)/sizeof(uintptr_t); k++) {
- assert_ptr_eq(rtree_get(&rtree, keys[k], true),
- &node, "rtree_get() should return "
- "previously set value and ignore "
- "insignificant key bits; i=%u, j=%u, k=%u, "
- "set key=%#"FMTxPTR", get key=%#"FMTxPTR, i,
- j, k, keys[j], keys[k]);
- }
- assert_ptr_null(rtree_get(&rtree,
- (((uintptr_t)1) << (sizeof(uintptr_t)*8-i)), false),
- "Only leftmost rtree leaf should be set; "
- "i=%u, j=%u", i, j);
- assert_false(rtree_set(&rtree, keys[j], NULL),
- "Unexpected rtree_set() failure");
- }
+#undef NTHREADS
+#undef NITERS
+#undef SEED
- rtree_delete(&rtree);
- }
+TEST_BEGIN(test_rtree_extrema) {
+ extent_t extent_a, extent_b;
+ extent_init(&extent_a, NULL, NULL, LARGE_MINCLASS, false,
+ sz_size2index(LARGE_MINCLASS), 0, extent_state_active, false,
+ false, true);
+ extent_init(&extent_b, NULL, NULL, 0, false, NSIZES, 0,
+ extent_state_active, false, false, true);
+
+ tsdn_t *tsdn = tsdn_fetch();
+
+ rtree_t *rtree = &test_rtree;
+ rtree_ctx_t rtree_ctx;
+ rtree_ctx_data_init(&rtree_ctx);
+ assert_false(rtree_new(rtree, false), "Unexpected rtree_new() failure");
+
+ assert_false(rtree_write(tsdn, rtree, &rtree_ctx, PAGE, &extent_a,
+ extent_szind_get(&extent_a), extent_slab_get(&extent_a)),
+ "Unexpected rtree_write() failure");
+ rtree_szind_slab_update(tsdn, rtree, &rtree_ctx, PAGE,
+ extent_szind_get(&extent_a), extent_slab_get(&extent_a));
+ assert_ptr_eq(rtree_extent_read(tsdn, rtree, &rtree_ctx, PAGE, true),
+ &extent_a,
+ "rtree_extent_read() should return previously set value");
+
+ assert_false(rtree_write(tsdn, rtree, &rtree_ctx, ~((uintptr_t)0),
+ &extent_b, extent_szind_get_maybe_invalid(&extent_b),
+ extent_slab_get(&extent_b)), "Unexpected rtree_write() failure");
+ assert_ptr_eq(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ ~((uintptr_t)0), true), &extent_b,
+ "rtree_extent_read() should return previously set value");
+
+ rtree_delete(tsdn, rtree);
}
TEST_END
-TEST_BEGIN(test_rtree_random)
-{
- unsigned i;
- sfmt_t *sfmt;
-#define NSET 16
-#define SEED 42
-
- sfmt = init_gen_rand(SEED);
- for (i = 1; i <= (sizeof(uintptr_t) << 3); i++) {
- uintptr_t keys[NSET];
- extent_node_t node;
- unsigned j;
- rtree_t rtree;
-
- assert_false(rtree_new(&rtree, i, node_alloc, node_dalloc),
- "Unexpected rtree_new() failure");
-
- for (j = 0; j < NSET; j++) {
- keys[j] = (uintptr_t)gen_rand64(sfmt);
- assert_false(rtree_set(&rtree, keys[j], &node),
- "Unexpected rtree_set() failure");
- assert_ptr_eq(rtree_get(&rtree, keys[j], true), &node,
- "rtree_get() should return previously set value");
- }
- for (j = 0; j < NSET; j++) {
- assert_ptr_eq(rtree_get(&rtree, keys[j], true), &node,
- "rtree_get() should return previously set value");
+TEST_BEGIN(test_rtree_bits) {
+ tsdn_t *tsdn = tsdn_fetch();
+
+ uintptr_t keys[] = {PAGE, PAGE + 1,
+ PAGE + (((uintptr_t)1) << LG_PAGE) - 1};
+
+ extent_t extent;
+ extent_init(&extent, NULL, NULL, 0, false, NSIZES, 0,
+ extent_state_active, false, false, true);
+
+ rtree_t *rtree = &test_rtree;
+ rtree_ctx_t rtree_ctx;
+ rtree_ctx_data_init(&rtree_ctx);
+ assert_false(rtree_new(rtree, false), "Unexpected rtree_new() failure");
+
+ for (unsigned i = 0; i < sizeof(keys)/sizeof(uintptr_t); i++) {
+ assert_false(rtree_write(tsdn, rtree, &rtree_ctx, keys[i],
+ &extent, NSIZES, false),
+ "Unexpected rtree_write() failure");
+ for (unsigned j = 0; j < sizeof(keys)/sizeof(uintptr_t); j++) {
+ assert_ptr_eq(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ keys[j], true), &extent,
+ "rtree_extent_read() should return previously set "
+ "value and ignore insignificant key bits; i=%u, "
+ "j=%u, set key=%#"FMTxPTR", get key=%#"FMTxPTR, i,
+ j, keys[i], keys[j]);
}
+ assert_ptr_null(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ (((uintptr_t)2) << LG_PAGE), false),
+ "Only leftmost rtree leaf should be set; i=%u", i);
+ rtree_clear(tsdn, rtree, &rtree_ctx, keys[i]);
+ }
- for (j = 0; j < NSET; j++) {
- assert_false(rtree_set(&rtree, keys[j], NULL),
- "Unexpected rtree_set() failure");
- assert_ptr_null(rtree_get(&rtree, keys[j], true),
- "rtree_get() should return previously set value");
- }
- for (j = 0; j < NSET; j++) {
- assert_ptr_null(rtree_get(&rtree, keys[j], true),
- "rtree_get() should return previously set value");
- }
+ rtree_delete(tsdn, rtree);
+}
+TEST_END
+
+TEST_BEGIN(test_rtree_random) {
+#define NSET 16
+#define SEED 42
+ sfmt_t *sfmt = init_gen_rand(SEED);
+ tsdn_t *tsdn = tsdn_fetch();
+ uintptr_t keys[NSET];
+ rtree_t *rtree = &test_rtree;
+ rtree_ctx_t rtree_ctx;
+ rtree_ctx_data_init(&rtree_ctx);
+
+ extent_t extent;
+ extent_init(&extent, NULL, NULL, 0, false, NSIZES, 0,
+ extent_state_active, false, false, true);
+
+ assert_false(rtree_new(rtree, false), "Unexpected rtree_new() failure");
+
+ for (unsigned i = 0; i < NSET; i++) {
+ keys[i] = (uintptr_t)gen_rand64(sfmt);
+ rtree_leaf_elm_t *elm = rtree_leaf_elm_lookup(tsdn, rtree,
+ &rtree_ctx, keys[i], false, true);
+ assert_ptr_not_null(elm,
+ "Unexpected rtree_leaf_elm_lookup() failure");
+ rtree_leaf_elm_write(tsdn, rtree, elm, &extent, NSIZES, false);
+ assert_ptr_eq(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ keys[i], true), &extent,
+ "rtree_extent_read() should return previously set value");
+ }
+ for (unsigned i = 0; i < NSET; i++) {
+ assert_ptr_eq(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ keys[i], true), &extent,
+ "rtree_extent_read() should return previously set value, "
+ "i=%u", i);
+ }
- rtree_delete(&rtree);
+ for (unsigned i = 0; i < NSET; i++) {
+ rtree_clear(tsdn, rtree, &rtree_ctx, keys[i]);
+ assert_ptr_null(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ keys[i], true),
+ "rtree_extent_read() should return previously set value");
}
+ for (unsigned i = 0; i < NSET; i++) {
+ assert_ptr_null(rtree_extent_read(tsdn, rtree, &rtree_ctx,
+ keys[i], true),
+ "rtree_extent_read() should return previously set value");
+ }
+
+ rtree_delete(tsdn, rtree);
fini_gen_rand(sfmt);
#undef NSET
#undef SEED
@@ -140,12 +209,19 @@ TEST_BEGIN(test_rtree_random)
TEST_END
int
-main(void)
-{
-
- return (test(
- test_rtree_get_empty,
+main(void) {
+ rtree_node_alloc_orig = rtree_node_alloc;
+ rtree_node_alloc = rtree_node_alloc_intercept;
+ rtree_node_dalloc_orig = rtree_node_dalloc;
+ rtree_node_dalloc = rtree_node_dalloc_intercept;
+ rtree_leaf_alloc_orig = rtree_leaf_alloc;
+ rtree_leaf_alloc = rtree_leaf_alloc_intercept;
+ rtree_leaf_dalloc_orig = rtree_leaf_dalloc;
+ rtree_leaf_dalloc = rtree_leaf_dalloc_intercept;
+
+ return test(
+ test_rtree_read_empty,
test_rtree_extrema,
test_rtree_bits,
- test_rtree_random));
+ test_rtree_random);
}
diff --git a/deps/jemalloc/test/unit/size_classes.c b/deps/jemalloc/test/unit/size_classes.c
index d3aaebd77..bcff56098 100644
--- a/deps/jemalloc/test/unit/size_classes.c
+++ b/deps/jemalloc/test/unit/size_classes.c
@@ -1,39 +1,37 @@
#include "test/jemalloc_test.h"
static size_t
-get_max_size_class(void)
-{
- unsigned nhchunks;
+get_max_size_class(void) {
+ unsigned nlextents;
size_t mib[4];
size_t sz, miblen, max_size_class;
sz = sizeof(unsigned);
- assert_d_eq(mallctl("arenas.nhchunks", &nhchunks, &sz, NULL, 0), 0,
- "Unexpected mallctl() error");
+ assert_d_eq(mallctl("arenas.nlextents", (void *)&nlextents, &sz, NULL,
+ 0), 0, "Unexpected mallctl() error");
miblen = sizeof(mib) / sizeof(size_t);
- assert_d_eq(mallctlnametomib("arenas.hchunk.0.size", mib, &miblen), 0,
+ assert_d_eq(mallctlnametomib("arenas.lextent.0.size", mib, &miblen), 0,
"Unexpected mallctlnametomib() error");
- mib[2] = nhchunks - 1;
+ mib[2] = nlextents - 1;
sz = sizeof(size_t);
- assert_d_eq(mallctlbymib(mib, miblen, &max_size_class, &sz, NULL, 0), 0,
- "Unexpected mallctlbymib() error");
+ assert_d_eq(mallctlbymib(mib, miblen, (void *)&max_size_class, &sz,
+ NULL, 0), 0, "Unexpected mallctlbymib() error");
- return (max_size_class);
+ return max_size_class;
}
-TEST_BEGIN(test_size_classes)
-{
+TEST_BEGIN(test_size_classes) {
size_t size_class, max_size_class;
szind_t index, max_index;
max_size_class = get_max_size_class();
- max_index = size2index(max_size_class);
+ max_index = sz_size2index(max_size_class);
- for (index = 0, size_class = index2size(index); index < max_index ||
+ for (index = 0, size_class = sz_index2size(index); index < max_index ||
size_class < max_size_class; index++, size_class =
- index2size(index)) {
+ sz_index2size(index)) {
assert_true(index < max_index,
"Loop conditionals should be equivalent; index=%u, "
"size_class=%zu (%#zx)", index, size_class, size_class);
@@ -41,49 +39,145 @@ TEST_BEGIN(test_size_classes)
"Loop conditionals should be equivalent; index=%u, "
"size_class=%zu (%#zx)", index, size_class, size_class);
- assert_u_eq(index, size2index(size_class),
- "size2index() does not reverse index2size(): index=%u -->"
- " size_class=%zu --> index=%u --> size_class=%zu", index,
- size_class, size2index(size_class),
- index2size(size2index(size_class)));
- assert_zu_eq(size_class, index2size(size2index(size_class)),
- "index2size() does not reverse size2index(): index=%u -->"
- " size_class=%zu --> index=%u --> size_class=%zu", index,
- size_class, size2index(size_class),
- index2size(size2index(size_class)));
-
- assert_u_eq(index+1, size2index(size_class+1),
+ assert_u_eq(index, sz_size2index(size_class),
+ "sz_size2index() does not reverse sz_index2size(): index=%u"
+ " --> size_class=%zu --> index=%u --> size_class=%zu",
+ index, size_class, sz_size2index(size_class),
+ sz_index2size(sz_size2index(size_class)));
+ assert_zu_eq(size_class,
+ sz_index2size(sz_size2index(size_class)),
+ "sz_index2size() does not reverse sz_size2index(): index=%u"
+ " --> size_class=%zu --> index=%u --> size_class=%zu",
+ index, size_class, sz_size2index(size_class),
+ sz_index2size(sz_size2index(size_class)));
+
+ assert_u_eq(index+1, sz_size2index(size_class+1),
"Next size_class does not round up properly");
assert_zu_eq(size_class, (index > 0) ?
- s2u(index2size(index-1)+1) : s2u(1),
- "s2u() does not round up to size class");
- assert_zu_eq(size_class, s2u(size_class-1),
- "s2u() does not round up to size class");
- assert_zu_eq(size_class, s2u(size_class),
- "s2u() does not compute same size class");
- assert_zu_eq(s2u(size_class+1), index2size(index+1),
- "s2u() does not round up to next size class");
+ sz_s2u(sz_index2size(index-1)+1) : sz_s2u(1),
+ "sz_s2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_s2u(size_class-1),
+ "sz_s2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_s2u(size_class),
+ "sz_s2u() does not compute same size class");
+ assert_zu_eq(sz_s2u(size_class+1), sz_index2size(index+1),
+ "sz_s2u() does not round up to next size class");
}
- assert_u_eq(index, size2index(index2size(index)),
- "size2index() does not reverse index2size()");
- assert_zu_eq(max_size_class, index2size(size2index(max_size_class)),
- "index2size() does not reverse size2index()");
-
- assert_zu_eq(size_class, s2u(index2size(index-1)+1),
- "s2u() does not round up to size class");
- assert_zu_eq(size_class, s2u(size_class-1),
- "s2u() does not round up to size class");
- assert_zu_eq(size_class, s2u(size_class),
- "s2u() does not compute same size class");
+ assert_u_eq(index, sz_size2index(sz_index2size(index)),
+ "sz_size2index() does not reverse sz_index2size()");
+ assert_zu_eq(max_size_class, sz_index2size(
+ sz_size2index(max_size_class)),
+ "sz_index2size() does not reverse sz_size2index()");
+
+ assert_zu_eq(size_class, sz_s2u(sz_index2size(index-1)+1),
+ "sz_s2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_s2u(size_class-1),
+ "sz_s2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_s2u(size_class),
+ "sz_s2u() does not compute same size class");
}
TEST_END
-int
-main(void)
-{
+TEST_BEGIN(test_psize_classes) {
+ size_t size_class, max_psz;
+ pszind_t pind, max_pind;
+
+ max_psz = get_max_size_class() + PAGE;
+ max_pind = sz_psz2ind(max_psz);
+
+ for (pind = 0, size_class = sz_pind2sz(pind);
+ pind < max_pind || size_class < max_psz;
+ pind++, size_class = sz_pind2sz(pind)) {
+ assert_true(pind < max_pind,
+ "Loop conditionals should be equivalent; pind=%u, "
+ "size_class=%zu (%#zx)", pind, size_class, size_class);
+ assert_true(size_class < max_psz,
+ "Loop conditionals should be equivalent; pind=%u, "
+ "size_class=%zu (%#zx)", pind, size_class, size_class);
+
+ assert_u_eq(pind, sz_psz2ind(size_class),
+ "sz_psz2ind() does not reverse sz_pind2sz(): pind=%u -->"
+ " size_class=%zu --> pind=%u --> size_class=%zu", pind,
+ size_class, sz_psz2ind(size_class),
+ sz_pind2sz(sz_psz2ind(size_class)));
+ assert_zu_eq(size_class, sz_pind2sz(sz_psz2ind(size_class)),
+ "sz_pind2sz() does not reverse sz_psz2ind(): pind=%u -->"
+ " size_class=%zu --> pind=%u --> size_class=%zu", pind,
+ size_class, sz_psz2ind(size_class),
+ sz_pind2sz(sz_psz2ind(size_class)));
+
+ assert_u_eq(pind+1, sz_psz2ind(size_class+1),
+ "Next size_class does not round up properly");
+
+ assert_zu_eq(size_class, (pind > 0) ?
+ sz_psz2u(sz_pind2sz(pind-1)+1) : sz_psz2u(1),
+ "sz_psz2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_psz2u(size_class-1),
+ "sz_psz2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_psz2u(size_class),
+ "sz_psz2u() does not compute same size class");
+ assert_zu_eq(sz_psz2u(size_class+1), sz_pind2sz(pind+1),
+ "sz_psz2u() does not round up to next size class");
+ }
+
+ assert_u_eq(pind, sz_psz2ind(sz_pind2sz(pind)),
+ "sz_psz2ind() does not reverse sz_pind2sz()");
+ assert_zu_eq(max_psz, sz_pind2sz(sz_psz2ind(max_psz)),
+ "sz_pind2sz() does not reverse sz_psz2ind()");
+
+ assert_zu_eq(size_class, sz_psz2u(sz_pind2sz(pind-1)+1),
+ "sz_psz2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_psz2u(size_class-1),
+ "sz_psz2u() does not round up to size class");
+ assert_zu_eq(size_class, sz_psz2u(size_class),
+ "sz_psz2u() does not compute same size class");
+}
+TEST_END
+
+TEST_BEGIN(test_overflow) {
+ size_t max_size_class, max_psz;
+
+ max_size_class = get_max_size_class();
+ max_psz = max_size_class + PAGE;
+
+ assert_u_eq(sz_size2index(max_size_class+1), NSIZES,
+ "sz_size2index() should return NSIZES on overflow");
+ assert_u_eq(sz_size2index(ZU(PTRDIFF_MAX)+1), NSIZES,
+ "sz_size2index() should return NSIZES on overflow");
+ assert_u_eq(sz_size2index(SIZE_T_MAX), NSIZES,
+ "sz_size2index() should return NSIZES on overflow");
- return (test(
- test_size_classes));
+ assert_zu_eq(sz_s2u(max_size_class+1), 0,
+ "sz_s2u() should return 0 for unsupported size");
+ assert_zu_eq(sz_s2u(ZU(PTRDIFF_MAX)+1), 0,
+ "sz_s2u() should return 0 for unsupported size");
+ assert_zu_eq(sz_s2u(SIZE_T_MAX), 0,
+ "sz_s2u() should return 0 on overflow");
+
+ assert_u_eq(sz_psz2ind(max_size_class+1), NPSIZES,
+ "sz_psz2ind() should return NPSIZES on overflow");
+ assert_u_eq(sz_psz2ind(ZU(PTRDIFF_MAX)+1), NPSIZES,
+ "sz_psz2ind() should return NPSIZES on overflow");
+ assert_u_eq(sz_psz2ind(SIZE_T_MAX), NPSIZES,
+ "sz_psz2ind() should return NPSIZES on overflow");
+
+ assert_zu_eq(sz_psz2u(max_size_class+1), max_psz,
+ "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) for unsupported"
+ " size");
+ assert_zu_eq(sz_psz2u(ZU(PTRDIFF_MAX)+1), max_psz,
+ "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) for unsupported "
+ "size");
+ assert_zu_eq(sz_psz2u(SIZE_T_MAX), max_psz,
+ "sz_psz2u() should return (LARGE_MAXCLASS + PAGE) on overflow");
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_size_classes,
+ test_psize_classes,
+ test_overflow);
}
diff --git a/deps/jemalloc/test/unit/slab.c b/deps/jemalloc/test/unit/slab.c
new file mode 100644
index 000000000..7e662aed1
--- /dev/null
+++ b/deps/jemalloc/test/unit/slab.c
@@ -0,0 +1,32 @@
+#include "test/jemalloc_test.h"
+
+TEST_BEGIN(test_arena_slab_regind) {
+ szind_t binind;
+
+ for (binind = 0; binind < NBINS; binind++) {
+ size_t regind;
+ extent_t slab;
+ const bin_info_t *bin_info = &bin_infos[binind];
+ extent_init(&slab, NULL, mallocx(bin_info->slab_size,
+ MALLOCX_LG_ALIGN(LG_PAGE)), bin_info->slab_size, true,
+ binind, 0, extent_state_active, false, true, true);
+ assert_ptr_not_null(extent_addr_get(&slab),
+ "Unexpected malloc() failure");
+ for (regind = 0; regind < bin_info->nregs; regind++) {
+ void *reg = (void *)((uintptr_t)extent_addr_get(&slab) +
+ (bin_info->reg_size * regind));
+ assert_zu_eq(arena_slab_regind(&slab, binind, reg),
+ regind,
+ "Incorrect region index computed for size %zu",
+ bin_info->reg_size);
+ }
+ free(extent_addr_get(&slab));
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_arena_slab_regind);
+}
diff --git a/deps/jemalloc/test/unit/smoothstep.c b/deps/jemalloc/test/unit/smoothstep.c
new file mode 100644
index 000000000..7c5dbb7e0
--- /dev/null
+++ b/deps/jemalloc/test/unit/smoothstep.c
@@ -0,0 +1,102 @@
+#include "test/jemalloc_test.h"
+
+static const uint64_t smoothstep_tab[] = {
+#define STEP(step, h, x, y) \
+ h,
+ SMOOTHSTEP
+#undef STEP
+};
+
+TEST_BEGIN(test_smoothstep_integral) {
+ uint64_t sum, min, max;
+ unsigned i;
+
+ /*
+ * The integral of smoothstep in the [0..1] range equals 1/2. Verify
+ * that the fixed point representation's integral is no more than
+ * rounding error distant from 1/2. Regarding rounding, each table
+ * element is rounded down to the nearest fixed point value, so the
+ * integral may be off by as much as SMOOTHSTEP_NSTEPS ulps.
+ */
+ sum = 0;
+ for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) {
+ sum += smoothstep_tab[i];
+ }
+
+ max = (KQU(1) << (SMOOTHSTEP_BFP-1)) * (SMOOTHSTEP_NSTEPS+1);
+ min = max - SMOOTHSTEP_NSTEPS;
+
+ assert_u64_ge(sum, min,
+ "Integral too small, even accounting for truncation");
+ assert_u64_le(sum, max, "Integral exceeds 1/2");
+ if (false) {
+ malloc_printf("%"FMTu64" ulps under 1/2 (limit %d)\n",
+ max - sum, SMOOTHSTEP_NSTEPS);
+ }
+}
+TEST_END
+
+TEST_BEGIN(test_smoothstep_monotonic) {
+ uint64_t prev_h;
+ unsigned i;
+
+ /*
+ * The smoothstep function is monotonic in [0..1], i.e. its slope is
+ * non-negative. In practice we want to parametrize table generation
+ * such that piecewise slope is greater than zero, but do not require
+ * that here.
+ */
+ prev_h = 0;
+ for (i = 0; i < SMOOTHSTEP_NSTEPS; i++) {
+ uint64_t h = smoothstep_tab[i];
+ assert_u64_ge(h, prev_h, "Piecewise non-monotonic, i=%u", i);
+ prev_h = h;
+ }
+ assert_u64_eq(smoothstep_tab[SMOOTHSTEP_NSTEPS-1],
+ (KQU(1) << SMOOTHSTEP_BFP), "Last step must equal 1");
+}
+TEST_END
+
+TEST_BEGIN(test_smoothstep_slope) {
+ uint64_t prev_h, prev_delta;
+ unsigned i;
+
+ /*
+ * The smoothstep slope strictly increases until x=0.5, and then
+ * strictly decreases until x=1.0. Verify the slightly weaker
+ * requirement of monotonicity, so that inadequate table precision does
+ * not cause false test failures.
+ */
+ prev_h = 0;
+ prev_delta = 0;
+ for (i = 0; i < SMOOTHSTEP_NSTEPS / 2 + SMOOTHSTEP_NSTEPS % 2; i++) {
+ uint64_t h = smoothstep_tab[i];
+ uint64_t delta = h - prev_h;
+ assert_u64_ge(delta, prev_delta,
+ "Slope must monotonically increase in 0.0 <= x <= 0.5, "
+ "i=%u", i);
+ prev_h = h;
+ prev_delta = delta;
+ }
+
+ prev_h = KQU(1) << SMOOTHSTEP_BFP;
+ prev_delta = 0;
+ for (i = SMOOTHSTEP_NSTEPS-1; i >= SMOOTHSTEP_NSTEPS / 2; i--) {
+ uint64_t h = smoothstep_tab[i];
+ uint64_t delta = prev_h - h;
+ assert_u64_ge(delta, prev_delta,
+ "Slope must monotonically decrease in 0.5 <= x <= 1.0, "
+ "i=%u", i);
+ prev_h = h;
+ prev_delta = delta;
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_smoothstep_integral,
+ test_smoothstep_monotonic,
+ test_smoothstep_slope);
+}
diff --git a/deps/jemalloc/test/unit/spin.c b/deps/jemalloc/test/unit/spin.c
new file mode 100644
index 000000000..b965f7427
--- /dev/null
+++ b/deps/jemalloc/test/unit/spin.c
@@ -0,0 +1,18 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/spin.h"
+
+TEST_BEGIN(test_spin) {
+ spin_t spinner = SPIN_INITIALIZER;
+
+ for (unsigned i = 0; i < 100; i++) {
+ spin_adaptive(&spinner);
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_spin);
+}
diff --git a/deps/jemalloc/test/unit/stats.c b/deps/jemalloc/test/unit/stats.c
index 8e4bc631e..231010e43 100644
--- a/deps/jemalloc/test/unit/stats.c
+++ b/deps/jemalloc/test/unit/stats.c
@@ -1,28 +1,20 @@
#include "test/jemalloc_test.h"
-TEST_BEGIN(test_stats_summary)
-{
- size_t *cactive;
+TEST_BEGIN(test_stats_summary) {
size_t sz, allocated, active, resident, mapped;
int expected = config_stats ? 0 : ENOENT;
- sz = sizeof(cactive);
- assert_d_eq(mallctl("stats.cactive", &cactive, &sz, NULL, 0), expected,
- "Unexpected mallctl() result");
-
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.allocated", &allocated, &sz, NULL, 0),
+ assert_d_eq(mallctl("stats.allocated", (void *)&allocated, &sz, NULL,
+ 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.active", (void *)&active, &sz, NULL, 0),
expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.active", &active, &sz, NULL, 0), expected,
- "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.resident", &resident, &sz, NULL, 0),
+ assert_d_eq(mallctl("stats.resident", (void *)&resident, &sz, NULL, 0),
+ expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.mapped", (void *)&mapped, &sz, NULL, 0),
expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.mapped", &mapped, &sz, NULL, 0), expected,
- "Unexpected mallctl() result");
if (config_stats) {
- assert_zu_le(active, *cactive,
- "active should be no larger than cactive");
assert_zu_le(allocated, active,
"allocated should be no larger than active");
assert_zu_lt(active, resident,
@@ -33,8 +25,7 @@ TEST_BEGIN(test_stats_summary)
}
TEST_END
-TEST_BEGIN(test_stats_huge)
-{
+TEST_BEGIN(test_stats_large) {
void *p;
uint64_t epoch;
size_t allocated;
@@ -42,22 +33,24 @@ TEST_BEGIN(test_stats_huge)
size_t sz;
int expected = config_stats ? 0 : ENOENT;
- p = mallocx(large_maxclass+1, 0);
+ p = mallocx(SMALL_MAXCLASS+1, MALLOCX_ARENA(0));
assert_ptr_not_null(p, "Unexpected mallocx() failure");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.huge.allocated", &allocated, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.allocated",
+ (void *)&allocated, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.huge.nmalloc", &nmalloc, &sz, NULL,
- 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.huge.ndalloc", &ndalloc, &sz, NULL,
- 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.huge.nrequests", &nrequests, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.nmalloc", (void *)&nmalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.ndalloc", (void *)&ndalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.nrequests",
+ (void *)&nrequests, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
if (config_stats) {
assert_zu_gt(allocated, 0,
@@ -72,76 +65,82 @@ TEST_BEGIN(test_stats_huge)
}
TEST_END
-TEST_BEGIN(test_stats_arenas_summary)
-{
- unsigned arena;
- void *little, *large, *huge;
+TEST_BEGIN(test_stats_arenas_summary) {
+ void *little, *large;
uint64_t epoch;
size_t sz;
int expected = config_stats ? 0 : ENOENT;
size_t mapped;
- uint64_t npurge, nmadvise, purged;
-
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
+ uint64_t dirty_npurge, dirty_nmadvise, dirty_purged;
+ uint64_t muzzy_npurge, muzzy_nmadvise, muzzy_purged;
- little = mallocx(SMALL_MAXCLASS, 0);
+ little = mallocx(SMALL_MAXCLASS, MALLOCX_ARENA(0));
assert_ptr_not_null(little, "Unexpected mallocx() failure");
- large = mallocx(large_maxclass, 0);
+ large = mallocx((1U << LG_LARGE_MINCLASS), MALLOCX_ARENA(0));
assert_ptr_not_null(large, "Unexpected mallocx() failure");
- huge = mallocx(chunksize, 0);
- assert_ptr_not_null(huge, "Unexpected mallocx() failure");
+ dallocx(little, 0);
+ dallocx(large, 0);
+
+ assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
+ opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result");
assert_d_eq(mallctl("arena.0.purge", NULL, NULL, NULL, 0), 0,
"Unexpected mallctl() failure");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.mapped", &mapped, &sz, NULL, 0),
- expected, "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.mapped", (void *)&mapped, &sz, NULL,
+ 0), expected, "Unexepected mallctl() result");
+
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.npurge", &npurge, &sz, NULL, 0),
- expected, "Unexepected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.nmadvise", &nmadvise, &sz, NULL, 0),
- expected, "Unexepected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.purged", &purged, &sz, NULL, 0),
- expected, "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.dirty_npurge",
+ (void *)&dirty_npurge, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.dirty_nmadvise",
+ (void *)&dirty_nmadvise, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.dirty_purged",
+ (void *)&dirty_purged, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.muzzy_npurge",
+ (void *)&muzzy_npurge, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.muzzy_nmadvise",
+ (void *)&muzzy_nmadvise, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.muzzy_purged",
+ (void *)&muzzy_purged, &sz, NULL, 0), expected,
+ "Unexepected mallctl() result");
if (config_stats) {
- assert_u64_gt(npurge, 0,
- "At least one purge should have occurred");
- assert_u64_le(nmadvise, purged,
- "nmadvise should be no greater than purged");
+ if (!background_thread_enabled()) {
+ assert_u64_gt(dirty_npurge + muzzy_npurge, 0,
+ "At least one purge should have occurred");
+ }
+ assert_u64_le(dirty_nmadvise, dirty_purged,
+ "dirty_nmadvise should be no greater than dirty_purged");
+ assert_u64_le(muzzy_nmadvise, muzzy_purged,
+ "muzzy_nmadvise should be no greater than muzzy_purged");
}
-
- dallocx(little, 0);
- dallocx(large, 0);
- dallocx(huge, 0);
}
TEST_END
void *
-thd_start(void *arg)
-{
-
- return (NULL);
+thd_start(void *arg) {
+ return NULL;
}
static void
-no_lazy_lock(void)
-{
+no_lazy_lock(void) {
thd_t thd;
thd_create(&thd, thd_start, NULL);
thd_join(thd, NULL);
}
-TEST_BEGIN(test_stats_arenas_small)
-{
- unsigned arena;
+TEST_BEGIN(test_stats_arenas_small) {
void *p;
size_t sz, allocated;
uint64_t epoch, nmalloc, ndalloc, nrequests;
@@ -149,29 +148,27 @@ TEST_BEGIN(test_stats_arenas_small)
no_lazy_lock(); /* Lazy locking would dodge tcache testing. */
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
-
- p = mallocx(SMALL_MAXCLASS, 0);
+ p = mallocx(SMALL_MAXCLASS, MALLOCX_ARENA(0));
assert_ptr_not_null(p, "Unexpected mallocx() failure");
assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
- config_tcache ? 0 : ENOENT, "Unexpected mallctl() result");
+ opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.small.allocated", &allocated, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.small.allocated",
+ (void *)&allocated, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.small.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.small.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.small.nrequests", &nrequests, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.small.nmalloc", (void *)&nmalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.small.ndalloc", (void *)&ndalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.small.nrequests",
+ (void *)&nrequests, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
if (config_stats) {
assert_zu_gt(allocated, 0,
@@ -188,138 +185,109 @@ TEST_BEGIN(test_stats_arenas_small)
}
TEST_END
-TEST_BEGIN(test_stats_arenas_large)
-{
- unsigned arena;
+TEST_BEGIN(test_stats_arenas_large) {
void *p;
size_t sz, allocated;
- uint64_t epoch, nmalloc, ndalloc, nrequests;
+ uint64_t epoch, nmalloc, ndalloc;
int expected = config_stats ? 0 : ENOENT;
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
-
- p = mallocx(large_maxclass, 0);
+ p = mallocx((1U << LG_LARGE_MINCLASS), MALLOCX_ARENA(0));
assert_ptr_not_null(p, "Unexpected mallocx() failure");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.large.allocated", &allocated, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.allocated",
+ (void *)&allocated, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.large.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.large.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.large.nrequests", &nrequests, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.nmalloc", (void *)&nmalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.large.ndalloc", (void *)&ndalloc,
+ &sz, NULL, 0), expected, "Unexpected mallctl() result");
if (config_stats) {
assert_zu_gt(allocated, 0,
"allocated should be greater than zero");
- assert_zu_gt(nmalloc, 0,
+ assert_u64_gt(nmalloc, 0,
"nmalloc should be greater than zero");
- assert_zu_ge(nmalloc, ndalloc,
+ assert_u64_ge(nmalloc, ndalloc,
"nmalloc should be at least as large as ndalloc");
- assert_zu_gt(nrequests, 0,
- "nrequests should be greater than zero");
}
dallocx(p, 0);
}
TEST_END
-TEST_BEGIN(test_stats_arenas_huge)
-{
- unsigned arena;
- void *p;
- size_t sz, allocated;
- uint64_t epoch, nmalloc, ndalloc;
- int expected = config_stats ? 0 : ENOENT;
-
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
-
- p = mallocx(chunksize, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
-
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
-
- sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.huge.allocated", &allocated, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.huge.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.huge.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
-
- if (config_stats) {
- assert_zu_gt(allocated, 0,
- "allocated should be greater than zero");
- assert_zu_gt(nmalloc, 0,
- "nmalloc should be greater than zero");
- assert_zu_ge(nmalloc, ndalloc,
- "nmalloc should be at least as large as ndalloc");
- }
-
- dallocx(p, 0);
+static void
+gen_mallctl_str(char *cmd, char *name, unsigned arena_ind) {
+ sprintf(cmd, "stats.arenas.%u.bins.0.%s", arena_ind, name);
}
-TEST_END
-TEST_BEGIN(test_stats_arenas_bins)
-{
- unsigned arena;
+TEST_BEGIN(test_stats_arenas_bins) {
void *p;
- size_t sz, curruns, curregs;
+ size_t sz, curslabs, curregs;
uint64_t epoch, nmalloc, ndalloc, nrequests, nfills, nflushes;
- uint64_t nruns, nreruns;
+ uint64_t nslabs, nreslabs;
int expected = config_stats ? 0 : ENOENT;
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
+ /* Make sure allocation below isn't satisfied by tcache. */
+ assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
+ opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result");
+
+ unsigned arena_ind, old_arena_ind;
+ sz = sizeof(unsigned);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind, &sz, NULL, 0),
+ 0, "Arena creation failure");
+ sz = sizeof(arena_ind);
+ assert_d_eq(mallctl("thread.arena", (void *)&old_arena_ind, &sz,
+ (void *)&arena_ind, sizeof(arena_ind)), 0,
+ "Unexpected mallctl() failure");
- p = mallocx(arena_bin_info[0].reg_size, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
+ p = malloc(bin_infos[0].reg_size);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
- config_tcache ? 0 : ENOENT, "Unexpected mallctl() result");
+ opt_tcache ? 0 : EFAULT, "Unexpected mallctl() result");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
+ char cmd[128];
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.bins.0.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nrequests", &nrequests, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "nmalloc", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nmalloc, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "ndalloc", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&ndalloc, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "nrequests", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nrequests, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.bins.0.curregs", &curregs, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "curregs", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&curregs, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nfills", &nfills, &sz,
- NULL, 0), config_tcache ? expected : ENOENT,
+ gen_mallctl_str(cmd, "nfills", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nfills, &sz, NULL, 0), expected,
"Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nflushes", &nflushes, &sz,
- NULL, 0), config_tcache ? expected : ENOENT,
+ gen_mallctl_str(cmd, "nflushes", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nflushes, &sz, NULL, 0), expected,
"Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nruns", &nruns, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.bins.0.nreruns", &nreruns, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "nslabs", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nslabs, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "nreslabs", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&nreslabs, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.bins.0.curruns", &curruns, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ gen_mallctl_str(cmd, "curslabs", arena_ind);
+ assert_d_eq(mallctl(cmd, (void *)&curslabs, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
if (config_stats) {
assert_u64_gt(nmalloc, 0,
@@ -330,100 +298,57 @@ TEST_BEGIN(test_stats_arenas_bins)
"nrequests should be greater than zero");
assert_zu_gt(curregs, 0,
"allocated should be greater than zero");
- if (config_tcache) {
+ if (opt_tcache) {
assert_u64_gt(nfills, 0,
"At least one fill should have occurred");
assert_u64_gt(nflushes, 0,
"At least one flush should have occurred");
}
- assert_u64_gt(nruns, 0,
- "At least one run should have been allocated");
- assert_zu_gt(curruns, 0,
- "At least one run should be currently allocated");
- }
-
- dallocx(p, 0);
-}
-TEST_END
-
-TEST_BEGIN(test_stats_arenas_lruns)
-{
- unsigned arena;
- void *p;
- uint64_t epoch, nmalloc, ndalloc, nrequests;
- size_t curruns, sz;
- int expected = config_stats ? 0 : ENOENT;
-
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
-
- p = mallocx(LARGE_MINCLASS, 0);
- assert_ptr_not_null(p, "Unexpected mallocx() failure");
-
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
-
- sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.lruns.0.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.lruns.0.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.lruns.0.nrequests", &nrequests, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.lruns.0.curruns", &curruns, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
-
- if (config_stats) {
- assert_u64_gt(nmalloc, 0,
- "nmalloc should be greater than zero");
- assert_u64_ge(nmalloc, ndalloc,
- "nmalloc should be at least as large as ndalloc");
- assert_u64_gt(nrequests, 0,
- "nrequests should be greater than zero");
- assert_u64_gt(curruns, 0,
- "At least one run should be currently allocated");
+ assert_u64_gt(nslabs, 0,
+ "At least one slab should have been allocated");
+ assert_zu_gt(curslabs, 0,
+ "At least one slab should be currently allocated");
}
dallocx(p, 0);
}
TEST_END
-TEST_BEGIN(test_stats_arenas_hchunks)
-{
- unsigned arena;
+TEST_BEGIN(test_stats_arenas_lextents) {
void *p;
uint64_t epoch, nmalloc, ndalloc;
- size_t curhchunks, sz;
+ size_t curlextents, sz, hsize;
int expected = config_stats ? 0 : ENOENT;
- arena = 0;
- assert_d_eq(mallctl("thread.arena", NULL, NULL, &arena, sizeof(arena)),
- 0, "Unexpected mallctl() failure");
+ sz = sizeof(size_t);
+ assert_d_eq(mallctl("arenas.lextent.0.size", (void *)&hsize, &sz, NULL,
+ 0), 0, "Unexpected mallctl() failure");
- p = mallocx(chunksize, 0);
+ p = mallocx(hsize, MALLOCX_ARENA(0));
assert_ptr_not_null(p, "Unexpected mallocx() failure");
- assert_d_eq(mallctl("epoch", NULL, NULL, &epoch, sizeof(epoch)), 0,
- "Unexpected mallctl() failure");
+ assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
+ 0, "Unexpected mallctl() failure");
sz = sizeof(uint64_t);
- assert_d_eq(mallctl("stats.arenas.0.hchunks.0.nmalloc", &nmalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
- assert_d_eq(mallctl("stats.arenas.0.hchunks.0.ndalloc", &ndalloc, &sz,
- NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.lextents.0.nmalloc",
+ (void *)&nmalloc, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.lextents.0.ndalloc",
+ (void *)&ndalloc, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
sz = sizeof(size_t);
- assert_d_eq(mallctl("stats.arenas.0.hchunks.0.curhchunks", &curhchunks,
- &sz, NULL, 0), expected, "Unexpected mallctl() result");
+ assert_d_eq(mallctl("stats.arenas.0.lextents.0.curlextents",
+ (void *)&curlextents, &sz, NULL, 0), expected,
+ "Unexpected mallctl() result");
if (config_stats) {
assert_u64_gt(nmalloc, 0,
"nmalloc should be greater than zero");
assert_u64_ge(nmalloc, ndalloc,
"nmalloc should be at least as large as ndalloc");
- assert_u64_gt(curhchunks, 0,
- "At least one chunk should be currently allocated");
+ assert_u64_gt(curlextents, 0,
+ "At least one extent should be currently allocated");
}
dallocx(p, 0);
@@ -431,17 +356,13 @@ TEST_BEGIN(test_stats_arenas_hchunks)
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test_no_reentrancy(
test_stats_summary,
- test_stats_huge,
+ test_stats_large,
test_stats_arenas_summary,
test_stats_arenas_small,
test_stats_arenas_large,
- test_stats_arenas_huge,
test_stats_arenas_bins,
- test_stats_arenas_lruns,
- test_stats_arenas_hchunks));
+ test_stats_arenas_lextents);
}
diff --git a/deps/jemalloc/test/unit/stats_print.c b/deps/jemalloc/test/unit/stats_print.c
new file mode 100644
index 000000000..014d002fd
--- /dev/null
+++ b/deps/jemalloc/test/unit/stats_print.c
@@ -0,0 +1,999 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/util.h"
+
+typedef enum {
+ TOKEN_TYPE_NONE,
+ TOKEN_TYPE_ERROR,
+ TOKEN_TYPE_EOI,
+ TOKEN_TYPE_NULL,
+ TOKEN_TYPE_FALSE,
+ TOKEN_TYPE_TRUE,
+ TOKEN_TYPE_LBRACKET,
+ TOKEN_TYPE_RBRACKET,
+ TOKEN_TYPE_LBRACE,
+ TOKEN_TYPE_RBRACE,
+ TOKEN_TYPE_COLON,
+ TOKEN_TYPE_COMMA,
+ TOKEN_TYPE_STRING,
+ TOKEN_TYPE_NUMBER
+} token_type_t;
+
+typedef struct parser_s parser_t;
+typedef struct {
+ parser_t *parser;
+ token_type_t token_type;
+ size_t pos;
+ size_t len;
+ size_t line;
+ size_t col;
+} token_t;
+
+struct parser_s {
+ bool verbose;
+ char *buf; /* '\0'-terminated. */
+ size_t len; /* Number of characters preceding '\0' in buf. */
+ size_t pos;
+ size_t line;
+ size_t col;
+ token_t token;
+};
+
+static void
+token_init(token_t *token, parser_t *parser, token_type_t token_type,
+ size_t pos, size_t len, size_t line, size_t col) {
+ token->parser = parser;
+ token->token_type = token_type;
+ token->pos = pos;
+ token->len = len;
+ token->line = line;
+ token->col = col;
+}
+
+static void
+token_error(token_t *token) {
+ if (!token->parser->verbose) {
+ return;
+ }
+ switch (token->token_type) {
+ case TOKEN_TYPE_NONE:
+ not_reached();
+ case TOKEN_TYPE_ERROR:
+ malloc_printf("%zu:%zu: Unexpected character in token: ",
+ token->line, token->col);
+ break;
+ default:
+ malloc_printf("%zu:%zu: Unexpected token: ", token->line,
+ token->col);
+ break;
+ }
+ UNUSED ssize_t err = malloc_write_fd(STDERR_FILENO,
+ &token->parser->buf[token->pos], token->len);
+ malloc_printf("\n");
+}
+
+static void
+parser_init(parser_t *parser, bool verbose) {
+ parser->verbose = verbose;
+ parser->buf = NULL;
+ parser->len = 0;
+ parser->pos = 0;
+ parser->line = 1;
+ parser->col = 0;
+}
+
+static void
+parser_fini(parser_t *parser) {
+ if (parser->buf != NULL) {
+ dallocx(parser->buf, MALLOCX_TCACHE_NONE);
+ }
+}
+
+static bool
+parser_append(parser_t *parser, const char *str) {
+ size_t len = strlen(str);
+ char *buf = (parser->buf == NULL) ? mallocx(len + 1,
+ MALLOCX_TCACHE_NONE) : rallocx(parser->buf, parser->len + len + 1,
+ MALLOCX_TCACHE_NONE);
+ if (buf == NULL) {
+ return true;
+ }
+ memcpy(&buf[parser->len], str, len + 1);
+ parser->buf = buf;
+ parser->len += len;
+ return false;
+}
+
+static bool
+parser_tokenize(parser_t *parser) {
+ enum {
+ STATE_START,
+ STATE_EOI,
+ STATE_N, STATE_NU, STATE_NUL, STATE_NULL,
+ STATE_F, STATE_FA, STATE_FAL, STATE_FALS, STATE_FALSE,
+ STATE_T, STATE_TR, STATE_TRU, STATE_TRUE,
+ STATE_LBRACKET,
+ STATE_RBRACKET,
+ STATE_LBRACE,
+ STATE_RBRACE,
+ STATE_COLON,
+ STATE_COMMA,
+ STATE_CHARS,
+ STATE_CHAR_ESCAPE,
+ STATE_CHAR_U, STATE_CHAR_UD, STATE_CHAR_UDD, STATE_CHAR_UDDD,
+ STATE_STRING,
+ STATE_MINUS,
+ STATE_LEADING_ZERO,
+ STATE_DIGITS,
+ STATE_DECIMAL,
+ STATE_FRAC_DIGITS,
+ STATE_EXP,
+ STATE_EXP_SIGN,
+ STATE_EXP_DIGITS,
+ STATE_ACCEPT
+ } state = STATE_START;
+ size_t token_pos JEMALLOC_CC_SILENCE_INIT(0);
+ size_t token_line JEMALLOC_CC_SILENCE_INIT(1);
+ size_t token_col JEMALLOC_CC_SILENCE_INIT(0);
+
+ assert_zu_le(parser->pos, parser->len,
+ "Position is past end of buffer");
+
+ while (state != STATE_ACCEPT) {
+ char c = parser->buf[parser->pos];
+
+ switch (state) {
+ case STATE_START:
+ token_pos = parser->pos;
+ token_line = parser->line;
+ token_col = parser->col;
+ switch (c) {
+ case ' ': case '\b': case '\n': case '\r': case '\t':
+ break;
+ case '\0':
+ state = STATE_EOI;
+ break;
+ case 'n':
+ state = STATE_N;
+ break;
+ case 'f':
+ state = STATE_F;
+ break;
+ case 't':
+ state = STATE_T;
+ break;
+ case '[':
+ state = STATE_LBRACKET;
+ break;
+ case ']':
+ state = STATE_RBRACKET;
+ break;
+ case '{':
+ state = STATE_LBRACE;
+ break;
+ case '}':
+ state = STATE_RBRACE;
+ break;
+ case ':':
+ state = STATE_COLON;
+ break;
+ case ',':
+ state = STATE_COMMA;
+ break;
+ case '"':
+ state = STATE_CHARS;
+ break;
+ case '-':
+ state = STATE_MINUS;
+ break;
+ case '0':
+ state = STATE_LEADING_ZERO;
+ break;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ state = STATE_DIGITS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_EOI:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_EOI, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_N:
+ switch (c) {
+ case 'u':
+ state = STATE_NU;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_NU:
+ switch (c) {
+ case 'l':
+ state = STATE_NUL;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_NUL:
+ switch (c) {
+ case 'l':
+ state = STATE_NULL;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_NULL:
+ switch (c) {
+ case ' ': case '\b': case '\n': case '\r': case '\t':
+ case '\0':
+ case '[': case ']': case '{': case '}': case ':':
+ case ',':
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ token_init(&parser->token, parser, TOKEN_TYPE_NULL,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_F:
+ switch (c) {
+ case 'a':
+ state = STATE_FA;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_FA:
+ switch (c) {
+ case 'l':
+ state = STATE_FAL;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_FAL:
+ switch (c) {
+ case 's':
+ state = STATE_FALS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_FALS:
+ switch (c) {
+ case 'e':
+ state = STATE_FALSE;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_FALSE:
+ switch (c) {
+ case ' ': case '\b': case '\n': case '\r': case '\t':
+ case '\0':
+ case '[': case ']': case '{': case '}': case ':':
+ case ',':
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_FALSE, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_T:
+ switch (c) {
+ case 'r':
+ state = STATE_TR;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_TR:
+ switch (c) {
+ case 'u':
+ state = STATE_TRU;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_TRU:
+ switch (c) {
+ case 'e':
+ state = STATE_TRUE;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_TRUE:
+ switch (c) {
+ case ' ': case '\b': case '\n': case '\r': case '\t':
+ case '\0':
+ case '[': case ']': case '{': case '}': case ':':
+ case ',':
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ token_init(&parser->token, parser, TOKEN_TYPE_TRUE,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_LBRACKET:
+ token_init(&parser->token, parser, TOKEN_TYPE_LBRACKET,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_RBRACKET:
+ token_init(&parser->token, parser, TOKEN_TYPE_RBRACKET,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_LBRACE:
+ token_init(&parser->token, parser, TOKEN_TYPE_LBRACE,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_RBRACE:
+ token_init(&parser->token, parser, TOKEN_TYPE_RBRACE,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_COLON:
+ token_init(&parser->token, parser, TOKEN_TYPE_COLON,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_COMMA:
+ token_init(&parser->token, parser, TOKEN_TYPE_COMMA,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_CHARS:
+ switch (c) {
+ case '\\':
+ state = STATE_CHAR_ESCAPE;
+ break;
+ case '"':
+ state = STATE_STRING;
+ break;
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04:
+ case 0x05: case 0x06: case 0x07: case 0x08: case 0x09:
+ case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e:
+ case 0x0f: case 0x10: case 0x11: case 0x12: case 0x13:
+ case 0x14: case 0x15: case 0x16: case 0x17: case 0x18:
+ case 0x19: case 0x1a: case 0x1b: case 0x1c: case 0x1d:
+ case 0x1e: case 0x1f:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ default:
+ break;
+ }
+ break;
+ case STATE_CHAR_ESCAPE:
+ switch (c) {
+ case '"': case '\\': case '/': case 'b': case 'n':
+ case 'r': case 't':
+ state = STATE_CHARS;
+ break;
+ case 'u':
+ state = STATE_CHAR_U;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_CHAR_U:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ state = STATE_CHAR_UD;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_CHAR_UD:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ state = STATE_CHAR_UDD;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_CHAR_UDD:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ state = STATE_CHAR_UDDD;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_CHAR_UDDD:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F':
+ state = STATE_CHARS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_STRING:
+ token_init(&parser->token, parser, TOKEN_TYPE_STRING,
+ token_pos, parser->pos - token_pos, token_line,
+ token_col);
+ state = STATE_ACCEPT;
+ break;
+ case STATE_MINUS:
+ switch (c) {
+ case '0':
+ state = STATE_LEADING_ZERO;
+ break;
+ case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ state = STATE_DIGITS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_LEADING_ZERO:
+ switch (c) {
+ case '.':
+ state = STATE_DECIMAL;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_NUMBER, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ }
+ break;
+ case STATE_DIGITS:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ break;
+ case '.':
+ state = STATE_DECIMAL;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_NUMBER, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ }
+ break;
+ case STATE_DECIMAL:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ state = STATE_FRAC_DIGITS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_FRAC_DIGITS:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ break;
+ case 'e': case 'E':
+ state = STATE_EXP;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_NUMBER, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ }
+ break;
+ case STATE_EXP:
+ switch (c) {
+ case '-': case '+':
+ state = STATE_EXP_SIGN;
+ break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ state = STATE_EXP_DIGITS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_EXP_SIGN:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ state = STATE_EXP_DIGITS;
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_ERROR, token_pos, parser->pos + 1
+ - token_pos, token_line, token_col);
+ return true;
+ }
+ break;
+ case STATE_EXP_DIGITS:
+ switch (c) {
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ break;
+ default:
+ token_init(&parser->token, parser,
+ TOKEN_TYPE_NUMBER, token_pos, parser->pos -
+ token_pos, token_line, token_col);
+ state = STATE_ACCEPT;
+ break;
+ }
+ break;
+ default:
+ not_reached();
+ }
+
+ if (state != STATE_ACCEPT) {
+ if (c == '\n') {
+ parser->line++;
+ parser->col = 0;
+ } else {
+ parser->col++;
+ }
+ parser->pos++;
+ }
+ }
+ return false;
+}
+
+static bool parser_parse_array(parser_t *parser);
+static bool parser_parse_object(parser_t *parser);
+
+static bool
+parser_parse_value(parser_t *parser) {
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_NULL:
+ case TOKEN_TYPE_FALSE:
+ case TOKEN_TYPE_TRUE:
+ case TOKEN_TYPE_STRING:
+ case TOKEN_TYPE_NUMBER:
+ return false;
+ case TOKEN_TYPE_LBRACE:
+ return parser_parse_object(parser);
+ case TOKEN_TYPE_LBRACKET:
+ return parser_parse_array(parser);
+ default:
+ return true;
+ }
+ not_reached();
+}
+
+static bool
+parser_parse_pair(parser_t *parser) {
+ assert_d_eq(parser->token.token_type, TOKEN_TYPE_STRING,
+ "Pair should start with string");
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_COLON:
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ return parser_parse_value(parser);
+ default:
+ return true;
+ }
+}
+
+static bool
+parser_parse_values(parser_t *parser) {
+ if (parser_parse_value(parser)) {
+ return true;
+ }
+
+ while (true) {
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_COMMA:
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ if (parser_parse_value(parser)) {
+ return true;
+ }
+ break;
+ case TOKEN_TYPE_RBRACKET:
+ return false;
+ default:
+ return true;
+ }
+ }
+}
+
+static bool
+parser_parse_array(parser_t *parser) {
+ assert_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACKET,
+ "Array should start with [");
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_RBRACKET:
+ return false;
+ default:
+ return parser_parse_values(parser);
+ }
+ not_reached();
+}
+
+static bool
+parser_parse_pairs(parser_t *parser) {
+ assert_d_eq(parser->token.token_type, TOKEN_TYPE_STRING,
+ "Object should start with string");
+ if (parser_parse_pair(parser)) {
+ return true;
+ }
+
+ while (true) {
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_COMMA:
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_STRING:
+ if (parser_parse_pair(parser)) {
+ return true;
+ }
+ break;
+ default:
+ return true;
+ }
+ break;
+ case TOKEN_TYPE_RBRACE:
+ return false;
+ default:
+ return true;
+ }
+ }
+}
+
+static bool
+parser_parse_object(parser_t *parser) {
+ assert_d_eq(parser->token.token_type, TOKEN_TYPE_LBRACE,
+ "Object should start with {");
+ if (parser_tokenize(parser)) {
+ return true;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_STRING:
+ return parser_parse_pairs(parser);
+ case TOKEN_TYPE_RBRACE:
+ return false;
+ default:
+ return true;
+ }
+ not_reached();
+}
+
+static bool
+parser_parse(parser_t *parser) {
+ if (parser_tokenize(parser)) {
+ goto label_error;
+ }
+ if (parser_parse_value(parser)) {
+ goto label_error;
+ }
+
+ if (parser_tokenize(parser)) {
+ goto label_error;
+ }
+ switch (parser->token.token_type) {
+ case TOKEN_TYPE_EOI:
+ return false;
+ default:
+ goto label_error;
+ }
+ not_reached();
+
+label_error:
+ token_error(&parser->token);
+ return true;
+}
+
+TEST_BEGIN(test_json_parser) {
+ size_t i;
+ const char *invalid_inputs[] = {
+ /* Tokenizer error case tests. */
+ "{ \"string\": X }",
+ "{ \"string\": nXll }",
+ "{ \"string\": nuXl }",
+ "{ \"string\": nulX }",
+ "{ \"string\": nullX }",
+ "{ \"string\": fXlse }",
+ "{ \"string\": faXse }",
+ "{ \"string\": falXe }",
+ "{ \"string\": falsX }",
+ "{ \"string\": falseX }",
+ "{ \"string\": tXue }",
+ "{ \"string\": trXe }",
+ "{ \"string\": truX }",
+ "{ \"string\": trueX }",
+ "{ \"string\": \"\n\" }",
+ "{ \"string\": \"\\z\" }",
+ "{ \"string\": \"\\uX000\" }",
+ "{ \"string\": \"\\u0X00\" }",
+ "{ \"string\": \"\\u00X0\" }",
+ "{ \"string\": \"\\u000X\" }",
+ "{ \"string\": -X }",
+ "{ \"string\": 0.X }",
+ "{ \"string\": 0.0eX }",
+ "{ \"string\": 0.0e+X }",
+
+ /* Parser error test cases. */
+ "{\"string\": }",
+ "{\"string\" }",
+ "{\"string\": [ 0 }",
+ "{\"string\": {\"a\":0, 1 } }",
+ "{\"string\": {\"a\":0: } }",
+ "{",
+ "{}{",
+ };
+ const char *valid_inputs[] = {
+ /* Token tests. */
+ "null",
+ "false",
+ "true",
+ "{}",
+ "{\"a\": 0}",
+ "[]",
+ "[0, 1]",
+ "0",
+ "1",
+ "10",
+ "-10",
+ "10.23",
+ "10.23e4",
+ "10.23e-4",
+ "10.23e+4",
+ "10.23E4",
+ "10.23E-4",
+ "10.23E+4",
+ "-10.23",
+ "-10.23e4",
+ "-10.23e-4",
+ "-10.23e+4",
+ "-10.23E4",
+ "-10.23E-4",
+ "-10.23E+4",
+ "\"value\"",
+ "\" \\\" \\/ \\b \\n \\r \\t \\u0abc \\u1DEF \"",
+
+ /* Parser test with various nesting. */
+ "{\"a\":null, \"b\":[1,[{\"c\":2},3]], \"d\":{\"e\":true}}",
+ };
+
+ for (i = 0; i < sizeof(invalid_inputs)/sizeof(const char *); i++) {
+ const char *input = invalid_inputs[i];
+ parser_t parser;
+ parser_init(&parser, false);
+ assert_false(parser_append(&parser, input),
+ "Unexpected input appending failure");
+ assert_true(parser_parse(&parser),
+ "Unexpected parse success for input: %s", input);
+ parser_fini(&parser);
+ }
+
+ for (i = 0; i < sizeof(valid_inputs)/sizeof(const char *); i++) {
+ const char *input = valid_inputs[i];
+ parser_t parser;
+ parser_init(&parser, true);
+ assert_false(parser_append(&parser, input),
+ "Unexpected input appending failure");
+ assert_false(parser_parse(&parser),
+ "Unexpected parse error for input: %s", input);
+ parser_fini(&parser);
+ }
+}
+TEST_END
+
+void
+write_cb(void *opaque, const char *str) {
+ parser_t *parser = (parser_t *)opaque;
+ if (parser_append(parser, str)) {
+ test_fail("Unexpected input appending failure");
+ }
+}
+
+TEST_BEGIN(test_stats_print_json) {
+ const char *opts[] = {
+ "J",
+ "Jg",
+ "Jm",
+ "Jd",
+ "Jmd",
+ "Jgd",
+ "Jgm",
+ "Jgmd",
+ "Ja",
+ "Jb",
+ "Jl",
+ "Jx",
+ "Jbl",
+ "Jal",
+ "Jab",
+ "Jabl",
+ "Jax",
+ "Jbx",
+ "Jlx",
+ "Jablx",
+ "Jgmdablx",
+ };
+ unsigned arena_ind, i;
+
+ for (i = 0; i < 3; i++) {
+ unsigned j;
+
+ switch (i) {
+ case 0:
+ break;
+ case 1: {
+ size_t sz = sizeof(arena_ind);
+ assert_d_eq(mallctl("arenas.create", (void *)&arena_ind,
+ &sz, NULL, 0), 0, "Unexpected mallctl failure");
+ break;
+ } case 2: {
+ size_t mib[3];
+ size_t miblen = sizeof(mib)/sizeof(size_t);
+ assert_d_eq(mallctlnametomib("arena.0.destroy",
+ mib, &miblen), 0,
+ "Unexpected mallctlnametomib failure");
+ mib[1] = arena_ind;
+ assert_d_eq(mallctlbymib(mib, miblen, NULL, NULL, NULL,
+ 0), 0, "Unexpected mallctlbymib failure");
+ break;
+ } default:
+ not_reached();
+ }
+
+ for (j = 0; j < sizeof(opts)/sizeof(const char *); j++) {
+ parser_t parser;
+
+ parser_init(&parser, true);
+ malloc_stats_print(write_cb, (void *)&parser, opts[j]);
+ assert_false(parser_parse(&parser),
+ "Unexpected parse error, opts=\"%s\"", opts[j]);
+ parser_fini(&parser);
+ }
+ }
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_json_parser,
+ test_stats_print_json);
+}
diff --git a/deps/jemalloc/test/unit/ticker.c b/deps/jemalloc/test/unit/ticker.c
new file mode 100644
index 000000000..e5790a316
--- /dev/null
+++ b/deps/jemalloc/test/unit/ticker.c
@@ -0,0 +1,73 @@
+#include "test/jemalloc_test.h"
+
+#include "jemalloc/internal/ticker.h"
+
+TEST_BEGIN(test_ticker_tick) {
+#define NREPS 2
+#define NTICKS 3
+ ticker_t ticker;
+ int32_t i, j;
+
+ ticker_init(&ticker, NTICKS);
+ for (i = 0; i < NREPS; i++) {
+ for (j = 0; j < NTICKS; j++) {
+ assert_u_eq(ticker_read(&ticker), NTICKS - j,
+ "Unexpected ticker value (i=%d, j=%d)", i, j);
+ assert_false(ticker_tick(&ticker),
+ "Unexpected ticker fire (i=%d, j=%d)", i, j);
+ }
+ assert_u32_eq(ticker_read(&ticker), 0,
+ "Expected ticker depletion");
+ assert_true(ticker_tick(&ticker),
+ "Expected ticker fire (i=%d)", i);
+ assert_u32_eq(ticker_read(&ticker), NTICKS,
+ "Expected ticker reset");
+ }
+#undef NTICKS
+}
+TEST_END
+
+TEST_BEGIN(test_ticker_ticks) {
+#define NTICKS 3
+ ticker_t ticker;
+
+ ticker_init(&ticker, NTICKS);
+
+ assert_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value");
+ assert_false(ticker_ticks(&ticker, NTICKS), "Unexpected ticker fire");
+ assert_u_eq(ticker_read(&ticker), 0, "Unexpected ticker value");
+ assert_true(ticker_ticks(&ticker, NTICKS), "Expected ticker fire");
+ assert_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value");
+
+ assert_true(ticker_ticks(&ticker, NTICKS + 1), "Expected ticker fire");
+ assert_u_eq(ticker_read(&ticker), NTICKS, "Unexpected ticker value");
+#undef NTICKS
+}
+TEST_END
+
+TEST_BEGIN(test_ticker_copy) {
+#define NTICKS 3
+ ticker_t ta, tb;
+
+ ticker_init(&ta, NTICKS);
+ ticker_copy(&tb, &ta);
+ assert_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value");
+ assert_true(ticker_ticks(&tb, NTICKS + 1), "Expected ticker fire");
+ assert_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value");
+
+ ticker_tick(&ta);
+ ticker_copy(&tb, &ta);
+ assert_u_eq(ticker_read(&tb), NTICKS - 1, "Unexpected ticker value");
+ assert_true(ticker_ticks(&tb, NTICKS), "Expected ticker fire");
+ assert_u_eq(ticker_read(&tb), NTICKS, "Unexpected ticker value");
+#undef NTICKS
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_ticker_tick,
+ test_ticker_ticks,
+ test_ticker_copy);
+}
diff --git a/deps/jemalloc/test/unit/tsd.c b/deps/jemalloc/test/unit/tsd.c
index 8be787fda..6c479139b 100644
--- a/deps/jemalloc/test/unit/tsd.c
+++ b/deps/jemalloc/test/unit/tsd.c
@@ -1,38 +1,29 @@
#include "test/jemalloc_test.h"
-#define THREAD_DATA 0x72b65c10
-
-typedef unsigned int data_t;
-
-static bool data_cleanup_executed;
-
-malloc_tsd_types(data_, data_t)
-malloc_tsd_protos(, data_, data_t)
+static int data_cleanup_count;
void
-data_cleanup(void *arg)
-{
- data_t *data = (data_t *)arg;
-
- if (!data_cleanup_executed) {
- assert_x_eq(*data, THREAD_DATA,
+data_cleanup(int *data) {
+ if (data_cleanup_count == 0) {
+ assert_x_eq(*data, MALLOC_TSD_TEST_DATA_INIT,
"Argument passed into cleanup function should match tsd "
"value");
}
- data_cleanup_executed = true;
+ ++data_cleanup_count;
/*
* Allocate during cleanup for two rounds, in order to assure that
* jemalloc's internal tsd reinitialization happens.
*/
+ bool reincarnate = false;
switch (*data) {
- case THREAD_DATA:
+ case MALLOC_TSD_TEST_DATA_INIT:
*data = 1;
- data_tsd_set(data);
+ reincarnate = true;
break;
case 1:
*data = 2;
- data_tsd_set(data);
+ reincarnate = true;
break;
case 2:
return;
@@ -40,68 +31,109 @@ data_cleanup(void *arg)
not_reached();
}
- {
+ if (reincarnate) {
void *p = mallocx(1, 0);
assert_ptr_not_null(p, "Unexpeced mallocx() failure");
dallocx(p, 0);
}
}
-malloc_tsd_externs(data_, data_t)
-#define DATA_INIT 0x12345678
-malloc_tsd_data(, data_, data_t, DATA_INIT)
-malloc_tsd_funcs(, data_, data_t, DATA_INIT, data_cleanup)
-
static void *
-thd_start(void *arg)
-{
- data_t d = (data_t)(uintptr_t)arg;
+thd_start(void *arg) {
+ int d = (int)(uintptr_t)arg;
void *p;
- assert_x_eq(*data_tsd_get(), DATA_INIT,
+ tsd_t *tsd = tsd_fetch();
+ assert_x_eq(tsd_test_data_get(tsd), MALLOC_TSD_TEST_DATA_INIT,
"Initial tsd get should return initialization value");
p = malloc(1);
assert_ptr_not_null(p, "Unexpected malloc() failure");
- data_tsd_set(&d);
- assert_x_eq(*data_tsd_get(), d,
+ tsd_test_data_set(tsd, d);
+ assert_x_eq(tsd_test_data_get(tsd), d,
"After tsd set, tsd get should return value that was set");
d = 0;
- assert_x_eq(*data_tsd_get(), (data_t)(uintptr_t)arg,
+ assert_x_eq(tsd_test_data_get(tsd), (int)(uintptr_t)arg,
"Resetting local data should have no effect on tsd");
+ tsd_test_callback_set(tsd, &data_cleanup);
+
free(p);
- return (NULL);
+ return NULL;
}
-TEST_BEGIN(test_tsd_main_thread)
-{
-
- thd_start((void *) 0xa5f3e329);
+TEST_BEGIN(test_tsd_main_thread) {
+ thd_start((void *)(uintptr_t)0xa5f3e329);
}
TEST_END
-TEST_BEGIN(test_tsd_sub_thread)
-{
+TEST_BEGIN(test_tsd_sub_thread) {
thd_t thd;
- data_cleanup_executed = false;
- thd_create(&thd, thd_start, (void *)THREAD_DATA);
+ data_cleanup_count = 0;
+ thd_create(&thd, thd_start, (void *)MALLOC_TSD_TEST_DATA_INIT);
thd_join(thd, NULL);
- assert_true(data_cleanup_executed,
- "Cleanup function should have executed");
+ /*
+ * We reincarnate twice in the data cleanup, so it should execute at
+ * least 3 times.
+ */
+ assert_x_ge(data_cleanup_count, 3,
+ "Cleanup function should have executed multiple times.");
}
TEST_END
-int
-main(void)
-{
+static void *
+thd_start_reincarnated(void *arg) {
+ tsd_t *tsd = tsd_fetch();
+ assert(tsd);
+
+ void *p = malloc(1);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
+
+ /* Manually trigger reincarnation. */
+ assert_ptr_not_null(tsd_arena_get(tsd),
+ "Should have tsd arena set.");
+ tsd_cleanup((void *)tsd);
+ assert_ptr_null(*tsd_arenap_get_unsafe(tsd),
+ "TSD arena should have been cleared.");
+ assert_u_eq(tsd->state, tsd_state_purgatory,
+ "TSD state should be purgatory\n");
- data_tsd_boot();
+ free(p);
+ assert_u_eq(tsd->state, tsd_state_reincarnated,
+ "TSD state should be reincarnated\n");
+ p = mallocx(1, MALLOCX_TCACHE_NONE);
+ assert_ptr_not_null(p, "Unexpected malloc() failure");
+ assert_ptr_null(*tsd_arenap_get_unsafe(tsd),
+ "Should not have tsd arena set after reincarnation.");
+
+ free(p);
+ tsd_cleanup((void *)tsd);
+ assert_ptr_null(*tsd_arenap_get_unsafe(tsd),
+ "TSD arena should have been cleared after 2nd cleanup.");
+
+ return NULL;
+}
+
+TEST_BEGIN(test_tsd_reincarnation) {
+ thd_t thd;
+ thd_create(&thd, thd_start_reincarnated, NULL);
+ thd_join(thd, NULL);
+}
+TEST_END
+
+int
+main(void) {
+ /* Ensure tsd bootstrapped. */
+ if (nallocx(1, 0) == 0) {
+ malloc_printf("Initialization error");
+ return test_status_fail;
+ }
- return (test(
+ return test_no_reentrancy(
test_tsd_main_thread,
- test_tsd_sub_thread));
+ test_tsd_sub_thread,
+ test_tsd_reincarnation);
}
diff --git a/deps/jemalloc/test/unit/witness.c b/deps/jemalloc/test/unit/witness.c
new file mode 100644
index 000000000..5986da400
--- /dev/null
+++ b/deps/jemalloc/test/unit/witness.c
@@ -0,0 +1,280 @@
+#include "test/jemalloc_test.h"
+
+static witness_lock_error_t *witness_lock_error_orig;
+static witness_owner_error_t *witness_owner_error_orig;
+static witness_not_owner_error_t *witness_not_owner_error_orig;
+static witness_depth_error_t *witness_depth_error_orig;
+
+static bool saw_lock_error;
+static bool saw_owner_error;
+static bool saw_not_owner_error;
+static bool saw_depth_error;
+
+static void
+witness_lock_error_intercept(const witness_list_t *witnesses,
+ const witness_t *witness) {
+ saw_lock_error = true;
+}
+
+static void
+witness_owner_error_intercept(const witness_t *witness) {
+ saw_owner_error = true;
+}
+
+static void
+witness_not_owner_error_intercept(const witness_t *witness) {
+ saw_not_owner_error = true;
+}
+
+static void
+witness_depth_error_intercept(const witness_list_t *witnesses,
+ witness_rank_t rank_inclusive, unsigned depth) {
+ saw_depth_error = true;
+}
+
+static int
+witness_comp(const witness_t *a, void *oa, const witness_t *b, void *ob) {
+ assert_u_eq(a->rank, b->rank, "Witnesses should have equal rank");
+
+ assert(oa == (void *)a);
+ assert(ob == (void *)b);
+
+ return strcmp(a->name, b->name);
+}
+
+static int
+witness_comp_reverse(const witness_t *a, void *oa, const witness_t *b,
+ void *ob) {
+ assert_u_eq(a->rank, b->rank, "Witnesses should have equal rank");
+
+ assert(oa == (void *)a);
+ assert(ob == (void *)b);
+
+ return -strcmp(a->name, b->name);
+}
+
+TEST_BEGIN(test_witness) {
+ witness_t a, b;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 0);
+
+ witness_init(&a, "a", 1, NULL, NULL);
+ witness_assert_not_owner(&witness_tsdn, &a);
+ witness_lock(&witness_tsdn, &a);
+ witness_assert_owner(&witness_tsdn, &a);
+ witness_assert_depth(&witness_tsdn, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 0);
+
+ witness_init(&b, "b", 2, NULL, NULL);
+ witness_assert_not_owner(&witness_tsdn, &b);
+ witness_lock(&witness_tsdn, &b);
+ witness_assert_owner(&witness_tsdn, &b);
+ witness_assert_depth(&witness_tsdn, 2);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 2);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)3U, 0);
+
+ witness_unlock(&witness_tsdn, &a);
+ witness_assert_depth(&witness_tsdn, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)2U, 1);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)3U, 0);
+ witness_unlock(&witness_tsdn, &b);
+
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+ witness_assert_depth_to_rank(&witness_tsdn, (witness_rank_t)1U, 0);
+}
+TEST_END
+
+TEST_BEGIN(test_witness_comp) {
+ witness_t a, b, c, d;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_init(&a, "a", 1, witness_comp, &a);
+ witness_assert_not_owner(&witness_tsdn, &a);
+ witness_lock(&witness_tsdn, &a);
+ witness_assert_owner(&witness_tsdn, &a);
+ witness_assert_depth(&witness_tsdn, 1);
+
+ witness_init(&b, "b", 1, witness_comp, &b);
+ witness_assert_not_owner(&witness_tsdn, &b);
+ witness_lock(&witness_tsdn, &b);
+ witness_assert_owner(&witness_tsdn, &b);
+ witness_assert_depth(&witness_tsdn, 2);
+ witness_unlock(&witness_tsdn, &b);
+ witness_assert_depth(&witness_tsdn, 1);
+
+ witness_lock_error_orig = witness_lock_error;
+ witness_lock_error = witness_lock_error_intercept;
+ saw_lock_error = false;
+
+ witness_init(&c, "c", 1, witness_comp_reverse, &c);
+ witness_assert_not_owner(&witness_tsdn, &c);
+ assert_false(saw_lock_error, "Unexpected witness lock error");
+ witness_lock(&witness_tsdn, &c);
+ assert_true(saw_lock_error, "Expected witness lock error");
+ witness_unlock(&witness_tsdn, &c);
+ witness_assert_depth(&witness_tsdn, 1);
+
+ saw_lock_error = false;
+
+ witness_init(&d, "d", 1, NULL, NULL);
+ witness_assert_not_owner(&witness_tsdn, &d);
+ assert_false(saw_lock_error, "Unexpected witness lock error");
+ witness_lock(&witness_tsdn, &d);
+ assert_true(saw_lock_error, "Expected witness lock error");
+ witness_unlock(&witness_tsdn, &d);
+ witness_assert_depth(&witness_tsdn, 1);
+
+ witness_unlock(&witness_tsdn, &a);
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_lock_error = witness_lock_error_orig;
+}
+TEST_END
+
+TEST_BEGIN(test_witness_reversal) {
+ witness_t a, b;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_lock_error_orig = witness_lock_error;
+ witness_lock_error = witness_lock_error_intercept;
+ saw_lock_error = false;
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_init(&a, "a", 1, NULL, NULL);
+ witness_init(&b, "b", 2, NULL, NULL);
+
+ witness_lock(&witness_tsdn, &b);
+ witness_assert_depth(&witness_tsdn, 1);
+ assert_false(saw_lock_error, "Unexpected witness lock error");
+ witness_lock(&witness_tsdn, &a);
+ assert_true(saw_lock_error, "Expected witness lock error");
+
+ witness_unlock(&witness_tsdn, &a);
+ witness_assert_depth(&witness_tsdn, 1);
+ witness_unlock(&witness_tsdn, &b);
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_lock_error = witness_lock_error_orig;
+}
+TEST_END
+
+TEST_BEGIN(test_witness_recursive) {
+ witness_t a;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_not_owner_error_orig = witness_not_owner_error;
+ witness_not_owner_error = witness_not_owner_error_intercept;
+ saw_not_owner_error = false;
+
+ witness_lock_error_orig = witness_lock_error;
+ witness_lock_error = witness_lock_error_intercept;
+ saw_lock_error = false;
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_init(&a, "a", 1, NULL, NULL);
+
+ witness_lock(&witness_tsdn, &a);
+ assert_false(saw_lock_error, "Unexpected witness lock error");
+ assert_false(saw_not_owner_error, "Unexpected witness not owner error");
+ witness_lock(&witness_tsdn, &a);
+ assert_true(saw_lock_error, "Expected witness lock error");
+ assert_true(saw_not_owner_error, "Expected witness not owner error");
+
+ witness_unlock(&witness_tsdn, &a);
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_owner_error = witness_owner_error_orig;
+ witness_lock_error = witness_lock_error_orig;
+
+}
+TEST_END
+
+TEST_BEGIN(test_witness_unlock_not_owned) {
+ witness_t a;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_owner_error_orig = witness_owner_error;
+ witness_owner_error = witness_owner_error_intercept;
+ saw_owner_error = false;
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_init(&a, "a", 1, NULL, NULL);
+
+ assert_false(saw_owner_error, "Unexpected owner error");
+ witness_unlock(&witness_tsdn, &a);
+ assert_true(saw_owner_error, "Expected owner error");
+
+ witness_assert_lockless(&witness_tsdn);
+
+ witness_owner_error = witness_owner_error_orig;
+}
+TEST_END
+
+TEST_BEGIN(test_witness_depth) {
+ witness_t a;
+ witness_tsdn_t witness_tsdn = { WITNESS_TSD_INITIALIZER };
+
+ test_skip_if(!config_debug);
+
+ witness_depth_error_orig = witness_depth_error;
+ witness_depth_error = witness_depth_error_intercept;
+ saw_depth_error = false;
+
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+
+ witness_init(&a, "a", 1, NULL, NULL);
+
+ assert_false(saw_depth_error, "Unexpected depth error");
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+
+ witness_lock(&witness_tsdn, &a);
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+ assert_true(saw_depth_error, "Expected depth error");
+
+ witness_unlock(&witness_tsdn, &a);
+
+ witness_assert_lockless(&witness_tsdn);
+ witness_assert_depth(&witness_tsdn, 0);
+
+ witness_depth_error = witness_depth_error_orig;
+}
+TEST_END
+
+int
+main(void) {
+ return test(
+ test_witness,
+ test_witness_comp,
+ test_witness_reversal,
+ test_witness_recursive,
+ test_witness_unlock_not_owned,
+ test_witness_depth);
+}
diff --git a/deps/jemalloc/test/unit/zero.c b/deps/jemalloc/test/unit/zero.c
index 93afc2b87..553692ba7 100644
--- a/deps/jemalloc/test/unit/zero.c
+++ b/deps/jemalloc/test/unit/zero.c
@@ -1,78 +1,59 @@
#include "test/jemalloc_test.h"
-#ifdef JEMALLOC_FILL
-const char *malloc_conf =
- "abort:false,junk:false,zero:true,redzone:false,quarantine:0";
-#endif
-
static void
-test_zero(size_t sz_min, size_t sz_max)
-{
- char *s;
+test_zero(size_t sz_min, size_t sz_max) {
+ uint8_t *s;
size_t sz_prev, sz, i;
+#define MAGIC ((uint8_t)0x61)
sz_prev = 0;
- s = (char *)mallocx(sz_min, 0);
+ s = (uint8_t *)mallocx(sz_min, 0);
assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
for (sz = sallocx(s, 0); sz <= sz_max;
sz_prev = sz, sz = sallocx(s, 0)) {
if (sz_prev > 0) {
- assert_c_eq(s[0], 'a',
+ assert_u_eq(s[0], MAGIC,
"Previously allocated byte %zu/%zu is corrupted",
ZU(0), sz_prev);
- assert_c_eq(s[sz_prev-1], 'a',
+ assert_u_eq(s[sz_prev-1], MAGIC,
"Previously allocated byte %zu/%zu is corrupted",
sz_prev-1, sz_prev);
}
for (i = sz_prev; i < sz; i++) {
- assert_c_eq(s[i], 0x0,
+ assert_u_eq(s[i], 0x0,
"Newly allocated byte %zu/%zu isn't zero-filled",
i, sz);
- s[i] = 'a';
+ s[i] = MAGIC;
}
if (xallocx(s, sz+1, 0, 0) == sz) {
- s = (char *)rallocx(s, sz+1, 0);
+ s = (uint8_t *)rallocx(s, sz+1, 0);
assert_ptr_not_null((void *)s,
"Unexpected rallocx() failure");
}
}
dallocx(s, 0);
+#undef MAGIC
}
-TEST_BEGIN(test_zero_small)
-{
-
+TEST_BEGIN(test_zero_small) {
test_skip_if(!config_fill);
test_zero(1, SMALL_MAXCLASS-1);
}
TEST_END
-TEST_BEGIN(test_zero_large)
-{
-
+TEST_BEGIN(test_zero_large) {
test_skip_if(!config_fill);
- test_zero(SMALL_MAXCLASS+1, large_maxclass);
-}
-TEST_END
-
-TEST_BEGIN(test_zero_huge)
-{
-
- test_skip_if(!config_fill);
- test_zero(large_maxclass+1, chunksize*2);
+ test_zero(SMALL_MAXCLASS+1, (1U << (LG_LARGE_MINCLASS+1)));
}
TEST_END
int
-main(void)
-{
-
- return (test(
+main(void) {
+ return test(
test_zero_small,
- test_zero_large,
- test_zero_huge));
+ test_zero_large);
}
diff --git a/deps/jemalloc/test/unit/zero.sh b/deps/jemalloc/test/unit/zero.sh
new file mode 100644
index 000000000..b4540b27e
--- /dev/null
+++ b/deps/jemalloc/test/unit/zero.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+if [ "x${enable_fill}" = "x1" ] ; then
+ export MALLOC_CONF="abort:false,junk:false,zero:true"
+fi
diff --git a/deps/lua/src/lua_cmsgpack.c b/deps/lua/src/lua_cmsgpack.c
index 90a388f3f..892154793 100644
--- a/deps/lua/src/lua_cmsgpack.c
+++ b/deps/lua/src/lua_cmsgpack.c
@@ -385,6 +385,7 @@ void mp_encode_lua_table_as_array(lua_State *L, mp_buf *buf, int level) {
#endif
mp_encode_array(L,buf,len);
+ luaL_checkstack(L, 1, "in function mp_encode_lua_table_as_array");
for (j = 1; j <= len; j++) {
lua_pushnumber(L,j);
lua_gettable(L,-2);
@@ -400,6 +401,7 @@ void mp_encode_lua_table_as_map(lua_State *L, mp_buf *buf, int level) {
* Lua API, we need to iterate a first time. Note that an alternative
* would be to do a single run, and then hack the buffer to insert the
* map opcodes for message pack. Too hackish for this lib. */
+ luaL_checkstack(L, 3, "in function mp_encode_lua_table_as_map");
lua_pushnil(L);
while(lua_next(L,-2)) {
lua_pop(L,1); /* remove value, keep key for next iteration. */
@@ -515,10 +517,14 @@ int mp_pack(lua_State *L) {
if (nargs == 0)
return luaL_argerror(L, 0, "MessagePack pack needs input.");
+ if (!lua_checkstack(L, nargs))
+ return luaL_argerror(L, 0, "Too many arguments for MessagePack pack.");
+
buf = mp_buf_new(L);
for(i = 1; i <= nargs; i++) {
/* Copy argument i to top of stack for _encode processing;
* the encode function pops it from the stack when complete. */
+ luaL_checkstack(L, 1, "in function mp_check");
lua_pushvalue(L, i);
mp_encode_lua_type(L,buf,0);
@@ -547,6 +553,7 @@ void mp_decode_to_lua_array(lua_State *L, mp_cur *c, size_t len) {
int index = 1;
lua_newtable(L);
+ luaL_checkstack(L, 1, "in function mp_decode_to_lua_array");
while(len--) {
lua_pushnumber(L,index++);
mp_decode_to_lua_type(L,c);
@@ -821,6 +828,9 @@ int mp_unpack_full(lua_State *L, int limit, int offset) {
* subtract the entire buffer size from the unprocessed size
* to get our next start offset */
int offset = len - c.left;
+
+ luaL_checkstack(L, 1, "in function mp_unpack_full");
+
/* Return offset -1 when we have have processed the entire buffer. */
lua_pushinteger(L, c.left == 0 ? -1 : offset);
/* Results are returned with the arg elements still
diff --git a/deps/lua/src/lua_struct.c b/deps/lua/src/lua_struct.c
index a602bb430..4d5f027b8 100644
--- a/deps/lua/src/lua_struct.c
+++ b/deps/lua/src/lua_struct.c
@@ -1,7 +1,7 @@
/*
** {======================================================
** Library for packing/unpacking structures.
-** $Id: struct.c,v 1.4 2012/07/04 18:54:29 roberto Exp $
+** $Id: struct.c,v 1.7 2018/05/11 22:04:31 roberto Exp $
** See Copyright Notice at the end of this file
** =======================================================
*/
@@ -15,8 +15,8 @@
** h/H - signed/unsigned short
** l/L - signed/unsigned long
** T - size_t
-** i/In - signed/unsigned integer with size `n' (default is size of int)
-** cn - sequence of `n' chars (from/to a string); when packing, n==0 means
+** i/In - signed/unsigned integer with size 'n' (default is size of int)
+** cn - sequence of 'n' chars (from/to a string); when packing, n==0 means
the whole string; when unpacking, n==0 means use the previous
read number as the string length
** s - zero-terminated string
@@ -89,14 +89,12 @@ typedef struct Header {
} Header;
-static int getnum (lua_State *L, const char **fmt, int df) {
+static int getnum (const char **fmt, int df) {
if (!isdigit(**fmt)) /* no number? */
return df; /* return default value */
else {
int a = 0;
do {
- if (a > (INT_MAX / 10) || a * 10 > (INT_MAX - (**fmt - '0')))
- luaL_error(L, "integral size overflow");
a = a*10 + *((*fmt)++) - '0';
} while (isdigit(**fmt));
return a;
@@ -117,9 +115,9 @@ static size_t optsize (lua_State *L, char opt, const char **fmt) {
case 'f': return sizeof(float);
case 'd': return sizeof(double);
case 'x': return 1;
- case 'c': return getnum(L, fmt, 1);
+ case 'c': return getnum(fmt, 1);
case 'i': case 'I': {
- int sz = getnum(L, fmt, sizeof(int));
+ int sz = getnum(fmt, sizeof(int));
if (sz > MAXINTSIZE)
luaL_error(L, "integral size %d is larger than limit of %d",
sz, MAXINTSIZE);
@@ -152,7 +150,7 @@ static void controloptions (lua_State *L, int opt, const char **fmt,
case '>': h->endian = BIG; return;
case '<': h->endian = LITTLE; return;
case '!': {
- int a = getnum(L, fmt, MAXALIGN);
+ int a = getnum(fmt, MAXALIGN);
if (!isp2(a))
luaL_error(L, "alignment %d is not a power of 2", a);
h->align = a;
@@ -295,21 +293,26 @@ static int b_unpack (lua_State *L) {
const char *fmt = luaL_checkstring(L, 1);
size_t ld;
const char *data = luaL_checklstring(L, 2, &ld);
- size_t pos = luaL_optinteger(L, 3, 1) - 1;
+ size_t pos = luaL_optinteger(L, 3, 1);
+ luaL_argcheck(L, pos > 0, 3, "offset must be 1 or greater");
+ pos--; /* Lua indexes are 1-based, but here we want 0-based for C
+ * pointer math. */
+ int n = 0; /* number of results */
defaultoptions(&h);
- lua_settop(L, 2);
while (*fmt) {
int opt = *fmt++;
size_t size = optsize(L, opt, &fmt);
pos += gettoalign(pos, &h, opt, size);
- luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
- luaL_checkstack(L, 1, "too many results");
+ luaL_argcheck(L, size <= ld && pos <= ld - size,
+ 2, "data string too short");
+ /* stack space for item + next position */
+ luaL_checkstack(L, 2, "too many results");
switch (opt) {
case 'b': case 'B': case 'h': case 'H':
case 'l': case 'L': case 'T': case 'i': case 'I': { /* integer types */
int issigned = islower(opt);
lua_Number res = getinteger(data+pos, h.endian, issigned, size);
- lua_pushnumber(L, res);
+ lua_pushnumber(L, res); n++;
break;
}
case 'x': {
@@ -319,25 +322,26 @@ static int b_unpack (lua_State *L) {
float f;
memcpy(&f, data+pos, size);
correctbytes((char *)&f, sizeof(f), h.endian);
- lua_pushnumber(L, f);
+ lua_pushnumber(L, f); n++;
break;
}
case 'd': {
double d;
memcpy(&d, data+pos, size);
correctbytes((char *)&d, sizeof(d), h.endian);
- lua_pushnumber(L, d);
+ lua_pushnumber(L, d); n++;
break;
}
case 'c': {
if (size == 0) {
- if (!lua_isnumber(L, -1))
- luaL_error(L, "format `c0' needs a previous size");
+ if (n == 0 || !lua_isnumber(L, -1))
+ luaL_error(L, "format 'c0' needs a previous size");
size = lua_tonumber(L, -1);
- lua_pop(L, 1);
- luaL_argcheck(L, pos+size <= ld, 2, "data string too short");
+ lua_pop(L, 1); n--;
+ luaL_argcheck(L, size <= ld && pos <= ld - size,
+ 2, "data string too short");
}
- lua_pushlstring(L, data+pos, size);
+ lua_pushlstring(L, data+pos, size); n++;
break;
}
case 's': {
@@ -345,15 +349,15 @@ static int b_unpack (lua_State *L) {
if (e == NULL)
luaL_error(L, "unfinished string in data");
size = (e - (data+pos)) + 1;
- lua_pushlstring(L, data+pos, size - 1);
+ lua_pushlstring(L, data+pos, size - 1); n++;
break;
}
default: controloptions(L, opt, &fmt, &h);
}
pos += size;
}
- lua_pushinteger(L, pos + 1);
- return lua_gettop(L) - 2;
+ lua_pushinteger(L, pos + 1); /* next position */
+ return n + 1;
}
@@ -399,7 +403,7 @@ LUALIB_API int luaopen_struct (lua_State *L) {
/******************************************************************************
-* Copyright (C) 2010-2012 Lua.org, PUC-Rio. All rights reserved.
+* Copyright (C) 2010-2018 Lua.org, PUC-Rio. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
diff --git a/redis.conf b/redis.conf
index 0ead6e305..408426f15 100644
--- a/redis.conf
+++ b/redis.conf
@@ -129,6 +129,76 @@ timeout 0
# Redis default starting with Redis 3.2.1.
tcp-keepalive 300
+################################# TLS/SSL #####################################
+
+# By default, TLS/SSL is disabled. To enable it, the "tls-port" configuration
+# directive can be used to define TLS-listening ports. To enable TLS on the
+# default port, use:
+#
+# port 0
+# tls-port 6379
+
+# Configure a X.509 certificate and private key to use for authenticating the
+# server to connected clients, masters or cluster peers. These files should be
+# PEM formatted.
+#
+# tls-cert-file redis.crt tls-key-file redis.key
+
+# Configure a DH parameters file to enable Diffie-Hellman (DH) key exchange:
+#
+# tls-dh-params-file redis.dh
+
+# Configure a CA certificate(s) bundle or directory to authenticate TLS/SSL
+# clients and peers. Redis requires an explicit configuration of at least one
+# of these, and will not implicitly use the system wide configuration.
+#
+# tls-ca-cert-file ca.crt
+# tls-ca-cert-dir /etc/ssl/certs
+
+# If TLS/SSL clients are required to authenticate using a client side
+# certificate, use this directive.
+#
+# Note: this applies to all incoming clients, including replicas.
+#
+# tls-auth-clients yes
+
+# If TLS/SSL should be used when connecting as a replica to a master, enable
+# this configuration directive:
+#
+# tls-replication yes
+
+# If TLS/SSL should be used for the Redis Cluster bus, enable this configuration
+# directive.
+#
+# NOTE: If TLS/SSL is enabled for Cluster Bus, mutual authentication is always
+# enforced.
+#
+# tls-cluster yes
+
+# Explicitly specify TLS versions to support. Allowed values are case insensitive
+# and include "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" (OpenSSL >= 1.1.1) or
+# "default" which is currently >= TLSv1.1.
+#
+# tls-protocols TLSv1.2
+
+# Configure allowed ciphers. See the ciphers(1ssl) manpage for more information
+# about the syntax of this string.
+#
+# Note: this configuration applies only to <= TLSv1.2.
+#
+# tls-ciphers DEFAULT:!MEDIUM
+
+# Configure allowed TLSv1.3 ciphersuites. See the ciphers(1ssl) manpage for more
+# information about the syntax of this string, and specifically for TLSv1.3
+# ciphersuites.
+#
+# tls-ciphersuites TLS_CHACHA20_POLY1305_SHA256
+
+# When choosing a cipher, use the server's preference instead of the client
+# preference. By default, the server follows the client's preference.
+#
+# tls-prefer-server-cipher yes
+
################################# GENERAL #####################################
# By default Redis does not run as a daemon. Use 'yes' if you need it.
@@ -264,86 +334,100 @@ dir ./
################################# REPLICATION #################################
-# Master-Slave replication. Use slaveof to make a Redis instance a copy of
+# Master-Replica replication. Use replicaof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
#
+# +------------------+ +---------------+
+# | Master | ---> | Replica |
+# | (receive writes) | | (exact copy) |
+# +------------------+ +---------------+
+#
# 1) Redis replication is asynchronous, but you can configure a master to
# stop accepting writes if it appears to be not connected with at least
-# a given number of slaves.
-# 2) Redis slaves are able to perform a partial resynchronization with the
+# a given number of replicas.
+# 2) Redis replicas are able to perform a partial resynchronization with the
# master if the replication link is lost for a relatively small amount of
# time. You may want to configure the replication backlog size (see the next
# sections of this file) with a sensible value depending on your needs.
# 3) Replication is automatic and does not need user intervention. After a
-# network partition slaves automatically try to reconnect to masters
+# network partition replicas automatically try to reconnect to masters
# and resynchronize with them.
#
-# slaveof <masterip> <masterport>
+# replicaof <masterip> <masterport>
# If the master is password protected (using the "requirepass" configuration
-# directive below) it is possible to tell the slave to authenticate before
+# directive below) it is possible to tell the replica to authenticate before
# starting the replication synchronization process, otherwise the master will
-# refuse the slave request.
+# refuse the replica request.
#
# masterauth <master-password>
+#
+# However this is not enough if you are using Redis ACLs (for Redis version
+# 6 or greater), and the default user is not capable of running the PSYNC
+# command and/or other commands needed for replication. In this case it's
+# better to configure a special user to use with replication, and specify the
+# masteruser configuration as such:
+#
+# masteruser <username>
+#
+# When masteruser is specified, the replica will authenticate against its
+# master using the new AUTH form: AUTH <username> <password>.
-# When a slave loses its connection with the master, or when the replication
-# is still in progress, the slave can act in two different ways:
+# When a replica loses its connection with the master, or when the replication
+# is still in progress, the replica can act in two different ways:
#
-# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
+# 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
# still reply to client requests, possibly with out of date data, or the
# data set may just be empty if this is the first synchronization.
#
-# 2) if slave-serve-stale-data is set to 'no' the slave will reply with
+# 2) if replica-serve-stale-data is set to 'no' the replica will reply with
# an error "SYNC with master in progress" to all the kind of commands
-# but to INFO, SLAVEOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
+# but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
# SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
# COMMAND, POST, HOST: and LATENCY.
#
-slave-serve-stale-data yes
+replica-serve-stale-data yes
-# You can configure a slave instance to accept writes or not. Writing against
-# a slave instance may be useful to store some ephemeral data (because data
-# written on a slave will be easily deleted after resync with the master) but
+# You can configure a replica instance to accept writes or not. Writing against
+# a replica instance may be useful to store some ephemeral data (because data
+# written on a replica will be easily deleted after resync with the master) but
# may also cause problems if clients are writing to it because of a
# misconfiguration.
#
-# Since Redis 2.6 by default slaves are read-only.
+# Since Redis 2.6 by default replicas are read-only.
#
-# Note: read only slaves are not designed to be exposed to untrusted clients
+# Note: read only replicas are not designed to be exposed to untrusted clients
# on the internet. It's just a protection layer against misuse of the instance.
-# Still a read only slave exports by default all the administrative commands
+# Still a read only replica exports by default all the administrative commands
# such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
-# security of read only slaves using 'rename-command' to shadow all the
+# security of read only replicas using 'rename-command' to shadow all the
# administrative / dangerous commands.
-slave-read-only yes
+replica-read-only yes
# Replication SYNC strategy: disk or socket.
#
-# -------------------------------------------------------
-# WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
-# -------------------------------------------------------
+# New replicas and reconnecting replicas that are not able to continue the
+# replication process just receiving differences, need to do what is called a
+# "full synchronization". An RDB file is transmitted from the master to the
+# replicas.
#
-# New slaves and reconnecting slaves that are not able to continue the replication
-# process just receiving differences, need to do what is called a "full
-# synchronization". An RDB file is transmitted from the master to the slaves.
# The transmission can happen in two different ways:
#
# 1) Disk-backed: The Redis master creates a new process that writes the RDB
# file on disk. Later the file is transferred by the parent
-# process to the slaves incrementally.
+# process to the replicas incrementally.
# 2) Diskless: The Redis master creates a new process that directly writes the
-# RDB file to slave sockets, without touching the disk at all.
+# RDB file to replica sockets, without touching the disk at all.
#
-# With disk-backed replication, while the RDB file is generated, more slaves
-# can be queued and served with the RDB file as soon as the current child producing
-# the RDB file finishes its work. With diskless replication instead once
-# the transfer starts, new slaves arriving will be queued and a new transfer
-# will start when the current one terminates.
+# With disk-backed replication, while the RDB file is generated, more replicas
+# can be queued and served with the RDB file as soon as the current child
+# producing the RDB file finishes its work. With diskless replication instead
+# once the transfer starts, new replicas arriving will be queued and a new
+# transfer will start when the current one terminates.
#
# When diskless replication is used, the master waits a configurable amount of
-# time (in seconds) before starting the transfer in the hope that multiple slaves
-# will arrive and the transfer can be parallelized.
+# time (in seconds) before starting the transfer in the hope that multiple
+# replicas will arrive and the transfer can be parallelized.
#
# With slow disks and fast (large bandwidth) networks, diskless replication
# works better.
@@ -351,157 +435,323 @@ repl-diskless-sync no
# When diskless replication is enabled, it is possible to configure the delay
# the server waits in order to spawn the child that transfers the RDB via socket
-# to the slaves.
+# to the replicas.
#
# This is important since once the transfer starts, it is not possible to serve
-# new slaves arriving, that will be queued for the next RDB transfer, so the server
-# waits a delay in order to let more slaves arrive.
+# new replicas arriving, that will be queued for the next RDB transfer, so the
+# server waits a delay in order to let more replicas arrive.
#
# The delay is specified in seconds, and by default is 5 seconds. To disable
# it entirely just set it to 0 seconds and the transfer will start ASAP.
repl-diskless-sync-delay 5
-# Slaves send PINGs to server in a predefined interval. It's possible to change
-# this interval with the repl_ping_slave_period option. The default value is 10
-# seconds.
-#
-# repl-ping-slave-period 10
+# -----------------------------------------------------------------------------
+# WARNING: RDB diskless load is experimental. Since in this setup the replica
+# does not immediately store an RDB on disk, it may cause data loss during
+# failovers. RDB diskless load + Redis modules not handling I/O reads may also
+# cause Redis to abort in case of I/O errors during the initial synchronization
+# stage with the master. Use only if your do what you are doing.
+# -----------------------------------------------------------------------------
+#
+# Replica can load the RDB it reads from the replication link directly from the
+# socket, or store the RDB to a file and read that file after it was completely
+# recived from the master.
+#
+# In many cases the disk is slower than the network, and storing and loading
+# the RDB file may increase replication time (and even increase the master's
+# Copy on Write memory and salve buffers).
+# However, parsing the RDB file directly from the socket may mean that we have
+# to flush the contents of the current database before the full rdb was
+# received. For this reason we have the following options:
+#
+# "disabled" - Don't use diskless load (store the rdb file to the disk first)
+# "on-empty-db" - Use diskless load only when it is completely safe.
+# "swapdb" - Keep a copy of the current db contents in RAM while parsing
+# the data directly from the socket. note that this requires
+# sufficient memory, if you don't have it, you risk an OOM kill.
+repl-diskless-load disabled
+
+# Replicas send PINGs to server in a predefined interval. It's possible to
+# change this interval with the repl_ping_replica_period option. The default
+# value is 10 seconds.
+#
+# repl-ping-replica-period 10
# The following option sets the replication timeout for:
#
-# 1) Bulk transfer I/O during SYNC, from the point of view of slave.
-# 2) Master timeout from the point of view of slaves (data, pings).
-# 3) Slave timeout from the point of view of masters (REPLCONF ACK pings).
+# 1) Bulk transfer I/O during SYNC, from the point of view of replica.
+# 2) Master timeout from the point of view of replicas (data, pings).
+# 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
#
# It is important to make sure that this value is greater than the value
-# specified for repl-ping-slave-period otherwise a timeout will be detected
-# every time there is low traffic between the master and the slave.
+# specified for repl-ping-replica-period otherwise a timeout will be detected
+# every time there is low traffic between the master and the replica.
#
# repl-timeout 60
-# Disable TCP_NODELAY on the slave socket after SYNC?
+# Disable TCP_NODELAY on the replica socket after SYNC?
#
# If you select "yes" Redis will use a smaller number of TCP packets and
-# less bandwidth to send data to slaves. But this can add a delay for
-# the data to appear on the slave side, up to 40 milliseconds with
+# less bandwidth to send data to replicas. But this can add a delay for
+# the data to appear on the replica side, up to 40 milliseconds with
# Linux kernels using a default configuration.
#
-# If you select "no" the delay for data to appear on the slave side will
+# If you select "no" the delay for data to appear on the replica side will
# be reduced but more bandwidth will be used for replication.
#
# By default we optimize for low latency, but in very high traffic conditions
-# or when the master and slaves are many hops away, turning this to "yes" may
+# or when the master and replicas are many hops away, turning this to "yes" may
# be a good idea.
repl-disable-tcp-nodelay no
# Set the replication backlog size. The backlog is a buffer that accumulates
-# slave data when slaves are disconnected for some time, so that when a slave
-# wants to reconnect again, often a full resync is not needed, but a partial
-# resync is enough, just passing the portion of data the slave missed while
-# disconnected.
+# replica data when replicas are disconnected for some time, so that when a
+# replica wants to reconnect again, often a full resync is not needed, but a
+# partial resync is enough, just passing the portion of data the replica
+# missed while disconnected.
#
-# The bigger the replication backlog, the longer the time the slave can be
+# The bigger the replication backlog, the longer the time the replica can be
# disconnected and later be able to perform a partial resynchronization.
#
-# The backlog is only allocated once there is at least a slave connected.
+# The backlog is only allocated once there is at least a replica connected.
#
# repl-backlog-size 1mb
-# After a master has no longer connected slaves for some time, the backlog
+# After a master has no longer connected replicas for some time, the backlog
# will be freed. The following option configures the amount of seconds that
-# need to elapse, starting from the time the last slave disconnected, for
+# need to elapse, starting from the time the last replica disconnected, for
# the backlog buffer to be freed.
#
-# Note that slaves never free the backlog for timeout, since they may be
+# Note that replicas never free the backlog for timeout, since they may be
# promoted to masters later, and should be able to correctly "partially
-# resynchronize" with the slaves: hence they should always accumulate backlog.
+# resynchronize" with the replicas: hence they should always accumulate backlog.
#
# A value of 0 means to never release the backlog.
#
# repl-backlog-ttl 3600
-# The slave priority is an integer number published by Redis in the INFO output.
-# It is used by Redis Sentinel in order to select a slave to promote into a
-# master if the master is no longer working correctly.
+# The replica priority is an integer number published by Redis in the INFO
+# output. It is used by Redis Sentinel in order to select a replica to promote
+# into a master if the master is no longer working correctly.
#
-# A slave with a low priority number is considered better for promotion, so
-# for instance if there are three slaves with priority 10, 100, 25 Sentinel will
-# pick the one with priority 10, that is the lowest.
+# A replica with a low priority number is considered better for promotion, so
+# for instance if there are three replicas with priority 10, 100, 25 Sentinel
+# will pick the one with priority 10, that is the lowest.
#
-# However a special priority of 0 marks the slave as not able to perform the
-# role of master, so a slave with priority of 0 will never be selected by
+# However a special priority of 0 marks the replica as not able to perform the
+# role of master, so a replica with priority of 0 will never be selected by
# Redis Sentinel for promotion.
#
# By default the priority is 100.
-slave-priority 100
+replica-priority 100
# It is possible for a master to stop accepting writes if there are less than
-# N slaves connected, having a lag less or equal than M seconds.
+# N replicas connected, having a lag less or equal than M seconds.
#
-# The N slaves need to be in "online" state.
+# The N replicas need to be in "online" state.
#
# The lag in seconds, that must be <= the specified value, is calculated from
-# the last ping received from the slave, that is usually sent every second.
+# the last ping received from the replica, that is usually sent every second.
#
# This option does not GUARANTEE that N replicas will accept the write, but
-# will limit the window of exposure for lost writes in case not enough slaves
+# will limit the window of exposure for lost writes in case not enough replicas
# are available, to the specified number of seconds.
#
-# For example to require at least 3 slaves with a lag <= 10 seconds use:
+# For example to require at least 3 replicas with a lag <= 10 seconds use:
#
-# min-slaves-to-write 3
-# min-slaves-max-lag 10
+# min-replicas-to-write 3
+# min-replicas-max-lag 10
#
# Setting one or the other to 0 disables the feature.
#
-# By default min-slaves-to-write is set to 0 (feature disabled) and
-# min-slaves-max-lag is set to 10.
+# By default min-replicas-to-write is set to 0 (feature disabled) and
+# min-replicas-max-lag is set to 10.
# A Redis master is able to list the address and port of the attached
-# slaves in different ways. For example the "INFO replication" section
+# replicas in different ways. For example the "INFO replication" section
# offers this information, which is used, among other tools, by
-# Redis Sentinel in order to discover slave instances.
+# Redis Sentinel in order to discover replica instances.
# Another place where this info is available is in the output of the
# "ROLE" command of a master.
#
-# The listed IP and address normally reported by a slave is obtained
+# The listed IP and address normally reported by a replica is obtained
# in the following way:
#
# IP: The address is auto detected by checking the peer address
-# of the socket used by the slave to connect with the master.
+# of the socket used by the replica to connect with the master.
#
-# Port: The port is communicated by the slave during the replication
-# handshake, and is normally the port that the slave is using to
-# list for connections.
+# Port: The port is communicated by the replica during the replication
+# handshake, and is normally the port that the replica is using to
+# listen for connections.
#
# However when port forwarding or Network Address Translation (NAT) is
-# used, the slave may be actually reachable via different IP and port
-# pairs. The following two options can be used by a slave in order to
+# used, the replica may be actually reachable via different IP and port
+# pairs. The following two options can be used by a replica in order to
# report to its master a specific set of IP and port, so that both INFO
# and ROLE will report those values.
#
# There is no need to use both the options if you need to override just
# the port or the IP address.
#
-# slave-announce-ip 5.5.5.5
-# slave-announce-port 1234
+# replica-announce-ip 5.5.5.5
+# replica-announce-port 1234
-################################## SECURITY ###################################
+############################### KEYS TRACKING #################################
-# Require clients to issue AUTH <PASSWORD> before processing any other
-# commands. This might be useful in environments in which you do not trust
-# others with access to the host running redis-server.
+# Redis implements server assisted support for client side caching of values.
+# This is implemented using an invalidation table that remembers, using
+# 16 millions of slots, what clients may have certain subsets of keys. In turn
+# this is used in order to send invalidation messages to clients. Please
+# to understand more about the feature check this page:
+#
+# https://redis.io/topics/client-side-caching
#
-# This should stay commented out for backward compatibility and because most
-# people do not need auth (e.g. they run their own servers).
+# When tracking is enabled for a client, all the read only queries are assumed
+# to be cached: this will force Redis to store information in the invalidation
+# table. When keys are modified, such information is flushed away, and
+# invalidation messages are sent to the clients. However if the workload is
+# heavily dominated by reads, Redis could use more and more memory in order
+# to track the keys fetched by many clients.
#
+# For this reason it is possible to configure a maximum fill value for the
+# invalidation table. By default it is set to 10%, and once this limit is
+# reached, Redis will start to evict caching slots in the invalidation table
+# even if keys are not modified, just to reclaim memory: this will in turn
+# force the clients to invalidate the cached values. Basically the table
+# maximum fill rate is a trade off between the memory you want to spend server
+# side to track information about who cached what, and the ability of clients
+# to retain cached objects in memory.
+#
+# If you set the value to 0, it means there are no limits, and all the 16
+# millions of caching slots can be used at the same time. In the "stats"
+# INFO section, you can find information about the amount of caching slots
+# used at every given moment.
+#
+# tracking-table-max-fill 10
+
+################################## SECURITY ###################################
+
# Warning: since Redis is pretty fast an outside user can try up to
-# 150k passwords per second against a good box. This means that you should
-# use a very strong password otherwise it will be very easy to break.
+# 1 million passwords per second against a modern box. This means that you
+# should use very strong passwords, otherwise they will be very easy to break.
+# Note that because the password is really a shared secret between the client
+# and the server, and should not be memorized by any human, the password
+# can be easily a long string from /dev/urandom or whatever, so by using a
+# long and unguessable password no brute force attack will be possible.
+
+# Redis ACL users are defined in the following format:
+#
+# user <username> ... acl rules ...
+#
+# For example:
+#
+# user worker +@list +@connection ~jobs:* on >ffa9203c493aa99
+#
+# The special username "default" is used for new connections. If this user
+# has the "nopass" rule, then new connections will be immediately authenticated
+# as the "default" user without the need of any password provided via the
+# AUTH command. Otherwise if the "default" user is not flagged with "nopass"
+# the connections will start in not authenticated state, and will require
+# AUTH (or the HELLO command AUTH option) in order to be authenticated and
+# start to work.
+#
+# The ACL rules that describe what an user can do are the following:
+#
+# on Enable the user: it is possible to authenticate as this user.
+# off Disable the user: it's no longer possible to authenticate
+# with this user, however the already authenticated connections
+# will still work.
+# +<command> Allow the execution of that command
+# -<command> Disallow the execution of that command
+# +@<category> Allow the execution of all the commands in such category
+# with valid categories are like @admin, @set, @sortedset, ...
+# and so forth, see the full list in the server.c file where
+# the Redis command table is described and defined.
+# The special category @all means all the commands, but currently
+# present in the server, and that will be loaded in the future
+# via modules.
+# +<command>|subcommand Allow a specific subcommand of an otherwise
+# disabled command. Note that this form is not
+# allowed as negative like -DEBUG|SEGFAULT, but
+# only additive starting with "+".
+# allcommands Alias for +@all. Note that it implies the ability to execute
+# all the future commands loaded via the modules system.
+# nocommands Alias for -@all.
+# ~<pattern> Add a pattern of keys that can be mentioned as part of
+# commands. For instance ~* allows all the keys. The pattern
+# is a glob-style pattern like the one of KEYS.
+# It is possible to specify multiple patterns.
+# allkeys Alias for ~*
+# resetkeys Flush the list of allowed keys patterns.
+# ><password> Add this passowrd to the list of valid password for the user.
+# For example >mypass will add "mypass" to the list.
+# This directive clears the "nopass" flag (see later).
+# <<password> Remove this password from the list of valid passwords.
+# nopass All the set passwords of the user are removed, and the user
+# is flagged as requiring no password: it means that every
+# password will work against this user. If this directive is
+# used for the default user, every new connection will be
+# immediately authenticated with the default user without
+# any explicit AUTH command required. Note that the "resetpass"
+# directive will clear this condition.
+# resetpass Flush the list of allowed passwords. Moreover removes the
+# "nopass" status. After "resetpass" the user has no associated
+# passwords and there is no way to authenticate without adding
+# some password (or setting it as "nopass" later).
+# reset Performs the following actions: resetpass, resetkeys, off,
+# -@all. The user returns to the same state it has immediately
+# after its creation.
+#
+# ACL rules can be specified in any order: for instance you can start with
+# passwords, then flags, or key patterns. However note that the additive
+# and subtractive rules will CHANGE MEANING depending on the ordering.
+# For instance see the following example:
+#
+# user alice on +@all -DEBUG ~* >somepassword
+#
+# This will allow "alice" to use all the commands with the exception of the
+# DEBUG command, since +@all added all the commands to the set of the commands
+# alice can use, and later DEBUG was removed. However if we invert the order
+# of two ACL rules the result will be different:
+#
+# user alice on -DEBUG +@all ~* >somepassword
+#
+# Now DEBUG was removed when alice had yet no commands in the set of allowed
+# commands, later all the commands are added, so the user will be able to
+# execute everything.
+#
+# Basically ACL rules are processed left-to-right.
+#
+# For more information about ACL configuration please refer to
+# the Redis web site at https://redis.io/topics/acl
+
+# Using an external ACL file
+#
+# Instead of configuring users here in this file, it is possible to use
+# a stand-alone file just listing users. The two methods cannot be mixed:
+# if you configure users here and at the same time you activate the exteranl
+# ACL file, the server will refuse to start.
+#
+# The format of the external ACL user file is exactly the same as the
+# format that is used inside redis.conf to describe users.
+#
+# aclfile /etc/redis/users.acl
+
+# IMPORTANT NOTE: starting with Redis 6 "requirepass" is just a compatiblity
+# layer on top of the new ACL system. The option effect will be just setting
+# the password for the default user. Clients will still authenticate using
+# AUTH <password> as usually, or more explicitly with AUTH default <password>
+# if they follow the new protocol: both will work.
#
# requirepass foobared
-# Command renaming.
+# Command renaming (DEPRECATED).
+#
+# ------------------------------------------------------------------------
+# WARNING: avoid using this option if possible. Instead use ACLs to remove
+# commands from the default user, and put them only in some admin user you
+# create for administrative purposes.
+# ------------------------------------------------------------------------
#
# It is possible to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
@@ -518,7 +768,7 @@ slave-priority 100
# rename-command CONFIG ""
#
# Please note that changing the name of commands that are logged into the
-# AOF file or transmitted to slaves may cause problems.
+# AOF file or transmitted to replicas may cause problems.
################################### CLIENTS ####################################
@@ -547,15 +797,15 @@ slave-priority 100
# This option is usually useful when using Redis as an LRU or LFU cache, or to
# set a hard memory limit for an instance (using the 'noeviction' policy).
#
-# WARNING: If you have slaves attached to an instance with maxmemory on,
-# the size of the output buffers needed to feed the slaves are subtracted
+# WARNING: If you have replicas attached to an instance with maxmemory on,
+# the size of the output buffers needed to feed the replicas are subtracted
# from the used memory count, so that network problems / resyncs will
# not trigger a loop where keys are evicted, and in turn the output
-# buffer of slaves is full with DELs of keys evicted triggering the deletion
+# buffer of replicas is full with DELs of keys evicted triggering the deletion
# of more keys, and so forth until the database is completely emptied.
#
-# In short... if you have slaves attached it is suggested that you set a lower
-# limit for maxmemory so that there is some free RAM on the system for slave
+# In short... if you have replicas attached it is suggested that you set a lower
+# limit for maxmemory so that there is some free RAM on the system for replica
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>
@@ -602,6 +852,26 @@ slave-priority 100
#
# maxmemory-samples 5
+# Starting from Redis 5, by default a replica will ignore its maxmemory setting
+# (unless it is promoted to master after a failover or manually). It means
+# that the eviction of keys will be just handled by the master, sending the
+# DEL commands to the replica as keys evict in the master side.
+#
+# This behavior ensures that masters and replicas stay consistent, and is usually
+# what you want, however if your replica is writable, or you want the replica
+# to have a different memory setting, and you are sure all the writes performed
+# to the replica are idempotent, then you may change this default (but be sure
+# to understand what you are doing).
+#
+# Note that since the replica by default does not evict, it may end using more
+# memory than the one set via maxmemory (there are certain buffers that may
+# be larger on the replica, or data structures may sometimes take more memory
+# and so forth). So make sure you monitor your replicas and make sure they
+# have enough memory to never hit a real out-of-memory condition before the
+# master hits the configured maxmemory setting.
+#
+# replica-ignore-maxmemory yes
+
############################# LAZY FREEING ####################################
# Redis has two primitives to delete keys. One is called DEL and is a blocking
@@ -637,9 +907,9 @@ slave-priority 100
# or SORT with STORE option may delete existing keys. The SET command
# itself removes any old content of the specified key in order to replace
# it with the specified string.
-# 4) During replication, when a slave performs a full resynchronization with
+# 4) During replication, when a replica performs a full resynchronization with
# its master, the content of the whole database is removed in order to
-# load the RDB file just transfered.
+# load the RDB file just transferred.
#
# In all the above cases the default is to delete objects in a blocking way,
# like if DEL was called. However you can configure each case specifically
@@ -649,7 +919,7 @@ slave-priority 100
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
-slave-lazy-flush no
+replica-lazy-flush no
############################## APPEND ONLY MODE ###############################
@@ -778,10 +1048,7 @@ aof-load-truncated yes
# When loading Redis recognizes that the AOF file starts with the "REDIS"
# string and loads the prefixed RDB file, and continues loading the AOF
# tail.
-#
-# This is currently turned off by default in order to avoid the surprise
-# of a format change, but will at some point be used as the default.
-aof-use-rdb-preamble no
+aof-use-rdb-preamble yes
################################ LUA SCRIPTING ###############################
@@ -802,13 +1069,7 @@ aof-use-rdb-preamble no
lua-time-limit 5000
################################ REDIS CLUSTER ###############################
-#
-# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-# WARNING EXPERIMENTAL: Redis Cluster is considered to be stable code, however
-# in order to mark it as "mature" we need to wait for a non trivial percentage
-# of users to deploy it in production.
-# ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
-#
+
# Normal Redis instances can't be part of a Redis Cluster; only nodes that are
# started as cluster nodes can. In order to start a Redis instance as a
# cluster node enable the cluster support uncommenting the following:
@@ -829,42 +1090,42 @@ lua-time-limit 5000
#
# cluster-node-timeout 15000
-# A slave of a failing master will avoid to start a failover if its data
+# A replica of a failing master will avoid to start a failover if its data
# looks too old.
#
-# There is no simple way for a slave to actually have an exact measure of
+# There is no simple way for a replica to actually have an exact measure of
# its "data age", so the following two checks are performed:
#
-# 1) If there are multiple slaves able to failover, they exchange messages
-# in order to try to give an advantage to the slave with the best
+# 1) If there are multiple replicas able to failover, they exchange messages
+# in order to try to give an advantage to the replica with the best
# replication offset (more data from the master processed).
-# Slaves will try to get their rank by offset, and apply to the start
+# Replicas will try to get their rank by offset, and apply to the start
# of the failover a delay proportional to their rank.
#
-# 2) Every single slave computes the time of the last interaction with
+# 2) Every single replica computes the time of the last interaction with
# its master. This can be the last ping or command received (if the master
# is still in the "connected" state), or the time that elapsed since the
# disconnection with the master (if the replication link is currently down).
-# If the last interaction is too old, the slave will not try to failover
+# If the last interaction is too old, the replica will not try to failover
# at all.
#
-# The point "2" can be tuned by user. Specifically a slave will not perform
+# The point "2" can be tuned by user. Specifically a replica will not perform
# the failover if, since the last interaction with the master, the time
# elapsed is greater than:
#
-# (node-timeout * slave-validity-factor) + repl-ping-slave-period
+# (node-timeout * replica-validity-factor) + repl-ping-replica-period
#
-# So for example if node-timeout is 30 seconds, and the slave-validity-factor
-# is 10, and assuming a default repl-ping-slave-period of 10 seconds, the
-# slave will not try to failover if it was not able to talk with the master
+# So for example if node-timeout is 30 seconds, and the replica-validity-factor
+# is 10, and assuming a default repl-ping-replica-period of 10 seconds, the
+# replica will not try to failover if it was not able to talk with the master
# for longer than 310 seconds.
#
-# A large slave-validity-factor may allow slaves with too old data to failover
+# A large replica-validity-factor may allow replicas with too old data to failover
# a master, while a too small value may prevent the cluster from being able to
-# elect a slave at all.
+# elect a replica at all.
#
-# For maximum availability, it is possible to set the slave-validity-factor
-# to a value of 0, which means, that slaves will always try to failover the
+# For maximum availability, it is possible to set the replica-validity-factor
+# to a value of 0, which means, that replicas will always try to failover the
# master regardless of the last time they interacted with the master.
# (However they'll always try to apply a delay proportional to their
# offset rank).
@@ -872,22 +1133,22 @@ lua-time-limit 5000
# Zero is the only value able to guarantee that when all the partitions heal
# the cluster will always be able to continue.
#
-# cluster-slave-validity-factor 10
+# cluster-replica-validity-factor 10
-# Cluster slaves are able to migrate to orphaned masters, that are masters
-# that are left without working slaves. This improves the cluster ability
+# Cluster replicas are able to migrate to orphaned masters, that are masters
+# that are left without working replicas. This improves the cluster ability
# to resist to failures as otherwise an orphaned master can't be failed over
-# in case of failure if it has no working slaves.
+# in case of failure if it has no working replicas.
#
-# Slaves migrate to orphaned masters only if there are still at least a
-# given number of other working slaves for their old master. This number
-# is the "migration barrier". A migration barrier of 1 means that a slave
-# will migrate only if there is at least 1 other working slave for its master
-# and so forth. It usually reflects the number of slaves you want for every
+# Replicas migrate to orphaned masters only if there are still at least a
+# given number of other working replicas for their old master. This number
+# is the "migration barrier". A migration barrier of 1 means that a replica
+# will migrate only if there is at least 1 other working replica for its master
+# and so forth. It usually reflects the number of replicas you want for every
# master in your cluster.
#
-# Default is 1 (slaves migrate only if their masters remain with at least
-# one slave). To disable migration just set it to a very large value.
+# Default is 1 (replicas migrate only if their masters remain with at least
+# one replica). To disable migration just set it to a very large value.
# A value of 0 can be set but is useful only for debugging and dangerous
# in production.
#
@@ -906,7 +1167,7 @@ lua-time-limit 5000
#
# cluster-require-full-coverage yes
-# This option, when set to yes, prevents slaves from trying to failover its
+# This option, when set to yes, prevents replicas from trying to failover its
# master during master failures. However the master can still perform a
# manual failover, if forced to do so.
#
@@ -914,7 +1175,7 @@ lua-time-limit 5000
# data center operations, where we want one side to never be promoted if not
# in the case of a total DC failure.
#
-# cluster-slave-no-failover no
+# cluster-replica-no-failover no
# In order to setup your cluster make sure to read the documentation
# available at http://redis.io web site.
@@ -1043,6 +1304,61 @@ latency-monitor-threshold 0
# specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""
+############################### GOPHER SERVER #################################
+
+# Redis contains an implementation of the Gopher protocol, as specified in
+# the RFC 1436 (https://www.ietf.org/rfc/rfc1436.txt).
+#
+# The Gopher protocol was very popular in the late '90s. It is an alternative
+# to the web, and the implementation both server and client side is so simple
+# that the Redis server has just 100 lines of code in order to implement this
+# support.
+#
+# What do you do with Gopher nowadays? Well Gopher never *really* died, and
+# lately there is a movement in order for the Gopher more hierarchical content
+# composed of just plain text documents to be resurrected. Some want a simpler
+# internet, others believe that the mainstream internet became too much
+# controlled, and it's cool to create an alternative space for people that
+# want a bit of fresh air.
+#
+# Anyway for the 10nth birthday of the Redis, we gave it the Gopher protocol
+# as a gift.
+#
+# --- HOW IT WORKS? ---
+#
+# The Redis Gopher support uses the inline protocol of Redis, and specifically
+# two kind of inline requests that were anyway illegal: an empty request
+# or any request that starts with "/" (there are no Redis commands starting
+# with such a slash). Normal RESP2/RESP3 requests are completely out of the
+# path of the Gopher protocol implementation and are served as usually as well.
+#
+# If you open a connection to Redis when Gopher is enabled and send it
+# a string like "/foo", if there is a key named "/foo" it is served via the
+# Gopher protocol.
+#
+# In order to create a real Gopher "hole" (the name of a Gopher site in Gopher
+# talking), you likely need a script like the following:
+#
+# https://github.com/antirez/gopher2redis
+#
+# --- SECURITY WARNING ---
+#
+# If you plan to put Redis on the internet in a publicly accessible address
+# to server Gopher pages MAKE SURE TO SET A PASSWORD to the instance.
+# Once a password is set:
+#
+# 1. The Gopher server (when enabled, not by default) will kill serve
+# content via Gopher.
+# 2. However other commands cannot be called before the client will
+# authenticate.
+#
+# So use the 'requirepass' option to protect your instance.
+#
+# To enable Gopher support uncomment the following line and set
+# the option from no (the default) to yes.
+#
+# gopher-enabled no
+
############################### ADVANCED CONFIG ###############################
# Hashes are encoded using a memory efficient data structure when they have a
@@ -1109,6 +1425,17 @@ zset-max-ziplist-value 64
# composed of many HyperLogLogs with cardinality in the 0 - 15000 range.
hll-sparse-max-bytes 3000
+# Streams macro node max size / items. The stream data structure is a radix
+# tree of big nodes that encode multiple items inside. Using this configuration
+# it is possible to configure how big a single node can be in bytes, and the
+# maximum number of items it may contain before switching to a new node when
+# appending new stream entries. If any of the following settings are set to
+# zero, the limit is ignored, so for instance it is possible to set just a
+# max entires limit by setting max-bytes to 0 and max-entries to the desired
+# value.
+stream-node-max-bytes 4096
+stream-node-max-entries 100
+
# Active rehashing uses 1 millisecond every 100 milliseconds of CPU time in
# order to help rehashing the main Redis hash table (the one mapping top-level
# keys to values). The hash table implementation Redis uses (see dict.c)
@@ -1137,7 +1464,7 @@ activerehashing yes
# The limit can be set differently for the three different classes of clients:
#
# normal -> normal clients including MONITOR clients
-# slave -> slave clients
+# replica -> replica clients
# pubsub -> clients subscribed to at least one pubsub channel or pattern
#
# The syntax of every client-output-buffer-limit directive is the following:
@@ -1158,12 +1485,12 @@ activerehashing yes
# asynchronous clients may create a scenario where data is requested faster
# than it can read.
#
-# Instead there is a default limit for pubsub and slave clients, since
-# subscribers and slaves receive data in a push fashion.
+# Instead there is a default limit for pubsub and replica clients, since
+# subscribers and replicas receive data in a push fashion.
#
# Both the hard or the soft limit can be disabled by setting them to zero.
client-output-buffer-limit normal 0 0 0
-client-output-buffer-limit slave 256mb 64mb 60
+client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
# Client query buffers accumulate new commands. They are limited to a fixed
@@ -1197,12 +1524,34 @@ client-output-buffer-limit pubsub 32mb 8mb 60
# 100 only in environments where very low latency is required.
hz 10
+# Normally it is useful to have an HZ value which is proportional to the
+# number of clients connected. This is useful in order, for instance, to
+# avoid too many clients are processed for each background task invocation
+# in order to avoid latency spikes.
+#
+# Since the default HZ value by default is conservatively set to 10, Redis
+# offers, and enables by default, the ability to use an adaptive HZ value
+# which will temporary raise when there are many connected clients.
+#
+# When dynamic HZ is enabled, the actual configured HZ will be used as
+# as a baseline, but multiples of the configured HZ value will be actually
+# used as needed once more clients are connected. In this way an idle
+# instance will use very little CPU time while a busy instance will be
+# more responsive.
+dynamic-hz yes
+
# When a child rewrites the AOF file, if the following option is enabled
# the file will be fsync-ed every 32 MB of data generated. This is useful
# in order to commit the file to the disk more incrementally and avoid
# big latency spikes.
aof-rewrite-incremental-fsync yes
+# When redis saves RDB file, if the following option is enabled
+# the file will be fsync-ed every 32 MB of data generated. This is useful
+# in order to commit the file to the disk more incrementally and avoid
+# big latency spikes.
+rdb-save-incremental-fsync yes
+
# Redis LFU eviction (see maxmemory setting) can be tuned. However it is a good
# idea to start with the default settings and only change them after investigating
# how to improve the performances and how the keys LFU change over time, which
@@ -1312,8 +1661,12 @@ aof-rewrite-incremental-fsync yes
# active-defrag-threshold-upper 100
# Minimal effort for defrag in CPU percentage
-# active-defrag-cycle-min 25
+# active-defrag-cycle-min 5
# Maximal effort for defrag in CPU percentage
# active-defrag-cycle-max 75
+# Maximum number of set/hash/zset/list fields that will be processed from
+# the main dictionary scan
+# active-defrag-max-scan-fields 1000
+
diff --git a/runtest b/runtest
index d8451df57..ade1bd09a 100755
--- a/runtest
+++ b/runtest
@@ -11,4 +11,4 @@ then
echo "You need tcl 8.5 or newer in order to run the Redis test"
exit 1
fi
-$TCLSH tests/test_helper.tcl $*
+$TCLSH tests/test_helper.tcl "${@}"
diff --git a/runtest-moduleapi b/runtest-moduleapi
new file mode 100755
index 000000000..444204919
--- /dev/null
+++ b/runtest-moduleapi
@@ -0,0 +1,16 @@
+#!/bin/sh
+TCL_VERSIONS="8.5 8.6"
+TCLSH=""
+
+for VERSION in $TCL_VERSIONS; do
+ TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
+done
+
+if [ -z $TCLSH ]
+then
+ echo "You need tcl 8.5 or newer in order to run the Redis test"
+ exit 1
+fi
+
+make -C tests/modules && \
+$TCLSH tests/test_helper.tcl --single unit/moduleapi/commandfilter --single unit/moduleapi/fork --single unit/moduleapi/testrdb --single unit/moduleapi/infotest --single unit/moduleapi/propagate --single unit/moduleapi/hooks "${@}"
diff --git a/sentinel.conf b/sentinel.conf
index 0e1b266ed..bc9a705ac 100644
--- a/sentinel.conf
+++ b/sentinel.conf
@@ -20,6 +20,21 @@
# The port that this sentinel instance will run on
port 26379
+# By default Redis Sentinel does not run as a daemon. Use 'yes' if you need it.
+# Note that Redis will write a pid file in /var/run/redis-sentinel.pid when
+# daemonized.
+daemonize no
+
+# When running daemonized, Redis Sentinel writes a pid file in
+# /var/run/redis-sentinel.pid by default. You can specify a custom pid file
+# location here.
+pidfile /var/run/redis-sentinel.pid
+
+# Specify the log file name. Also the empty string can be used to force
+# Sentinel to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile ""
+
# sentinel announce-ip <ip>
# sentinel announce-port <port>
#
@@ -58,11 +73,11 @@ dir /tmp
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
-# Slaves are auto-discovered, so you don't need to specify slaves in
+# Replicas are auto-discovered, so you don't need to specify replicas in
# any way. Sentinel itself will rewrite this configuration file adding
-# the slaves using additional configuration options.
+# the replicas using additional configuration options.
# Also note that the configuration file is rewritten when a
-# slave is promoted to master.
+# replica is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
@@ -70,11 +85,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass <master-name> <password>
#
-# Set the password to use to authenticate with the master and slaves.
+# Set the password to use to authenticate with the master and replicas.
# Useful if there is a password set in the Redis instances to monitor.
#
-# Note that the master password is also used for slaves, so it is not
-# possible to set a different password in masters and slaves instances
+# Note that the master password is also used for replicas, so it is not
+# possible to set a different password in masters and replicas instances
# if you want to be able to monitor these instances with Sentinel.
#
# However you can have Redis instances without the authentication enabled
@@ -89,7 +104,7 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel down-after-milliseconds <master-name> <milliseconds>
#
-# Number of milliseconds the master (or any attached slave or sentinel) should
+# Number of milliseconds the master (or any attached replica or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
@@ -97,11 +112,11 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
-# sentinel parallel-syncs <master-name> <numslaves>
+# sentinel parallel-syncs <master-name> <numreplicas>
#
-# How many slaves we can reconfigure to point to the new slave simultaneously
-# during the failover. Use a low number if you use the slaves to serve query
-# to avoid that all the slaves will be unreachable at about the same
+# How many replicas we can reconfigure to point to the new replica simultaneously
+# during the failover. Use a low number if you use the replicas to serve query
+# to avoid that all the replicas will be unreachable at about the same
# time while performing the synchronization with the master.
sentinel parallel-syncs mymaster 1
@@ -113,18 +128,18 @@ sentinel parallel-syncs mymaster 1
# already tried against the same master by a given Sentinel, is two
# times the failover timeout.
#
-# - The time needed for a slave replicating to a wrong master according
+# - The time needed for a replica replicating to a wrong master according
# to a Sentinel current configuration, to be forced to replicate
# with the right master, is exactly the failover timeout (counting since
# the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
# did not produced any configuration change (SLAVEOF NO ONE yet not
-# acknowledged by the promoted slave).
+# acknowledged by the promoted replica).
#
-# - The maximum time a failover in progress waits for all the slaves to be
-# reconfigured as slaves of the new master. However even after this time
-# the slaves will be reconfigured by the Sentinels anyway, but not with
+# - The maximum time a failover in progress waits for all the replicas to be
+# reconfigured as replicas of the new master. However even after this time
+# the replicas will be reconfigured by the Sentinels anyway, but not with
# the exact parallel-syncs progression as specified.
#
# Default is 3 minutes.
@@ -185,7 +200,7 @@ sentinel failover-timeout mymaster 180000
# <role> is either "leader" or "observer"
#
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
-# the old address of the master and the new address of the elected slave
+# the old address of the master and the new address of the elected replica
# (now a master).
#
# This script should be resistant to multiple invocations.
@@ -194,3 +209,36 @@ sentinel failover-timeout mymaster 180000
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
+# SECURITY
+#
+# By default SENTINEL SET will not be able to change the notification-script
+# and client-reconfig-script at runtime. This avoids a trivial security issue
+# where clients can set the script to anything and trigger a failover in order
+# to get the program executed.
+
+sentinel deny-scripts-reconfig yes
+
+# REDIS COMMANDS RENAMING
+#
+# Sometimes the Redis server has certain commands, that are needed for Sentinel
+# to work correctly, renamed to unguessable strings. This is often the case
+# of CONFIG and SLAVEOF in the context of providers that provide Redis as
+# a service, and don't want the customers to reconfigure the instances outside
+# of the administration console.
+#
+# In such case it is possible to tell Sentinel to use different command names
+# instead of the normal ones. For example if the master "mymaster", and the
+# associated replicas, have "CONFIG" all renamed to "GUESSME", I could use:
+#
+# SENTINEL rename-command mymaster CONFIG GUESSME
+#
+# After such configuration is set, every time Sentinel would use CONFIG it will
+# use GUESSME instead. Note that there is no actual need to respect the command
+# case, so writing "config guessme" is the same in the example above.
+#
+# SENTINEL SET can also be used in order to perform this configuration at runtime.
+#
+# In order to set a command back to its original name (undo the renaming), it
+# is possible to just rename a command to itsef:
+#
+# SENTINEL rename-command mymaster CONFIG CONFIG
diff --git a/src/Makefile b/src/Makefile
index b896b1263..9fc230f94 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -20,7 +20,12 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
NODEPS:=clean distclean
# Default settings
-STD=-std=c99 -pedantic -DREDIS_STATIC=''
+STD=-std=c11 -pedantic -DREDIS_STATIC=''
+ifneq (,$(findstring clang,$(CC)))
+ifneq (,$(findstring FreeBSD,$(uname_S)))
+ STD+=-Wno-c11-extensions
+endif
+endif
WARN=-Wall -W -Wno-missing-field-initializers
OPT=$(OPTIMIZATION)
@@ -39,9 +44,13 @@ endif
endif
# To get ARM stack traces if Redis crashes we need a special C flag.
+ifneq (,$(filter aarch64 armv,$(uname_M)))
+ CFLAGS+=-funwind-tables
+else
ifneq (,$(findstring armv,$(uname_M)))
CFLAGS+=-funwind-tables
endif
+endif
# Backwards compatibility for selecting an allocator
ifeq ($(USE_TCMALLOC),yes)
@@ -84,6 +93,8 @@ else
ifeq ($(uname_S),Darwin)
# Darwin
FINAL_LIBS+= -ldl
+ OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
+ OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
else
ifeq ($(uname_S),AIX)
# AIX
@@ -93,14 +104,25 @@ else
ifeq ($(uname_S),OpenBSD)
# OpenBSD
FINAL_LIBS+= -lpthread
+ ifeq ($(USE_BACKTRACE),yes)
+ FINAL_CFLAGS+= -DUSE_BACKTRACE -I/usr/local/include
+ FINAL_LDFLAGS+= -L/usr/local/lib
+ FINAL_LIBS+= -lexecinfo
+ endif
+
else
ifeq ($(uname_S),FreeBSD)
# FreeBSD
- FINAL_LIBS+= -lpthread
+ FINAL_LIBS+= -lpthread -lexecinfo
+else
+ifeq ($(uname_S),DragonFly)
+ # FreeBSD
+ FINAL_LIBS+= -lpthread -lexecinfo
else
# All the other OSes (notably Linux)
FINAL_LDFLAGS+= -rdynamic
- FINAL_LIBS+=-ldl -pthread
+ FINAL_LIBS+=-ldl -pthread -lrt
+endif
endif
endif
endif
@@ -122,7 +144,13 @@ endif
ifeq ($(MALLOC),jemalloc)
DEPENDENCY_TARGETS+= jemalloc
FINAL_CFLAGS+= -DUSE_JEMALLOC -I../deps/jemalloc/include
- FINAL_LIBS+= ../deps/jemalloc/lib/libjemalloc.a
+ FINAL_LIBS := ../deps/jemalloc/lib/libjemalloc.a $(FINAL_LIBS)
+endif
+
+ifeq ($(BUILD_TLS),yes)
+ FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
+ FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
+ FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
endif
REDIS_CC=$(QUIET_CC)$(CC) $(FINAL_CFLAGS)
@@ -144,11 +172,11 @@ endif
REDIS_SERVER_NAME=redis-server
REDIS_SENTINEL_NAME=redis-sentinel
-REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.c
+REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o lolwut.o lolwut5.o lolwut6.o acl.o gopher.o tracking.o connection.o tls.o sha256.o
REDIS_CLI_NAME=redis-cli
-REDIS_CLI_OBJ=anet.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
+REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o siphash.o crc16.o
REDIS_BENCHMARK_NAME=redis-benchmark
-REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o zmalloc.o redis-benchmark.o
+REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o
REDIS_CHECK_RDB_NAME=redis-check-rdb
REDIS_CHECK_AOF_NAME=redis-check-aof
@@ -290,3 +318,6 @@ install: all
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
+
+uninstall:
+ rm -f $(INSTALL_BIN)/{$(REDIS_SERVER_NAME),$(REDIS_BENCHMARK_NAME),$(REDIS_CLI_NAME),$(REDIS_CHECK_RDB_NAME),$(REDIS_CHECK_AOF_NAME),$(REDIS_SENTINEL_NAME)}
diff --git a/src/acl.c b/src/acl.c
new file mode 100644
index 000000000..4c43add14
--- /dev/null
+++ b/src/acl.c
@@ -0,0 +1,1731 @@
+/*
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "server.h"
+#include "sha256.h"
+#include <fcntl.h>
+
+/* =============================================================================
+ * Global state for ACLs
+ * ==========================================================================*/
+
+rax *Users; /* Table mapping usernames to user structures. */
+
+user *DefaultUser; /* Global reference to the default user.
+ Every new connection is associated to it, if no
+ AUTH or HELLO is used to authenticate with a
+ different user. */
+
+list *UsersToLoad; /* This is a list of users found in the configuration file
+ that we'll need to load in the final stage of Redis
+ initialization, after all the modules are already
+ loaded. Every list element is a NULL terminated
+ array of SDS pointers: the first is the user name,
+ all the remaining pointers are ACL rules in the same
+ format as ACLSetUser(). */
+
+struct ACLCategoryItem {
+ const char *name;
+ uint64_t flag;
+} ACLCommandCategories[] = {
+ {"keyspace", CMD_CATEGORY_KEYSPACE},
+ {"read", CMD_CATEGORY_READ},
+ {"write", CMD_CATEGORY_WRITE},
+ {"set", CMD_CATEGORY_SET},
+ {"sortedset", CMD_CATEGORY_SORTEDSET},
+ {"list", CMD_CATEGORY_LIST},
+ {"hash", CMD_CATEGORY_HASH},
+ {"string", CMD_CATEGORY_STRING},
+ {"bitmap", CMD_CATEGORY_BITMAP},
+ {"hyperloglog", CMD_CATEGORY_HYPERLOGLOG},
+ {"geo", CMD_CATEGORY_GEO},
+ {"stream", CMD_CATEGORY_STREAM},
+ {"pubsub", CMD_CATEGORY_PUBSUB},
+ {"admin", CMD_CATEGORY_ADMIN},
+ {"fast", CMD_CATEGORY_FAST},
+ {"slow", CMD_CATEGORY_SLOW},
+ {"blocking", CMD_CATEGORY_BLOCKING},
+ {"dangerous", CMD_CATEGORY_DANGEROUS},
+ {"connection", CMD_CATEGORY_CONNECTION},
+ {"transaction", CMD_CATEGORY_TRANSACTION},
+ {"scripting", CMD_CATEGORY_SCRIPTING},
+ {NULL,0} /* Terminator. */
+};
+
+struct ACLUserFlag {
+ const char *name;
+ uint64_t flag;
+} ACLUserFlags[] = {
+ {"on", USER_FLAG_ENABLED},
+ {"off", USER_FLAG_DISABLED},
+ {"allkeys", USER_FLAG_ALLKEYS},
+ {"allcommands", USER_FLAG_ALLCOMMANDS},
+ {"nopass", USER_FLAG_NOPASS},
+ {NULL,0} /* Terminator. */
+};
+
+void ACLResetSubcommandsForCommand(user *u, unsigned long id);
+void ACLResetSubcommands(user *u);
+void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
+
+/* The length of the string representation of a hashed password. */
+#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
+
+/* =============================================================================
+ * Helper functions for the rest of the ACL implementation
+ * ==========================================================================*/
+
+/* Return zero if strings are the same, non-zero if they are not.
+ * The comparison is performed in a way that prevents an attacker to obtain
+ * information about the nature of the strings just monitoring the execution
+ * time of the function.
+ *
+ * Note that limiting the comparison length to strings up to 512 bytes we
+ * can avoid leaking any information about the password length and any
+ * possible branch misprediction related leak.
+ */
+int time_independent_strcmp(char *a, char *b) {
+ char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
+ /* The above two strlen perform len(a) + len(b) operations where either
+ * a or b are fixed (our password) length, and the difference is only
+ * relative to the length of the user provided string, so no information
+ * leak is possible in the following two lines of code. */
+ unsigned int alen = strlen(a);
+ unsigned int blen = strlen(b);
+ unsigned int j;
+ int diff = 0;
+
+ /* We can't compare strings longer than our static buffers.
+ * Note that this will never pass the first test in practical circumstances
+ * so there is no info leak. */
+ if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
+
+ memset(bufa,0,sizeof(bufa)); /* Constant time. */
+ memset(bufb,0,sizeof(bufb)); /* Constant time. */
+ /* Again the time of the following two copies is proportional to
+ * len(a) + len(b) so no info is leaked. */
+ memcpy(bufa,a,alen);
+ memcpy(bufb,b,blen);
+
+ /* Always compare all the chars in the two buffers without
+ * conditional expressions. */
+ for (j = 0; j < sizeof(bufa); j++) {
+ diff |= (bufa[j] ^ bufb[j]);
+ }
+ /* Length must be equal as well. */
+ diff |= alen ^ blen;
+ return diff; /* If zero strings are the same. */
+}
+
+/* Given an SDS string, returns the SHA256 hex representation as a
+ * new SDS string. */
+sds ACLHashPassword(unsigned char *cleartext, size_t len) {
+ SHA256_CTX ctx;
+ unsigned char hash[SHA256_BLOCK_SIZE];
+ char hex[HASH_PASSWORD_LEN];
+ char *cset = "0123456789abcdef";
+
+ sha256_init(&ctx);
+ sha256_update(&ctx,(unsigned char*)cleartext,len);
+ sha256_final(&ctx,hash);
+
+ for (int j = 0; j < SHA256_BLOCK_SIZE; j++) {
+ hex[j*2] = cset[((hash[j]&0xF0)>>4)];
+ hex[j*2+1] = cset[(hash[j]&0xF)];
+ }
+ return sdsnewlen(hex,HASH_PASSWORD_LEN);
+}
+
+/* =============================================================================
+ * Low level ACL API
+ * ==========================================================================*/
+
+/* Given the category name the command returns the corresponding flag, or
+ * zero if there is no match. */
+uint64_t ACLGetCommandCategoryFlagByName(const char *name) {
+ for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
+ if (!strcasecmp(name,ACLCommandCategories[j].name)) {
+ return ACLCommandCategories[j].flag;
+ }
+ }
+ return 0; /* No match. */
+}
+
+/* Method for passwords/pattern comparison used for the user->passwords list
+ * so that we can search for items with listSearchKey(). */
+int ACLListMatchSds(void *a, void *b) {
+ return sdscmp(a,b) == 0;
+}
+
+/* Method to free list elements from ACL users password/ptterns lists. */
+void ACLListFreeSds(void *item) {
+ sdsfree(item);
+}
+
+/* Method to duplicate list elements from ACL users password/ptterns lists. */
+void *ACLListDupSds(void *item) {
+ return sdsdup(item);
+}
+
+/* Create a new user with the specified name, store it in the list
+ * of users (the Users global radix tree), and returns a reference to
+ * the structure representing the user.
+ *
+ * If the user with such name already exists NULL is returned. */
+user *ACLCreateUser(const char *name, size_t namelen) {
+ if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
+ user *u = zmalloc(sizeof(*u));
+ u->name = sdsnewlen(name,namelen);
+ u->flags = USER_FLAG_DISABLED;
+ u->allowed_subcommands = NULL;
+ u->passwords = listCreate();
+ u->patterns = listCreate();
+ listSetMatchMethod(u->passwords,ACLListMatchSds);
+ listSetFreeMethod(u->passwords,ACLListFreeSds);
+ listSetDupMethod(u->passwords,ACLListDupSds);
+ listSetMatchMethod(u->patterns,ACLListMatchSds);
+ listSetFreeMethod(u->patterns,ACLListFreeSds);
+ listSetDupMethod(u->patterns,ACLListDupSds);
+ memset(u->allowed_commands,0,sizeof(u->allowed_commands));
+ raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
+ return u;
+}
+
+/* This function should be called when we need an unlinked "fake" user
+ * we can use in order to validate ACL rules or for other similar reasons.
+ * The user will not get linked to the Users radix tree. The returned
+ * user should be released with ACLFreeUser() as usually. */
+user *ACLCreateUnlinkedUser(void) {
+ char username[64];
+ for (int j = 0; ; j++) {
+ snprintf(username,sizeof(username),"__fakeuser:%d__",j);
+ user *fakeuser = ACLCreateUser(username,strlen(username));
+ if (fakeuser == NULL) continue;
+ int retval = raxRemove(Users,(unsigned char*) username,
+ strlen(username),NULL);
+ serverAssert(retval != 0);
+ return fakeuser;
+ }
+}
+
+/* Release the memory used by the user structure. Note that this function
+ * will not remove the user from the Users global radix tree. */
+void ACLFreeUser(user *u) {
+ sdsfree(u->name);
+ listRelease(u->passwords);
+ listRelease(u->patterns);
+ ACLResetSubcommands(u);
+ zfree(u);
+}
+
+/* When a user is deleted we need to cycle the active
+ * connections in order to kill all the pending ones that
+ * are authenticated with such user. */
+void ACLFreeUserAndKillClients(user *u) {
+ listIter li;
+ listNode *ln;
+ listRewind(server.clients,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ client *c = listNodeValue(ln);
+ if (c->user == u) {
+ /* We'll free the conenction asynchronously, so
+ * in theory to set a different user is not needed.
+ * However if there are bugs in Redis, soon or later
+ * this may result in some security hole: it's much
+ * more defensive to set the default user and put
+ * it in non authenticated mode. */
+ c->user = DefaultUser;
+ c->authenticated = 0;
+ freeClientAsync(c);
+ }
+ }
+ ACLFreeUser(u);
+}
+
+/* Copy the user ACL rules from the source user 'src' to the destination
+ * user 'dst' so that at the end of the process they'll have exactly the
+ * same rules (but the names will continue to be the original ones). */
+void ACLCopyUser(user *dst, user *src) {
+ listRelease(dst->passwords);
+ listRelease(dst->patterns);
+ dst->passwords = listDup(src->passwords);
+ dst->patterns = listDup(src->patterns);
+ memcpy(dst->allowed_commands,src->allowed_commands,
+ sizeof(dst->allowed_commands));
+ dst->flags = src->flags;
+ ACLResetSubcommands(dst);
+ /* Copy the allowed subcommands array of array of SDS strings. */
+ if (src->allowed_subcommands) {
+ for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
+ if (src->allowed_subcommands[j]) {
+ for (int i = 0; src->allowed_subcommands[j][i]; i++)
+ {
+ ACLAddAllowedSubcommand(dst, j,
+ src->allowed_subcommands[j][i]);
+ }
+ }
+ }
+ }
+}
+
+/* Free all the users registered in the radix tree 'users' and free the
+ * radix tree itself. */
+void ACLFreeUsersSet(rax *users) {
+ raxFreeWithCallback(users,(void(*)(void*))ACLFreeUserAndKillClients);
+}
+
+/* Given a command ID, this function set by reference 'word' and 'bit'
+ * so that user->allowed_commands[word] will address the right word
+ * where the corresponding bit for the provided ID is stored, and
+ * so that user->allowed_commands[word]&bit will identify that specific
+ * bit. The function returns C_ERR in case the specified ID overflows
+ * the bitmap in the user representation. */
+int ACLGetCommandBitCoordinates(uint64_t id, uint64_t *word, uint64_t *bit) {
+ if (id >= USER_COMMAND_BITS_COUNT) return C_ERR;
+ *word = id / sizeof(uint64_t) / 8;
+ *bit = 1ULL << (id % (sizeof(uint64_t) * 8));
+ return C_OK;
+}
+
+/* Check if the specified command bit is set for the specified user.
+ * The function returns 1 is the bit is set or 0 if it is not.
+ * Note that this function does not check the ALLCOMMANDS flag of the user
+ * but just the lowlevel bitmask.
+ *
+ * If the bit overflows the user internal representation, zero is returned
+ * in order to disallow the execution of the command in such edge case. */
+int ACLGetUserCommandBit(user *u, unsigned long id) {
+ uint64_t word, bit;
+ if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return 0;
+ return (u->allowed_commands[word] & bit) != 0;
+}
+
+/* When +@all or allcommands is given, we set a reserved bit as well that we
+ * can later test, to see if the user has the right to execute "future commands",
+ * that is, commands loaded later via modules. */
+int ACLUserCanExecuteFutureCommands(user *u) {
+ return ACLGetUserCommandBit(u,USER_COMMAND_BITS_COUNT-1);
+}
+
+/* Set the specified command bit for the specified user to 'value' (0 or 1).
+ * If the bit overflows the user internal representation, no operation
+ * is performed. As a side effect of calling this function with a value of
+ * zero, the user flag ALLCOMMANDS is cleared since it is no longer possible
+ * to skip the command bit explicit test. */
+void ACLSetUserCommandBit(user *u, unsigned long id, int value) {
+ uint64_t word, bit;
+ if (value == 0) u->flags &= ~USER_FLAG_ALLCOMMANDS;
+ if (ACLGetCommandBitCoordinates(id,&word,&bit) == C_ERR) return;
+ if (value)
+ u->allowed_commands[word] |= bit;
+ else
+ u->allowed_commands[word] &= ~bit;
+}
+
+/* This is like ACLSetUserCommandBit(), but instead of setting the specified
+ * ID, it will check all the commands in the category specified as argument,
+ * and will set all the bits corresponding to such commands to the specified
+ * value. Since the category passed by the user may be non existing, the
+ * function returns C_ERR if the category was not found, or C_OK if it was
+ * found and the operation was performed. */
+int ACLSetUserCommandBitsForCategory(user *u, const char *category, int value) {
+ uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
+ if (!cflag) return C_ERR;
+ dictIterator *di = dictGetIterator(server.orig_commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ if (cmd->flags & CMD_MODULE) continue; /* Ignore modules commands. */
+ if (cmd->flags & cflag) {
+ ACLSetUserCommandBit(u,cmd->id,value);
+ ACLResetSubcommandsForCommand(u,cmd->id);
+ }
+ }
+ dictReleaseIterator(di);
+ return C_OK;
+}
+
+/* Return the number of commands allowed (on) and denied (off) for the user 'u'
+ * in the subset of commands flagged with the specified category name.
+ * If the category name is not valid, C_ERR is returned, otherwise C_OK is
+ * returned and on and off are populated by reference. */
+int ACLCountCategoryBitsForUser(user *u, unsigned long *on, unsigned long *off,
+ const char *category)
+{
+ uint64_t cflag = ACLGetCommandCategoryFlagByName(category);
+ if (!cflag) return C_ERR;
+
+ *on = *off = 0;
+ dictIterator *di = dictGetIterator(server.orig_commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ if (cmd->flags & cflag) {
+ if (ACLGetUserCommandBit(u,cmd->id))
+ (*on)++;
+ else
+ (*off)++;
+ }
+ }
+ dictReleaseIterator(di);
+ return C_OK;
+}
+
+/* This function returns an SDS string representing the specified user ACL
+ * rules related to command execution, in the same format you could set them
+ * back using ACL SETUSER. The function will return just the set of rules needed
+ * to recreate the user commands bitmap, without including other user flags such
+ * as on/off, passwords and so forth. The returned string always starts with
+ * the +@all or -@all rule, depending on the user bitmap, and is followed, if
+ * needed, by the other rules needed to narrow or extend what the user can do. */
+sds ACLDescribeUserCommandRules(user *u) {
+ sds rules = sdsempty();
+ int additive; /* If true we start from -@all and add, otherwise if
+ false we start from +@all and remove. */
+
+ /* This code is based on a trick: as we generate the rules, we apply
+ * them to a fake user, so that as we go we still know what are the
+ * bit differences we should try to address by emitting more rules. */
+ user fu = {0};
+ user *fakeuser = &fu;
+
+ /* Here we want to understand if we should start with +@all and remove
+ * the commands corresponding to the bits that are not set in the user
+ * commands bitmap, or the contrary. Note that semantically the two are
+ * different. For instance starting with +@all and subtracting, the user
+ * will be able to execute future commands, while -@all and adding will just
+ * allow the user the run the selected commands and/or categories.
+ * How do we test for that? We use the trick of a reserved command ID bit
+ * that is set only by +@all (and its alias "allcommands"). */
+ if (ACLUserCanExecuteFutureCommands(u)) {
+ additive = 0;
+ rules = sdscat(rules,"+@all ");
+ ACLSetUser(fakeuser,"+@all",-1);
+ } else {
+ additive = 1;
+ rules = sdscat(rules,"-@all ");
+ ACLSetUser(fakeuser,"-@all",-1);
+ }
+
+ /* Try to add or subtract each category one after the other. Often a
+ * single category will not perfectly match the set of commands into
+ * it, so at the end we do a final pass adding/removing the single commands
+ * needed to make the bitmap exactly match. */
+ for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
+ unsigned long on, off;
+ ACLCountCategoryBitsForUser(u,&on,&off,ACLCommandCategories[j].name);
+ if ((additive && on > off) || (!additive && off > on)) {
+ sds op = sdsnewlen(additive ? "+@" : "-@", 2);
+ op = sdscat(op,ACLCommandCategories[j].name);
+ ACLSetUser(fakeuser,op,-1);
+ rules = sdscatsds(rules,op);
+ rules = sdscatlen(rules," ",1);
+ sdsfree(op);
+ }
+ }
+
+ /* Fix the final ACLs with single commands differences. */
+ dictIterator *di = dictGetIterator(server.orig_commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ int userbit = ACLGetUserCommandBit(u,cmd->id);
+ int fakebit = ACLGetUserCommandBit(fakeuser,cmd->id);
+ if (userbit != fakebit) {
+ rules = sdscatlen(rules, userbit ? "+" : "-", 1);
+ rules = sdscat(rules,cmd->name);
+ rules = sdscatlen(rules," ",1);
+ ACLSetUserCommandBit(fakeuser,cmd->id,userbit);
+ }
+
+ /* Emit the subcommands if there are any. */
+ if (userbit == 0 && u->allowed_subcommands &&
+ u->allowed_subcommands[cmd->id])
+ {
+ for (int j = 0; u->allowed_subcommands[cmd->id][j]; j++) {
+ rules = sdscatlen(rules,"+",1);
+ rules = sdscat(rules,cmd->name);
+ rules = sdscatlen(rules,"|",1);
+ rules = sdscatsds(rules,u->allowed_subcommands[cmd->id][j]);
+ rules = sdscatlen(rules," ",1);
+ }
+ }
+ }
+ dictReleaseIterator(di);
+
+ /* Trim the final useless space. */
+ sdsrange(rules,0,-2);
+
+ /* This is technically not needed, but we want to verify that now the
+ * predicted bitmap is exactly the same as the user bitmap, and abort
+ * otherwise, because aborting is better than a security risk in this
+ * code path. */
+ if (memcmp(fakeuser->allowed_commands,
+ u->allowed_commands,
+ sizeof(u->allowed_commands)) != 0)
+ {
+ serverLog(LL_WARNING,
+ "CRITICAL ERROR: User ACLs don't match final bitmap: '%s'",
+ rules);
+ serverPanic("No bitmap match in ACLDescribeUserCommandRules()");
+ }
+ return rules;
+}
+
+/* This is similar to ACLDescribeUserCommandRules(), however instead of
+ * describing just the user command rules, everything is described: user
+ * flags, keys, passwords and finally the command rules obtained via
+ * the ACLDescribeUserCommandRules() function. This is the function we call
+ * when we want to rewrite the configuration files describing ACLs and
+ * in order to show users with ACL LIST. */
+sds ACLDescribeUser(user *u) {
+ sds res = sdsempty();
+
+ /* Flags. */
+ for (int j = 0; ACLUserFlags[j].flag; j++) {
+ /* Skip the allcommands and allkeys flags because they'll be emitted
+ * later as ~* and +@all. */
+ if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
+ ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
+ if (u->flags & ACLUserFlags[j].flag) {
+ res = sdscat(res,ACLUserFlags[j].name);
+ res = sdscatlen(res," ",1);
+ }
+ }
+
+ /* Passwords. */
+ listIter li;
+ listNode *ln;
+ listRewind(u->passwords,&li);
+ while((ln = listNext(&li))) {
+ sds thispass = listNodeValue(ln);
+ res = sdscatlen(res,"#",1);
+ res = sdscatsds(res,thispass);
+ res = sdscatlen(res," ",1);
+ }
+
+ /* Key patterns. */
+ if (u->flags & USER_FLAG_ALLKEYS) {
+ res = sdscatlen(res,"~* ",3);
+ } else {
+ listRewind(u->patterns,&li);
+ while((ln = listNext(&li))) {
+ sds thispat = listNodeValue(ln);
+ res = sdscatlen(res,"~",1);
+ res = sdscatsds(res,thispat);
+ res = sdscatlen(res," ",1);
+ }
+ }
+
+ /* Command rules. */
+ sds rules = ACLDescribeUserCommandRules(u);
+ res = sdscatsds(res,rules);
+ sdsfree(rules);
+ return res;
+}
+
+/* Get a command from the original command table, that is not affected
+ * by the command renaming operations: we base all the ACL work from that
+ * table, so that ACLs are valid regardless of command renaming. */
+struct redisCommand *ACLLookupCommand(const char *name) {
+ struct redisCommand *cmd;
+ sds sdsname = sdsnew(name);
+ cmd = dictFetchValue(server.orig_commands, sdsname);
+ sdsfree(sdsname);
+ return cmd;
+}
+
+/* Flush the array of allowed subcommands for the specified user
+ * and command ID. */
+void ACLResetSubcommandsForCommand(user *u, unsigned long id) {
+ if (u->allowed_subcommands && u->allowed_subcommands[id]) {
+ for (int i = 0; u->allowed_subcommands[id][i]; i++)
+ sdsfree(u->allowed_subcommands[id][i]);
+ zfree(u->allowed_subcommands[id]);
+ u->allowed_subcommands[id] = NULL;
+ }
+}
+
+/* Flush the entire table of subcommands. This is useful on +@all, -@all
+ * or similar to return back to the minimal memory usage (and checks to do)
+ * for the user. */
+void ACLResetSubcommands(user *u) {
+ if (u->allowed_subcommands == NULL) return;
+ for (int j = 0; j < USER_COMMAND_BITS_COUNT; j++) {
+ if (u->allowed_subcommands[j]) {
+ for (int i = 0; u->allowed_subcommands[j][i]; i++)
+ sdsfree(u->allowed_subcommands[j][i]);
+ zfree(u->allowed_subcommands[j]);
+ }
+ }
+ zfree(u->allowed_subcommands);
+ u->allowed_subcommands = NULL;
+}
+
+
+/* Add a subcommand to the list of subcommands for the user 'u' and
+ * the command id specified. */
+void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
+ /* If this is the first subcommand to be configured for
+ * this user, we have to allocate the subcommands array. */
+ if (u->allowed_subcommands == NULL) {
+ u->allowed_subcommands = zcalloc(USER_COMMAND_BITS_COUNT *
+ sizeof(sds*));
+ }
+
+ /* We also need to enlarge the allocation pointing to the
+ * null terminated SDS array, to make space for this one.
+ * To start check the current size, and while we are here
+ * make sure the subcommand is not already specified inside. */
+ long items = 0;
+ if (u->allowed_subcommands[id]) {
+ while(u->allowed_subcommands[id][items]) {
+ /* If it's already here do not add it again. */
+ if (!strcasecmp(u->allowed_subcommands[id][items],sub)) return;
+ items++;
+ }
+ }
+
+ /* Now we can make space for the new item (and the null term). */
+ items += 2;
+ u->allowed_subcommands[id] = zrealloc(u->allowed_subcommands[id],
+ sizeof(sds)*items);
+ u->allowed_subcommands[id][items-2] = sdsnew(sub);
+ u->allowed_subcommands[id][items-1] = NULL;
+}
+
+/* Set user properties according to the string "op". The following
+ * is a description of what different strings will do:
+ *
+ * on Enable the user: it is possible to authenticate as this user.
+ * off Disable the user: it's no longer possible to authenticate
+ * with this user, however the already authenticated connections
+ * will still work.
+ * +<command> Allow the execution of that command
+ * -<command> Disallow the execution of that command
+ * +@<category> Allow the execution of all the commands in such category
+ * with valid categories are like @admin, @set, @sortedset, ...
+ * and so forth, see the full list in the server.c file where
+ * the Redis command table is described and defined.
+ * The special category @all means all the commands, but currently
+ * present in the server, and that will be loaded in the future
+ * via modules.
+ * +<command>|subcommand Allow a specific subcommand of an otherwise
+ * disabled command. Note that this form is not
+ * allowed as negative like -DEBUG|SEGFAULT, but
+ * only additive starting with "+".
+ * allcommands Alias for +@all. Note that it implies the ability to execute
+ * all the future commands loaded via the modules system.
+ * nocommands Alias for -@all.
+ * ~<pattern> Add a pattern of keys that can be mentioned as part of
+ * commands. For instance ~* allows all the keys. The pattern
+ * is a glob-style pattern like the one of KEYS.
+ * It is possible to specify multiple patterns.
+ * allkeys Alias for ~*
+ * resetkeys Flush the list of allowed keys patterns.
+ * ><password> Add this password to the list of valid password for the user.
+ * For example >mypass will add "mypass" to the list.
+ * This directive clears the "nopass" flag (see later).
+ * #<hash> Add this password hash to the list of valid hashes for
+ * the user. This is useful if you have previously computed
+ * the hash, and don't want to store it in plaintext.
+ * This directive clears the "nopass" flag (see later).
+ * <<password> Remove this password from the list of valid passwords.
+ * !<hash> Remove this hashed password from the list of valid passwords.
+ * This is useful when you want to remove a password just by
+ * hash without knowing its plaintext version at all.
+ * nopass All the set passwords of the user are removed, and the user
+ * is flagged as requiring no password: it means that every
+ * password will work against this user. If this directive is
+ * used for the default user, every new connection will be
+ * immediately authenticated with the default user without
+ * any explicit AUTH command required. Note that the "resetpass"
+ * directive will clear this condition.
+ * resetpass Flush the list of allowed passwords. Moreover removes the
+ * "nopass" status. After "resetpass" the user has no associated
+ * passwords and there is no way to authenticate without adding
+ * some password (or setting it as "nopass" later).
+ * reset Performs the following actions: resetpass, resetkeys, off,
+ * -@all. The user returns to the same state it has immediately
+ * after its creation.
+ *
+ * The 'op' string must be null terminated. The 'oplen' argument should
+ * specify the length of the 'op' string in case the caller requires to pass
+ * binary data (for instance the >password form may use a binary password).
+ * Otherwise the field can be set to -1 and the function will use strlen()
+ * to determine the length.
+ *
+ * The function returns C_OK if the action to perform was understood because
+ * the 'op' string made sense. Otherwise C_ERR is returned if the operation
+ * is unknown or has some syntax error.
+ *
+ * When an error is returned, errno is set to the following values:
+ *
+ * EINVAL: The specified opcode is not understood.
+ * ENOENT: The command name or command category provided with + or - is not
+ * known.
+ * EBUSY: The subcommand you want to add is about a command that is currently
+ * fully added.
+ * EEXIST: You are adding a key pattern after "*" was already added. This is
+ * almost surely an error on the user side.
+ * ENODEV: The password you are trying to remove from the user does not exist.
+ * EBADMSG: The hash you are trying to add is not a valid hash.
+ */
+int ACLSetUser(user *u, const char *op, ssize_t oplen) {
+ if (oplen == -1) oplen = strlen(op);
+ if (!strcasecmp(op,"on")) {
+ u->flags |= USER_FLAG_ENABLED;
+ u->flags &= ~USER_FLAG_DISABLED;
+ } else if (!strcasecmp(op,"off")) {
+ u->flags |= USER_FLAG_DISABLED;
+ u->flags &= ~USER_FLAG_ENABLED;
+ } else if (!strcasecmp(op,"allkeys") ||
+ !strcasecmp(op,"~*"))
+ {
+ u->flags |= USER_FLAG_ALLKEYS;
+ listEmpty(u->patterns);
+ } else if (!strcasecmp(op,"resetkeys")) {
+ u->flags &= ~USER_FLAG_ALLKEYS;
+ listEmpty(u->patterns);
+ } else if (!strcasecmp(op,"allcommands") ||
+ !strcasecmp(op,"+@all"))
+ {
+ memset(u->allowed_commands,255,sizeof(u->allowed_commands));
+ u->flags |= USER_FLAG_ALLCOMMANDS;
+ ACLResetSubcommands(u);
+ } else if (!strcasecmp(op,"nocommands") ||
+ !strcasecmp(op,"-@all"))
+ {
+ memset(u->allowed_commands,0,sizeof(u->allowed_commands));
+ u->flags &= ~USER_FLAG_ALLCOMMANDS;
+ ACLResetSubcommands(u);
+ } else if (!strcasecmp(op,"nopass")) {
+ u->flags |= USER_FLAG_NOPASS;
+ listEmpty(u->passwords);
+ } else if (!strcasecmp(op,"resetpass")) {
+ u->flags &= ~USER_FLAG_NOPASS;
+ listEmpty(u->passwords);
+ } else if (op[0] == '>' || op[0] == '#') {
+ sds newpass;
+ if (op[0] == '>') {
+ newpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
+ } else {
+ if (oplen != HASH_PASSWORD_LEN + 1) {
+ errno = EBADMSG;
+ return C_ERR;
+ }
+
+ /* Password hashes can only be characters that represent
+ * hexadecimal values, which are numbers and lowercase
+ * characters 'a' through 'f'.
+ */
+ for(int i = 1; i < HASH_PASSWORD_LEN + 1; i++) {
+ char c = op[i];
+ if ((c < 'a' || c > 'f') && (c < '0' || c > '9')) {
+ errno = EBADMSG;
+ return C_ERR;
+ }
+ }
+ newpass = sdsnewlen(op+1,oplen-1);
+ }
+
+ listNode *ln = listSearchKey(u->passwords,newpass);
+ /* Avoid re-adding the same password multiple times. */
+ if (ln == NULL)
+ listAddNodeTail(u->passwords,newpass);
+ else
+ sdsfree(newpass);
+ u->flags &= ~USER_FLAG_NOPASS;
+ } else if (op[0] == '<' || op[0] == '!') {
+ sds delpass;
+ if (op[0] == '<') {
+ delpass = ACLHashPassword((unsigned char*)op+1,oplen-1);
+ } else {
+ if (oplen != HASH_PASSWORD_LEN + 1) {
+ errno = EBADMSG;
+ return C_ERR;
+ }
+ delpass = sdsnewlen(op+1,oplen-1);
+ }
+ listNode *ln = listSearchKey(u->passwords,delpass);
+ sdsfree(delpass);
+ if (ln) {
+ listDelNode(u->passwords,ln);
+ } else {
+ errno = ENODEV;
+ return C_ERR;
+ }
+ } else if (op[0] == '~') {
+ if (u->flags & USER_FLAG_ALLKEYS) {
+ errno = EEXIST;
+ return C_ERR;
+ }
+ sds newpat = sdsnewlen(op+1,oplen-1);
+ listNode *ln = listSearchKey(u->patterns,newpat);
+ /* Avoid re-adding the same pattern multiple times. */
+ if (ln == NULL)
+ listAddNodeTail(u->patterns,newpat);
+ else
+ sdsfree(newpat);
+ u->flags &= ~USER_FLAG_ALLKEYS;
+ } else if (op[0] == '+' && op[1] != '@') {
+ if (strchr(op,'|') == NULL) {
+ if (ACLLookupCommand(op+1) == NULL) {
+ errno = ENOENT;
+ return C_ERR;
+ }
+ unsigned long id = ACLGetCommandID(op+1);
+ ACLSetUserCommandBit(u,id,1);
+ ACLResetSubcommandsForCommand(u,id);
+ } else {
+ /* Split the command and subcommand parts. */
+ char *copy = zstrdup(op+1);
+ char *sub = strchr(copy,'|');
+ sub[0] = '\0';
+ sub++;
+
+ /* Check if the command exists. We can't check the
+ * subcommand to see if it is valid. */
+ if (ACLLookupCommand(copy) == NULL) {
+ zfree(copy);
+ errno = ENOENT;
+ return C_ERR;
+ }
+ unsigned long id = ACLGetCommandID(copy);
+
+ /* The subcommand cannot be empty, so things like DEBUG|
+ * are syntax errors of course. */
+ if (strlen(sub) == 0) {
+ zfree(copy);
+ errno = EINVAL;
+ return C_ERR;
+ }
+
+ /* The command should not be set right now in the command
+ * bitmap, because adding a subcommand of a fully added
+ * command is probably an error on the user side. */
+ if (ACLGetUserCommandBit(u,id) == 1) {
+ zfree(copy);
+ errno = EBUSY;
+ return C_ERR;
+ }
+
+ /* Add the subcommand to the list of valid ones. */
+ ACLAddAllowedSubcommand(u,id,sub);
+
+ /* We have to clear the command bit so that we force the
+ * subcommand check. */
+ ACLSetUserCommandBit(u,id,0);
+ zfree(copy);
+ }
+ } else if (op[0] == '-' && op[1] != '@') {
+ if (ACLLookupCommand(op+1) == NULL) {
+ errno = ENOENT;
+ return C_ERR;
+ }
+ unsigned long id = ACLGetCommandID(op+1);
+ ACLSetUserCommandBit(u,id,0);
+ ACLResetSubcommandsForCommand(u,id);
+ } else if ((op[0] == '+' || op[0] == '-') && op[1] == '@') {
+ int bitval = op[0] == '+' ? 1 : 0;
+ if (ACLSetUserCommandBitsForCategory(u,op+2,bitval) == C_ERR) {
+ errno = ENOENT;
+ return C_ERR;
+ }
+ } else if (!strcasecmp(op,"reset")) {
+ serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
+ serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
+ serverAssert(ACLSetUser(u,"off",-1) == C_OK);
+ serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
+ } else {
+ errno = EINVAL;
+ return C_ERR;
+ }
+ return C_OK;
+}
+
+/* Return a description of the error that occurred in ACLSetUser() according to
+ * the errno value set by the function on error. */
+char *ACLSetUserStringError(void) {
+ char *errmsg = "Wrong format";
+ if (errno == ENOENT)
+ errmsg = "Unknown command or category name in ACL";
+ else if (errno == EINVAL)
+ errmsg = "Syntax error";
+ else if (errno == EBUSY)
+ errmsg = "Adding a subcommand of a command already fully "
+ "added is not allowed. Remove the command to start. "
+ "Example: -DEBUG +DEBUG|DIGEST";
+ else if (errno == EEXIST)
+ errmsg = "Adding a pattern after the * pattern (or the "
+ "'allkeys' flag) is not valid and does not have any "
+ "effect. Try 'resetkeys' to start with an empty "
+ "list of patterns";
+ else if (errno == ENODEV)
+ errmsg = "The password you are trying to remove from the user does "
+ "not exist";
+ else if (errno == EBADMSG)
+ errmsg = "The password hash must be exactly 64 characters and contain "
+ "only lowercase hexadecimal characters";
+ return errmsg;
+}
+
+/* Return the first password of the default user or NULL.
+ * This function is needed for backward compatibility with the old
+ * directive "requirepass" when Redis supported a single global
+ * password. */
+sds ACLDefaultUserFirstPassword(void) {
+ if (listLength(DefaultUser->passwords) == 0) return NULL;
+ listNode *first = listFirst(DefaultUser->passwords);
+ return listNodeValue(first);
+}
+
+/* Initialize the default user, that will always exist for all the process
+ * lifetime. */
+void ACLInitDefaultUser(void) {
+ DefaultUser = ACLCreateUser("default",7);
+ ACLSetUser(DefaultUser,"+@all",-1);
+ ACLSetUser(DefaultUser,"~*",-1);
+ ACLSetUser(DefaultUser,"on",-1);
+ ACLSetUser(DefaultUser,"nopass",-1);
+}
+
+/* Initialization of the ACL subsystem. */
+void ACLInit(void) {
+ Users = raxNew();
+ UsersToLoad = listCreate();
+ ACLInitDefaultUser();
+}
+
+/* Check the username and password pair and return C_OK if they are valid,
+ * otherwise C_ERR is returned and errno is set to:
+ *
+ * EINVAL: if the username-password do not match.
+ * ENONENT: if the specified user does not exist at all.
+ */
+int ACLCheckUserCredentials(robj *username, robj *password) {
+ user *u = ACLGetUserByName(username->ptr,sdslen(username->ptr));
+ if (u == NULL) {
+ errno = ENOENT;
+ return C_ERR;
+ }
+
+ /* Disabled users can't login. */
+ if (u->flags & USER_FLAG_DISABLED) {
+ errno = EINVAL;
+ return C_ERR;
+ }
+
+ /* If the user is configured to don't require any password, we
+ * are already fine here. */
+ if (u->flags & USER_FLAG_NOPASS) return C_OK;
+
+ /* Check all the user passwords for at least one to match. */
+ listIter li;
+ listNode *ln;
+ listRewind(u->passwords,&li);
+ sds hashed = ACLHashPassword(password->ptr,sdslen(password->ptr));
+ while((ln = listNext(&li))) {
+ sds thispass = listNodeValue(ln);
+ if (!time_independent_strcmp(hashed, thispass)) {
+ sdsfree(hashed);
+ return C_OK;
+ }
+ }
+ sdsfree(hashed);
+
+ /* If we reached this point, no password matched. */
+ errno = EINVAL;
+ return C_ERR;
+}
+
+/* This is like ACLCheckUserCredentials(), however if the user/pass
+ * are correct, the connection is put in authenticated state and the
+ * connection user reference is populated.
+ *
+ * The return value is C_OK or C_ERR with the same meaning as
+ * ACLCheckUserCredentials(). */
+int ACLAuthenticateUser(client *c, robj *username, robj *password) {
+ if (ACLCheckUserCredentials(username,password) == C_OK) {
+ c->authenticated = 1;
+ c->user = ACLGetUserByName(username->ptr,sdslen(username->ptr));
+ return C_OK;
+ } else {
+ return C_ERR;
+ }
+}
+
+/* For ACL purposes, every user has a bitmap with the commands that such
+ * user is allowed to execute. In order to populate the bitmap, every command
+ * should have an assigned ID (that is used to index the bitmap). This function
+ * creates such an ID: it uses sequential IDs, reusing the same ID for the same
+ * command name, so that a command retains the same ID in case of modules that
+ * are unloaded and later reloaded. */
+unsigned long ACLGetCommandID(const char *cmdname) {
+ static rax *map = NULL;
+ static unsigned long nextid = 0;
+
+ sds lowername = sdsnew(cmdname);
+ sdstolower(lowername);
+ if (map == NULL) map = raxNew();
+ void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername));
+ if (id != raxNotFound) {
+ sdsfree(lowername);
+ return (unsigned long)id;
+ }
+ raxInsert(map,(unsigned char*)lowername,strlen(lowername),
+ (void*)nextid,NULL);
+ sdsfree(lowername);
+ unsigned long thisid = nextid;
+ nextid++;
+
+ /* We never assign the last bit in the user commands bitmap structure,
+ * this way we can later check if this bit is set, understanding if the
+ * current ACL for the user was created starting with a +@all to add all
+ * the possible commands and just subtracting other single commands or
+ * categories, or if, instead, the ACL was created just adding commands
+ * and command categories from scratch, not allowing future commands by
+ * default (loaded via modules). This is useful when rewriting the ACLs
+ * with ACL SAVE. */
+ if (nextid == USER_COMMAND_BITS_COUNT-1) nextid++;
+ return thisid;
+}
+
+/* Return an username by its name, or NULL if the user does not exist. */
+user *ACLGetUserByName(const char *name, size_t namelen) {
+ void *myuser = raxFind(Users,(unsigned char*)name,namelen);
+ if (myuser == raxNotFound) return NULL;
+ return myuser;
+}
+
+/* Check if the command is ready to be executed in the client 'c', already
+ * referenced by c->cmd, and can be executed by this client according to the
+ * ACLs associated to the client user c->user.
+ *
+ * If the user can execute the command ACL_OK is returned, otherwise
+ * ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
+ * command cannot be executed because the user is not allowed to run such
+ * command, the second if the command is denied because the user is trying
+ * to access keys that are not among the specified patterns. */
+int ACLCheckCommandPerm(client *c) {
+ user *u = c->user;
+ uint64_t id = c->cmd->id;
+
+ /* If there is no associated user, the connection can run anything. */
+ if (u == NULL) return ACL_OK;
+
+ /* Check if the user can execute this command. */
+ if (!(u->flags & USER_FLAG_ALLCOMMANDS) &&
+ c->cmd->proc != authCommand)
+ {
+ /* If the bit is not set we have to check further, in case the
+ * command is allowed just with that specific subcommand. */
+ if (ACLGetUserCommandBit(u,id) == 0) {
+ /* Check if the subcommand matches. */
+ if (c->argc < 2 ||
+ u->allowed_subcommands == NULL ||
+ u->allowed_subcommands[id] == NULL)
+ {
+ return ACL_DENIED_CMD;
+ }
+
+ long subid = 0;
+ while (1) {
+ if (u->allowed_subcommands[id][subid] == NULL)
+ return ACL_DENIED_CMD;
+ if (!strcasecmp(c->argv[1]->ptr,
+ u->allowed_subcommands[id][subid]))
+ break; /* Subcommand match found. Stop here. */
+ subid++;
+ }
+ }
+ }
+
+ /* Check if the user can execute commands explicitly touching the keys
+ * mentioned in the command arguments. */
+ if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
+ (c->cmd->getkeys_proc || c->cmd->firstkey))
+ {
+ int numkeys;
+ int *keyidx = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
+ for (int j = 0; j < numkeys; j++) {
+ listIter li;
+ listNode *ln;
+ listRewind(u->patterns,&li);
+
+ /* Test this key against every pattern. */
+ int match = 0;
+ while((ln = listNext(&li))) {
+ sds pattern = listNodeValue(ln);
+ size_t plen = sdslen(pattern);
+ int idx = keyidx[j];
+ if (stringmatchlen(pattern,plen,c->argv[idx]->ptr,
+ sdslen(c->argv[idx]->ptr),0))
+ {
+ match = 1;
+ break;
+ }
+ }
+ if (!match) {
+ getKeysFreeResult(keyidx);
+ return ACL_DENIED_KEY;
+ }
+ }
+ getKeysFreeResult(keyidx);
+ }
+
+ /* If we survived all the above checks, the user can execute the
+ * command. */
+ return ACL_OK;
+}
+
+/* =============================================================================
+ * ACL loading / saving functions
+ * ==========================================================================*/
+
+/* Given an argument vector describing a user in the form:
+ *
+ * user <username> ... ACL rules and flags ...
+ *
+ * this function validates, and if the syntax is valid, appends
+ * the user definition to a list for later loading.
+ *
+ * The rules are tested for validity and if there obvious syntax errors
+ * the function returns C_ERR and does nothing, otherwise C_OK is returned
+ * and the user is appended to the list.
+ *
+ * Note that this function cannot stop in case of commands that are not found
+ * and, in that case, the error will be emitted later, because certain
+ * commands may be defined later once modules are loaded.
+ *
+ * When an error is detected and C_ERR is returned, the function populates
+ * by reference (if not set to NULL) the argc_err argument with the index
+ * of the argv vector that caused the error. */
+int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err) {
+ if (argc < 2 || strcasecmp(argv[0],"user")) {
+ if (argc_err) *argc_err = 0;
+ return C_ERR;
+ }
+
+ /* Try to apply the user rules in a fake user to see if they
+ * are actually valid. */
+ user *fakeuser = ACLCreateUnlinkedUser();
+
+ for (int j = 2; j < argc; j++) {
+ if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) == C_ERR) {
+ if (errno != ENOENT) {
+ ACLFreeUser(fakeuser);
+ if (argc_err) *argc_err = j;
+ return C_ERR;
+ }
+ }
+ }
+
+ /* Rules look valid, let's append the user to the list. */
+ sds *copy = zmalloc(sizeof(sds)*argc);
+ for (int j = 1; j < argc; j++) copy[j-1] = sdsdup(argv[j]);
+ copy[argc-1] = NULL;
+ listAddNodeTail(UsersToLoad,copy);
+ ACLFreeUser(fakeuser);
+ return C_OK;
+}
+
+/* This function will load the configured users appended to the server
+ * configuration via ACLAppendUserForLoading(). On loading errors it will
+ * log an error and return C_ERR, otherwise C_OK will be returned. */
+int ACLLoadConfiguredUsers(void) {
+ listIter li;
+ listNode *ln;
+ listRewind(UsersToLoad,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds *aclrules = listNodeValue(ln);
+ sds username = aclrules[0];
+ user *u = ACLCreateUser(username,sdslen(username));
+ if (!u) {
+ u = ACLGetUserByName(username,sdslen(username));
+ serverAssert(u != NULL);
+ ACLSetUser(u,"reset",-1);
+ }
+
+ /* Load every rule defined for this user. */
+ for (int j = 1; aclrules[j]; j++) {
+ if (ACLSetUser(u,aclrules[j],sdslen(aclrules[j])) != C_OK) {
+ char *errmsg = ACLSetUserStringError();
+ serverLog(LL_WARNING,"Error loading ACL rule '%s' for "
+ "the user named '%s': %s",
+ aclrules[j],aclrules[0],errmsg);
+ return C_ERR;
+ }
+ }
+
+ /* Having a disabled user in the configuration may be an error,
+ * warn about it without returning any error to the caller. */
+ if (u->flags & USER_FLAG_DISABLED) {
+ serverLog(LL_NOTICE, "The user '%s' is disabled (there is no "
+ "'on' modifier in the user description). Make "
+ "sure this is not a configuration error.",
+ aclrules[0]);
+ }
+ }
+ return C_OK;
+}
+
+/* This function loads the ACL from the specified filename: every line
+ * is validated and should be either empty or in the format used to specify
+ * users in the redis.conf configuration or in the ACL file, that is:
+ *
+ * user <username> ... rules ...
+ *
+ * Note that this function considers comments starting with '#' as errors
+ * because the ACL file is meant to be rewritten, and comments would be
+ * lost after the rewrite. Yet empty lines are allowed to avoid being too
+ * strict.
+ *
+ * One important part of implementing ACL LOAD, that uses this function, is
+ * to avoid ending with broken rules if the ACL file is invalid for some
+ * reason, so the function will attempt to validate the rules before loading
+ * each user. For every line that will be found broken the function will
+ * collect an error message.
+ *
+ * IMPORTANT: If there is at least a single error, nothing will be loaded
+ * and the rules will remain exactly as they were.
+ *
+ * At the end of the process, if no errors were found in the whole file then
+ * NULL is returned. Otherwise an SDS string describing in a single line
+ * a description of all the issues found is returned. */
+sds ACLLoadFromFile(const char *filename) {
+ FILE *fp;
+ char buf[1024];
+
+ /* Open the ACL file. */
+ if ((fp = fopen(filename,"r")) == NULL) {
+ sds errors = sdscatprintf(sdsempty(),
+ "Error loading ACLs, opening file '%s': %s",
+ filename, strerror(errno));
+ return errors;
+ }
+
+ /* Load the whole file as a single string in memory. */
+ sds acls = sdsempty();
+ while(fgets(buf,sizeof(buf),fp) != NULL)
+ acls = sdscat(acls,buf);
+ fclose(fp);
+
+ /* Split the file into lines and attempt to load each line. */
+ int totlines;
+ sds *lines, errors = sdsempty();
+ lines = sdssplitlen(acls,strlen(acls),"\n",1,&totlines);
+ sdsfree(acls);
+
+ /* We need a fake user to validate the rules before making changes
+ * to the real user mentioned in the ACL line. */
+ user *fakeuser = ACLCreateUnlinkedUser();
+
+ /* We do all the loading in a fresh instance of the Users radix tree,
+ * so if there are errors loading the ACL file we can rollback to the
+ * old version. */
+ rax *old_users = Users;
+ user *old_default_user = DefaultUser;
+ Users = raxNew();
+ ACLInitDefaultUser();
+
+ /* Load each line of the file. */
+ for (int i = 0; i < totlines; i++) {
+ sds *argv;
+ int argc;
+ int linenum = i+1;
+
+ lines[i] = sdstrim(lines[i]," \t\r\n");
+
+ /* Skip blank lines */
+ if (lines[i][0] == '\0') continue;
+
+ /* Split into arguments */
+ argv = sdssplitargs(lines[i],&argc);
+ if (argv == NULL) {
+ errors = sdscatprintf(errors,
+ "%s:%d: unbalanced quotes in acl line. ",
+ server.acl_filename, linenum);
+ continue;
+ }
+
+ /* Skip this line if the resulting command vector is empty. */
+ if (argc == 0) {
+ sdsfreesplitres(argv,argc);
+ continue;
+ }
+
+ /* The line should start with the "user" keyword. */
+ if (strcmp(argv[0],"user") || argc < 2) {
+ errors = sdscatprintf(errors,
+ "%s:%d should start with user keyword followed "
+ "by the username. ", server.acl_filename,
+ linenum);
+ sdsfreesplitres(argv,argc);
+ continue;
+ }
+
+ /* Try to process the line using the fake user to validate iif
+ * the rules are able to apply cleanly. */
+ ACLSetUser(fakeuser,"reset",-1);
+ int j;
+ for (j = 2; j < argc; j++) {
+ if (ACLSetUser(fakeuser,argv[j],sdslen(argv[j])) != C_OK) {
+ char *errmsg = ACLSetUserStringError();
+ errors = sdscatprintf(errors,
+ "%s:%d: %s. ",
+ server.acl_filename, linenum, errmsg);
+ continue;
+ }
+ }
+
+ /* Apply the rule to the new users set only if so far there
+ * are no errors, otherwise it's useless since we are going
+ * to discard the new users set anyway. */
+ if (sdslen(errors) != 0) {
+ sdsfreesplitres(argv,argc);
+ continue;
+ }
+
+ /* We can finally lookup the user and apply the rule. If the
+ * user already exists we always reset it to start. */
+ user *u = ACLCreateUser(argv[1],sdslen(argv[1]));
+ if (!u) {
+ u = ACLGetUserByName(argv[1],sdslen(argv[1]));
+ serverAssert(u != NULL);
+ ACLSetUser(u,"reset",-1);
+ }
+
+ /* Note that the same rules already applied to the fake user, so
+ * we just assert that everything goes well: it should. */
+ for (j = 2; j < argc; j++)
+ serverAssert(ACLSetUser(u,argv[j],sdslen(argv[j])) == C_OK);
+
+ sdsfreesplitres(argv,argc);
+ }
+
+ ACLFreeUser(fakeuser);
+ sdsfreesplitres(lines,totlines);
+ DefaultUser = old_default_user; /* This pointer must never change. */
+
+ /* Check if we found errors and react accordingly. */
+ if (sdslen(errors) == 0) {
+ /* The default user pointer is referenced in different places: instead
+ * of replacing such occurrences it is much simpler to copy the new
+ * default user configuration in the old one. */
+ user *new = ACLGetUserByName("default",7);
+ serverAssert(new != NULL);
+ ACLCopyUser(DefaultUser,new);
+ ACLFreeUser(new);
+ raxInsert(Users,(unsigned char*)"default",7,DefaultUser,NULL);
+ raxRemove(old_users,(unsigned char*)"default",7,NULL);
+ ACLFreeUsersSet(old_users);
+ sdsfree(errors);
+ return NULL;
+ } else {
+ ACLFreeUsersSet(Users);
+ Users = old_users;
+ errors = sdscat(errors,"WARNING: ACL errors detected, no change to the previously active ACL rules was performed");
+ return errors;
+ }
+}
+
+/* Generate a copy of the ACLs currently in memory in the specified filename.
+ * Returns C_OK on success or C_ERR if there was an error during the I/O.
+ * When C_ERR is returned a log is produced with hints about the issue. */
+int ACLSaveToFile(const char *filename) {
+ sds acl = sdsempty();
+ int fd = -1;
+ sds tmpfilename = NULL;
+ int retval = C_ERR;
+
+ /* Let's generate an SDS string containing the new version of the
+ * ACL file. */
+ raxIterator ri;
+ raxStart(&ri,Users);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ user *u = ri.data;
+ /* Return information in the configuration file format. */
+ sds user = sdsnew("user ");
+ user = sdscatsds(user,u->name);
+ user = sdscatlen(user," ",1);
+ sds descr = ACLDescribeUser(u);
+ user = sdscatsds(user,descr);
+ sdsfree(descr);
+ acl = sdscatsds(acl,user);
+ acl = sdscatlen(acl,"\n",1);
+ sdsfree(user);
+ }
+ raxStop(&ri);
+
+ /* Create a temp file with the new content. */
+ tmpfilename = sdsnew(filename);
+ tmpfilename = sdscatfmt(tmpfilename,".tmp-%i-%I",
+ (int)getpid(),(int)mstime());
+ if ((fd = open(tmpfilename,O_WRONLY|O_CREAT,0644)) == -1) {
+ serverLog(LL_WARNING,"Opening temp ACL file for ACL SAVE: %s",
+ strerror(errno));
+ goto cleanup;
+ }
+
+ /* Write it. */
+ if (write(fd,acl,sdslen(acl)) != (ssize_t)sdslen(acl)) {
+ serverLog(LL_WARNING,"Writing ACL file for ACL SAVE: %s",
+ strerror(errno));
+ goto cleanup;
+ }
+ close(fd); fd = -1;
+
+ /* Let's replace the new file with the old one. */
+ if (rename(tmpfilename,filename) == -1) {
+ serverLog(LL_WARNING,"Renaming ACL file for ACL SAVE: %s",
+ strerror(errno));
+ goto cleanup;
+ }
+ sdsfree(tmpfilename); tmpfilename = NULL;
+ retval = C_OK; /* If we reached this point, everything is fine. */
+
+cleanup:
+ if (fd != -1) close(fd);
+ if (tmpfilename) unlink(tmpfilename);
+ sdsfree(tmpfilename);
+ sdsfree(acl);
+ return retval;
+}
+
+/* This function is called once the server is already running, modules are
+ * loaded, and we are ready to start, in order to load the ACLs either from
+ * the pending list of users defined in redis.conf, or from the ACL file.
+ * The function will just exit with an error if the user is trying to mix
+ * both the loading methods. */
+void ACLLoadUsersAtStartup(void) {
+ if (server.acl_filename[0] != '\0' && listLength(UsersToLoad) != 0) {
+ serverLog(LL_WARNING,
+ "Configuring Redis with users defined in redis.conf and at "
+ "the same setting an ACL file path is invalid. This setup "
+ "is very likely to lead to configuration errors and security "
+ "holes, please define either an ACL file or declare users "
+ "directly in your redis.conf, but not both.");
+ exit(1);
+ }
+
+ if (ACLLoadConfiguredUsers() == C_ERR) {
+ serverLog(LL_WARNING,
+ "Critical error while loading ACLs. Exiting.");
+ exit(1);
+ }
+
+ if (server.acl_filename[0] != '\0') {
+ sds errors = ACLLoadFromFile(server.acl_filename);
+ if (errors) {
+ serverLog(LL_WARNING,
+ "Aborting Redis startup because of ACL errors: %s", errors);
+ sdsfree(errors);
+ exit(1);
+ }
+ }
+}
+
+/* =============================================================================
+ * ACL related commands
+ * ==========================================================================*/
+
+/* ACL -- show and modify the configuration of ACL users.
+ * ACL HELP
+ * ACL LOAD
+ * ACL LIST
+ * ACL USERS
+ * ACL CAT [<category>]
+ * ACL SETUSER <username> ... acl rules ...
+ * ACL DELUSER <username> [...]
+ * ACL GETUSER <username>
+ * ACL GENPASS
+ * ACL WHOAMI
+ */
+void aclCommand(client *c) {
+ char *sub = c->argv[1]->ptr;
+ if (!strcasecmp(sub,"setuser") && c->argc >= 3) {
+ sds username = c->argv[2]->ptr;
+ /* Create a temporary user to validate and stage all changes against
+ * before applying to an existing user or creating a new user. If all
+ * arguments are valid the user parameters will all be applied together.
+ * If there are any errors then none of the changes will be applied. */
+ user *tempu = ACLCreateUnlinkedUser();
+ user *u = ACLGetUserByName(username,sdslen(username));
+ if (u) ACLCopyUser(tempu, u);
+
+ for (int j = 3; j < c->argc; j++) {
+ if (ACLSetUser(tempu,c->argv[j]->ptr,sdslen(c->argv[j]->ptr)) != C_OK) {
+ char *errmsg = ACLSetUserStringError();
+ addReplyErrorFormat(c,
+ "Error in ACL SETUSER modifier '%s': %s",
+ (char*)c->argv[j]->ptr, errmsg);
+
+ ACLFreeUser(tempu);
+ return;
+ }
+ }
+
+ /* Overwrite the user with the temporary user we modified above. */
+ if (!u) u = ACLCreateUser(username,sdslen(username));
+ serverAssert(u != NULL);
+ ACLCopyUser(u, tempu);
+ ACLFreeUser(tempu);
+ addReply(c,shared.ok);
+ } else if (!strcasecmp(sub,"deluser") && c->argc >= 3) {
+ int deleted = 0;
+ for (int j = 2; j < c->argc; j++) {
+ sds username = c->argv[j]->ptr;
+ if (!strcmp(username,"default")) {
+ addReplyError(c,"The 'default' user cannot be removed");
+ return;
+ }
+ }
+
+ for (int j = 2; j < c->argc; j++) {
+ sds username = c->argv[j]->ptr;
+ user *u;
+ if (raxRemove(Users,(unsigned char*)username,
+ sdslen(username),
+ (void**)&u))
+ {
+ ACLFreeUserAndKillClients(u);
+ deleted++;
+ }
+ }
+ addReplyLongLong(c,deleted);
+ } else if (!strcasecmp(sub,"getuser") && c->argc == 3) {
+ user *u = ACLGetUserByName(c->argv[2]->ptr,sdslen(c->argv[2]->ptr));
+ if (u == NULL) {
+ addReplyNull(c);
+ return;
+ }
+
+ addReplyMapLen(c,4);
+
+ /* Flags */
+ addReplyBulkCString(c,"flags");
+ void *deflen = addReplyDeferredLen(c);
+ int numflags = 0;
+ for (int j = 0; ACLUserFlags[j].flag; j++) {
+ if (u->flags & ACLUserFlags[j].flag) {
+ addReplyBulkCString(c,ACLUserFlags[j].name);
+ numflags++;
+ }
+ }
+ setDeferredSetLen(c,deflen,numflags);
+
+ /* Passwords */
+ addReplyBulkCString(c,"passwords");
+ addReplyArrayLen(c,listLength(u->passwords));
+ listIter li;
+ listNode *ln;
+ listRewind(u->passwords,&li);
+ while((ln = listNext(&li))) {
+ sds thispass = listNodeValue(ln);
+ addReplyBulkCBuffer(c,thispass,sdslen(thispass));
+ }
+
+ /* Commands */
+ addReplyBulkCString(c,"commands");
+ sds cmddescr = ACLDescribeUserCommandRules(u);
+ addReplyBulkSds(c,cmddescr);
+
+ /* Key patterns */
+ addReplyBulkCString(c,"keys");
+ if (u->flags & USER_FLAG_ALLKEYS) {
+ addReplyArrayLen(c,1);
+ addReplyBulkCBuffer(c,"*",1);
+ } else {
+ addReplyArrayLen(c,listLength(u->patterns));
+ listIter li;
+ listNode *ln;
+ listRewind(u->patterns,&li);
+ while((ln = listNext(&li))) {
+ sds thispat = listNodeValue(ln);
+ addReplyBulkCBuffer(c,thispat,sdslen(thispat));
+ }
+ }
+ } else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
+ c->argc == 2)
+ {
+ int justnames = !strcasecmp(sub,"users");
+ addReplyArrayLen(c,raxSize(Users));
+ raxIterator ri;
+ raxStart(&ri,Users);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ user *u = ri.data;
+ if (justnames) {
+ addReplyBulkCBuffer(c,u->name,sdslen(u->name));
+ } else {
+ /* Return information in the configuration file format. */
+ sds config = sdsnew("user ");
+ config = sdscatsds(config,u->name);
+ config = sdscatlen(config," ",1);
+ sds descr = ACLDescribeUser(u);
+ config = sdscatsds(config,descr);
+ sdsfree(descr);
+ addReplyBulkSds(c,config);
+ }
+ }
+ raxStop(&ri);
+ } else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
+ if (c->user != NULL) {
+ addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
+ } else {
+ addReplyNull(c);
+ }
+ } else if (server.acl_filename[0] == '\0' &&
+ (!strcasecmp(sub,"load") || !strcasecmp(sub,"save")))
+ {
+ addReplyError(c,"This Redis instance is not configured to use an ACL file. You may want to specify users via the ACL SETUSER command and then issue a CONFIG REWRITE (assuming you have a Redis configuration file set) in order to store users in the Redis configuration.");
+ return;
+ } else if (!strcasecmp(sub,"load") && c->argc == 2) {
+ sds errors = ACLLoadFromFile(server.acl_filename);
+ if (errors == NULL) {
+ addReply(c,shared.ok);
+ } else {
+ addReplyError(c,errors);
+ sdsfree(errors);
+ }
+ } else if (!strcasecmp(sub,"save") && c->argc == 2) {
+ if (ACLSaveToFile(server.acl_filename) == C_OK) {
+ addReply(c,shared.ok);
+ } else {
+ addReplyError(c,"There was an error trying to save the ACLs. "
+ "Please check the server logs for more "
+ "information");
+ }
+ } else if (!strcasecmp(sub,"cat") && c->argc == 2) {
+ void *dl = addReplyDeferredLen(c);
+ int j;
+ for (j = 0; ACLCommandCategories[j].flag != 0; j++)
+ addReplyBulkCString(c,ACLCommandCategories[j].name);
+ setDeferredArrayLen(c,dl,j);
+ } else if (!strcasecmp(sub,"cat") && c->argc == 3) {
+ uint64_t cflag = ACLGetCommandCategoryFlagByName(c->argv[2]->ptr);
+ if (cflag == 0) {
+ addReplyErrorFormat(c, "Unknown category '%s'", (char*)c->argv[2]->ptr);
+ return;
+ }
+ int arraylen = 0;
+ void *dl = addReplyDeferredLen(c);
+ dictIterator *di = dictGetIterator(server.orig_commands);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ struct redisCommand *cmd = dictGetVal(de);
+ if (cmd->flags & CMD_MODULE) continue;
+ if (cmd->flags & cflag) {
+ addReplyBulkCString(c,cmd->name);
+ arraylen++;
+ }
+ }
+ dictReleaseIterator(di);
+ setDeferredArrayLen(c,dl,arraylen);
+ } else if (!strcasecmp(sub,"genpass") && c->argc == 2) {
+ char pass[32]; /* 128 bits of actual pseudo random data. */
+ getRandomHexChars(pass,sizeof(pass));
+ addReplyBulkCBuffer(c,pass,sizeof(pass));
+ } else if (!strcasecmp(sub,"help")) {
+ const char *help[] = {
+"LOAD -- Reload users from the ACL file.",
+"LIST -- Show user details in config file format.",
+"USERS -- List all the registered usernames.",
+"SETUSER <username> [attribs ...] -- Create or modify a user.",
+"GETUSER <username> -- Get the user details.",
+"DELUSER <username> [...] -- Delete a list of users.",
+"CAT -- List available categories.",
+"CAT <category> -- List commands inside category.",
+"GENPASS -- Generate a secure user password.",
+"WHOAMI -- Return the current connection username.",
+NULL
+ };
+ addReplyHelp(c,help);
+ } else {
+ addReplySubcommandSyntaxError(c);
+ }
+}
+
+void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
+ int flagcount = 0;
+ void *flaglen = addReplyDeferredLen(c);
+ for (int j = 0; ACLCommandCategories[j].flag != 0; j++) {
+ if (cmd->flags & ACLCommandCategories[j].flag) {
+ addReplyStatusFormat(c, "@%s", ACLCommandCategories[j].name);
+ flagcount++;
+ }
+ }
+ setDeferredSetLen(c, flaglen, flagcount);
+}
+
+/* AUTH <password>
+ * AUTH <username> <password> (Redis >= 6.0 form)
+ *
+ * When the user is omitted it means that we are trying to authenticate
+ * against the default user. */
+void authCommand(client *c) {
+ /* Only two or three argument forms are allowed. */
+ if (c->argc > 3) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* Handle the two different forms here. The form with two arguments
+ * will just use "default" as username. */
+ robj *username, *password;
+ if (c->argc == 2) {
+ /* Mimic the old behavior of giving an error for the two commands
+ * from if no password is configured. */
+ if (DefaultUser->flags & USER_FLAG_NOPASS) {
+ addReplyError(c,"AUTH <password> called without any password "
+ "configured for the default user. Are you sure "
+ "your configuration is correct?");
+ return;
+ }
+
+ username = createStringObject("default",7);
+ password = c->argv[1];
+ } else {
+ username = c->argv[1];
+ password = c->argv[2];
+ }
+
+ if (ACLAuthenticateUser(c,username,password) == C_OK) {
+ addReply(c,shared.ok);
+ } else {
+ addReplyError(c,"-WRONGPASS invalid username-password pair");
+ }
+
+ /* Free the "default" string object we created for the two
+ * arguments form. */
+ if (c->argc == 2) decrRefCount(username);
+}
+
diff --git a/src/ae.c b/src/ae.c
index 6ed366fe1..2c1dae512 100644
--- a/src/ae.c
+++ b/src/ae.c
@@ -76,6 +76,7 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
eventLoop->maxfd = -1;
eventLoop->beforesleep = NULL;
eventLoop->aftersleep = NULL;
+ eventLoop->flags = 0;
if (aeApiCreate(eventLoop) == -1) goto err;
/* Events with mask == AE_NONE are not set. So let's initialize the
* vector with it. */
@@ -97,6 +98,14 @@ int aeGetSetSize(aeEventLoop *eventLoop) {
return eventLoop->setsize;
}
+/* Tells the next iteration/s of the event processing to set timeout of 0. */
+void aeSetDontWait(aeEventLoop *eventLoop, int noWait) {
+ if (noWait)
+ eventLoop->flags |= AE_DONT_WAIT;
+ else
+ eventLoop->flags &= ~AE_DONT_WAIT;
+}
+
/* Resize the maximum set size of the event loop.
* If the requested set size is smaller than the current set size, but
* there is already a file descriptor in use that is >= the requested
@@ -219,7 +228,10 @@ long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
+ te->prev = NULL;
te->next = eventLoop->timeEventHead;
+ if (te->next)
+ te->next->prev = te;
eventLoop->timeEventHead = te;
return id;
}
@@ -266,7 +278,7 @@ static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
/* Process time events */
static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0;
- aeTimeEvent *te, *prev;
+ aeTimeEvent *te;
long long maxId;
time_t now = time(NULL);
@@ -287,7 +299,6 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
}
eventLoop->lastTime = now;
- prev = NULL;
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
while(te) {
@@ -297,10 +308,12 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
/* Remove events scheduled for deletion. */
if (te->id == AE_DELETED_EVENT_ID) {
aeTimeEvent *next = te->next;
- if (prev == NULL)
- eventLoop->timeEventHead = te->next;
+ if (te->prev)
+ te->prev->next = te->next;
else
- prev->next = te->next;
+ eventLoop->timeEventHead = te->next;
+ if (te->next)
+ te->next->prev = te->prev;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
@@ -332,7 +345,6 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
te->id = AE_DELETED_EVENT_ID;
}
}
- prev = te;
te = te->next;
}
return processed;
@@ -348,8 +360,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
* if flags has AE_FILE_EVENTS set, file events are processed.
* if flags has AE_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all
- * if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
* the events that's possible to process without to wait are processed.
+ * if flags has AE_CALL_AFTER_SLEEP set, the aftersleep callback is called.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
@@ -403,6 +415,11 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
}
}
+ if (eventLoop->flags & AE_DONT_WAIT) {
+ tv.tv_sec = tv.tv_usec = 0;
+ tvp = &tv;
+ }
+
/* Call the multiplexing API, will return only on timeout or when
* some event fires. */
numevents = aeApiPoll(eventLoop, tvp);
@@ -430,7 +447,7 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
* before replying to a client. */
int invert = fe->mask & AE_BARRIER;
- /* Note the "fe->mask & mask & ..." code: maybe an already
+ /* Note the "fe->mask & mask & ..." code: maybe an already
* processed event removed an element that fired and we still
* didn't processed, so we check if the event is still valid.
*
@@ -482,7 +499,7 @@ int aeWait(int fd, int mask, long long milliseconds) {
if ((retval = poll(&pfd, 1, milliseconds))== 1) {
if (pfd.revents & POLLIN) retmask |= AE_READABLE;
if (pfd.revents & POLLOUT) retmask |= AE_WRITABLE;
- if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
+ if (pfd.revents & POLLERR) retmask |= AE_WRITABLE;
if (pfd.revents & POLLHUP) retmask |= AE_WRITABLE;
return retmask;
} else {
diff --git a/src/ae.h b/src/ae.h
index df5174838..9acd72434 100644
--- a/src/ae.h
+++ b/src/ae.h
@@ -83,6 +83,7 @@ typedef struct aeTimeEvent {
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
+ struct aeTimeEvent *prev;
struct aeTimeEvent *next;
} aeTimeEvent;
@@ -105,6 +106,7 @@ typedef struct aeEventLoop {
void *apidata; /* This is used for polling API specific data */
aeBeforeSleepProc *beforesleep;
aeBeforeSleepProc *aftersleep;
+ int flags;
} aeEventLoop;
/* Prototypes */
@@ -127,5 +129,6 @@ void aeSetBeforeSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *beforesleep
void aeSetAfterSleepProc(aeEventLoop *eventLoop, aeBeforeSleepProc *aftersleep);
int aeGetSetSize(aeEventLoop *eventLoop);
int aeResizeSetSize(aeEventLoop *eventLoop, int setsize);
+void aeSetDontWait(aeEventLoop *eventLoop, int noWait);
#endif
diff --git a/src/ae_epoll.c b/src/ae_epoll.c
index 410aac70d..fa197297e 100644
--- a/src/ae_epoll.c
+++ b/src/ae_epoll.c
@@ -121,8 +121,8 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
if (e->events & EPOLLIN) mask |= AE_READABLE;
if (e->events & EPOLLOUT) mask |= AE_WRITABLE;
- if (e->events & EPOLLERR) mask |= AE_WRITABLE;
- if (e->events & EPOLLHUP) mask |= AE_WRITABLE;
+ if (e->events & EPOLLERR) mask |= AE_WRITABLE|AE_READABLE;
+ if (e->events & EPOLLHUP) mask |= AE_WRITABLE|AE_READABLE;
eventLoop->fired[j].fd = e->data.fd;
eventLoop->fired[j].mask = mask;
}
diff --git a/src/anet.c b/src/anet.c
index e9530398d..46ea7e145 100644
--- a/src/anet.c
+++ b/src/anet.c
@@ -193,6 +193,20 @@ int anetSendTimeout(char *err, int fd, long long ms) {
return ANET_OK;
}
+/* Set the socket receive timeout (SO_RCVTIMEO socket option) to the specified
+ * number of milliseconds, or disable it if the 'ms' argument is zero. */
+int anetRecvTimeout(char *err, int fd, long long ms) {
+ struct timeval tv;
+
+ tv.tv_sec = ms/1000;
+ tv.tv_usec = (ms%1000)*1000;
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
+ anetSetError(err, "setsockopt SO_RCVTIMEO: %s", strerror(errno));
+ return ANET_ERR;
+ }
+ return ANET_OK;
+}
+
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to
* do the actual work. It resolves the hostname "host" and set the string
* representation of the IP address into the buffer pointed by "ipbuf".
@@ -265,8 +279,8 @@ static int anetCreateSocket(char *err, int domain) {
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
#define ANET_CONNECT_BE_BINDING 2 /* Best effort binding. */
-static int anetTcpGenericConnect(char *err, char *addr, int port,
- char *source_addr, int flags)
+static int anetTcpGenericConnect(char *err, const char *addr, int port,
+ const char *source_addr, int flags)
{
int s = ANET_ERR, rv;
char portstr[6]; /* strlen("65535") + 1; */
@@ -345,31 +359,31 @@ end:
}
}
-int anetTcpConnect(char *err, char *addr, int port)
+int anetTcpConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONE);
}
-int anetTcpNonBlockConnect(char *err, char *addr, int port)
+int anetTcpNonBlockConnect(char *err, const char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,NULL,ANET_CONNECT_NONBLOCK);
}
-int anetTcpNonBlockBindConnect(char *err, char *addr, int port,
- char *source_addr)
+int anetTcpNonBlockBindConnect(char *err, const char *addr, int port,
+ const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK);
}
-int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port,
- char *source_addr)
+int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
+ const char *source_addr)
{
return anetTcpGenericConnect(err,addr,port,source_addr,
ANET_CONNECT_NONBLOCK|ANET_CONNECT_BE_BINDING);
}
-int anetUnixGenericConnect(char *err, char *path, int flags)
+int anetUnixGenericConnect(char *err, const char *path, int flags)
{
int s;
struct sockaddr_un sa;
@@ -397,12 +411,12 @@ int anetUnixGenericConnect(char *err, char *path, int flags)
return s;
}
-int anetUnixConnect(char *err, char *path)
+int anetUnixConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONE);
}
-int anetUnixNonBlockConnect(char *err, char *path)
+int anetUnixNonBlockConnect(char *err, const char *path)
{
return anetUnixGenericConnect(err,path,ANET_CONNECT_NONBLOCK);
}
@@ -484,7 +498,7 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
if (af == AF_INET6 && anetV6Only(err,s) == ANET_ERR) goto error;
if (anetSetReuseAddr(err,s) == ANET_ERR) goto error;
- if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) goto error;
+ if (anetListen(err,s,p->ai_addr,p->ai_addrlen,backlog) == ANET_ERR) s = ANET_ERR;
goto end;
}
if (p == NULL) {
diff --git a/src/anet.h b/src/anet.h
index 7142f78d2..23f19643c 100644
--- a/src/anet.h
+++ b/src/anet.h
@@ -49,12 +49,12 @@
#undef ip_len
#endif
-int anetTcpConnect(char *err, char *addr, int port);
-int anetTcpNonBlockConnect(char *err, char *addr, int port);
-int anetTcpNonBlockBindConnect(char *err, char *addr, int port, char *source_addr);
-int anetTcpNonBlockBestEffortBindConnect(char *err, char *addr, int port, char *source_addr);
-int anetUnixConnect(char *err, char *path);
-int anetUnixNonBlockConnect(char *err, char *path);
+int anetTcpConnect(char *err, const char *addr, int port);
+int anetTcpNonBlockConnect(char *err, const char *addr, int port);
+int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
+int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, const char *source_addr);
+int anetUnixConnect(char *err, const char *path);
+int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len);
@@ -70,6 +70,7 @@ int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms);
+int anetRecvTimeout(char *err, int fd, long long ms);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port);
int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
diff --git a/src/aof.c b/src/aof.c
index 4a7d749d0..0e3648ff0 100644
--- a/src/aof.c
+++ b/src/aof.c
@@ -197,6 +197,12 @@ ssize_t aofRewriteBufferWrite(int fd) {
* AOF file implementation
* ------------------------------------------------------------------------- */
+/* Return true if an AOf fsync is currently already in progress in a
+ * BIO thread. */
+int aofFsyncInProgress(void) {
+ return bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
+}
+
/* Starts a background task that performs fsync() against the specified
* file descriptor (the one of the AOF file) in another thread. */
void aof_background_fsync(int fd) {
@@ -204,7 +210,7 @@ void aof_background_fsync(int fd) {
}
/* Kills an AOFRW child process if exists */
-static void killAppendOnlyChild(void) {
+void killAppendOnlyChild(void) {
int statloc;
/* No AOFRW child? return. */
if (server.aof_child_pid == -1) return;
@@ -221,6 +227,8 @@ static void killAppendOnlyChild(void) {
server.aof_rewrite_time_start = -1;
/* Close pipes used for IPC between the two processes. */
aofClosePipes();
+ closeChildInfoPipe();
+ updateDictResizePolicy();
}
/* Called when the user switches from "appendonly yes" to "appendonly no"
@@ -228,7 +236,7 @@ static void killAppendOnlyChild(void) {
void stopAppendOnly(void) {
serverAssert(server.aof_state != AOF_OFF);
flushAppendOnlyFile(1);
- aof_fsync(server.aof_fd);
+ redis_fsync(server.aof_fd);
close(server.aof_fd);
server.aof_fd = -1;
@@ -256,12 +264,12 @@ int startAppendOnly(void) {
strerror(errno));
return C_ERR;
}
- if (server.rdb_child_pid != -1) {
+ if (hasActiveChildProcess() && server.aof_child_pid == -1) {
server.aof_rewrite_scheduled = 1;
- serverLog(LL_WARNING,"AOF was enabled but there is already a child process saving an RDB file on disk. An AOF background was scheduled to start when possible.");
+ serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
} else {
/* If there is a pending AOF rewrite, we need to switch it off and
- * start a new one: the old one cannot be reused becuase it is not
+ * start a new one: the old one cannot be reused because it is not
* accumulating the AOF buffer. */
if (server.aof_child_pid != -1) {
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
@@ -295,9 +303,7 @@ ssize_t aofWrite(int fd, const char *buf, size_t len) {
nwritten = write(fd, buf, len);
if (nwritten < 0) {
- if (errno == EINTR) {
- continue;
- }
+ if (errno == EINTR) continue;
return totwritten ? totwritten : -1;
}
@@ -333,10 +339,24 @@ void flushAppendOnlyFile(int force) {
int sync_in_progress = 0;
mstime_t latency;
- if (sdslen(server.aof_buf) == 0) return;
+ if (sdslen(server.aof_buf) == 0) {
+ /* Check if we need to do fsync even the aof buffer is empty,
+ * because previously in AOF_FSYNC_EVERYSEC mode, fsync is
+ * called only when aof buffer is not empty, so if users
+ * stop write commands before fsync called in one second,
+ * the data in page cache cannot be flushed in time. */
+ if (server.aof_fsync == AOF_FSYNC_EVERYSEC &&
+ server.aof_fsync_offset != server.aof_current_size &&
+ server.unixtime > server.aof_last_fsync &&
+ !(sync_in_progress = aofFsyncInProgress())) {
+ goto try_fsync;
+ } else {
+ return;
+ }
+ }
if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
- sync_in_progress = bioPendingJobsOfType(BIO_AOF_FSYNC) != 0;
+ sync_in_progress = aofFsyncInProgress();
if (server.aof_fsync == AOF_FSYNC_EVERYSEC && !force) {
/* With this append fsync policy we do background fsyncing.
@@ -365,6 +385,10 @@ void flushAppendOnlyFile(int force) {
* there is much to do about the whole server stopping for power problems
* or alike */
+ if (server.aof_flush_sleep && sdslen(server.aof_buf)) {
+ usleep(server.aof_flush_sleep);
+ }
+
latencyStartMonitor(latency);
nwritten = aofWrite(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
latencyEndMonitor(latency);
@@ -375,7 +399,7 @@ void flushAppendOnlyFile(int force) {
* useful for graphing / monitoring purposes. */
if (sync_in_progress) {
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
- } else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
+ } else if (hasActiveChildProcess()) {
latencyAddSampleIfNeeded("aof-write-active-child",latency);
} else {
latencyAddSampleIfNeeded("aof-write-alone",latency);
@@ -468,24 +492,28 @@ void flushAppendOnlyFile(int force) {
server.aof_buf = sdsempty();
}
+try_fsync:
/* Don't fsync if no-appendfsync-on-rewrite is set to yes and there are
* children doing I/O in the background. */
- if (server.aof_no_fsync_on_rewrite &&
- (server.aof_child_pid != -1 || server.rdb_child_pid != -1))
- return;
+ if (server.aof_no_fsync_on_rewrite && hasActiveChildProcess())
+ return;
/* Perform the fsync if needed. */
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
- /* aof_fsync is defined as fdatasync() for Linux in order to avoid
+ /* redis_fsync is defined as fdatasync() for Linux in order to avoid
* flushing metadata. */
latencyStartMonitor(latency);
- aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */
+ redis_fsync(server.aof_fd); /* Let's try to get this data on the disk */
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-fsync-always",latency);
+ server.aof_fsync_offset = server.aof_current_size;
server.aof_last_fsync = server.unixtime;
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
server.unixtime > server.aof_last_fsync)) {
- if (!sync_in_progress) aof_background_fsync(server.aof_fd);
+ if (!sync_in_progress) {
+ aof_background_fsync(server.aof_fd);
+ server.aof_fsync_offset = server.aof_current_size;
+ }
server.aof_last_fsync = server.unixtime;
}
}
@@ -624,11 +652,12 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
/* In Redis commands are always executed in the context of a client, so in
* order to load the append only file we need to create a fake client. */
-struct client *createFakeClient(void) {
+struct client *createAOFClient(void) {
struct client *c = zmalloc(sizeof(*c));
selectDb(c,0);
- c->fd = -1;
+ c->id = CLIENT_ID_AOF; /* So modules can identify it's the AOF client. */
+ c->conn = NULL;
c->name = NULL;
c->querybuf = sdsempty();
c->querybuf_peak = 0;
@@ -645,7 +674,9 @@ struct client *createFakeClient(void) {
c->obuf_soft_limit_reached_time = 0;
c->watched_keys = listCreate();
c->peerid = NULL;
- listSetFreeMethod(c->reply,decrRefCountVoid);
+ c->resp = 2;
+ c->user = NULL;
+ listSetFreeMethod(c->reply,freeClientReplyValue);
listSetDupMethod(c->reply,dupClientReplyValue);
initClientMultiState(c);
return c;
@@ -677,18 +708,20 @@ int loadAppendOnlyFile(char *filename) {
int old_aof_state = server.aof_state;
long loops = 0;
off_t valid_up_to = 0; /* Offset of latest well-formed command loaded. */
+ off_t valid_before_multi = 0; /* Offset before MULTI command loaded. */
if (fp == NULL) {
serverLog(LL_WARNING,"Fatal error: can't open the append log file for reading: %s",strerror(errno));
exit(1);
}
- /* Handle a zero-length AOF file as a special case. An emtpy AOF file
+ /* Handle a zero-length AOF file as a special case. An empty AOF file
* is a valid AOF because an empty server with AOF enabled will create
* a zero length file at startup, that will remain like that if no write
* operation is received. */
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
server.aof_current_size = 0;
+ server.aof_fsync_offset = server.aof_current_size;
fclose(fp);
return C_ERR;
}
@@ -697,8 +730,8 @@ int loadAppendOnlyFile(char *filename) {
* to the same file we're about to read. */
server.aof_state = AOF_OFF;
- fakeClient = createFakeClient();
- startLoading(fp);
+ fakeClient = createAOFClient();
+ startLoadingFile(fp, filename);
/* Check if this AOF file has an RDB preamble. In that case we need to
* load the RDB file and later continue loading the AOF tail. */
@@ -713,7 +746,7 @@ int loadAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE,"Reading RDB preamble from AOF file...");
if (fseek(fp,0,SEEK_SET) == -1) goto readerr;
rioInitWithFile(&rdb,fp);
- if (rdbLoadRio(&rdb,NULL) != C_OK) {
+ if (rdbLoadRio(&rdb,NULL,1) != C_OK) {
serverLog(LL_WARNING,"Error reading the RDB preamble of the AOF file, AOF loading aborted");
goto readerr;
} else {
@@ -777,16 +810,28 @@ int loadAppendOnlyFile(char *filename) {
/* Command lookup */
cmd = lookupCommand(argv[0]->ptr);
if (!cmd) {
- serverLog(LL_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
+ serverLog(LL_WARNING,
+ "Unknown command '%s' reading the append only file",
+ (char*)argv[0]->ptr);
exit(1);
}
+ if (cmd == server.multiCommand) valid_before_multi = valid_up_to;
+
/* Run the command in the context of a fake client */
fakeClient->cmd = cmd;
- cmd->proc(fakeClient);
+ if (fakeClient->flags & CLIENT_MULTI &&
+ fakeClient->cmd->proc != execCommand)
+ {
+ queueMultiCommand(fakeClient);
+ } else {
+ cmd->proc(fakeClient);
+ }
/* The fake client should not have a reply */
- serverAssert(fakeClient->bufpos == 0 && listLength(fakeClient->reply) == 0);
+ serverAssert(fakeClient->bufpos == 0 &&
+ listLength(fakeClient->reply) == 0);
+
/* The fake client should never get blocked */
serverAssert((fakeClient->flags & CLIENT_BLOCKED) == 0);
@@ -795,11 +840,20 @@ int loadAppendOnlyFile(char *filename) {
freeFakeClientArgv(fakeClient);
fakeClient->cmd = NULL;
if (server.aof_load_truncated) valid_up_to = ftello(fp);
+ if (server.key_load_delay)
+ usleep(server.key_load_delay);
}
/* This point can only be reached when EOF is reached without errors.
- * If the client is in the middle of a MULTI/EXEC, log error and quit. */
- if (fakeClient->flags & CLIENT_MULTI) goto uxeof;
+ * If the client is in the middle of a MULTI/EXEC, handle it as it was
+ * a short read, even if technically the protocol is correct: we want
+ * to remove the unprocessed tail and continue. */
+ if (fakeClient->flags & CLIENT_MULTI) {
+ serverLog(LL_WARNING,
+ "Revert incomplete MULTI/EXEC transaction in AOF file");
+ valid_up_to = valid_before_multi;
+ goto uxeof;
+ }
loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
fclose(fp);
@@ -808,11 +862,13 @@ loaded_ok: /* DB loaded, cleanup and return C_OK to the caller. */
stopLoading();
aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size;
+ server.aof_fsync_offset = server.aof_current_size;
return C_OK;
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
if (!feof(fp)) {
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ fclose(fp);
serverLog(LL_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
exit(1);
}
@@ -843,11 +899,13 @@ uxeof: /* Unexpected AOF end of file. */
}
}
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ fclose(fp);
serverLog(LL_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
exit(1);
fmterr: /* Format error. */
if (fakeClient) freeFakeClient(fakeClient); /* avoid valgrind warning */
+ fclose(fp);
serverLog(LL_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
exit(1);
}
@@ -1074,33 +1132,136 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
return 1;
}
+/* Helper for rewriteStreamObject() that generates a bulk string into the
+ * AOF representing the ID 'id'. */
+int rioWriteBulkStreamID(rio *r,streamID *id) {
+ int retval;
+
+ sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
+ if ((retval = rioWriteBulkString(r,replyid,sdslen(replyid))) == 0) return 0;
+ sdsfree(replyid);
+ return retval;
+}
+
+/* Helper for rewriteStreamObject(): emit the XCLAIM needed in order to
+ * add the message described by 'nack' having the id 'rawid', into the pending
+ * list of the specified consumer. All this in the context of the specified
+ * key and group. */
+int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t groupname_len, streamConsumer *consumer, unsigned char *rawid, streamNACK *nack) {
+ /* XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
+ RETRYCOUNT <count> JUSTID FORCE. */
+ streamID id;
+ streamDecodeID(rawid,&id);
+ if (rioWriteBulkCount(r,'*',12) == 0) return 0;
+ if (rioWriteBulkString(r,"XCLAIM",6) == 0) return 0;
+ if (rioWriteBulkObject(r,key) == 0) return 0;
+ if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0;
+ if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 0) return 0;
+ if (rioWriteBulkString(r,"0",1) == 0) return 0;
+ if (rioWriteBulkStreamID(r,&id) == 0) return 0;
+ if (rioWriteBulkString(r,"TIME",4) == 0) return 0;
+ if (rioWriteBulkLongLong(r,nack->delivery_time) == 0) return 0;
+ if (rioWriteBulkString(r,"RETRYCOUNT",10) == 0) return 0;
+ if (rioWriteBulkLongLong(r,nack->delivery_count) == 0) return 0;
+ if (rioWriteBulkString(r,"JUSTID",6) == 0) return 0;
+ if (rioWriteBulkString(r,"FORCE",5) == 0) return 0;
+ return 1;
+}
+
/* Emit the commands needed to rebuild a stream object.
* The function returns 0 on error, 1 on success. */
int rewriteStreamObject(rio *r, robj *key, robj *o) {
+ stream *s = o->ptr;
streamIterator si;
- streamIteratorStart(&si,o->ptr,NULL,NULL,0);
+ streamIteratorStart(&si,s,NULL,NULL,0);
streamID id;
int64_t numfields;
- while(streamIteratorGetID(&si,&id,&numfields)) {
- /* Emit a two elements array for each item. The first is
- * the ID, the second is an array of field-value pairs. */
+ if (s->length) {
+ /* Reconstruct the stream data using XADD commands. */
+ while(streamIteratorGetID(&si,&id,&numfields)) {
+ /* Emit a two elements array for each item. The first is
+ * the ID, the second is an array of field-value pairs. */
- /* Emit the XADD <key> <id> ...fields... command. */
- if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
+ /* Emit the XADD <key> <id> ...fields... command. */
+ if (rioWriteBulkCount(r,'*',3+numfields*2) == 0) return 0;
+ if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
+ if (rioWriteBulkObject(r,key) == 0) return 0;
+ if (rioWriteBulkStreamID(r,&id) == 0) return 0;
+ while(numfields--) {
+ unsigned char *field, *value;
+ int64_t field_len, value_len;
+ streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
+ if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
+ if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
+ }
+ }
+ } else {
+ /* Use the XADD MAXLEN 0 trick to generate an empty stream if
+ * the key we are serializing is an empty string, which is possible
+ * for the Stream type. */
+ if (rioWriteBulkCount(r,'*',7) == 0) return 0;
if (rioWriteBulkString(r,"XADD",4) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
- sds replyid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
- if (rioWriteBulkString(r,replyid,sdslen(replyid)) == 0) return 0;
- sdsfree(replyid);
- while(numfields--) {
- unsigned char *field, *value;
- int64_t field_len, value_len;
- streamIteratorGetField(&si,&field,&value,&field_len,&value_len);
- if (rioWriteBulkString(r,(char*)field,field_len) == 0) return 0;
- if (rioWriteBulkString(r,(char*)value,value_len) == 0) return 0;
+ if (rioWriteBulkString(r,"MAXLEN",6) == 0) return 0;
+ if (rioWriteBulkString(r,"0",1) == 0) return 0;
+ if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
+ if (rioWriteBulkString(r,"x",1) == 0) return 0;
+ if (rioWriteBulkString(r,"y",1) == 0) return 0;
+ }
+
+ /* Append XSETID after XADD, make sure lastid is correct,
+ * in case of XDEL lastid. */
+ if (rioWriteBulkCount(r,'*',3) == 0) return 0;
+ if (rioWriteBulkString(r,"XSETID",6) == 0) return 0;
+ if (rioWriteBulkObject(r,key) == 0) return 0;
+ if (rioWriteBulkStreamID(r,&s->last_id) == 0) return 0;
+
+
+ /* Create all the stream consumer groups. */
+ if (s->cgroups) {
+ raxIterator ri;
+ raxStart(&ri,s->cgroups);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamCG *group = ri.data;
+ /* Emit the XGROUP CREATE in order to create the group. */
+ if (rioWriteBulkCount(r,'*',5) == 0) return 0;
+ if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0;
+ if (rioWriteBulkString(r,"CREATE",6) == 0) return 0;
+ if (rioWriteBulkObject(r,key) == 0) return 0;
+ if (rioWriteBulkString(r,(char*)ri.key,ri.key_len) == 0) return 0;
+ if (rioWriteBulkStreamID(r,&group->last_id) == 0) return 0;
+
+ /* Generate XCLAIMs for each consumer that happens to
+ * have pending entries. Empty consumers have no semantical
+ * value so they are discarded. */
+ raxIterator ri_cons;
+ raxStart(&ri_cons,group->consumers);
+ raxSeek(&ri_cons,"^",NULL,0);
+ while(raxNext(&ri_cons)) {
+ streamConsumer *consumer = ri_cons.data;
+ /* For the current consumer, iterate all the PEL entries
+ * to emit the XCLAIM protocol. */
+ raxIterator ri_pel;
+ raxStart(&ri_pel,consumer->pel);
+ raxSeek(&ri_pel,"^",NULL,0);
+ while(raxNext(&ri_pel)) {
+ streamNACK *nack = ri_pel.data;
+ if (rioWriteStreamPendingEntry(r,key,(char*)ri.key,
+ ri.key_len,consumer,
+ ri_pel.key,nack) == 0)
+ {
+ return 0;
+ }
+ }
+ raxStop(&ri_pel);
+ }
+ raxStop(&ri_cons);
}
+ raxStop(&ri);
}
+
streamIteratorStop(&si);
return 1;
}
@@ -1112,7 +1273,7 @@ int rewriteModuleObject(rio *r, robj *key, robj *o) {
RedisModuleIO io;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
- moduleInitIOContext(io,mt,r);
+ moduleInitIOContext(io,mt,r,key);
mt->aof_rewrite(&io,key,mv->value);
if (io.ctx) {
moduleFreeContext(io.ctx);
@@ -1140,7 +1301,6 @@ int rewriteAppendOnlyFileRio(rio *aof) {
dictIterator *di = NULL;
dictEntry *de;
size_t processed = 0;
- long long now = mstime();
int j;
for (j = 0; j < server.dbnum; j++) {
@@ -1166,9 +1326,6 @@ int rewriteAppendOnlyFileRio(rio *aof) {
expiretime = getExpire(db,&key);
- /* If this key is already expired skip it */
- if (expiretime != -1 && expiretime < now) continue;
-
/* Save the key and associated value */
if (o->type == OBJ_STRING) {
/* Emit a SET command */
@@ -1241,7 +1398,7 @@ int rewriteAppendOnlyFile(char *filename) {
rioInitWithFile(&aof,fp);
if (server.aof_rewrite_incremental_fsync)
- rioSetAutoSync(&aof,AOF_AUTOSYNC_BYTES);
+ rioSetAutoSync(&aof,REDIS_AUTOSYNC_BYTES);
if (server.aof_use_rdb_preamble) {
int error;
@@ -1412,39 +1569,24 @@ void aofClosePipes(void) {
*/
int rewriteAppendOnlyFileBackground(void) {
pid_t childpid;
- long long start;
- if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
+ if (hasActiveChildProcess()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR;
openChildInfoPipe();
- start = ustime();
- if ((childpid = fork()) == 0) {
+ if ((childpid = redisFork()) == 0) {
char tmpfile[256];
/* Child */
- closeListeningSockets(0);
redisSetProcTitle("redis-aof-rewrite");
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
- size_t private_dirty = zmalloc_get_private_dirty(-1);
-
- if (private_dirty) {
- serverLog(LL_NOTICE,
- "AOF rewrite: %zu MB of memory used by copy-on-write",
- private_dirty/(1024*1024));
- }
-
- server.child_info_data.cow_size = private_dirty;
- sendChildInfo(CHILD_INFO_TYPE_AOF);
+ sendChildCOWInfo(CHILD_INFO_TYPE_AOF, "AOF rewrite");
exitFromChild(0);
} else {
exitFromChild(1);
}
} else {
/* Parent */
- server.stat_fork_time = ustime()-start;
- server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
- latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) {
closeChildInfoPipe();
serverLog(LL_WARNING,
@@ -1458,7 +1600,6 @@ int rewriteAppendOnlyFileBackground(void) {
server.aof_rewrite_scheduled = 0;
server.aof_rewrite_time_start = time(NULL);
server.aof_child_pid = childpid;
- updateDictResizePolicy();
/* We set appendseldb to -1 in order to force the next call to the
* feedAppendOnlyFile() to issue a SELECT command, so the differences
* accumulated by the parent into server.aof_rewrite_buf will start
@@ -1473,13 +1614,14 @@ int rewriteAppendOnlyFileBackground(void) {
void bgrewriteaofCommand(client *c) {
if (server.aof_child_pid != -1) {
addReplyError(c,"Background append only file rewriting already in progress");
- } else if (server.rdb_child_pid != -1) {
+ } else if (hasActiveChildProcess()) {
server.aof_rewrite_scheduled = 1;
addReplyStatus(c,"Background append only file rewriting scheduled");
} else if (rewriteAppendOnlyFileBackground() == C_OK) {
addReplyStatus(c,"Background append only file rewriting started");
} else {
- addReply(c,shared.err);
+ addReplyError(c,"Can't execute an AOF background rewriting. "
+ "Please check the server logs for more information.");
}
}
@@ -1488,6 +1630,9 @@ void aofRemoveTempFile(pid_t childpid) {
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) childpid);
unlink(tmpfile);
+
+ snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) childpid);
+ unlink(tmpfile);
}
/* Update the server.aof_current_size field explicitly using stat(2)
@@ -1609,12 +1754,13 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
oldfd = server.aof_fd;
server.aof_fd = newfd;
if (server.aof_fsync == AOF_FSYNC_ALWAYS)
- aof_fsync(newfd);
+ redis_fsync(newfd);
else if (server.aof_fsync == AOF_FSYNC_EVERYSEC)
aof_background_fsync(newfd);
server.aof_selected_db = -1; /* Make sure SELECT is re-issued */
aofUpdateCurrentSize();
server.aof_rewrite_base_size = server.aof_current_size;
+ server.aof_current_size = server.aof_current_size;
/* Clear regular AOF buffer since its contents was just written to
* the new AOF from the background rewrite buffer. */
@@ -1636,7 +1782,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
"Background AOF rewrite signal handler took %lldus", ustime()-now);
} else if (!bysignal && exitcode != 0) {
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
- * tirggering an error conditon. */
+ * tirggering an error condition. */
if (bysignal != SIGUSR1)
server.aof_lastbgrewrite_status = C_ERR;
serverLog(LL_WARNING,
diff --git a/src/atomicvar.h b/src/atomicvar.h
index 84a5bbc5c..160056cd7 100644
--- a/src/atomicvar.h
+++ b/src/atomicvar.h
@@ -1,7 +1,7 @@
/* This file implements atomic counters using __atomic or __sync macros if
* available, otherwise synchronizing different threads using a mutex.
*
- * The exported interaface is composed of three macros:
+ * The exported interface is composed of three macros:
*
* atomicIncr(var,count) -- Increment the atomic counter
* atomicGetIncr(var,oldvalue_var,count) -- Get and increment the atomic counter
@@ -16,7 +16,7 @@
* pthread_mutex_t myvar_mutex;
* atomicSet(myvar,12345);
*
- * If atomic primitives are availble (tested in config.h) the mutex
+ * If atomic primitives are available (tested in config.h) the mutex
* is not used.
*
* Never use return value from the macros, instead use the AtomicGetIncr()
diff --git a/src/bio.c b/src/bio.c
index da11f7b86..2af684570 100644
--- a/src/bio.c
+++ b/src/bio.c
@@ -17,7 +17,7 @@
*
* The design is trivial, we have a structure representing a job to perform
* and a different thread and job queue for every job type.
- * Every thread wait for new jobs in its queue, and process every job
+ * Every thread waits for new jobs in its queue, and process every job
* sequentially.
*
* Jobs of the same type are guaranteed to be processed from the least
@@ -187,7 +187,7 @@ void *bioProcessBackgroundJobs(void *arg) {
if (type == BIO_CLOSE_FILE) {
close((long)job->arg1);
} else if (type == BIO_AOF_FSYNC) {
- aof_fsync((long)job->arg1);
+ redis_fsync((long)job->arg1);
} else if (type == BIO_LAZY_FREE) {
/* What we free changes depending on what arguments are set:
* arg1 -> free the object at pointer.
@@ -204,14 +204,14 @@ void *bioProcessBackgroundJobs(void *arg) {
}
zfree(job);
- /* Unblock threads blocked on bioWaitStepOfType() if any. */
- pthread_cond_broadcast(&bio_step_cond[type]);
-
/* Lock again before reiterating the loop, if there are no longer
* jobs to process we'll block again in pthread_cond_wait(). */
pthread_mutex_lock(&bio_mutex[type]);
listDelNode(bio_jobs[type],ln);
bio_pending[type]--;
+
+ /* Unblock threads blocked on bioWaitStepOfType() if any. */
+ pthread_cond_broadcast(&bio_step_cond[type]);
}
}
diff --git a/src/bitops.c b/src/bitops.c
index 43450fca3..ee1ce0460 100644
--- a/src/bitops.c
+++ b/src/bitops.c
@@ -918,7 +918,7 @@ void bitfieldCommand(client *c) {
struct bitfieldOp *ops = NULL; /* Array of ops to execute at end. */
int owtype = BFOVERFLOW_WRAP; /* Overflow type. */
int readonly = 1;
- size_t higest_write_offset = 0;
+ size_t highest_write_offset = 0;
for (j = 2; j < c->argc; j++) {
int remargs = c->argc-j-1; /* Remaining args other than current. */
@@ -968,8 +968,8 @@ void bitfieldCommand(client *c) {
if (opcode != BITFIELDOP_GET) {
readonly = 0;
- if (higest_write_offset < bitoffset + bits - 1)
- higest_write_offset = bitoffset + bits - 1;
+ if (highest_write_offset < bitoffset + bits - 1)
+ highest_write_offset = bitoffset + bits - 1;
/* INCRBY and SET require another argument. */
if (getLongLongFromObjectOrReply(c,c->argv[j+3],&i64,NULL) != C_OK){
zfree(ops);
@@ -994,15 +994,21 @@ void bitfieldCommand(client *c) {
/* Lookup for read is ok if key doesn't exit, but errors
* if it's not a string. */
o = lookupKeyRead(c->db,c->argv[1]);
- if (o != NULL && checkType(c,o,OBJ_STRING)) return;
+ if (o != NULL && checkType(c,o,OBJ_STRING)) {
+ zfree(ops);
+ return;
+ }
} else {
/* Lookup by making room up to the farest bit reached by
* this operation. */
if ((o = lookupStringForBitCommand(c,
- higest_write_offset)) == NULL) return;
+ highest_write_offset)) == NULL) {
+ zfree(ops);
+ return;
+ }
}
- addReplyMultiBulkLen(c,numops);
+ addReplyArrayLen(c,numops);
/* Actually process the operations. */
for (j = 0; j < numops; j++) {
@@ -1047,7 +1053,7 @@ void bitfieldCommand(client *c) {
setSignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
} else {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
} else {
uint64_t oldval, newval, wrapped, retval;
@@ -1076,7 +1082,7 @@ void bitfieldCommand(client *c) {
setUnsignedBitfield(o->ptr,thisop->offset,
thisop->bits,newval);
} else {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
}
changes++;
diff --git a/src/blocked.c b/src/blocked.c
index f438c3353..867f03de6 100644
--- a/src/blocked.c
+++ b/src/blocked.c
@@ -77,10 +77,18 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
* is zero. */
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit) {
long long tval;
+ long double ftval;
- if (getLongLongFromObjectOrReply(c,object,&tval,
- "timeout is not an integer or out of range") != C_OK)
- return C_ERR;
+ if (unit == UNIT_SECONDS) {
+ if (getLongDoubleFromObjectOrReply(c,object,&ftval,
+ "timeout is not an float or out of range") != C_OK)
+ return C_ERR;
+ tval = (long long) (ftval * 1000.0);
+ } else {
+ if (getLongLongFromObjectOrReply(c,object,&tval,
+ "timeout is not an integer or out of range") != C_OK)
+ return C_ERR;
+ }
if (tval < 0) {
addReplyError(c,"timeout is negative");
@@ -88,7 +96,6 @@ int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int
}
if (tval > 0) {
- if (unit == UNIT_SECONDS) tval *= 1000;
tval += mstime();
}
*timeout = tval;
@@ -126,16 +133,43 @@ void processUnblockedClients(void) {
* the code is conceptually more correct this way. */
if (!(c->flags & CLIENT_BLOCKED)) {
if (c->querybuf && sdslen(c->querybuf) > 0) {
- processInputBuffer(c);
+ processInputBufferAndReplicate(c);
}
}
}
}
+/* This function will schedule the client for reprocessing at a safe time.
+ *
+ * This is useful when a client was blocked for some reason (blocking opeation,
+ * CLIENT PAUSE, or whatever), because it may end with some accumulated query
+ * buffer that needs to be processed ASAP:
+ *
+ * 1. When a client is blocked, its readable handler is still active.
+ * 2. However in this case it only gets data into the query buffer, but the
+ * query is not parsed or executed once there is enough to proceed as
+ * usually (because the client is blocked... so we can't execute commands).
+ * 3. When the client is unblocked, without this function, the client would
+ * have to write some query in order for the readable handler to finally
+ * call processQueryBuffer*() on it.
+ * 4. With this function instead we can put the client in a queue that will
+ * process it for queries ready to be executed at a safe time.
+ */
+void queueClientForReprocessing(client *c) {
+ /* The client may already be into the unblocked list because of a previous
+ * blocking operation, don't add back it into the list multiple times. */
+ if (!(c->flags & CLIENT_UNBLOCKED)) {
+ c->flags |= CLIENT_UNBLOCKED;
+ listAddNodeTail(server.unblocked_clients,c);
+ }
+}
+
/* Unblock a client calling the right function depending on the kind
* of operation the client is blocking for. */
void unblockClient(client *c) {
- if (c->btype == BLOCKED_LIST || c->btype == BLOCKED_STREAM) {
+ if (c->btype == BLOCKED_LIST ||
+ c->btype == BLOCKED_ZSET ||
+ c->btype == BLOCKED_STREAM) {
unblockClientWaitingData(c);
} else if (c->btype == BLOCKED_WAIT) {
unblockClientWaitingReplicas(c);
@@ -150,20 +184,17 @@ void unblockClient(client *c) {
server.blocked_clients_by_type[c->btype]--;
c->flags &= ~CLIENT_BLOCKED;
c->btype = BLOCKED_NONE;
- /* The client may already be into the unblocked list because of a previous
- * blocking operation, don't add back it into the list multiple times. */
- if (!(c->flags & CLIENT_UNBLOCKED)) {
- c->flags |= CLIENT_UNBLOCKED;
- listAddNodeTail(server.unblocked_clients,c);
- }
+ queueClientForReprocessing(c);
}
/* This function gets called when a blocked client timed out in order to
* send it a reply of some kind. After this function is called,
* unblockClient() will be called with the same client as argument. */
void replyToBlockedClientTimedOut(client *c) {
- if (c->btype == BLOCKED_LIST || c->btype == BLOCKED_STREAM) {
- addReply(c,shared.nullmultibulk);
+ if (c->btype == BLOCKED_LIST ||
+ c->btype == BLOCKED_ZSET ||
+ c->btype == BLOCKED_STREAM) {
+ addReplyNullArray(c);
} else if (c->btype == BLOCKED_WAIT) {
addReplyLongLong(c,replicationCountAcksByOffset(c->bpop.reploffset));
} else if (c->btype == BLOCKED_MODULE) {
@@ -191,23 +222,235 @@ void disconnectAllBlockedClients(void) {
if (c->flags & CLIENT_BLOCKED) {
addReplySds(c,sdsnew(
"-UNBLOCKED force unblock from blocking operation, "
- "instance state changed (master -> slave?)\r\n"));
+ "instance state changed (master -> replica?)\r\n"));
unblockClient(c);
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
}
}
}
+/* Helper function for handleClientsBlockedOnKeys(). This function is called
+ * when there may be clients blocked on a list key, and there may be new
+ * data to fetch (the key is ready). */
+void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
+ /* We serve clients in the same order they blocked for
+ * this key, from the first blocked to the last. */
+ dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
+ if (de) {
+ list *clients = dictGetVal(de);
+ int numclients = listLength(clients);
+
+ while(numclients--) {
+ listNode *clientnode = listFirst(clients);
+ client *receiver = clientnode->value;
+
+ if (receiver->btype != BLOCKED_LIST) {
+ /* Put at the tail, so that at the next call
+ * we'll not run into it again. */
+ listDelNode(clients,clientnode);
+ listAddNodeTail(clients,receiver);
+ continue;
+ }
+
+ robj *dstkey = receiver->bpop.target;
+ int where = (receiver->lastcmd &&
+ receiver->lastcmd->proc == blpopCommand) ?
+ LIST_HEAD : LIST_TAIL;
+ robj *value = listTypePop(o,where);
+
+ if (value) {
+ /* Protect receiver->bpop.target, that will be
+ * freed by the next unblockClient()
+ * call. */
+ if (dstkey) incrRefCount(dstkey);
+ unblockClient(receiver);
+
+ if (serveClientBlockedOnList(receiver,
+ rl->key,dstkey,rl->db,value,
+ where) == C_ERR)
+ {
+ /* If we failed serving the client we need
+ * to also undo the POP operation. */
+ listTypePush(o,value,where);
+ }
+
+ if (dstkey) decrRefCount(dstkey);
+ decrRefCount(value);
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (listTypeLength(o) == 0) {
+ dbDelete(rl->db,rl->key);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"del",rl->key,rl->db->id);
+ }
+ /* We don't call signalModifiedKey() as it was already called
+ * when an element was pushed on the list. */
+}
+
+/* Helper function for handleClientsBlockedOnKeys(). This function is called
+ * when there may be clients blocked on a sorted set key, and there may be new
+ * data to fetch (the key is ready). */
+void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
+ /* We serve clients in the same order they blocked for
+ * this key, from the first blocked to the last. */
+ dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
+ if (de) {
+ list *clients = dictGetVal(de);
+ int numclients = listLength(clients);
+ unsigned long zcard = zsetLength(o);
+
+ while(numclients-- && zcard) {
+ listNode *clientnode = listFirst(clients);
+ client *receiver = clientnode->value;
+
+ if (receiver->btype != BLOCKED_ZSET) {
+ /* Put at the tail, so that at the next call
+ * we'll not run into it again. */
+ listDelNode(clients,clientnode);
+ listAddNodeTail(clients,receiver);
+ continue;
+ }
+
+ int where = (receiver->lastcmd &&
+ receiver->lastcmd->proc == bzpopminCommand)
+ ? ZSET_MIN : ZSET_MAX;
+ unblockClient(receiver);
+ genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
+ zcard--;
+
+ /* Replicate the command. */
+ robj *argv[2];
+ struct redisCommand *cmd = where == ZSET_MIN ?
+ server.zpopminCommand :
+ server.zpopmaxCommand;
+ argv[0] = createStringObject(cmd->name,strlen(cmd->name));
+ argv[1] = rl->key;
+ incrRefCount(rl->key);
+ propagate(cmd,receiver->db->id,
+ argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
+ decrRefCount(argv[0]);
+ decrRefCount(argv[1]);
+ }
+ }
+}
+
+/* Helper function for handleClientsBlockedOnKeys(). This function is called
+ * when there may be clients blocked on a stream key, and there may be new
+ * data to fetch (the key is ready). */
+void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
+ dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
+ stream *s = o->ptr;
+
+ /* We need to provide the new data arrived on the stream
+ * to all the clients that are waiting for an offset smaller
+ * than the current top item. */
+ if (de) {
+ list *clients = dictGetVal(de);
+ listNode *ln;
+ listIter li;
+ listRewind(clients,&li);
+
+ while((ln = listNext(&li))) {
+ client *receiver = listNodeValue(ln);
+ if (receiver->btype != BLOCKED_STREAM) continue;
+ streamID *gt = dictFetchValue(receiver->bpop.keys,
+ rl->key);
+
+ /* If we blocked in the context of a consumer
+ * group, we need to resolve the group and update the
+ * last ID the client is blocked for: this is needed
+ * because serving other clients in the same consumer
+ * group will alter the "last ID" of the consumer
+ * group, and clients blocked in a consumer group are
+ * always blocked for the ">" ID: we need to deliver
+ * only new messages and avoid unblocking the client
+ * otherwise. */
+ streamCG *group = NULL;
+ if (receiver->bpop.xread_group) {
+ group = streamLookupCG(s,
+ receiver->bpop.xread_group->ptr);
+ /* If the group was not found, send an error
+ * to the consumer. */
+ if (!group) {
+ addReplyError(receiver,
+ "-NOGROUP the consumer group this client "
+ "was blocked on no longer exists");
+ unblockClient(receiver);
+ continue;
+ } else {
+ *gt = group->last_id;
+ }
+ }
+
+ if (streamCompareID(&s->last_id, gt) > 0) {
+ streamID start = *gt;
+ start.seq++; /* Can't overflow, it's an uint64_t */
+
+ /* Lookup the consumer for the group, if any. */
+ streamConsumer *consumer = NULL;
+ int noack = 0;
+
+ if (group) {
+ consumer = streamLookupConsumer(group,
+ receiver->bpop.xread_consumer->ptr,
+ 1);
+ noack = receiver->bpop.xread_group_noack;
+ }
+
+ /* Emit the two elements sub-array consisting of
+ * the name of the stream and the data we
+ * extracted from it. Wrapped in a single-item
+ * array, since we have just one key. */
+ if (receiver->resp == 2) {
+ addReplyArrayLen(receiver,1);
+ addReplyArrayLen(receiver,2);
+ } else {
+ addReplyMapLen(receiver,1);
+ }
+ addReplyBulk(receiver,rl->key);
+
+ streamPropInfo pi = {
+ rl->key,
+ receiver->bpop.xread_group
+ };
+ streamReplyWithRange(receiver,s,&start,NULL,
+ receiver->bpop.xread_count,
+ 0, group, consumer, noack, &pi);
+
+ /* Note that after we unblock the client, 'gt'
+ * and other receiver->bpop stuff are no longer
+ * valid, so we must do the setup above before
+ * this call. */
+ unblockClient(receiver);
+ }
+ }
+ }
+}
+
/* This function should be called by Redis every time a single command,
* a MULTI/EXEC block, or a Lua script, terminated its execution after
- * being called by a client.
+ * being called by a client. It handles serving clients blocked in
+ * lists, streams, and sorted sets, via a blocking commands.
*
* All the keys with at least one client blocked that received at least
- * one new element via some PUSH/XADD operation are accumulated into
+ * one new element via some write operation are accumulated into
* the server.ready_keys list. This function will run the list and will
* serve clients accordingly. Note that the function will iterate again and
* again as a result of serving BRPOPLPUSH we can have new blocking clients
- * to serve because of the PUSH side of BRPOPLPUSH. */
+ * to serve because of the PUSH side of BRPOPLPUSH.
+ *
+ * This function is normally "fair", that is, it will server clients
+ * using a FIFO behavior. However this fairness is violated in certain
+ * edge cases, that is, when we have clients blocked at the same time
+ * in a sorted set and in a list, for the same key (a very odd thing to
+ * do client side, indeed!). Because mismatching clients (blocking for
+ * a different type compared to the current key type) are moved in the
+ * other side of the linked list. However as long as the key starts to
+ * be used only for a single type, like virtually any Redis application will
+ * do, the function is already fair. */
void handleClientsBlockedOnKeys(void) {
while(listLength(server.ready_keys) != 0) {
list *l;
@@ -229,107 +472,14 @@ void handleClientsBlockedOnKeys(void) {
/* Serve clients blocked on list key. */
robj *o = lookupKeyWrite(rl->db,rl->key);
- if (o != NULL && o->type == OBJ_LIST) {
- dictEntry *de;
-
- /* We serve clients in the same order they blocked for
- * this key, from the first blocked to the last. */
- de = dictFind(rl->db->blocking_keys,rl->key);
- if (de) {
- list *clients = dictGetVal(de);
- int numclients = listLength(clients);
-
- while(numclients--) {
- listNode *clientnode = listFirst(clients);
- client *receiver = clientnode->value;
-
- if (receiver->btype != BLOCKED_LIST) {
- /* Put on the tail, so that at the next call
- * we'll not run into it again. */
- listDelNode(clients,clientnode);
- listAddNodeTail(clients,receiver);
- continue;
- }
-
- robj *dstkey = receiver->bpop.target;
- int where = (receiver->lastcmd &&
- receiver->lastcmd->proc == blpopCommand) ?
- LIST_HEAD : LIST_TAIL;
- robj *value = listTypePop(o,where);
-
- if (value) {
- /* Protect receiver->bpop.target, that will be
- * freed by the next unblockClient()
- * call. */
- if (dstkey) incrRefCount(dstkey);
- unblockClient(receiver);
-
- if (serveClientBlockedOnList(receiver,
- rl->key,dstkey,rl->db,value,
- where) == C_ERR)
- {
- /* If we failed serving the client we need
- * to also undo the POP operation. */
- listTypePush(o,value,where);
- }
-
- if (dstkey) decrRefCount(dstkey);
- decrRefCount(value);
- } else {
- break;
- }
- }
- }
- if (listTypeLength(o) == 0) {
- dbDelete(rl->db,rl->key);
- }
- /* We don't call signalModifiedKey() as it was already called
- * when an element was pushed on the list. */
- }
-
- /* Serve clients blocked on stream key. */
- else if (o != NULL && o->type == OBJ_STREAM) {
- dictEntry *de = dictFind(rl->db->blocking_keys,rl->key);
- stream *s = o->ptr;
-
- /* We need to provide the new data arrived on the stream
- * to all the clients that are waiting for an offset smaller
- * than the current top item. */
- if (de) {
- list *clients = dictGetVal(de);
- listNode *ln;
- listIter li;
- listRewind(clients,&li);
-
- while((ln = listNext(&li))) {
- client *receiver = listNodeValue(ln);
- if (receiver->btype != BLOCKED_STREAM) continue;
- streamID *gt = dictFetchValue(receiver->bpop.keys,
- rl->key);
- if (s->last_id.ms > gt->ms ||
- (s->last_id.ms == gt->ms &&
- s->last_id.seq > gt->seq))
- {
- streamID start = *gt;
- start.seq++; /* Can't overflow, it's an uint64_t */
- /* Note that after we unblock the client, 'gt'
- * is no longer valid, so we must do it after
- * we copied the ID into the 'start' variable. */
- unblockClient(receiver);
-
- /* Emit the two elements sub-array consisting of
- * the name of the stream and the data we
- * extracted from it. Wrapped in a single-item
- * array, since we have just one key. */
- addReplyMultiBulkLen(receiver,1);
- addReplyMultiBulkLen(receiver,2);
- addReplyBulk(receiver,rl->key);
- streamReplyWithRange(receiver,s,&start,NULL,
- receiver->bpop.xread_count,0);
- }
- }
- }
+ if (o != NULL) {
+ if (o->type == OBJ_LIST)
+ serveClientsBlockedOnListKey(o,rl);
+ else if (o->type == OBJ_ZSET)
+ serveClientsBlockedOnSortedSetKey(o,rl);
+ else if (o->type == OBJ_STREAM)
+ serveClientsBlockedOnStreamKey(o,rl);
}
/* Free this item. */
@@ -341,8 +491,9 @@ void handleClientsBlockedOnKeys(void) {
}
}
-/* This is how the current blocking lists/streams work, we use BLPOP as
- * example, but the concept is the same for other list ops and XREAD.
+/* This is how the current blocking lists/sorted sets/streams work, we use
+ * BLPOP as example, but the concept is the same for other list ops, sorted
+ * sets and XREAD.
* - If the user calls BLPOP and the key exists and contains a non empty list
* then LPOP is called instead. So BLPOP is semantically the same as LPOP
* if blocking is not required.
@@ -359,14 +510,14 @@ void handleClientsBlockedOnKeys(void) {
* to the number of elements we have in the ready list.
*/
-/* Set a client in blocking mode for the specified key (list or stream), with
- * the specified timeout. The 'type' argument is BLOCKED_LIST or BLOCKED_STREAM
- * depending on the kind of operation we are waiting for an empty key in
- * order to awake the client. The client is blocked for all the 'numkeys'
- * keys as in the 'keys' argument. When we block for stream keys, we also
- * provide an array of streamID structures: clients will be unblocked only
- * when items with an ID greater or equal to the specified one is appended
- * to the stream. */
+/* Set a client in blocking mode for the specified key (list, zset or stream),
+ * with the specified timeout. The 'type' argument is BLOCKED_LIST,
+ * BLOCKED_ZSET or BLOCKED_STREAM depending on the kind of operation we are
+ * waiting for an empty key in order to awake the client. The client is blocked
+ * for all the 'numkeys' keys as in the 'keys' argument. When we block for
+ * stream keys, we also provide an array of streamID structures: clients will
+ * be unblocked only when items with an ID greater or equal to the specified
+ * one is appended to the stream. */
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids) {
dictEntry *de;
list *l;
@@ -379,7 +530,7 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
for (j = 0; j < numkeys; j++) {
/* The value associated with the key name in the bpop.keys dictionary
- * is NULL for lists, or the stream ID for streams. */
+ * is NULL for lists and sorted sets, or the stream ID for streams. */
void *key_data = NULL;
if (btype == BLOCKED_STREAM) {
key_data = zmalloc(sizeof(streamID));
@@ -442,7 +593,9 @@ void unblockClientWaitingData(client *c) {
}
if (c->bpop.xread_group) {
decrRefCount(c->bpop.xread_group);
+ decrRefCount(c->bpop.xread_consumer);
c->bpop.xread_group = NULL;
+ c->bpop.xread_consumer = NULL;
}
}
@@ -452,7 +605,7 @@ void unblockClientWaitingData(client *c) {
* the same key again and again in the list in case of multiple pushes
* made by a script or in the context of MULTI/EXEC.
*
- * The list will be finally processed by handleClientsBlockedOnLists() */
+ * The list will be finally processed by handleClientsBlockedOnKeys() */
void signalKeyAsReady(redisDb *db, robj *key) {
readyList *rl;
diff --git a/src/childinfo.c b/src/childinfo.c
index 719025e8c..fa0600552 100644
--- a/src/childinfo.c
+++ b/src/childinfo.c
@@ -80,6 +80,8 @@ void receiveChildInfo(void) {
server.stat_rdb_cow_bytes = server.child_info_data.cow_size;
} else if (server.child_info_data.process_type == CHILD_INFO_TYPE_AOF) {
server.stat_aof_cow_bytes = server.child_info_data.cow_size;
+ } else if (server.child_info_data.process_type == CHILD_INFO_TYPE_MODULE) {
+ server.stat_module_cow_bytes = server.child_info_data.cow_size;
}
}
}
diff --git a/src/cluster.c b/src/cluster.c
index 85fe265f1..a7d8a02c3 100644
--- a/src/cluster.c
+++ b/src/cluster.c
@@ -49,14 +49,14 @@ clusterNode *myself = NULL;
clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node);
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
-void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+void clusterReadHandler(connection *conn);
void clusterSendPing(clusterLink *link, int type);
void clusterSendFail(char *nodename);
void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request);
void clusterUpdateState(void);
int clusterNodeGetSlotBit(clusterNode *n, int slot);
sds clusterGenNodesDescription(int filter);
-clusterNode *clusterLookupNode(char *name);
+clusterNode *clusterLookupNode(const char *name);
int clusterNodeAddSlave(clusterNode *master, clusterNode *slave);
int clusterAddSlot(clusterNode *n, int slot);
int clusterDelSlot(int slot);
@@ -75,6 +75,7 @@ void clusterDelNode(clusterNode *delnode);
sds representClusterNodeFlags(sds ci, uint16_t flags);
uint64_t clusterGetMaxEpoch(void);
int clusterBumpConfigEpochWithoutConsensus(void);
+void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8_t type, const unsigned char *payload, uint32_t len);
/* -----------------------------------------------------------------------------
* Initialization
@@ -137,6 +138,7 @@ int clusterLoadConfig(char *filename) {
/* Handle the special "vars" line. Don't pretend it is the last
* line even if it actually is when generated by Redis. */
if (strcasecmp(argv[0],"vars") == 0) {
+ if (!(argc % 2)) goto fmterr;
for (j = 1; j < argc; j += 2) {
if (strcasecmp(argv[j],"currentEpoch") == 0) {
server.cluster->currentEpoch =
@@ -475,7 +477,8 @@ void clusterInit(void) {
/* Port sanity check II
* The other handshake port check is triggered too late to stop
* us from trying to use a too-high cluster port number. */
- if (server.port > (65535-CLUSTER_PORT_INCR)) {
+ int port = server.tls_cluster ? server.tls_port : server.port;
+ if (port > (65535-CLUSTER_PORT_INCR)) {
serverLog(LL_WARNING, "Redis port number too high. "
"Cluster communication port is 10,000 port "
"numbers higher than your Redis port. "
@@ -483,8 +486,7 @@ void clusterInit(void) {
"lower than 55535.");
exit(1);
}
-
- if (listenToPort(server.port+CLUSTER_PORT_INCR,
+ if (listenToPort(port+CLUSTER_PORT_INCR,
server.cfd,&server.cfd_count) == C_ERR)
{
exit(1);
@@ -506,8 +508,8 @@ void clusterInit(void) {
/* Set myself->port / cport to my listening ports, we'll just need to
* discover the IP address via MEET messages. */
- myself->port = server.port;
- myself->cport = server.port+CLUSTER_PORT_INCR;
+ myself->port = port;
+ myself->cport = port+CLUSTER_PORT_INCR;
if (server.cluster_announce_port)
myself->port = server.cluster_announce_port;
if (server.cluster_announce_bus_port)
@@ -591,7 +593,7 @@ clusterLink *createClusterLink(clusterNode *node) {
link->sndbuf = sdsempty();
link->rcvbuf = sdsempty();
link->node = node;
- link->fd = -1;
+ link->conn = NULL;
return link;
}
@@ -599,24 +601,45 @@ clusterLink *createClusterLink(clusterNode *node) {
* This function will just make sure that the original node associated
* with this link will have the 'link' field set to NULL. */
void freeClusterLink(clusterLink *link) {
- if (link->fd != -1) {
- aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
- aeDeleteFileEvent(server.el, link->fd, AE_READABLE);
+ if (link->conn) {
+ connClose(link->conn);
+ link->conn = NULL;
}
sdsfree(link->sndbuf);
sdsfree(link->rcvbuf);
if (link->node)
link->node->link = NULL;
- close(link->fd);
zfree(link);
}
+static void clusterConnAcceptHandler(connection *conn) {
+ clusterLink *link;
+
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node connection: %s", connGetLastError(conn));
+ connClose(conn);
+ return;
+ }
+
+ /* Create a link object we use to handle the connection.
+ * It gets passed to the readable handler when data is available.
+ * Initiallly the link->node pointer is set to NULL as we don't know
+ * which node is, but the right node is references once we know the
+ * node identity. */
+ link = createClusterLink(NULL);
+ link->conn = conn;
+ connSetPrivateData(conn, link);
+
+ /* Register read handler */
+ connSetReadHandler(conn, clusterReadHandler);
+}
+
#define MAX_CLUSTER_ACCEPTS_PER_CALL 1000
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
int cport, cfd;
int max = MAX_CLUSTER_ACCEPTS_PER_CALL;
char cip[NET_IP_STR_LEN];
- clusterLink *link;
UNUSED(el);
UNUSED(mask);
UNUSED(privdata);
@@ -633,19 +656,24 @@ void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
"Error accepting cluster node: %s", server.neterr);
return;
}
- anetNonBlock(NULL,cfd);
- anetEnableTcpNoDelay(NULL,cfd);
+
+ connection *conn = server.tls_cluster ? connCreateAcceptedTLS(cfd,1) : connCreateAcceptedSocket(cfd);
+ connNonBlock(conn);
+ connEnableTcpNoDelay(conn);
/* Use non-blocking I/O for cluster messages. */
- serverLog(LL_VERBOSE,"Accepted cluster node %s:%d", cip, cport);
- /* Create a link object we use to handle the connection.
- * It gets passed to the readable handler when data is available.
- * Initiallly the link->node pointer is set to NULL as we don't know
- * which node is, but the right node is references once we know the
- * node identity. */
- link = createClusterLink(NULL);
- link->fd = cfd;
- aeCreateFileEvent(server.el,cfd,AE_READABLE,clusterReadHandler,link);
+ serverLog(LL_VERBOSE,"Accepting cluster node connection from %s:%d", cip, cport);
+
+ /* Accept the connection now. connAccept() may call our handler directly
+ * or schedule it for later depending on connection implementation.
+ */
+ if (connAccept(conn, clusterConnAcceptHandler) == C_ERR) {
+ serverLog(LL_VERBOSE,
+ "Error accepting cluster node connection: %s",
+ connGetLastError(conn));
+ connClose(conn);
+ return;
+ }
}
}
@@ -932,7 +960,7 @@ void clusterDelNode(clusterNode *delnode) {
}
/* Node lookup by name */
-clusterNode *clusterLookupNode(char *name) {
+clusterNode *clusterLookupNode(const char *name) {
sds s = sdsnewlen(name, CLUSTER_NAMELEN);
dictEntry *de;
@@ -1230,7 +1258,7 @@ void clearNodeFailureIfNeeded(clusterNode *node) {
serverLog(LL_NOTICE,
"Clear FAIL state for node %.40s: %s is reachable again.",
node->name,
- nodeIsSlave(node) ? "slave" : "master without slots");
+ nodeIsSlave(node) ? "replica" : "master without slots");
node->flags &= ~CLUSTER_NODE_FAIL;
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
}
@@ -1446,7 +1474,7 @@ void nodeIp2String(char *buf, clusterLink *link, char *announced_ip) {
memcpy(buf,announced_ip,NET_IP_STR_LEN);
buf[NET_IP_STR_LEN-1] = '\0'; /* We are not sure the input is sane. */
} else {
- anetPeerToString(link->fd, buf, NET_IP_STR_LEN, NULL);
+ connPeerToString(link->conn, buf, NET_IP_STR_LEN, NULL);
}
}
@@ -1589,6 +1617,12 @@ void clusterUpdateSlotsConfigWith(clusterNode *sender, uint64_t senderConfigEpoc
}
}
+ /* After updating the slots configuration, don't do any actual change
+ * in the state of the server if a module disabled Redis Cluster
+ * keys redirections. */
+ if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
+ return;
+
/* If at least one slot was reassigned from a node to another node
* with a greater configEpoch, it is possible that:
* 1) We are a master left without slots. This means that we were
@@ -1683,6 +1717,12 @@ int clusterProcessPacket(clusterLink *link) {
explen += sizeof(clusterMsgDataUpdate);
if (totlen != explen) return 1;
+ } else if (type == CLUSTERMSG_TYPE_MODULE) {
+ uint32_t explen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
+
+ explen += sizeof(clusterMsgDataPublish) -
+ 3 + ntohl(hdr->data.module.msg.len);
+ if (totlen != explen) return 1;
}
/* Check if the sender is a known node. */
@@ -1738,7 +1778,7 @@ int clusterProcessPacket(clusterLink *link) {
{
char ip[NET_IP_STR_LEN];
- if (anetSockName(link->fd,ip,sizeof(ip),NULL) != -1 &&
+ if (connSockName(link->conn,ip,sizeof(ip),NULL) != -1 &&
strcmp(ip,myself->ip))
{
memcpy(myself->ip,ip,NET_IP_STR_LEN);
@@ -2053,7 +2093,7 @@ int clusterProcessPacket(clusterLink *link) {
server.cluster->mf_end = mstime() + CLUSTER_MF_TIMEOUT;
server.cluster->mf_slave = sender;
pauseClients(mstime()+(CLUSTER_MF_TIMEOUT*2));
- serverLog(LL_WARNING,"Manual failover requested by slave %.40s.",
+ serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
sender->name);
} else if (type == CLUSTERMSG_TYPE_UPDATE) {
clusterNode *n; /* The node the update is about. */
@@ -2077,6 +2117,15 @@ int clusterProcessPacket(clusterLink *link) {
* config accordingly. */
clusterUpdateSlotsConfigWith(n,reportedConfigEpoch,
hdr->data.update.nodecfg.slots);
+ } else if (type == CLUSTERMSG_TYPE_MODULE) {
+ if (!sender) return 1; /* Protect the module from unknown nodes. */
+ /* We need to route this message back to the right module subscribed
+ * for the right message type. */
+ uint64_t module_id = hdr->data.module.msg.module_id; /* Endian-safe ID */
+ uint32_t len = ntohl(hdr->data.module.msg.len);
+ uint8_t type = hdr->data.module.msg.type;
+ unsigned char *payload = hdr->data.module.msg.bulk_data;
+ moduleCallClusterReceivers(sender->name,module_id,type,payload,len);
} else {
serverLog(LL_WARNING,"Received unknown packet type: %d", type);
}
@@ -2096,35 +2145,76 @@ void handleLinkIOError(clusterLink *link) {
/* Send data. This is handled using a trivial send buffer that gets
* consumed by write(). We don't try to optimize this for speed too much
* as this is a very low traffic channel. */
-void clusterWriteHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
- clusterLink *link = (clusterLink*) privdata;
+void clusterWriteHandler(connection *conn) {
+ clusterLink *link = connGetPrivateData(conn);
ssize_t nwritten;
- UNUSED(el);
- UNUSED(mask);
- nwritten = write(fd, link->sndbuf, sdslen(link->sndbuf));
+ nwritten = connWrite(conn, link->sndbuf, sdslen(link->sndbuf));
if (nwritten <= 0) {
serverLog(LL_DEBUG,"I/O error writing to node link: %s",
- strerror(errno));
+ (nwritten == -1) ? connGetLastError(conn) : "short write");
handleLinkIOError(link);
return;
}
sdsrange(link->sndbuf,nwritten,-1);
if (sdslen(link->sndbuf) == 0)
- aeDeleteFileEvent(server.el, link->fd, AE_WRITABLE);
+ connSetWriteHandler(link->conn, NULL);
+}
+
+/* A connect handler that gets called when a connection to another node
+ * gets established.
+ */
+void clusterLinkConnectHandler(connection *conn) {
+ clusterLink *link = connGetPrivateData(conn);
+ clusterNode *node = link->node;
+
+ /* Check if connection succeeded */
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s",
+ node->name, node->ip, node->cport,
+ connGetLastError(conn));
+ freeClusterLink(link);
+ return;
+ }
+
+ /* Register a read handler from now on */
+ connSetReadHandler(conn, clusterReadHandler);
+
+ /* Queue a PING in the new connection ASAP: this is crucial
+ * to avoid false positives in failure detection.
+ *
+ * If the node is flagged as MEET, we send a MEET message instead
+ * of a PING one, to force the receiver to add us in its node
+ * table. */
+ mstime_t old_ping_sent = node->ping_sent;
+ clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
+ CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
+ if (old_ping_sent) {
+ /* If there was an active ping before the link was
+ * disconnected, we want to restore the ping time, otherwise
+ * replaced by the clusterSendPing() call. */
+ node->ping_sent = old_ping_sent;
+ }
+ /* We can clear the flag after the first packet is sent.
+ * If we'll never receive a PONG, we'll never send new packets
+ * to this node. Instead after the PONG is received and we
+ * are no longer in meet/handshake status, we want to send
+ * normal PING packets. */
+ node->flags &= ~CLUSTER_NODE_MEET;
+
+ serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
+ node->name, node->ip, node->cport);
}
/* Read data. Try to read the first field of the header first to check the
* full length of the packet. When a whole packet is in memory this function
* will call the function to process the packet. And so forth. */
-void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
- char buf[sizeof(clusterMsg)];
+void clusterReadHandler(connection *conn) {
+ clusterMsg buf[1];
ssize_t nread;
clusterMsg *hdr;
- clusterLink *link = (clusterLink*) privdata;
+ clusterLink *link = connGetPrivateData(conn);
unsigned int readlen, rcvbuflen;
- UNUSED(el);
- UNUSED(mask);
while(1) { /* Read as long as there is data to read. */
rcvbuflen = sdslen(link->rcvbuf);
@@ -2152,13 +2242,13 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
if (readlen > sizeof(buf)) readlen = sizeof(buf);
}
- nread = read(fd,buf,readlen);
- if (nread == -1 && errno == EAGAIN) return; /* No more data ready. */
+ nread = connRead(conn,buf,readlen);
+ if (nread == -1 && (connGetState(conn) == CONN_STATE_CONNECTED)) return; /* No more data ready. */
if (nread <= 0) {
/* I/O error... */
serverLog(LL_DEBUG,"I/O error reading from node link: %s",
- (nread == 0) ? "connection closed" : strerror(errno));
+ (nread == 0) ? "connection closed" : connGetLastError(conn));
handleLinkIOError(link);
return;
} else {
@@ -2187,8 +2277,7 @@ void clusterReadHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* from event handlers that will do stuff with the same link later. */
void clusterSendMessage(clusterLink *link, unsigned char *msg, size_t msglen) {
if (sdslen(link->sndbuf) == 0 && msglen != 0)
- aeCreateFileEvent(server.el,link->fd,AE_WRITABLE|AE_BARRIER,
- clusterWriteHandler,link);
+ connSetWriteHandlerWithBarrier(link->conn, clusterWriteHandler, 1);
link->sndbuf = sdscatlen(link->sndbuf, msg, msglen);
@@ -2254,11 +2343,12 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) {
}
/* Handle cluster-announce-port as well. */
+ int port = server.tls_cluster ? server.tls_port : server.port;
int announced_port = server.cluster_announce_port ?
- server.cluster_announce_port : server.port;
+ server.cluster_announce_port : port;
int announced_cport = server.cluster_announce_bus_port ?
server.cluster_announce_bus_port :
- (server.port + CLUSTER_PORT_INCR);
+ (port + CLUSTER_PORT_INCR);
memcpy(hdr->myslots,master->slots,sizeof(hdr->myslots));
memset(hdr->slaveof,0,CLUSTER_NAMELEN);
@@ -2362,7 +2452,7 @@ void clusterSendPing(clusterLink *link, int type) {
* same time.
*
* Since we have non-voting slaves that lower the probability of an entry
- * to feature our node, we set the number of entires per packet as
+ * to feature our node, we set the number of entries per packet as
* 10% of the total nodes we have. */
wanted = floor(dictSize(server.cluster->nodes)/10);
if (wanted < 3) wanted = 3;
@@ -2495,7 +2585,8 @@ void clusterBroadcastPong(int target) {
*
* If link is NULL, then the message is broadcasted to the whole cluster. */
void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
- unsigned char buf[sizeof(clusterMsg)], *payload;
+ unsigned char *payload;
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen;
uint32_t channel_len, message_len;
@@ -2515,7 +2606,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
/* Try to use the local buffer if possible */
if (totlen < sizeof(buf)) {
- payload = buf;
+ payload = (unsigned char*)buf;
} else {
payload = zmalloc(totlen);
memcpy(payload,hdr,sizeof(*hdr));
@@ -2532,7 +2623,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
decrRefCount(channel);
decrRefCount(message);
- if (payload != buf) zfree(payload);
+ if (payload != (unsigned char*)buf) zfree(payload);
}
/* Send a FAIL message to all the nodes we are able to contact.
@@ -2541,7 +2632,7 @@ void clusterSendPublish(clusterLink *link, robj *channel, robj *message) {
* we switch the node state to CLUSTER_NODE_FAIL and ask all the other
* nodes to do the same ASAP. */
void clusterSendFail(char *nodename) {
- unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAIL);
@@ -2553,7 +2644,7 @@ void clusterSendFail(char *nodename) {
* slots configuration. The node name, slots bitmap, and configEpoch info
* are included. */
void clusterSendUpdate(clusterLink *link, clusterNode *node) {
- unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
if (link == NULL) return;
@@ -2561,7 +2652,63 @@ void clusterSendUpdate(clusterLink *link, clusterNode *node) {
memcpy(hdr->data.update.nodecfg.nodename,node->name,CLUSTER_NAMELEN);
hdr->data.update.nodecfg.configEpoch = htonu64(node->configEpoch);
memcpy(hdr->data.update.nodecfg.slots,node->slots,sizeof(node->slots));
- clusterSendMessage(link,buf,ntohl(hdr->totlen));
+ clusterSendMessage(link,(unsigned char*)buf,ntohl(hdr->totlen));
+}
+
+/* Send a MODULE message.
+ *
+ * If link is NULL, then the message is broadcasted to the whole cluster. */
+void clusterSendModule(clusterLink *link, uint64_t module_id, uint8_t type,
+ unsigned char *payload, uint32_t len) {
+ unsigned char *heapbuf;
+ clusterMsg buf[1];
+ clusterMsg *hdr = (clusterMsg*) buf;
+ uint32_t totlen;
+
+ clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MODULE);
+ totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
+ totlen += sizeof(clusterMsgModule) - 3 + len;
+
+ hdr->data.module.msg.module_id = module_id; /* Already endian adjusted. */
+ hdr->data.module.msg.type = type;
+ hdr->data.module.msg.len = htonl(len);
+ hdr->totlen = htonl(totlen);
+
+ /* Try to use the local buffer if possible */
+ if (totlen < sizeof(buf)) {
+ heapbuf = (unsigned char*)buf;
+ } else {
+ heapbuf = zmalloc(totlen);
+ memcpy(heapbuf,hdr,sizeof(*hdr));
+ hdr = (clusterMsg*) heapbuf;
+ }
+ memcpy(hdr->data.module.msg.bulk_data,payload,len);
+
+ if (link)
+ clusterSendMessage(link,heapbuf,totlen);
+ else
+ clusterBroadcastMessage(heapbuf,totlen);
+
+ if (heapbuf != (unsigned char*)buf) zfree(heapbuf);
+}
+
+/* This function gets a cluster node ID string as target, the same way the nodes
+ * addresses are represented in the modules side, resolves the node, and sends
+ * the message. If the target is NULL the message is broadcasted.
+ *
+ * The function returns C_OK if the target is valid, otherwise C_ERR is
+ * returned. */
+int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, unsigned char *payload, uint32_t len) {
+ clusterNode *node = NULL;
+
+ if (target != NULL) {
+ node = clusterLookupNode(target);
+ if (node == NULL || node->link == NULL) return C_ERR;
+ }
+
+ clusterSendModule(target ? node->link : NULL,
+ module_id, type, payload, len);
+ return C_OK;
}
/* -----------------------------------------------------------------------------
@@ -2586,7 +2733,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
* Note that we send the failover request to everybody, master and slave nodes,
* but only the masters are supposed to reply to our query. */
void clusterRequestFailoverAuth(void) {
- unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen;
@@ -2602,7 +2749,7 @@ void clusterRequestFailoverAuth(void) {
/* Send a FAILOVER_AUTH_ACK message to the specified node. */
void clusterSendFailoverAuth(clusterNode *node) {
- unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen;
@@ -2610,12 +2757,12 @@ void clusterSendFailoverAuth(clusterNode *node) {
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK);
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
hdr->totlen = htonl(totlen);
- clusterSendMessage(node->link,buf,totlen);
+ clusterSendMessage(node->link,(unsigned char*)buf,totlen);
}
/* Send a MFSTART message to the specified node. */
void clusterSendMFStart(clusterNode *node) {
- unsigned char buf[sizeof(clusterMsg)];
+ clusterMsg buf[1];
clusterMsg *hdr = (clusterMsg*) buf;
uint32_t totlen;
@@ -2623,7 +2770,7 @@ void clusterSendMFStart(clusterNode *node) {
clusterBuildMessageHdr(hdr,CLUSTERMSG_TYPE_MFSTART);
totlen = sizeof(clusterMsg)-sizeof(union clusterMsgData);
hdr->totlen = htonl(totlen);
- clusterSendMessage(node->link,buf,totlen);
+ clusterSendMessage(node->link,(unsigned char*)buf,totlen);
}
/* Vote for the node asking for our vote if there are the conditions. */
@@ -2803,7 +2950,7 @@ void clusterLogCantFailover(int reason) {
switch(reason) {
case CLUSTER_CANT_FAILOVER_DATA_AGE:
msg = "Disconnected from master for longer than allowed. "
- "Please check the 'cluster-slave-validity-factor' configuration "
+ "Please check the 'cluster-replica-validity-factor' configuration "
"option.";
break;
case CLUSTER_CANT_FAILOVER_WAITING_DELAY:
@@ -2955,6 +3102,7 @@ void clusterHandleSlaveFailover(void) {
if (server.cluster->mf_end) {
server.cluster->failover_auth_time = mstime();
server.cluster->failover_auth_rank = 0;
+ clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
}
serverLog(LL_WARNING,
"Start of election delayed for %lld milliseconds "
@@ -2984,7 +3132,7 @@ void clusterHandleSlaveFailover(void) {
server.cluster->failover_auth_time += added_delay;
server.cluster->failover_auth_rank = newrank;
serverLog(LL_WARNING,
- "Slave rank updated to #%d, added %lld milliseconds of delay.",
+ "Replica rank updated to #%d, added %lld milliseconds of delay.",
newrank, added_delay);
}
}
@@ -3030,7 +3178,7 @@ void clusterHandleSlaveFailover(void) {
(unsigned long long) myself->configEpoch);
}
- /* Take responsability for the cluster slots. */
+ /* Take responsibility for the cluster slots. */
clusterFailoverReplaceYourMaster();
} else {
clusterLogCantFailover(CLUSTER_CANT_FAILOVER_WAITING_VOTES);
@@ -3081,11 +3229,11 @@ void clusterHandleSlaveMigration(int max_slaves) {
!nodeTimedOut(mymaster->slaves[j])) okslaves++;
if (okslaves <= server.cluster_migration_barrier) return;
- /* Step 3: Idenitfy a candidate for migration, and check if among the
+ /* Step 3: Identify a candidate for migration, and check if among the
* masters with the greatest number of ok slaves, I'm the one with the
* smallest node ID (the "candidate slave").
*
- * Note: this means that eventually a replica migration will occurr
+ * Note: this means that eventually a replica migration will occur
* since slaves that are reachable again always have their FAIL flag
* cleared, so eventually there must be a candidate. At the same time
* this does not mean that there are no race conditions possible (two
@@ -3140,7 +3288,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
* the natural slaves of this instance to advertise their switch from
* the old master to the new one. */
if (target && candidate == myself &&
- (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY)
+ (mstime()-target->orphaned_time) > CLUSTER_SLAVE_MIGRATION_DELAY &&
+ !(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
{
serverLog(LL_WARNING,"Migrating to orphaned master %.40s",
target->name);
@@ -3251,14 +3400,18 @@ void clusterCron(void) {
int changed = 0;
if (prev_ip == NULL && curr_ip != NULL) changed = 1;
- if (prev_ip != NULL && curr_ip == NULL) changed = 1;
- if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
+ else if (prev_ip != NULL && curr_ip == NULL) changed = 1;
+ else if (prev_ip && curr_ip && strcmp(prev_ip,curr_ip)) changed = 1;
if (changed) {
+ if (prev_ip) zfree(prev_ip);
prev_ip = curr_ip;
- if (prev_ip) prev_ip = zstrdup(prev_ip);
if (curr_ip) {
+ /* We always take a copy of the previous IP address, by
+ * duplicating the string. This way later we can check if
+ * the address really changed. */
+ prev_ip = zstrdup(prev_ip);
strncpy(myself->ip,server.cluster_announce_ip,NET_IP_STR_LEN);
myself->ip[NET_IP_STR_LEN-1] = '\0';
} else {
@@ -3300,13 +3453,11 @@ void clusterCron(void) {
}
if (node->link == NULL) {
- int fd;
- mstime_t old_ping_sent;
- clusterLink *link;
-
- fd = anetTcpNonBlockBindConnect(server.neterr, node->ip,
- node->cport, NET_FIRST_BIND_ADDR);
- if (fd == -1) {
+ clusterLink *link = createClusterLink(node);
+ link->conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
+ connSetPrivateData(link->conn, link);
+ if (connConnect(link->conn, node->ip, node->cport, NET_FIRST_BIND_ADDR,
+ clusterLinkConnectHandler) == -1) {
/* We got a synchronous error from connect before
* clusterSendPing() had a chance to be called.
* If node->ping_sent is zero, failure detection can't work,
@@ -3316,37 +3467,11 @@ void clusterCron(void) {
serverLog(LL_DEBUG, "Unable to connect to "
"Cluster Node [%s]:%d -> %s", node->ip,
node->cport, server.neterr);
+
+ freeClusterLink(link);
continue;
}
- link = createClusterLink(node);
- link->fd = fd;
node->link = link;
- aeCreateFileEvent(server.el,link->fd,AE_READABLE,
- clusterReadHandler,link);
- /* Queue a PING in the new connection ASAP: this is crucial
- * to avoid false positives in failure detection.
- *
- * If the node is flagged as MEET, we send a MEET message instead
- * of a PING one, to force the receiver to add us in its node
- * table. */
- old_ping_sent = node->ping_sent;
- clusterSendPing(link, node->flags & CLUSTER_NODE_MEET ?
- CLUSTERMSG_TYPE_MEET : CLUSTERMSG_TYPE_PING);
- if (old_ping_sent) {
- /* If there was an active ping before the link was
- * disconnected, we want to restore the ping time, otherwise
- * replaced by the clusterSendPing() call. */
- node->ping_sent = old_ping_sent;
- }
- /* We can clear the flag after the first packet is sent.
- * If we'll never receive a PONG, we'll never send new packets
- * to this node. Instead after the PONG is received and we
- * are no longer in meet/handshake status, we want to send
- * normal PING packets. */
- node->flags &= ~CLUSTER_NODE_MEET;
-
- serverLog(LL_DEBUG,"Connecting with Node %.40s at %s:%d",
- node->name, node->ip, node->cport);
}
}
dictReleaseIterator(di);
@@ -3489,7 +3614,8 @@ void clusterCron(void) {
if (nodeIsSlave(myself)) {
clusterHandleManualFailover();
- clusterHandleSlaveFailover();
+ if (!(server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
+ clusterHandleSlaveFailover();
/* If there are orphaned slaves, and we are a slave among the masters
* with the max number of non-failing slaves, consider migrating to
* the orphaned masters. Note that it does not make sense to try
@@ -3666,7 +3792,7 @@ void clusterCloseAllSlots(void) {
* -------------------------------------------------------------------------- */
/* The following are defines that are only used in the evaluation function
- * and are based on heuristics. Actaully the main point about the rejoin and
+ * and are based on heuristics. Actually the main point about the rejoin and
* writable delay is that they should be a few orders of magnitude larger
* than the network latency. */
#define CLUSTER_MAX_REJOIN_DELAY 5000
@@ -3795,6 +3921,11 @@ int verifyClusterConfigWithData(void) {
int j;
int update_config = 0;
+ /* Return ASAP if a module disabled cluster redirections. In that case
+ * every master can store keys about every possible hash slot. */
+ if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
+ return C_OK;
+
/* If this node is a slave, don't perform the check at all as we
* completely depend on the replication stream. */
if (nodeIsSlave(myself)) return C_OK;
@@ -4009,6 +4140,7 @@ const char *clusterGetMessageTypeString(int type) {
case CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK: return "auth-ack";
case CLUSTERMSG_TYPE_UPDATE: return "update";
case CLUSTERMSG_TYPE_MFSTART: return "mfstart";
+ case CLUSTERMSG_TYPE_MODULE: return "module";
}
return "unknown";
}
@@ -4038,7 +4170,7 @@ void clusterReplyMultiBulkSlots(client *c) {
*/
int num_masters = 0;
- void *slot_replylen = addDeferredMultiBulkLength(c);
+ void *slot_replylen = addReplyDeferredLen(c);
dictEntry *de;
dictIterator *di = dictGetSafeIterator(server.cluster->nodes);
@@ -4058,7 +4190,7 @@ void clusterReplyMultiBulkSlots(client *c) {
}
if (start != -1 && (!bit || j == CLUSTER_SLOTS-1)) {
int nested_elements = 3; /* slots (2) + master addr (1). */
- void *nested_replylen = addDeferredMultiBulkLength(c);
+ void *nested_replylen = addReplyDeferredLen(c);
if (bit && j == CLUSTER_SLOTS-1) j++;
@@ -4074,7 +4206,7 @@ void clusterReplyMultiBulkSlots(client *c) {
start = -1;
/* First node reply position is always the master */
- addReplyMultiBulkLen(c, 3);
+ addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->ip);
addReplyLongLong(c, node->port);
addReplyBulkCBuffer(c, node->name, CLUSTER_NAMELEN);
@@ -4084,19 +4216,19 @@ void clusterReplyMultiBulkSlots(client *c) {
/* This loop is copy/pasted from clusterGenNodeDescription()
* with modifications for per-slot node aggregation */
if (nodeFailed(node->slaves[i])) continue;
- addReplyMultiBulkLen(c, 3);
+ addReplyArrayLen(c, 3);
addReplyBulkCString(c, node->slaves[i]->ip);
addReplyLongLong(c, node->slaves[i]->port);
addReplyBulkCBuffer(c, node->slaves[i]->name, CLUSTER_NAMELEN);
nested_elements++;
}
- setDeferredMultiBulkLength(c, nested_replylen, nested_elements);
+ setDeferredArrayLen(c, nested_replylen, nested_elements);
num_masters++;
}
}
}
dictReleaseIterator(di);
- setDeferredMultiBulkLength(c, slot_replylen, num_masters);
+ setDeferredArrayLen(c, slot_replylen, num_masters);
}
void clusterCommand(client *c) {
@@ -4107,27 +4239,27 @@ void clusterCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"addslots <slot> [slot ...] -- Assign slots to current node.",
-"bumpepoch -- Advance the cluster config epoch.",
-"count-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
-"countkeysinslot <slot> - Return the number of keys in <slot>.",
-"delslots <slot> [slot ...] -- Delete slots information from current node.",
-"failover [force|takeover] -- Promote current slave node to being a master.",
-"forget <node-id> -- Remove a node from the cluster.",
-"getkeysinslot <slot> <count> -- Return key names stored by current node in a slot.",
-"flushslots -- Delete current node own slots information.",
-"info - Return onformation about the cluster.",
-"keyslot <key> -- Return the hash slot for <key>.",
-"meet <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
-"myid -- Return the node id.",
-"nodes -- Return cluster configuration seen by node. Output format:",
+"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.",
+"BUMPEPOCH -- Advance the cluster config epoch.",
+"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.",
+"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.",
+"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.",
+"FAILOVER [force|takeover] -- Promote current replica node to being a master.",
+"FORGET <node-id> -- Remove a node from the cluster.",
+"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.",
+"FLUSHSLOTS -- Delete current node own slots information.",
+"INFO - Return onformation about the cluster.",
+"KEYSLOT <key> -- Return the hash slot for <key>.",
+"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.",
+"MYID -- Return the node id.",
+"NODES -- Return cluster configuration seen by node. Output format:",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>",
-"replicate <node-id> -- Configure current node as slave to <node-id>.",
-"reset [hard|soft] -- Reset current node (default: soft).",
-"set-config-epoch <epoch> - Set config epoch of current node.",
-"setslot <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
-"slaves <node-id> -- Return <node-id> slaves.",
-"slots -- Return information about slots range mappings. Each range is made of:",
+"REPLICATE <node-id> -- Configure current node as replica to <node-id>.",
+"RESET [hard|soft] -- Reset current node (default: soft).",
+"SET-config-epoch <epoch> - Set config epoch of current node.",
+"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.",
+"REPLICAS <node-id> -- Return <node-id> replicas.",
+"SLOTS -- Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids",
NULL
};
@@ -4162,12 +4294,9 @@ NULL
}
} else if (!strcasecmp(c->argv[1]->ptr,"nodes") && c->argc == 2) {
/* CLUSTER NODES */
- robj *o;
- sds ci = clusterGenNodesDescription(0);
-
- o = createObject(OBJ_STRING,ci);
- addReplyBulk(c,o);
- decrRefCount(o);
+ sds nodes = clusterGenNodesDescription(0);
+ addReplyVerbatim(c,nodes,sdslen(nodes),"txt");
+ sdsfree(nodes);
} else if (!strcasecmp(c->argv[1]->ptr,"myid") && c->argc == 2) {
/* CLUSTER MYID */
addReplyBulkCBuffer(c,myself->name, CLUSTER_NAMELEN);
@@ -4409,10 +4538,8 @@ NULL
"cluster_stats_messages_received:%lld\r\n", tot_msg_received);
/* Produce the reply protocol. */
- addReplySds(c,sdscatprintf(sdsempty(),"$%lu\r\n",
- (unsigned long)sdslen(info)));
- addReplySds(c,info);
- addReply(c,shared.crlf);
+ addReplyVerbatim(c,info,sdslen(info),"txt");
+ sdsfree(info);
} else if (!strcasecmp(c->argv[1]->ptr,"saveconfig") && c->argc == 2) {
int retval = clusterSaveConfig(1);
@@ -4460,7 +4587,7 @@ NULL
keys = zmalloc(sizeof(robj*)*maxkeys);
numkeys = getKeysInSlot(slot, keys, maxkeys);
- addReplyMultiBulkLen(c,numkeys);
+ addReplyArrayLen(c,numkeys);
for (j = 0; j < numkeys; j++) {
addReplyBulk(c,keys[j]);
decrRefCount(keys[j]);
@@ -4503,7 +4630,7 @@ NULL
/* Can't replicate a slave. */
if (nodeIsSlave(n)) {
- addReplyError(c,"I can only replicate a master, not a slave.");
+ addReplyError(c,"I can only replicate a master, not a replica.");
return;
}
@@ -4522,7 +4649,8 @@ NULL
clusterSetMaster(n);
clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG);
addReply(c,shared.ok);
- } else if (!strcasecmp(c->argv[1]->ptr,"slaves") && c->argc == 3) {
+ } else if ((!strcasecmp(c->argv[1]->ptr,"slaves") ||
+ !strcasecmp(c->argv[1]->ptr,"replicas")) && c->argc == 3) {
/* CLUSTER SLAVES <NODE ID> */
clusterNode *n = clusterLookupNode(c->argv[2]->ptr);
int j;
@@ -4538,7 +4666,7 @@ NULL
return;
}
- addReplyMultiBulkLen(c,n->numslaves);
+ addReplyArrayLen(c,n->numslaves);
for (j = 0; j < n->numslaves; j++) {
sds ni = clusterGenNodeDescription(n->slaves[j]);
addReplyBulkCString(c,ni);
@@ -4576,10 +4704,10 @@ NULL
/* Check preconditions. */
if (nodeIsMaster(myself)) {
- addReplyError(c,"You should send CLUSTER FAILOVER to a slave");
+ addReplyError(c,"You should send CLUSTER FAILOVER to a replica");
return;
} else if (myself->slaveof == NULL) {
- addReplyError(c,"I'm a slave but my master is unknown to me");
+ addReplyError(c,"I'm a replica but my master is unknown to me");
return;
} else if (!force &&
(nodeFailed(myself->slaveof) ||
@@ -4675,8 +4803,7 @@ NULL
clusterReset(hard);
addReply(c,shared.ok);
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLUSTER HELP",
- (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
return;
}
}
@@ -4687,7 +4814,7 @@ NULL
/* Generates a DUMP-format representation of the object 'o', adding it to the
* io stream pointed by 'rio'. This function can't fail. */
-void createDumpPayload(rio *payload, robj *o) {
+void createDumpPayload(rio *payload, robj *o, robj *key) {
unsigned char buf[2];
uint64_t crc;
@@ -4695,7 +4822,7 @@ void createDumpPayload(rio *payload, robj *o) {
* byte followed by the serialized object. This is understood by RESTORE. */
rioInitWithBuffer(payload,sdsempty());
serverAssert(rdbSaveObjectType(payload,o));
- serverAssert(rdbSaveObject(payload,o));
+ serverAssert(rdbSaveObject(payload,o,key));
/* Write the footer, this is how it looks like:
* ----------------+---------------------+---------------+
@@ -4743,36 +4870,58 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
* DUMP is actually not used by Redis Cluster but it is the obvious
* complement of RESTORE and can be useful for different applications. */
void dumpCommand(client *c) {
- robj *o, *dumpobj;
+ robj *o;
rio payload;
/* Check if the key is here. */
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
return;
}
/* Create the DUMP encoded representation. */
- createDumpPayload(&payload,o);
+ createDumpPayload(&payload,o,c->argv[1]);
/* Transfer to the client */
- dumpobj = createObject(OBJ_STRING,payload.io.buffer.ptr);
- addReplyBulk(c,dumpobj);
- decrRefCount(dumpobj);
+ addReplyBulkSds(c,payload.io.buffer.ptr);
return;
}
/* RESTORE key ttl serialized-value [REPLACE] */
void restoreCommand(client *c) {
- long long ttl;
+ long long ttl, lfu_freq = -1, lru_idle = -1, lru_clock = -1;
rio payload;
- int j, type, replace = 0;
+ int j, type, replace = 0, absttl = 0;
robj *obj;
/* Parse additional options */
for (j = 4; j < c->argc; j++) {
+ int additional = c->argc-j-1;
if (!strcasecmp(c->argv[j]->ptr,"replace")) {
replace = 1;
+ } else if (!strcasecmp(c->argv[j]->ptr,"absttl")) {
+ absttl = 1;
+ } else if (!strcasecmp(c->argv[j]->ptr,"idletime") && additional >= 1 &&
+ lfu_freq == -1)
+ {
+ if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lru_idle,NULL)
+ != C_OK) return;
+ if (lru_idle < 0) {
+ addReplyError(c,"Invalid IDLETIME value, must be >= 0");
+ return;
+ }
+ lru_clock = LRU_CLOCK();
+ j++; /* Consume additional arg. */
+ } else if (!strcasecmp(c->argv[j]->ptr,"freq") && additional >= 1 &&
+ lru_idle == -1)
+ {
+ if (getLongLongFromObjectOrReply(c,c->argv[j+1],&lfu_freq,NULL)
+ != C_OK) return;
+ if (lfu_freq < 0 || lfu_freq > 255) {
+ addReplyError(c,"Invalid FREQ value, must be >= 0 and <= 255");
+ return;
+ }
+ j++; /* Consume additional arg. */
} else {
addReply(c,shared.syntaxerr);
return;
@@ -4802,7 +4951,7 @@ void restoreCommand(client *c) {
rioInitWithBuffer(&payload,c->argv[3]->ptr);
if (((type = rdbLoadObjectType(&payload)) == -1) ||
- ((obj = rdbLoadObject(type,&payload)) == NULL))
+ ((obj = rdbLoadObject(type,&payload,c->argv[1])) == NULL))
{
addReplyError(c,"Bad data format");
return;
@@ -4813,7 +4962,11 @@ void restoreCommand(client *c) {
/* Create the key and set the TTL if any */
dbAdd(c->db,c->argv[1],obj);
- if (ttl) setExpire(c,c->db,c->argv[1],mstime()+ttl);
+ if (ttl) {
+ if (!absttl) ttl+=mstime();
+ setExpire(c,c->db,c->argv[1],ttl);
+ }
+ objectSetLRUOrLFU(obj,lfu_freq,lru_idle,lru_clock);
signalModifiedKey(c->db,c->argv[1]);
addReply(c,shared.ok);
server.dirty++;
@@ -4829,7 +4982,7 @@ void restoreCommand(client *c) {
#define MIGRATE_SOCKET_CACHE_TTL 10 /* close cached sockets after 10 sec. */
typedef struct migrateCachedSocket {
- int fd;
+ connection *conn;
long last_dbid;
time_t last_use_time;
} migrateCachedSocket;
@@ -4846,7 +4999,7 @@ typedef struct migrateCachedSocket {
* should be called so that the connection will be created from scratch
* the next time. */
migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long timeout) {
- int fd;
+ connection *conn;
sds name = sdsempty();
migrateCachedSocket *cs;
@@ -4866,34 +5019,27 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
/* Too many items, drop one at random. */
dictEntry *de = dictGetRandomKey(server.migrate_cached_sockets);
cs = dictGetVal(de);
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
}
/* Create the socket */
- fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
- atoi(c->argv[2]->ptr));
- if (fd == -1) {
- sdsfree(name);
- addReplyErrorFormat(c,"Can't connect to target node: %s",
- server.neterr);
- return NULL;
- }
- anetEnableTcpNoDelay(server.neterr,fd);
-
- /* Check if it connects within the specified timeout. */
- if ((aeWait(fd,AE_WRITABLE,timeout) & AE_WRITABLE) == 0) {
- sdsfree(name);
+ conn = server.tls_cluster ? connCreateTLS() : connCreateSocket();
+ if (connBlockingConnect(conn, c->argv[1]->ptr, atoi(c->argv[2]->ptr), timeout)
+ != C_OK) {
addReplySds(c,
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
- close(fd);
+ connClose(conn);
+ sdsfree(name);
return NULL;
}
+ connEnableTcpNoDelay(conn);
/* Add to the cache and return it to the caller. */
cs = zmalloc(sizeof(*cs));
- cs->fd = fd;
+ cs->conn = conn;
+
cs->last_dbid = -1;
cs->last_use_time = server.unixtime;
dictAdd(server.migrate_cached_sockets,name,cs);
@@ -4914,7 +5060,7 @@ void migrateCloseSocket(robj *host, robj *port) {
return;
}
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,name);
sdsfree(name);
@@ -4928,7 +5074,7 @@ void migrateCloseTimedoutSockets(void) {
migrateCachedSocket *cs = dictGetVal(de);
if ((server.unixtime - cs->last_use_time) > MIGRATE_SOCKET_CACHE_TTL) {
- close(cs->fd);
+ connClose(cs->conn);
zfree(cs);
dictDelete(server.migrate_cached_sockets,dictGetKey(de));
}
@@ -5048,6 +5194,11 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,dbid));
}
+ int non_expired = 0; /* Number of keys that we'll find non expired.
+ Note that serializing large keys may take some time
+ so certain keys that were found non expired by the
+ lookupKey() function, may be expired later. */
+
/* Create RESTORE payload and generate the protocol to call the command. */
for (j = 0; j < num_keys; j++) {
long long ttl = 0;
@@ -5055,8 +5206,17 @@ try_again:
if (expireat != -1) {
ttl = expireat-mstime();
+ if (ttl < 0) {
+ continue;
+ }
if (ttl < 1) ttl = 1;
}
+
+ /* Relocate valid (non expired) keys into the array in successive
+ * positions to remove holes created by the keys that were present
+ * in the first lookup but are now expired after the second lookup. */
+ kv[non_expired++] = kv[j];
+
serverAssertWithInfo(c,NULL,
rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));
@@ -5072,7 +5232,7 @@ try_again:
/* Emit the payload argument, that is the serialized object using
* the DUMP format. */
- createDumpPayload(&payload,ov[j]);
+ createDumpPayload(&payload,ov[j],kv[j]);
serverAssertWithInfo(c,NULL,
rioWriteBulkString(&cmd,payload.io.buffer.ptr,
sdslen(payload.io.buffer.ptr)));
@@ -5084,6 +5244,9 @@ try_again:
serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,"REPLACE",7));
}
+ /* Fix the actual number of keys we are migrating. */
+ num_keys = non_expired;
+
/* Transfer the query to the other node in 64K chunks. */
errno = 0;
{
@@ -5093,7 +5256,7 @@ try_again:
while ((towrite = sdslen(buf)-pos) > 0) {
towrite = (towrite > (64*1024) ? (64*1024) : towrite);
- nwritten = syncWrite(cs->fd,buf+pos,towrite,timeout);
+ nwritten = connSyncWrite(cs->conn,buf+pos,towrite,timeout);
if (nwritten != (signed)towrite) {
write_error = 1;
goto socket_err;
@@ -5107,11 +5270,11 @@ try_again:
char buf2[1024]; /* Restore reply. */
/* Read the AUTH reply if needed. */
- if (password && syncReadLine(cs->fd, buf0, sizeof(buf0), timeout) <= 0)
+ if (password && connSyncReadLine(cs->conn, buf0, sizeof(buf0), timeout) <= 0)
goto socket_err;
/* Read the SELECT reply if needed. */
- if (select && syncReadLine(cs->fd, buf1, sizeof(buf1), timeout) <= 0)
+ if (select && connSyncReadLine(cs->conn, buf1, sizeof(buf1), timeout) <= 0)
goto socket_err;
/* Read the RESTORE replies. */
@@ -5119,10 +5282,14 @@ try_again:
int socket_error = 0;
int del_idx = 1; /* Index of the key argument for the replicated DEL op. */
+ /* Allocate the new argument vector that will replace the current command,
+ * to propagate the MIGRATE as a DEL command (if no COPY option was given).
+ * We allocate num_keys+1 because the additional argument is for "DEL"
+ * command name itself. */
if (!copy) newargv = zmalloc(sizeof(robj*)*(num_keys+1));
for (j = 0; j < num_keys; j++) {
- if (syncReadLine(cs->fd, buf2, sizeof(buf2), timeout) <= 0) {
+ if (connSyncReadLine(cs->conn, buf2, sizeof(buf2), timeout) <= 0) {
socket_error = 1;
break;
}
@@ -5319,9 +5486,17 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
multiCmd mc;
int i, slot = 0, migrating_slot = 0, importing_slot = 0, missing_keys = 0;
+ /* Allow any key to be set if a module disabled cluster redirections. */
+ if (server.cluster_module_flags & CLUSTER_MODULE_FLAG_NO_REDIRECTION)
+ return myself;
+
/* Set error code optimistically for the base case. */
if (error_code) *error_code = CLUSTER_REDIR_NONE;
+ /* Modules can turn off Redis Cluster redirection: this is useful
+ * when writing a module that implements a completely different
+ * distributed system. */
+
/* We handle all the cases as if they were EXEC commands, so we have
* a common code path for everything */
if (cmd->proc == execCommand) {
@@ -5486,7 +5661,7 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n"));
} else if (error_code == CLUSTER_REDIR_UNSTABLE) {
- /* The request spawns mutliple keys in the same slot,
+ /* The request spawns multiple keys in the same slot,
* but the slot is not "stable" currently as there is
* a migration or import in progress. */
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n"));
@@ -5518,7 +5693,11 @@ void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_co
* longer handles, the client is sent a redirection error, and the function
* returns 1. Otherwise 0 is returned and no operation is performed. */
int clusterRedirectBlockedClientIfNeeded(client *c) {
- if (c->flags & CLIENT_BLOCKED && c->btype == BLOCKED_LIST) {
+ if (c->flags & CLIENT_BLOCKED &&
+ (c->btype == BLOCKED_LIST ||
+ c->btype == BLOCKED_ZSET ||
+ c->btype == BLOCKED_STREAM))
+ {
dictEntry *de;
dictIterator *di;
diff --git a/src/cluster.h b/src/cluster.h
index f2b9a4ecf..ffbb29f0d 100644
--- a/src/cluster.h
+++ b/src/cluster.h
@@ -40,7 +40,7 @@ struct clusterNode;
/* clusterLink encapsulates everything needed to talk with a remote node. */
typedef struct clusterLink {
mstime_t ctime; /* Link creation time */
- int fd; /* TCP socket file descriptor */
+ connection *conn; /* Connection to remote node */
sds sndbuf; /* Packet send buffer */
sds rcvbuf; /* Packet reception buffer */
struct clusterNode *node; /* Node related to this link if any, or NULL */
@@ -97,7 +97,15 @@ typedef struct clusterLink {
#define CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK 6 /* Yes, you have my vote */
#define CLUSTERMSG_TYPE_UPDATE 7 /* Another node slots configuration */
#define CLUSTERMSG_TYPE_MFSTART 8 /* Pause clients for manual failover */
-#define CLUSTERMSG_TYPE_COUNT 9 /* Total number of message types. */
+#define CLUSTERMSG_TYPE_MODULE 9 /* Module cluster API message. */
+#define CLUSTERMSG_TYPE_COUNT 10 /* Total number of message types. */
+
+/* Flags that a module can set in order to prevent certain Redis Cluster
+ * features to be enabled. Useful when implementing a different distributed
+ * system on top of Redis Cluster message bus, using modules. */
+#define CLUSTER_MODULE_FLAG_NONE 0
+#define CLUSTER_MODULE_FLAG_NO_FAILOVER (1<<1)
+#define CLUSTER_MODULE_FLAG_NO_REDIRECTION (1<<2)
/* This structure represent elements of node->fail_reports. */
typedef struct clusterNodeFailReport {
@@ -195,10 +203,7 @@ typedef struct {
typedef struct {
uint32_t channel_len;
uint32_t message_len;
- /* We can't reclare bulk_data as bulk_data[] since this structure is
- * nested. The 8 bytes are removed from the count during the message
- * length computation. */
- unsigned char bulk_data[8];
+ unsigned char bulk_data[8]; /* 8 bytes just as placeholder. */
} clusterMsgDataPublish;
typedef struct {
@@ -207,6 +212,13 @@ typedef struct {
unsigned char slots[CLUSTER_SLOTS/8]; /* Slots bitmap. */
} clusterMsgDataUpdate;
+typedef struct {
+ uint64_t module_id; /* ID of the sender module. */
+ uint32_t len; /* ID of the sender module. */
+ uint8_t type; /* Type from 0 to 255. */
+ unsigned char bulk_data[3]; /* 3 bytes just as placeholder. */
+} clusterMsgModule;
+
union clusterMsgData {
/* PING, MEET and PONG */
struct {
@@ -228,12 +240,17 @@ union clusterMsgData {
struct {
clusterMsgDataUpdate nodecfg;
} update;
+
+ /* MODULE */
+ struct {
+ clusterMsgModule msg;
+ } module;
};
#define CLUSTER_PROTO_VER 1 /* Cluster bus protocol version. */
typedef struct {
- char sig[4]; /* Siganture "RCmb" (Redis Cluster message bus). */
+ char sig[4]; /* Signature "RCmb" (Redis Cluster message bus). */
uint32_t totlen; /* Total length of this message */
uint16_t ver; /* Protocol version, currently set to 1. */
uint16_t port; /* TCP base port number. */
diff --git a/src/config.c b/src/config.c
index 595537821..505dabc9c 100644
--- a/src/config.c
+++ b/src/config.c
@@ -91,6 +91,13 @@ configEnum aof_fsync_enum[] = {
{NULL, 0}
};
+configEnum repl_diskless_load_enum[] = {
+ {"disabled", REPL_DISKLESS_LOAD_DISABLED},
+ {"on-empty-db", REPL_DISKLESS_LOAD_WHEN_DB_EMPTY},
+ {"swapdb", REPL_DISKLESS_LOAD_SWAPDB},
+ {NULL, 0}
+};
+
/* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{0, 0, 0}, /* normal */
@@ -98,6 +105,49 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{1024*1024*32, 1024*1024*8, 60} /* pubsub */
};
+/* Configuration values that require no special handling to set, get, load or
+ * rewrite. */
+typedef struct configYesNo {
+ const char *name; /* The user visible name of this config */
+ const char *alias; /* An alias that can also be used for this config */
+ int *config; /* The pointer to the server config this value is stored in */
+ const int modifiable; /* Can this value be updated by CONFIG SET? */
+ const int default_value; /* The default value of the config on rewrite */
+} configYesNo;
+
+configYesNo configs_yesno[] = {
+ /* Non-Modifiable */
+ {"rdbchecksum",NULL,&server.rdb_checksum,0,CONFIG_DEFAULT_RDB_CHECKSUM},
+ {"daemonize",NULL,&server.daemonize,0,0},
+ {"io-threads-do-reads",NULL,&server.io_threads_do_reads, 0, CONFIG_DEFAULT_IO_THREADS_DO_READS},
+ {"always-show-logo",NULL,&server.always_show_logo,0,CONFIG_DEFAULT_ALWAYS_SHOW_LOGO},
+ /* Modifiable */
+ {"protected-mode",NULL,&server.protected_mode,1,CONFIG_DEFAULT_PROTECTED_MODE},
+ {"rdbcompression",NULL,&server.rdb_compression,1,CONFIG_DEFAULT_RDB_COMPRESSION},
+ {"activerehashing",NULL,&server.activerehashing,1,CONFIG_DEFAULT_ACTIVE_REHASHING},
+ {"stop-writes-on-bgsave-error",NULL,&server.stop_writes_on_bgsave_err,1,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR},
+ {"dynamic-hz",NULL,&server.dynamic_hz,1,CONFIG_DEFAULT_DYNAMIC_HZ},
+ {"lazyfree-lazy-eviction",NULL,&server.lazyfree_lazy_eviction,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION},
+ {"lazyfree-lazy-expire",NULL,&server.lazyfree_lazy_expire,1,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE},
+ {"lazyfree-lazy-server-del",NULL,&server.lazyfree_lazy_server_del,1,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL},
+ {"repl-disable-tcp-nodelay",NULL,&server.repl_disable_tcp_nodelay,1,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY},
+ {"repl-diskless-sync",NULL,&server.repl_diskless_sync,1,CONFIG_DEFAULT_REPL_DISKLESS_SYNC},
+ {"gopher-enabled",NULL,&server.gopher_enabled,1,CONFIG_DEFAULT_GOPHER_ENABLED},
+ {"aof-rewrite-incremental-fsync",NULL,&server.aof_rewrite_incremental_fsync,1,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC},
+ {"no-appendfsync-on-rewrite",NULL,&server.aof_no_fsync_on_rewrite,1,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE},
+ {"cluster-require-full-coverage",NULL,&server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE},
+ {"rdb-save-incremental-fsync",NULL,&server.rdb_save_incremental_fsync,1,CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC},
+ {"aof-load-truncated",NULL,&server.aof_load_truncated,1,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED},
+ {"aof-use-rdb-preamble",NULL,&server.aof_use_rdb_preamble,1,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE},
+ {"cluster-replica-no-failover","cluster-slave-no-failover",&server.cluster_slave_no_failover,1,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER},
+ {"replica-lazy-flush","slave-lazy-flush",&server.repl_slave_lazy_flush,1,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH},
+ {"replica-serve-stale-data","slave-serve-stale-data",&server.repl_serve_stale_data,1,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA},
+ {"replica-read-only","slave-read-only",&server.repl_slave_ro,1,CONFIG_DEFAULT_SLAVE_READ_ONLY},
+ {"replica-ignore-maxmemory","slave-ignore-maxmemory",&server.repl_slave_ignore_maxmemory,1,CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY},
+ {"jemalloc-bg-thread",NULL,&server.jemalloc_bg_thread,1,1},
+ {NULL, NULL, 0, 0}
+};
+
/*-----------------------------------------------------------------------------
* Enum access functions
*----------------------------------------------------------------------------*/
@@ -120,7 +170,7 @@ const char *configEnumGetName(configEnum *ce, int val) {
return NULL;
}
-/* Wrapper for configEnumGetName() returning "unknown" insetad of NULL if
+/* Wrapper for configEnumGetName() returning "unknown" instead of NULL if
* there is no match. */
const char *configEnumGetNameOrUnknown(configEnum *ce, int val) {
const char *name = configEnumGetName(ce,val);
@@ -170,7 +220,7 @@ void queueLoadModule(sds path, sds *argv, int argc) {
}
void loadServerConfigFromString(char *config) {
- char *err = NULL;
+ const char *err = NULL;
int linenum = 0, totlines, i;
int slaveof_linenum = 0;
sds *lines;
@@ -201,6 +251,26 @@ void loadServerConfigFromString(char *config) {
}
sdstolower(argv[0]);
+ /* Iterate the configs that are standard */
+ int match = 0;
+ for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
+ if ((!strcasecmp(argv[0],config->name) ||
+ (config->alias && !strcasecmp(argv[0],config->alias))) &&
+ (argc == 2))
+ {
+ if ((*(config->config) = yesnotoi(argv[1])) == -1) {
+ err = "argument must be 'yes' or 'no'"; goto loaderr;
+ }
+ match = 1;
+ break;
+ }
+ }
+
+ if (match) {
+ sdsfreesplitres(argv,argc);
+ continue;
+ }
+
/* Execute config directives */
if (!strcasecmp(argv[0],"timeout") && argc == 2) {
server.maxidletime = atoi(argv[1]);
@@ -212,10 +282,6 @@ void loadServerConfigFromString(char *config) {
if (server.tcpkeepalive < 0) {
err = "Invalid tcp-keepalive value"; goto loaderr;
}
- } else if (!strcasecmp(argv[0],"protected-mode") && argc == 2) {
- if ((server.protected_mode = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
} else if (!strcasecmp(argv[0],"port") && argc == 2) {
server.port = atoi(argv[1]);
if (server.port < 0 || server.port > 65535) {
@@ -283,10 +349,9 @@ void loadServerConfigFromString(char *config) {
}
fclose(logfp);
}
- } else if (!strcasecmp(argv[0],"always-show-logo") && argc == 2) {
- if ((server.always_show_logo = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
+ } else if (!strcasecmp(argv[0],"aclfile") && argc == 2) {
+ zfree(server.acl_filename);
+ server.acl_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"syslog-enabled") && argc == 2) {
if ((server.syslog_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
@@ -306,6 +371,11 @@ void loadServerConfigFromString(char *config) {
if (server.dbnum < 1) {
err = "Invalid number of databases"; goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"io-threads") && argc == 2) {
+ server.io_threads_num = atoi(argv[1]);
+ if (server.io_threads_num < 1 || server.io_threads_num > 512) {
+ err = "Invalid number of I/O threads"; goto loaderr;
+ }
} else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1],NULL);
} else if (!strcasecmp(argv[0],"maxclients") && argc == 2) {
@@ -344,15 +414,19 @@ void loadServerConfigFromString(char *config) {
err = "lfu-decay-time must be 0 or greater";
goto loaderr;
}
- } else if (!strcasecmp(argv[0],"slaveof") && argc == 3) {
+ } else if ((!strcasecmp(argv[0],"slaveof") ||
+ !strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum;
server.masterhost = sdsnew(argv[1]);
server.masterport = atoi(argv[2]);
server.repl_state = REPL_STATE_CONNECT;
- } else if (!strcasecmp(argv[0],"repl-ping-slave-period") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"repl-ping-slave-period") ||
+ !strcasecmp(argv[0],"repl-ping-replica-period")) &&
+ argc == 2)
+ {
server.repl_ping_slave_period = atoi(argv[1]);
if (server.repl_ping_slave_period <= 0) {
- err = "repl-ping-slave-period must be 1 or greater";
+ err = "repl-ping-replica-period must be 1 or greater";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"repl-timeout") && argc == 2) {
@@ -361,13 +435,11 @@ void loadServerConfigFromString(char *config) {
err = "repl-timeout must be 1 or greater";
goto loaderr;
}
- } else if (!strcasecmp(argv[0],"repl-disable-tcp-nodelay") && argc==2) {
- if ((server.repl_disable_tcp_nodelay = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"repl-diskless-sync") && argc==2) {
- if ((server.repl_diskless_sync = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
+ } else if (!strcasecmp(argv[0],"repl-diskless-load") && argc==2) {
+ server.repl_diskless_load = configEnumGetValue(repl_diskless_load_enum,argv[1]);
+ if (server.repl_diskless_load == INT_MIN) {
+ err = "argument must be 'disabled', 'on-empty-db', 'swapdb' or 'flushdb'";
+ goto loaderr;
}
} else if (!strcasecmp(argv[0],"repl-diskless-sync-delay") && argc==2) {
server.repl_diskless_sync_delay = atoi(argv[1]);
@@ -388,64 +460,30 @@ void loadServerConfigFromString(char *config) {
err = "repl-backlog-ttl can't be negative ";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"masteruser") && argc == 2) {
+ zfree(server.masteruser);
+ server.masteruser = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else if (!strcasecmp(argv[0],"masterauth") && argc == 2) {
zfree(server.masterauth);
- server.masterauth = zstrdup(argv[1]);
- } else if (!strcasecmp(argv[0],"slave-serve-stale-data") && argc == 2) {
- if ((server.repl_serve_stale_data = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"slave-read-only") && argc == 2) {
- if ((server.repl_slave_ro = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"rdbcompression") && argc == 2) {
- if ((server.rdb_compression = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"rdbchecksum") && argc == 2) {
- if ((server.rdb_checksum = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"activerehashing") && argc == 2) {
- if ((server.activerehashing = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"lazyfree-lazy-eviction") && argc == 2) {
- if ((server.lazyfree_lazy_eviction = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"lazyfree-lazy-expire") && argc == 2) {
- if ((server.lazyfree_lazy_expire = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"lazyfree-lazy-server-del") && argc == 2){
- if ((server.lazyfree_lazy_server_del = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"slave-lazy-flush") && argc == 2) {
- if ((server.repl_slave_lazy_flush = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
+ server.masterauth = argv[1][0] ? zstrdup(argv[1]) : NULL;
} else if (!strcasecmp(argv[0],"activedefrag") && argc == 2) {
if ((server.active_defrag_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
- } else if (!strcasecmp(argv[0],"daemonize") && argc == 2) {
- if ((server.daemonize = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
+ if (server.active_defrag_enabled) {
+#ifndef HAVE_DEFRAG
+ err = "active defrag can't be enabled without proper jemalloc support"; goto loaderr;
+#endif
}
} else if (!strcasecmp(argv[0],"hz") && argc == 2) {
- server.hz = atoi(argv[1]);
- if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
- if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
+ server.config_hz = atoi(argv[1]);
+ if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
+ if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
} else if (!strcasecmp(argv[0],"appendonly") && argc == 2) {
- int yes;
-
- if ((yes = yesnotoi(argv[1])) == -1) {
+ if ((server.aof_enabled = yesnotoi(argv[1])) == -1) {
err = "argument must be 'yes' or 'no'"; goto loaderr;
}
- server.aof_state = yes ? AOF_ON : AOF_OFF;
+ server.aof_state = server.aof_enabled ? AOF_ON : AOF_OFF;
} else if (!strcasecmp(argv[0],"appendfilename") && argc == 2) {
if (!pathIsBaseName(argv[1])) {
err = "appendfilename can't be a path, just a filename";
@@ -453,11 +491,6 @@ void loadServerConfigFromString(char *config) {
}
zfree(server.aof_filename);
server.aof_filename = zstrdup(argv[1]);
- } else if (!strcasecmp(argv[0],"no-appendfsync-on-rewrite")
- && argc == 2) {
- if ((server.aof_no_fsync_on_rewrite= yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
} else if (!strcasecmp(argv[0],"appendfsync") && argc == 2) {
server.aof_fsync = configEnumGetValue(aof_fsync_enum,argv[1]);
if (server.aof_fsync == INT_MIN) {
@@ -476,27 +509,29 @@ void loadServerConfigFromString(char *config) {
argc == 2)
{
server.aof_rewrite_min_size = memtoll(argv[1],NULL);
- } else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
- argc == 2)
- {
- if ((server.aof_rewrite_incremental_fsync =
- yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
- if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
+ } else if (!strcasecmp(argv[0],"rdb-key-save-delay") && argc==2) {
+ server.rdb_key_save_delay = atoi(argv[1]);
+ if (server.rdb_key_save_delay < 0) {
+ err = "rdb-key-save-delay can't be negative";
+ goto loaderr;
}
- } else if (!strcasecmp(argv[0],"aof-use-rdb-preamble") && argc == 2) {
- if ((server.aof_use_rdb_preamble = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
+ } else if (!strcasecmp(argv[0],"key-load-delay") && argc==2) {
+ server.key_load_delay = atoi(argv[1]);
+ if (server.key_load_delay < 0) {
+ err = "key-load-delay can't be negative";
+ goto loaderr;
}
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
if (strlen(argv[1]) > CONFIG_AUTHPASS_MAX_LEN) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
goto loaderr;
}
- server.requirepass = zstrdup(argv[1]);
+ /* The old "requirepass" directive just translates to setting
+ * a password to the default user. */
+ ACLSetUser(DefaultUser,"resetpass",-1);
+ sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
+ ACLSetUser(DefaultUser,aclop,sdslen(aclop));
+ sdsfree(aclop);
} else if (!strcasecmp(argv[0],"pidfile") && argc == 2) {
zfree(server.pidfile);
server.pidfile = zstrdup(argv[1]);
@@ -509,14 +544,16 @@ void loadServerConfigFromString(char *config) {
server.rdb_filename = zstrdup(argv[1]);
} else if (!strcasecmp(argv[0],"active-defrag-threshold-lower") && argc == 2) {
server.active_defrag_threshold_lower = atoi(argv[1]);
- if (server.active_defrag_threshold_lower < 0) {
- err = "active-defrag-threshold-lower must be 0 or greater";
+ if (server.active_defrag_threshold_lower < 0 ||
+ server.active_defrag_threshold_lower > 1000) {
+ err = "active-defrag-threshold-lower must be between 0 and 1000";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"active-defrag-threshold-upper") && argc == 2) {
server.active_defrag_threshold_upper = atoi(argv[1]);
- if (server.active_defrag_threshold_upper < 0) {
- err = "active-defrag-threshold-upper must be 0 or greater";
+ if (server.active_defrag_threshold_upper < 0 ||
+ server.active_defrag_threshold_upper > 1000) {
+ err = "active-defrag-threshold-upper must be between 0 and 1000";
goto loaderr;
}
} else if (!strcasecmp(argv[0],"active-defrag-ignore-bytes") && argc == 2) {
@@ -537,10 +574,20 @@ void loadServerConfigFromString(char *config) {
err = "active-defrag-cycle-max must be between 1 and 99";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"active-defrag-max-scan-fields") && argc == 2) {
+ server.active_defrag_max_scan_fields = strtoll(argv[1],NULL,10);
+ if (server.active_defrag_max_scan_fields < 1) {
+ err = "active-defrag-max-scan-fields must be positive";
+ goto loaderr;
+ }
} else if (!strcasecmp(argv[0],"hash-max-ziplist-entries") && argc == 2) {
server.hash_max_ziplist_entries = memtoll(argv[1], NULL);
} else if (!strcasecmp(argv[0],"hash-max-ziplist-value") && argc == 2) {
server.hash_max_ziplist_value = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"stream-node-max-bytes") && argc == 2) {
+ server.stream_node_max_bytes = memtoll(argv[1], NULL);
+ } else if (!strcasecmp(argv[0],"stream-node-max-entries") && argc == 2) {
+ server.stream_node_max_entries = atoi(argv[1]);
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
@@ -607,13 +654,6 @@ void loadServerConfigFromString(char *config) {
{
err = "Invalid port"; goto loaderr;
}
- } else if (!strcasecmp(argv[0],"cluster-require-full-coverage") &&
- argc == 2)
- {
- if ((server.cluster_require_full_coverage = yesnotoi(argv[1])) == -1)
- {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
} else if (!strcasecmp(argv[0],"cluster-node-timeout") && argc == 2) {
server.cluster_node_timeout = strtoll(argv[1],NULL,10);
if (server.cluster_node_timeout <= 0) {
@@ -627,24 +667,23 @@ void loadServerConfigFromString(char *config) {
err = "cluster migration barrier must zero or positive";
goto loaderr;
}
- } else if (!strcasecmp(argv[0],"cluster-slave-validity-factor")
+ } else if ((!strcasecmp(argv[0],"cluster-slave-validity-factor") ||
+ !strcasecmp(argv[0],"cluster-replica-validity-factor"))
&& argc == 2)
{
server.cluster_slave_validity_factor = atoi(argv[1]);
if (server.cluster_slave_validity_factor < 0) {
- err = "cluster slave validity factor must be zero or positive";
+ err = "cluster replica validity factor must be zero or positive";
goto loaderr;
}
- } else if (!strcasecmp(argv[0],"cluster-slave-no-failover") &&
- argc == 2)
- {
- server.cluster_slave_no_failover = yesnotoi(argv[1]);
- if (server.cluster_slave_no_failover == -1) {
+ } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
+ server.lua_time_limit = strtoll(argv[1],NULL,10);
+ } else if (!strcasecmp(argv[0],"lua-replicate-commands") && argc == 2) {
+ server.lua_always_replicate_commands = yesnotoi(argv[1]);
+ if (server.lua_always_replicate_commands == -1) {
err = "argument must be 'yes' or 'no'";
goto loaderr;
}
- } else if (!strcasecmp(argv[0],"lua-time-limit") && argc == 2) {
- server.lua_time_limit = strtoll(argv[1],NULL,10);
} else if (!strcasecmp(argv[0],"slowlog-log-slower-than") &&
argc == 2)
{
@@ -659,6 +698,17 @@ void loadServerConfigFromString(char *config) {
}
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
server.slowlog_max_len = strtoll(argv[1],NULL,10);
+ } else if (!strcasecmp(argv[0],"tracking-table-max-fill") &&
+ argc == 2)
+ {
+ server.tracking_table_max_fill = strtoll(argv[1],NULL,10);
+ if (server.tracking_table_max_fill > 100 ||
+ server.tracking_table_max_fill < 0)
+ {
+ err = "The tracking table fill percentage must be an "
+ "integer between 0 and 100";
+ goto loaderr;
+ }
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
argc == 5)
{
@@ -681,32 +731,37 @@ void loadServerConfigFromString(char *config) {
server.client_obuf_limits[class].hard_limit_bytes = hard;
server.client_obuf_limits[class].soft_limit_bytes = soft;
server.client_obuf_limits[class].soft_limit_seconds = soft_seconds;
- } else if (!strcasecmp(argv[0],"stop-writes-on-bgsave-error") &&
- argc == 2) {
- if ((server.stop_writes_on_bgsave_err = yesnotoi(argv[1])) == -1) {
- err = "argument must be 'yes' or 'no'"; goto loaderr;
- }
- } else if (!strcasecmp(argv[0],"slave-priority") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"slave-priority") ||
+ !strcasecmp(argv[0],"replica-priority")) && argc == 2)
+ {
server.slave_priority = atoi(argv[1]);
- } else if (!strcasecmp(argv[0],"slave-announce-ip") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"slave-announce-ip") ||
+ !strcasecmp(argv[0],"replica-announce-ip")) && argc == 2)
+ {
zfree(server.slave_announce_ip);
server.slave_announce_ip = zstrdup(argv[1]);
- } else if (!strcasecmp(argv[0],"slave-announce-port") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"slave-announce-port") ||
+ !strcasecmp(argv[0],"replica-announce-port")) && argc == 2)
+ {
server.slave_announce_port = atoi(argv[1]);
if (server.slave_announce_port < 0 ||
server.slave_announce_port > 65535)
{
err = "Invalid port"; goto loaderr;
}
- } else if (!strcasecmp(argv[0],"min-slaves-to-write") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"min-slaves-to-write") ||
+ !strcasecmp(argv[0],"min-replicas-to-write")) && argc == 2)
+ {
server.repl_min_slaves_to_write = atoi(argv[1]);
if (server.repl_min_slaves_to_write < 0) {
- err = "Invalid value for min-slaves-to-write."; goto loaderr;
+ err = "Invalid value for min-replicas-to-write."; goto loaderr;
}
- } else if (!strcasecmp(argv[0],"min-slaves-max-lag") && argc == 2) {
+ } else if ((!strcasecmp(argv[0],"min-slaves-max-lag") ||
+ !strcasecmp(argv[0],"min-replicas-max-lag")) && argc == 2)
+ {
server.repl_min_slaves_max_lag = atoi(argv[1]);
if (server.repl_min_slaves_max_lag < 0) {
- err = "Invalid value for min-slaves-max-lag."; goto loaderr;
+ err = "Invalid value for min-replicas-max-lag."; goto loaderr;
}
} else if (!strcasecmp(argv[0],"notify-keyspace-events") && argc == 2) {
int flags = keyspaceEventsStringToFlags(argv[1]);
@@ -725,6 +780,16 @@ void loadServerConfigFromString(char *config) {
"Allowed values: 'upstart', 'systemd', 'auto', or 'no'";
goto loaderr;
}
+ } else if (!strcasecmp(argv[0],"user") && argc >= 2) {
+ int argc_err;
+ if (ACLAppendUserForLoading(argv,argc,&argc_err) == C_ERR) {
+ char buf[1024];
+ char *errmsg = ACLSetUserStringError();
+ snprintf(buf,sizeof(buf),"Error in user declaration '%s': %s",
+ argv[argc_err],errmsg);
+ err = buf;
+ goto loaderr;
+ }
} else if (!strcasecmp(argv[0],"loadmodule") && argc >= 2) {
queueLoadModule(argv[1],&argv[2],argc-2);
} else if (!strcasecmp(argv[0],"sentinel")) {
@@ -738,6 +803,45 @@ void loadServerConfigFromString(char *config) {
err = sentinelHandleConfiguration(argv+1,argc-1);
if (err) goto loaderr;
}
+#ifdef USE_OPENSSL
+ } else if (!strcasecmp(argv[0],"tls-port") && argc == 2) {
+ server.tls_port = atoi(argv[1]);
+ if (server.port < 0 || server.port > 65535) {
+ err = "Invalid tls-port"; goto loaderr;
+ }
+ } else if (!strcasecmp(argv[0],"tls-cluster") && argc == 2) {
+ server.tls_cluster = yesnotoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-replication") && argc == 2) {
+ server.tls_replication = yesnotoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-auth-clients") && argc == 2) {
+ server.tls_auth_clients = yesnotoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-cert-file") && argc == 2) {
+ zfree(server.tls_ctx_config.cert_file);
+ server.tls_ctx_config.cert_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-key-file") && argc == 2) {
+ zfree(server.tls_ctx_config.key_file);
+ server.tls_ctx_config.key_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-dh-params-file") && argc == 2) {
+ zfree(server.tls_ctx_config.dh_params_file);
+ server.tls_ctx_config.dh_params_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-ca-cert-file") && argc == 2) {
+ zfree(server.tls_ctx_config.ca_cert_file);
+ server.tls_ctx_config.ca_cert_file = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-ca-cert-dir") && argc == 2) {
+ zfree(server.tls_ctx_config.ca_cert_dir);
+ server.tls_ctx_config.ca_cert_dir = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-protocols") && argc >= 2) {
+ zfree(server.tls_ctx_config.protocols);
+ server.tls_ctx_config.protocols = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-ciphers") && argc == 2) {
+ zfree(server.tls_ctx_config.ciphers);
+ server.tls_ctx_config.ciphers = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-ciphersuites") && argc == 2) {
+ zfree(server.tls_ctx_config.ciphersuites);
+ server.tls_ctx_config.ciphersuites = zstrdup(argv[1]);
+ } else if (!strcasecmp(argv[0],"tls-prefer-server-ciphers") && argc == 2) {
+ server.tls_ctx_config.prefer_server_ciphers = yesnotoi(argv[1]);
+#endif /* USE_OPENSSL */
} else {
err = "Bad directive or wrong number of arguments"; goto loaderr;
}
@@ -748,7 +852,7 @@ void loadServerConfigFromString(char *config) {
if (server.cluster_enabled && server.masterhost) {
linenum = slaveof_linenum;
i = linenum-1;
- err = "slaveof directive not allowed in cluster mode";
+ err = "replicaof directive not allowed in cluster mode";
goto loaderr;
}
@@ -832,6 +936,10 @@ void loadServerConfig(char *filename, char *options) {
#define config_set_special_field(_name) \
} else if (!strcasecmp(c->argv[2]->ptr,_name)) {
+#define config_set_special_field_with_alias(_name1,_name2) \
+ } else if (!strcasecmp(c->argv[2]->ptr,_name1) || \
+ !strcasecmp(c->argv[2]->ptr,_name2)) {
+
#define config_set_else } else
void configSetCommand(client *c) {
@@ -842,6 +950,19 @@ void configSetCommand(client *c) {
serverAssertWithInfo(c,c->argv[3],sdsEncodedObject(c->argv[3]));
o = c->argv[3];
+ /* Iterate the configs that are standard */
+ for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
+ if(config->modifiable && (!strcasecmp(c->argv[2]->ptr,config->name) ||
+ (config->alias && !strcasecmp(c->argv[2]->ptr,config->alias))))
+ {
+ int yn = yesnotoi(o->ptr);
+ if (yn == -1) goto badfmt;
+ *(config->config) = yn;
+ addReply(c,shared.ok);
+ return;
+ }
+ }
+
if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */
@@ -854,8 +975,15 @@ void configSetCommand(client *c) {
server.rdb_filename = zstrdup(o->ptr);
} config_set_special_field("requirepass") {
if (sdslen(o->ptr) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt;
- zfree(server.requirepass);
- server.requirepass = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
+ /* The old "requirepass" directive just translates to setting
+ * a password to the default user. */
+ ACLSetUser(DefaultUser,"resetpass",-1);
+ sds aclop = sdscatprintf(sdsempty(),">%s",(char*)o->ptr);
+ ACLSetUser(DefaultUser,aclop,sdslen(aclop));
+ sdsfree(aclop);
+ } config_set_special_field("masteruser") {
+ zfree(server.masteruser);
+ server.masteruser = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
} config_set_special_field("masterauth") {
zfree(server.masterauth);
server.masterauth = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
@@ -892,6 +1020,7 @@ void configSetCommand(client *c) {
int enable = yesnotoi(o->ptr);
if (enable == -1) goto badfmt;
+ server.aof_enabled = enable;
if (enable == 0 && server.aof_state != AOF_OFF) {
stopAppendOnly();
} else if (enable && server.aof_state == AOF_OFF) {
@@ -977,8 +1106,8 @@ void configSetCommand(client *c) {
int soft_seconds;
class = getClientTypeByName(v[j]);
- hard = strtoll(v[j+1],NULL,10);
- soft = strtoll(v[j+2],NULL,10);
+ hard = memtoll(v[j+1],NULL);
+ soft = memtoll(v[j+2],NULL);
soft_seconds = strtoll(v[j+3],NULL,10);
server.client_obuf_limits[class].hard_limit_bytes = hard;
@@ -991,73 +1120,39 @@ void configSetCommand(client *c) {
if (flags == -1) goto badfmt;
server.notify_keyspace_events = flags;
- } config_set_special_field("slave-announce-ip") {
+ } config_set_special_field_with_alias("slave-announce-ip",
+ "replica-announce-ip")
+ {
zfree(server.slave_announce_ip);
server.slave_announce_ip = ((char*)o->ptr)[0] ? zstrdup(o->ptr) : NULL;
/* Boolean fields.
* config_set_bool_field(name,var). */
} config_set_bool_field(
- "rdbcompression", server.rdb_compression) {
- } config_set_bool_field(
- "repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay) {
- } config_set_bool_field(
- "repl-diskless-sync",server.repl_diskless_sync) {
- } config_set_bool_field(
- "cluster-require-full-coverage",server.cluster_require_full_coverage) {
- } config_set_bool_field(
- "cluster-slave-no-failover",server.cluster_slave_no_failover) {
- } config_set_bool_field(
- "aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync) {
- } config_set_bool_field(
- "aof-load-truncated",server.aof_load_truncated) {
- } config_set_bool_field(
- "aof-use-rdb-preamble",server.aof_use_rdb_preamble) {
- } config_set_bool_field(
- "slave-serve-stale-data",server.repl_serve_stale_data) {
- } config_set_bool_field(
- "slave-read-only",server.repl_slave_ro) {
- } config_set_bool_field(
- "activerehashing",server.activerehashing) {
- } config_set_bool_field(
"activedefrag",server.active_defrag_enabled) {
#ifndef HAVE_DEFRAG
if (server.active_defrag_enabled) {
server.active_defrag_enabled = 0;
addReplyError(c,
- "Active defragmentation cannot be enabled: it requires a "
- "Redis server compiled with a modified Jemalloc like the "
- "one shipped by default with the Redis source distribution");
+ "-DISABLED Active defragmentation cannot be enabled: it "
+ "requires a Redis server compiled with a modified Jemalloc "
+ "like the one shipped by default with the Redis source "
+ "distribution");
return;
}
#endif
- } config_set_bool_field(
- "protected-mode",server.protected_mode) {
- } config_set_bool_field(
- "stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err) {
- } config_set_bool_field(
- "lazyfree-lazy-eviction",server.lazyfree_lazy_eviction) {
- } config_set_bool_field(
- "lazyfree-lazy-expire",server.lazyfree_lazy_expire) {
- } config_set_bool_field(
- "lazyfree-lazy-server-del",server.lazyfree_lazy_server_del) {
- } config_set_bool_field(
- "slave-lazy-flush",server.repl_slave_lazy_flush) {
- } config_set_bool_field(
- "no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite) {
-
/* Numerical fields.
* config_set_numerical_field(name,var,min,max) */
} config_set_numerical_field(
- "tcp-keepalive",server.tcpkeepalive,0,LLONG_MAX) {
+ "tcp-keepalive",server.tcpkeepalive,0,INT_MAX) {
} config_set_numerical_field(
- "maxmemory-samples",server.maxmemory_samples,1,LLONG_MAX) {
+ "maxmemory-samples",server.maxmemory_samples,1,INT_MAX) {
} config_set_numerical_field(
- "lfu-log-factor",server.lfu_log_factor,0,LLONG_MAX) {
+ "lfu-log-factor",server.lfu_log_factor,0,INT_MAX) {
} config_set_numerical_field(
- "lfu-decay-time",server.lfu_decay_time,0,LLONG_MAX) {
+ "lfu-decay-time",server.lfu_decay_time,0,INT_MAX) {
} config_set_numerical_field(
- "timeout",server.maxidletime,0,LONG_MAX) {
+ "timeout",server.maxidletime,0,INT_MAX) {
} config_set_numerical_field(
"active-defrag-threshold-lower",server.active_defrag_threshold_lower,0,1000) {
} config_set_numerical_field(
@@ -1069,50 +1164,74 @@ void configSetCommand(client *c) {
} config_set_numerical_field(
"active-defrag-cycle-max",server.active_defrag_cycle_max,1,99) {
} config_set_numerical_field(
- "auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,LLONG_MAX){
+ "active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,1,LONG_MAX) {
+ } config_set_numerical_field(
+ "auto-aof-rewrite-percentage",server.aof_rewrite_perc,0,INT_MAX){
} config_set_numerical_field(
- "hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LLONG_MAX) {
+ "hash-max-ziplist-entries",server.hash_max_ziplist_entries,0,LONG_MAX) {
} config_set_numerical_field(
- "hash-max-ziplist-value",server.hash_max_ziplist_value,0,LLONG_MAX) {
+ "hash-max-ziplist-value",server.hash_max_ziplist_value,0,LONG_MAX) {
+ } config_set_numerical_field(
+ "stream-node-max-bytes",server.stream_node_max_bytes,0,LONG_MAX) {
+ } config_set_numerical_field(
+ "stream-node-max-entries",server.stream_node_max_entries,0,LLONG_MAX) {
} config_set_numerical_field(
"list-max-ziplist-size",server.list_max_ziplist_size,INT_MIN,INT_MAX) {
} config_set_numerical_field(
"list-compress-depth",server.list_compress_depth,0,INT_MAX) {
} config_set_numerical_field(
- "set-max-intset-entries",server.set_max_intset_entries,0,LLONG_MAX) {
+ "set-max-intset-entries",server.set_max_intset_entries,0,LONG_MAX) {
} config_set_numerical_field(
- "zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LLONG_MAX) {
+ "zset-max-ziplist-entries",server.zset_max_ziplist_entries,0,LONG_MAX) {
} config_set_numerical_field(
- "zset-max-ziplist-value",server.zset_max_ziplist_value,0,LLONG_MAX) {
+ "zset-max-ziplist-value",server.zset_max_ziplist_value,0,LONG_MAX) {
} config_set_numerical_field(
- "hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LLONG_MAX) {
+ "hll-sparse-max-bytes",server.hll_sparse_max_bytes,0,LONG_MAX) {
} config_set_numerical_field(
- "lua-time-limit",server.lua_time_limit,0,LLONG_MAX) {
+ "lua-time-limit",server.lua_time_limit,0,LONG_MAX) {
} config_set_numerical_field(
- "slowlog-log-slower-than",server.slowlog_log_slower_than,0,LLONG_MAX) {
+ "slowlog-log-slower-than",server.slowlog_log_slower_than,-1,LLONG_MAX) {
} config_set_numerical_field(
- "slowlog-max-len",ll,0,LLONG_MAX) {
+ "slowlog-max-len",ll,0,LONG_MAX) {
/* Cast to unsigned. */
- server.slowlog_max_len = (unsigned)ll;
+ server.slowlog_max_len = (unsigned long)ll;
+ } config_set_numerical_field(
+ "tracking-table-max-fill",server.tracking_table_max_fill,0,100) {
} config_set_numerical_field(
"latency-monitor-threshold",server.latency_monitor_threshold,0,LLONG_MAX){
} config_set_numerical_field(
- "repl-ping-slave-period",server.repl_ping_slave_period,1,LLONG_MAX) {
+ "repl-ping-slave-period",server.repl_ping_slave_period,1,INT_MAX) {
+ } config_set_numerical_field(
+ "repl-ping-replica-period",server.repl_ping_slave_period,1,INT_MAX) {
+ } config_set_numerical_field(
+ "repl-timeout",server.repl_timeout,1,INT_MAX) {
+ } config_set_numerical_field(
+ "repl-backlog-ttl",server.repl_backlog_time_limit,0,LONG_MAX) {
} config_set_numerical_field(
- "repl-timeout",server.repl_timeout,1,LLONG_MAX) {
+ "repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,INT_MAX) {
} config_set_numerical_field(
- "repl-backlog-ttl",server.repl_backlog_time_limit,0,LLONG_MAX) {
+ "slave-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field(
- "repl-diskless-sync-delay",server.repl_diskless_sync_delay,0,LLONG_MAX) {
+ "replica-priority",server.slave_priority,0,INT_MAX) {
} config_set_numerical_field(
- "slave-priority",server.slave_priority,0,LLONG_MAX) {
+ "rdb-key-save-delay",server.rdb_key_save_delay,0,LLONG_MAX) {
+ } config_set_numerical_field(
+ "key-load-delay",server.key_load_delay,0,LLONG_MAX) {
} config_set_numerical_field(
"slave-announce-port",server.slave_announce_port,0,65535) {
} config_set_numerical_field(
- "min-slaves-to-write",server.repl_min_slaves_to_write,0,LLONG_MAX) {
+ "replica-announce-port",server.slave_announce_port,0,65535) {
+ } config_set_numerical_field(
+ "min-slaves-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
+ refreshGoodSlavesCount();
+ } config_set_numerical_field(
+ "min-replicas-to-write",server.repl_min_slaves_to_write,0,INT_MAX) {
+ refreshGoodSlavesCount();
+ } config_set_numerical_field(
+ "min-slaves-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
refreshGoodSlavesCount();
} config_set_numerical_field(
- "min-slaves-max-lag",server.repl_min_slaves_max_lag,0,LLONG_MAX) {
+ "min-replicas-max-lag",server.repl_min_slaves_max_lag,0,INT_MAX) {
refreshGoodSlavesCount();
} config_set_numerical_field(
"cluster-node-timeout",server.cluster_node_timeout,0,LLONG_MAX) {
@@ -1121,17 +1240,19 @@ void configSetCommand(client *c) {
} config_set_numerical_field(
"cluster-announce-bus-port",server.cluster_announce_bus_port,0,65535) {
} config_set_numerical_field(
- "cluster-migration-barrier",server.cluster_migration_barrier,0,LLONG_MAX){
+ "cluster-migration-barrier",server.cluster_migration_barrier,0,INT_MAX){
} config_set_numerical_field(
- "cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,LLONG_MAX) {
+ "cluster-slave-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
} config_set_numerical_field(
- "hz",server.hz,0,LLONG_MAX) {
+ "cluster-replica-validity-factor",server.cluster_slave_validity_factor,0,INT_MAX) {
+ } config_set_numerical_field(
+ "hz",server.config_hz,0,INT_MAX) {
/* Hz is more an hint from the user, so we accept values out of range
* but cap them to reasonable values. */
- if (server.hz < CONFIG_MIN_HZ) server.hz = CONFIG_MIN_HZ;
- if (server.hz > CONFIG_MAX_HZ) server.hz = CONFIG_MAX_HZ;
+ if (server.config_hz < CONFIG_MIN_HZ) server.config_hz = CONFIG_MIN_HZ;
+ if (server.config_hz > CONFIG_MAX_HZ) server.config_hz = CONFIG_MAX_HZ;
} config_set_numerical_field(
- "watchdog-period",ll,0,LLONG_MAX) {
+ "watchdog-period",ll,0,INT_MAX) {
if (ll)
enableWatchdog(ll);
else
@@ -1142,9 +1263,9 @@ void configSetCommand(client *c) {
} config_set_memory_field("maxmemory",server.maxmemory) {
if (server.maxmemory) {
if (server.maxmemory < zmalloc_used_memory()) {
- serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in keys eviction and/or inability to accept new write commands depending on the maxmemory-policy.");
+ serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET is smaller than the current memory usage. This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.");
}
- freeMemoryIfNeeded();
+ freeMemoryIfNeededAndSafe();
}
} config_set_memory_field(
"proto-max-bulk-len",server.proto_max_bulk_len) {
@@ -1163,7 +1284,102 @@ void configSetCommand(client *c) {
"maxmemory-policy",server.maxmemory_policy,maxmemory_policy_enum) {
} config_set_enum_field(
"appendfsync",server.aof_fsync,aof_fsync_enum) {
-
+ } config_set_enum_field(
+ "repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum) {
+#ifdef USE_OPENSSL
+ /* TLS fields. */
+ } config_set_special_field("tls-cert-file") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.cert_file = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-cert-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.cert_file);
+ server.tls_ctx_config.cert_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-key-file") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.key_file = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-key-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.key_file);
+ server.tls_ctx_config.key_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-dh-params-file") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.dh_params_file = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-dh-params-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.dh_params_file);
+ server.tls_ctx_config.dh_params_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-ca-cert-file") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.ca_cert_file = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-ca-cert-file. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.ca_cert_file);
+ server.tls_ctx_config.ca_cert_file = zstrdup(o->ptr);
+ } config_set_special_field("tls-ca-cert-dir") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.ca_cert_dir = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-ca-cert-dir. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.ca_cert_dir);
+ server.tls_ctx_config.ca_cert_dir = zstrdup(o->ptr);
+ } config_set_bool_field("tls-auth-clients", server.tls_auth_clients) {
+ } config_set_bool_field("tls-replication", server.tls_replication) {
+ } config_set_bool_field("tls-cluster", server.tls_cluster) {
+ } config_set_special_field("tls-protocols") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.protocols = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-protocols. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.protocols);
+ server.tls_ctx_config.protocols = zstrdup(o->ptr);
+ } config_set_special_field("tls-ciphers") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.ciphers = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-ciphers. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.ciphers);
+ server.tls_ctx_config.ciphers = zstrdup(o->ptr);
+ } config_set_special_field("tls-ciphersuites") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.ciphersuites = (char *) o->ptr;
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c,
+ "Unable to configure tls-ciphersuites. Check server logs.");
+ return;
+ }
+ zfree(server.tls_ctx_config.ciphersuites);
+ server.tls_ctx_config.ciphersuites = zstrdup(o->ptr);
+ } config_set_special_field("tls-prefer-server-ciphers") {
+ redisTLSContextConfig tmpctx = server.tls_ctx_config;
+ tmpctx.prefer_server_ciphers = yesnotoi(o->ptr);
+ if (tlsConfigure(&tmpctx) == C_ERR) {
+ addReplyError(c, "Unable to reconfigure TLS. Check server logs.");
+ return;
+ }
+ server.tls_ctx_config.prefer_server_ciphers = tmpctx.prefer_server_ciphers;
+#endif /* USE_OPENSSL */
/* Everyhing else is an error... */
} config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
@@ -1220,7 +1436,7 @@ badfmt: /* Bad format errors */
void configGetCommand(client *c) {
robj *o = c->argv[2];
- void *replylen = addDeferredMultiBulkLength(c);
+ void *replylen = addReplyDeferredLen(c);
char *pattern = o->ptr;
char buf[128];
int matches = 0;
@@ -1228,13 +1444,25 @@ void configGetCommand(client *c) {
/* String values */
config_get_string_field("dbfilename",server.rdb_filename);
- config_get_string_field("requirepass",server.requirepass);
+ config_get_string_field("masteruser",server.masteruser);
config_get_string_field("masterauth",server.masterauth);
config_get_string_field("cluster-announce-ip",server.cluster_announce_ip);
config_get_string_field("unixsocket",server.unixsocket);
config_get_string_field("logfile",server.logfile);
+ config_get_string_field("aclfile",server.acl_filename);
config_get_string_field("pidfile",server.pidfile);
config_get_string_field("slave-announce-ip",server.slave_announce_ip);
+ config_get_string_field("replica-announce-ip",server.slave_announce_ip);
+#ifdef USE_OPENSSL
+ config_get_string_field("tls-cert-file",server.tls_ctx_config.cert_file);
+ config_get_string_field("tls-key-file",server.tls_ctx_config.key_file);
+ config_get_string_field("tls-dh-params-file",server.tls_ctx_config.dh_params_file);
+ config_get_string_field("tls-ca-cert-file",server.tls_ctx_config.ca_cert_file);
+ config_get_string_field("tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir);
+ config_get_string_field("tls-protocols",server.tls_ctx_config.protocols);
+ config_get_string_field("tls-ciphers",server.tls_ctx_config.ciphers);
+ config_get_string_field("tls-ciphersuites",server.tls_ctx_config.ciphersuites);
+#endif
/* Numerical values */
config_get_numerical_field("maxmemory",server.maxmemory);
@@ -1249,6 +1477,7 @@ void configGetCommand(client *c) {
config_get_numerical_field("active-defrag-ignore-bytes",server.active_defrag_ignore_bytes);
config_get_numerical_field("active-defrag-cycle-min",server.active_defrag_cycle_min);
config_get_numerical_field("active-defrag-cycle-max",server.active_defrag_cycle_max);
+ config_get_numerical_field("active-defrag-max-scan-fields",server.active_defrag_max_scan_fields);
config_get_numerical_field("auto-aof-rewrite-percentage",
server.aof_rewrite_perc);
config_get_numerical_field("auto-aof-rewrite-min-size",
@@ -1257,6 +1486,10 @@ void configGetCommand(client *c) {
server.hash_max_ziplist_entries);
config_get_numerical_field("hash-max-ziplist-value",
server.hash_max_ziplist_value);
+ config_get_numerical_field("stream-node-max-bytes",
+ server.stream_node_max_bytes);
+ config_get_numerical_field("stream-node-max-entries",
+ server.stream_node_max_entries);
config_get_numerical_field("list-max-ziplist-size",
server.list_max_ziplist_size);
config_get_numerical_field("list-compress-depth",
@@ -1274,68 +1507,55 @@ void configGetCommand(client *c) {
server.slowlog_log_slower_than);
config_get_numerical_field("latency-monitor-threshold",
server.latency_monitor_threshold);
- config_get_numerical_field("slowlog-max-len",
- server.slowlog_max_len);
+ config_get_numerical_field("slowlog-max-len", server.slowlog_max_len);
+ config_get_numerical_field("tracking-table-max-fill", server.tracking_table_max_fill);
config_get_numerical_field("port",server.port);
+ config_get_numerical_field("tls-port",server.tls_port);
config_get_numerical_field("cluster-announce-port",server.cluster_announce_port);
config_get_numerical_field("cluster-announce-bus-port",server.cluster_announce_bus_port);
config_get_numerical_field("tcp-backlog",server.tcp_backlog);
config_get_numerical_field("databases",server.dbnum);
+ config_get_numerical_field("io-threads",server.io_threads_num);
config_get_numerical_field("repl-ping-slave-period",server.repl_ping_slave_period);
+ config_get_numerical_field("repl-ping-replica-period",server.repl_ping_slave_period);
config_get_numerical_field("repl-timeout",server.repl_timeout);
config_get_numerical_field("repl-backlog-size",server.repl_backlog_size);
config_get_numerical_field("repl-backlog-ttl",server.repl_backlog_time_limit);
config_get_numerical_field("maxclients",server.maxclients);
config_get_numerical_field("watchdog-period",server.watchdog_period);
config_get_numerical_field("slave-priority",server.slave_priority);
+ config_get_numerical_field("replica-priority",server.slave_priority);
config_get_numerical_field("slave-announce-port",server.slave_announce_port);
+ config_get_numerical_field("replica-announce-port",server.slave_announce_port);
config_get_numerical_field("min-slaves-to-write",server.repl_min_slaves_to_write);
+ config_get_numerical_field("min-replicas-to-write",server.repl_min_slaves_to_write);
config_get_numerical_field("min-slaves-max-lag",server.repl_min_slaves_max_lag);
- config_get_numerical_field("hz",server.hz);
+ config_get_numerical_field("min-replicas-max-lag",server.repl_min_slaves_max_lag);
+ config_get_numerical_field("hz",server.config_hz);
config_get_numerical_field("cluster-node-timeout",server.cluster_node_timeout);
config_get_numerical_field("cluster-migration-barrier",server.cluster_migration_barrier);
config_get_numerical_field("cluster-slave-validity-factor",server.cluster_slave_validity_factor);
+ config_get_numerical_field("cluster-replica-validity-factor",server.cluster_slave_validity_factor);
config_get_numerical_field("repl-diskless-sync-delay",server.repl_diskless_sync_delay);
+ config_get_numerical_field("rdb-key-save-delay",server.rdb_key_save_delay);
+ config_get_numerical_field("key-load-delay",server.key_load_delay);
config_get_numerical_field("tcp-keepalive",server.tcpkeepalive);
/* Bool (yes/no) values */
- config_get_bool_field("cluster-require-full-coverage",
- server.cluster_require_full_coverage);
- config_get_bool_field("cluster-slave-no-failover",
- server.cluster_slave_no_failover);
- config_get_bool_field("no-appendfsync-on-rewrite",
- server.aof_no_fsync_on_rewrite);
- config_get_bool_field("slave-serve-stale-data",
- server.repl_serve_stale_data);
- config_get_bool_field("slave-read-only",
- server.repl_slave_ro);
- config_get_bool_field("stop-writes-on-bgsave-error",
- server.stop_writes_on_bgsave_err);
- config_get_bool_field("daemonize", server.daemonize);
- config_get_bool_field("rdbcompression", server.rdb_compression);
- config_get_bool_field("rdbchecksum", server.rdb_checksum);
- config_get_bool_field("activerehashing", server.activerehashing);
- config_get_bool_field("activedefrag", server.active_defrag_enabled);
- config_get_bool_field("protected-mode", server.protected_mode);
- config_get_bool_field("repl-disable-tcp-nodelay",
- server.repl_disable_tcp_nodelay);
- config_get_bool_field("repl-diskless-sync",
- server.repl_diskless_sync);
- config_get_bool_field("aof-rewrite-incremental-fsync",
- server.aof_rewrite_incremental_fsync);
- config_get_bool_field("aof-load-truncated",
- server.aof_load_truncated);
- config_get_bool_field("aof-use-rdb-preamble",
- server.aof_use_rdb_preamble);
- config_get_bool_field("lazyfree-lazy-eviction",
- server.lazyfree_lazy_eviction);
- config_get_bool_field("lazyfree-lazy-expire",
- server.lazyfree_lazy_expire);
- config_get_bool_field("lazyfree-lazy-server-del",
- server.lazyfree_lazy_server_del);
- config_get_bool_field("slave-lazy-flush",
- server.repl_slave_lazy_flush);
+ /* Iterate the configs that are standard */
+ for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
+ config_get_bool_field(config->name, *(config->config));
+ if (config->alias) {
+ config_get_bool_field(config->alias, *(config->config));
+ }
+ }
+ config_get_bool_field("activedefrag", server.active_defrag_enabled);
+ config_get_bool_field("tls-cluster",server.tls_cluster);
+ config_get_bool_field("tls-replication",server.tls_replication);
+ config_get_bool_field("tls-auth-clients",server.tls_auth_clients);
+ config_get_bool_field("tls-prefer-server-ciphers",
+ server.tls_ctx_config.prefer_server_ciphers);
/* Enum values */
config_get_enum_field("maxmemory-policy",
server.maxmemory_policy,maxmemory_policy_enum);
@@ -1347,12 +1567,14 @@ void configGetCommand(client *c) {
server.aof_fsync,aof_fsync_enum);
config_get_enum_field("syslog-facility",
server.syslog_facility,syslog_facility_enum);
+ config_get_enum_field("repl-diskless-load",
+ server.repl_diskless_load,repl_diskless_load_enum);
/* Everything we can't handle with macros follows. */
if (stringmatch(pattern,"appendonly",1)) {
addReplyBulkCString(c,"appendonly");
- addReplyBulkCString(c,server.aof_state == AOF_OFF ? "no" : "yes");
+ addReplyBulkCString(c,server.aof_enabled ? "yes" : "no");
matches++;
}
if (stringmatch(pattern,"dir",1)) {
@@ -1406,10 +1628,14 @@ void configGetCommand(client *c) {
addReplyBulkCString(c,buf);
matches++;
}
- if (stringmatch(pattern,"slaveof",1)) {
+ if (stringmatch(pattern,"slaveof",1) ||
+ stringmatch(pattern,"replicaof",1))
+ {
+ char *optname = stringmatch(pattern,"slaveof",1) ?
+ "slaveof" : "replicaof";
char buf[256];
- addReplyBulkCString(c,"slaveof");
+ addReplyBulkCString(c,optname);
if (server.masterhost)
snprintf(buf,sizeof(buf),"%s %d",
server.masterhost, server.masterport);
@@ -1419,12 +1645,10 @@ void configGetCommand(client *c) {
matches++;
}
if (stringmatch(pattern,"notify-keyspace-events",1)) {
- robj *flagsobj = createObject(OBJ_STRING,
- keyspaceEventsFlagsToString(server.notify_keyspace_events));
+ sds flags = keyspaceEventsFlagsToString(server.notify_keyspace_events);
addReplyBulkCString(c,"notify-keyspace-events");
- addReplyBulk(c,flagsobj);
- decrRefCount(flagsobj);
+ addReplyBulkSds(c,flags);
matches++;
}
if (stringmatch(pattern,"bind",1)) {
@@ -1435,7 +1659,18 @@ void configGetCommand(client *c) {
sdsfree(aux);
matches++;
}
- setDeferredMultiBulkLength(c,replylen,matches*2);
+ if (stringmatch(pattern,"requirepass",1)) {
+ addReplyBulkCString(c,"requirepass");
+ sds password = ACLDefaultUserFirstPassword();
+ if (password) {
+ addReplyBulkCBuffer(c,password,sdslen(password));
+ } else {
+ addReplyBulkCString(c,"");
+ }
+ matches++;
+ }
+
+ setDeferredMapLen(c,replylen,matches);
}
/*-----------------------------------------------------------------------------
@@ -1518,12 +1753,11 @@ void rewriteConfigMarkAsProcessed(struct rewriteConfigState *state, const char *
* If the old file does not exist at all, an empty state is returned. */
struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
FILE *fp = fopen(path,"r");
- struct rewriteConfigState *state = zmalloc(sizeof(*state));
- char buf[CONFIG_MAX_LINE+1];
- int linenum = -1;
-
if (fp == NULL && errno != ENOENT) return NULL;
+ char buf[CONFIG_MAX_LINE+1];
+ int linenum = -1;
+ struct rewriteConfigState *state = zmalloc(sizeof(*state));
state->option_to_line = dictCreate(&optionToLineDictType,NULL);
state->rewritten = dictCreate(&optionSetDictType,NULL);
state->numlines = 0;
@@ -1565,8 +1799,20 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
/* Now we populate the state according to the content of this line.
* Append the line and populate the option -> line numbers map. */
rewriteConfigAppendLine(state,line);
- rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
+ /* Translate options using the word "slave" to the corresponding name
+ * "replica", before adding such option to the config name -> lines
+ * mapping. */
+ char *p = strstr(argv[0],"slave");
+ if (p) {
+ sds alt = sdsempty();
+ alt = sdscatlen(alt,argv[0],p-argv[0]);;
+ alt = sdscatlen(alt,"replica",7);
+ alt = sdscatlen(alt,p+5,strlen(p+5));
+ sdsfree(argv[0]);
+ argv[0] = alt;
+ }
+ rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
sdsfreesplitres(argv,argc);
}
fclose(fp);
@@ -1654,7 +1900,7 @@ void rewriteConfigBytesOption(struct rewriteConfigState *state, char *option, lo
}
/* Rewrite a yes/no option. */
-void rewriteConfigYesNoOption(struct rewriteConfigState *state, char *option, int value, int defvalue) {
+void rewriteConfigYesNoOption(struct rewriteConfigState *state, const char *option, int value, int defvalue) {
int force = value != defvalue;
sds line = sdscatprintf(sdsempty(),"%s %s",option,
value ? "yes" : "no");
@@ -1741,6 +1987,38 @@ void rewriteConfigSaveOption(struct rewriteConfigState *state) {
rewriteConfigMarkAsProcessed(state,"save");
}
+/* Rewrite the user option. */
+void rewriteConfigUserOption(struct rewriteConfigState *state) {
+ /* If there is a user file defined we just mark this configuration
+ * directive as processed, so that all the lines containing users
+ * inside the config file gets discarded. */
+ if (server.acl_filename[0] != '\0') {
+ rewriteConfigMarkAsProcessed(state,"user");
+ return;
+ }
+
+ /* Otherwise scan the list of users and rewrite every line. Note that
+ * in case the list here is empty, the effect will just be to comment
+ * all the users directive inside the config file. */
+ raxIterator ri;
+ raxStart(&ri,Users);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ user *u = ri.data;
+ sds line = sdsnew("user ");
+ line = sdscatsds(line,u->name);
+ line = sdscatlen(line," ",1);
+ sds descr = ACLDescribeUser(u);
+ line = sdscatsds(line,descr);
+ sdsfree(descr);
+ rewriteConfigRewriteLine(state,"user",line,1);
+ }
+ raxStop(&ri);
+
+ /* Mark "user" as processed in case there are no defined users. */
+ rewriteConfigMarkAsProcessed(state,"user");
+}
+
/* Rewrite the dir option, always using absolute paths.*/
void rewriteConfigDirOption(struct rewriteConfigState *state) {
char cwd[1024];
@@ -1753,15 +2031,14 @@ void rewriteConfigDirOption(struct rewriteConfigState *state) {
}
/* Rewrite the slaveof option. */
-void rewriteConfigSlaveofOption(struct rewriteConfigState *state) {
- char *option = "slaveof";
+void rewriteConfigSlaveofOption(struct rewriteConfigState *state, char *option) {
sds line;
/* If this is a master, we want all the slaveof config options
* in the file to be removed. Note that if this is a cluster instance
* we don't want a slaveof directive inside redis.conf. */
if (server.cluster_enabled || server.masterhost == NULL) {
- rewriteConfigMarkAsProcessed(state,"slaveof");
+ rewriteConfigMarkAsProcessed(state,option);
return;
}
line = sdscatprintf(sdsempty(),"%s %s %d", option,
@@ -1803,8 +2080,10 @@ void rewriteConfigClientoutputbufferlimitOption(struct rewriteConfigState *state
rewriteConfigFormatMemory(soft,sizeof(soft),
server.client_obuf_limits[j].soft_limit_bytes);
+ char *typename = getClientTypeName(j);
+ if (!strcmp(typename,"slave")) typename = "replica";
line = sdscatprintf(sdsempty(),"%s %s %s %s %ld",
- option, getClientTypeName(j), hard, soft,
+ option, typename, hard, soft,
(long) server.client_obuf_limits[j].soft_limit_seconds);
rewriteConfigRewriteLine(state,option,line,force);
}
@@ -1832,6 +2111,26 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,option,line,force);
}
+/* Rewrite the requirepass option. */
+void rewriteConfigRequirepassOption(struct rewriteConfigState *state, char *option) {
+ int force = 1;
+ sds line;
+ sds password = ACLDefaultUserFirstPassword();
+
+ /* If there is no password set, we don't want the requirepass option
+ * to be present in the configuration at all. */
+ if (password == NULL) {
+ rewriteConfigMarkAsProcessed(state,option);
+ return;
+ }
+
+ line = sdsnew(option);
+ line = sdscatlen(line, " ", 1);
+ line = sdscatsds(line, password);
+
+ rewriteConfigRewriteLine(state,option,line,force);
+}
+
/* Glue together the configuration lines in the current configuration
* rewrite state into a single string, stripping multiple empty lines. */
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
@@ -1971,9 +2270,13 @@ int rewriteConfig(char *path) {
/* Step 2: rewrite every single option, replacing or appending it inside
* the rewrite state. */
- rewriteConfigYesNoOption(state,"daemonize",server.daemonize,0);
+ /* Iterate the configs that are standard */
+ for (configYesNo *config = configs_yesno; config->name != NULL; config++) {
+ rewriteConfigYesNoOption(state,config->name,*(config->config),config->default_value);
+ }
+
rewriteConfigStringOption(state,"pidfile",server.pidfile,CONFIG_DEFAULT_PID_FILE);
- rewriteConfigNumericalOption(state,"port",server.port,CONFIG_DEFAULT_SERVER_PORT);
+ rewriteConfigNumericalOption(state,"tls-port",server.tls_port,CONFIG_DEFAULT_SERVER_TLS_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-port",server.cluster_announce_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT);
rewriteConfigNumericalOption(state,"cluster-announce-bus-port",server.cluster_announce_bus_port,CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT);
rewriteConfigNumericalOption(state,"tcp-backlog",server.tcp_backlog,CONFIG_DEFAULT_TCP_BACKLOG);
@@ -1982,36 +2285,34 @@ int rewriteConfig(char *path) {
rewriteConfigOctalOption(state,"unixsocketperm",server.unixsocketperm,CONFIG_DEFAULT_UNIX_SOCKET_PERM);
rewriteConfigNumericalOption(state,"timeout",server.maxidletime,CONFIG_DEFAULT_CLIENT_TIMEOUT);
rewriteConfigNumericalOption(state,"tcp-keepalive",server.tcpkeepalive,CONFIG_DEFAULT_TCP_KEEPALIVE);
- rewriteConfigNumericalOption(state,"slave-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
+ rewriteConfigNumericalOption(state,"replica-announce-port",server.slave_announce_port,CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT);
rewriteConfigEnumOption(state,"loglevel",server.verbosity,loglevel_enum,CONFIG_DEFAULT_VERBOSITY);
rewriteConfigStringOption(state,"logfile",server.logfile,CONFIG_DEFAULT_LOGFILE);
+ rewriteConfigStringOption(state,"aclfile",server.acl_filename,CONFIG_DEFAULT_ACL_FILENAME);
rewriteConfigYesNoOption(state,"syslog-enabled",server.syslog_enabled,CONFIG_DEFAULT_SYSLOG_ENABLED);
rewriteConfigStringOption(state,"syslog-ident",server.syslog_ident,CONFIG_DEFAULT_SYSLOG_IDENT);
rewriteConfigSyslogfacilityOption(state);
rewriteConfigSaveOption(state);
+ rewriteConfigUserOption(state);
rewriteConfigNumericalOption(state,"databases",server.dbnum,CONFIG_DEFAULT_DBNUM);
- rewriteConfigYesNoOption(state,"stop-writes-on-bgsave-error",server.stop_writes_on_bgsave_err,CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR);
- rewriteConfigYesNoOption(state,"rdbcompression",server.rdb_compression,CONFIG_DEFAULT_RDB_COMPRESSION);
- rewriteConfigYesNoOption(state,"rdbchecksum",server.rdb_checksum,CONFIG_DEFAULT_RDB_CHECKSUM);
+ rewriteConfigNumericalOption(state,"io-threads",server.dbnum,CONFIG_DEFAULT_IO_THREADS_NUM);
rewriteConfigStringOption(state,"dbfilename",server.rdb_filename,CONFIG_DEFAULT_RDB_FILENAME);
rewriteConfigDirOption(state);
- rewriteConfigSlaveofOption(state);
- rewriteConfigStringOption(state,"slave-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
+ rewriteConfigSlaveofOption(state,"replicaof");
+ rewriteConfigStringOption(state,"replica-announce-ip",server.slave_announce_ip,CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP);
+ rewriteConfigStringOption(state,"masteruser",server.masteruser,NULL);
rewriteConfigStringOption(state,"masterauth",server.masterauth,NULL);
rewriteConfigStringOption(state,"cluster-announce-ip",server.cluster_announce_ip,NULL);
- rewriteConfigYesNoOption(state,"slave-serve-stale-data",server.repl_serve_stale_data,CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA);
- rewriteConfigYesNoOption(state,"slave-read-only",server.repl_slave_ro,CONFIG_DEFAULT_SLAVE_READ_ONLY);
- rewriteConfigNumericalOption(state,"repl-ping-slave-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
+ rewriteConfigNumericalOption(state,"repl-ping-replica-period",server.repl_ping_slave_period,CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD);
rewriteConfigNumericalOption(state,"repl-timeout",server.repl_timeout,CONFIG_DEFAULT_REPL_TIMEOUT);
rewriteConfigBytesOption(state,"repl-backlog-size",server.repl_backlog_size,CONFIG_DEFAULT_REPL_BACKLOG_SIZE);
rewriteConfigBytesOption(state,"repl-backlog-ttl",server.repl_backlog_time_limit,CONFIG_DEFAULT_REPL_BACKLOG_TIME_LIMIT);
- rewriteConfigYesNoOption(state,"repl-disable-tcp-nodelay",server.repl_disable_tcp_nodelay,CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY);
- rewriteConfigYesNoOption(state,"repl-diskless-sync",server.repl_diskless_sync,CONFIG_DEFAULT_REPL_DISKLESS_SYNC);
+ rewriteConfigEnumOption(state,"repl-diskless-load",server.repl_diskless_load,repl_diskless_load_enum,CONFIG_DEFAULT_REPL_DISKLESS_LOAD);
rewriteConfigNumericalOption(state,"repl-diskless-sync-delay",server.repl_diskless_sync_delay,CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY);
- rewriteConfigNumericalOption(state,"slave-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
- rewriteConfigNumericalOption(state,"min-slaves-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
- rewriteConfigNumericalOption(state,"min-slaves-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
- rewriteConfigStringOption(state,"requirepass",server.requirepass,NULL);
+ rewriteConfigNumericalOption(state,"replica-priority",server.slave_priority,CONFIG_DEFAULT_SLAVE_PRIORITY);
+ rewriteConfigNumericalOption(state,"min-replicas-to-write",server.repl_min_slaves_to_write,CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE);
+ rewriteConfigNumericalOption(state,"min-replicas-max-lag",server.repl_min_slaves_max_lag,CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG);
+ rewriteConfigRequirepassOption(state,"requirepass");
rewriteConfigNumericalOption(state,"maxclients",server.maxclients,CONFIG_DEFAULT_MAX_CLIENTS);
rewriteConfigBytesOption(state,"maxmemory",server.maxmemory,CONFIG_DEFAULT_MAXMEMORY);
rewriteConfigBytesOption(state,"proto-max-bulk-len",server.proto_max_bulk_len,CONFIG_DEFAULT_PROTO_MAX_BULK_LEN);
@@ -2025,45 +2326,53 @@ int rewriteConfig(char *path) {
rewriteConfigBytesOption(state,"active-defrag-ignore-bytes",server.active_defrag_ignore_bytes,CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES);
rewriteConfigNumericalOption(state,"active-defrag-cycle-min",server.active_defrag_cycle_min,CONFIG_DEFAULT_DEFRAG_CYCLE_MIN);
rewriteConfigNumericalOption(state,"active-defrag-cycle-max",server.active_defrag_cycle_max,CONFIG_DEFAULT_DEFRAG_CYCLE_MAX);
- rewriteConfigYesNoOption(state,"appendonly",server.aof_state != AOF_OFF,0);
+ rewriteConfigNumericalOption(state,"active-defrag-max-scan-fields",server.active_defrag_max_scan_fields,CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS);
+ rewriteConfigYesNoOption(state,"appendonly",server.aof_enabled,0);
rewriteConfigStringOption(state,"appendfilename",server.aof_filename,CONFIG_DEFAULT_AOF_FILENAME);
rewriteConfigEnumOption(state,"appendfsync",server.aof_fsync,aof_fsync_enum,CONFIG_DEFAULT_AOF_FSYNC);
- rewriteConfigYesNoOption(state,"no-appendfsync-on-rewrite",server.aof_no_fsync_on_rewrite,CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE);
rewriteConfigNumericalOption(state,"auto-aof-rewrite-percentage",server.aof_rewrite_perc,AOF_REWRITE_PERC);
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,AOF_REWRITE_MIN_SIZE);
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,LUA_SCRIPT_TIME_LIMIT);
rewriteConfigYesNoOption(state,"cluster-enabled",server.cluster_enabled,0);
rewriteConfigStringOption(state,"cluster-config-file",server.cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
- rewriteConfigYesNoOption(state,"cluster-require-full-coverage",server.cluster_require_full_coverage,CLUSTER_DEFAULT_REQUIRE_FULL_COVERAGE);
- rewriteConfigYesNoOption(state,"cluster-slave-no-failover",server.cluster_slave_no_failover,CLUSTER_DEFAULT_SLAVE_NO_FAILOVER);
rewriteConfigNumericalOption(state,"cluster-node-timeout",server.cluster_node_timeout,CLUSTER_DEFAULT_NODE_TIMEOUT);
rewriteConfigNumericalOption(state,"cluster-migration-barrier",server.cluster_migration_barrier,CLUSTER_DEFAULT_MIGRATION_BARRIER);
- rewriteConfigNumericalOption(state,"cluster-slave-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
+ rewriteConfigNumericalOption(state,"cluster-replica-validity-factor",server.cluster_slave_validity_factor,CLUSTER_DEFAULT_SLAVE_VALIDITY);
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN);
rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD);
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,CONFIG_DEFAULT_SLOWLOG_MAX_LEN);
+ rewriteConfigNumericalOption(state,"tracking-table-max-fill",server.tracking_table_max_fill,CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL);
rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,OBJ_HASH_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"hash-max-ziplist-value",server.hash_max_ziplist_value,OBJ_HASH_MAX_ZIPLIST_VALUE);
+ rewriteConfigNumericalOption(state,"stream-node-max-bytes",server.stream_node_max_bytes,OBJ_STREAM_NODE_MAX_BYTES);
+ rewriteConfigNumericalOption(state,"stream-node-max-entries",server.stream_node_max_entries,OBJ_STREAM_NODE_MAX_ENTRIES);
rewriteConfigNumericalOption(state,"list-max-ziplist-size",server.list_max_ziplist_size,OBJ_LIST_MAX_ZIPLIST_SIZE);
rewriteConfigNumericalOption(state,"list-compress-depth",server.list_compress_depth,OBJ_LIST_COMPRESS_DEPTH);
rewriteConfigNumericalOption(state,"set-max-intset-entries",server.set_max_intset_entries,OBJ_SET_MAX_INTSET_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-entries",server.zset_max_ziplist_entries,OBJ_ZSET_MAX_ZIPLIST_ENTRIES);
rewriteConfigNumericalOption(state,"zset-max-ziplist-value",server.zset_max_ziplist_value,OBJ_ZSET_MAX_ZIPLIST_VALUE);
rewriteConfigNumericalOption(state,"hll-sparse-max-bytes",server.hll_sparse_max_bytes,CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES);
- rewriteConfigYesNoOption(state,"activerehashing",server.activerehashing,CONFIG_DEFAULT_ACTIVE_REHASHING);
rewriteConfigYesNoOption(state,"activedefrag",server.active_defrag_enabled,CONFIG_DEFAULT_ACTIVE_DEFRAG);
- rewriteConfigYesNoOption(state,"protected-mode",server.protected_mode,CONFIG_DEFAULT_PROTECTED_MODE);
rewriteConfigClientoutputbufferlimitOption(state);
- rewriteConfigNumericalOption(state,"hz",server.hz,CONFIG_DEFAULT_HZ);
- rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
- rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,CONFIG_DEFAULT_AOF_LOAD_TRUNCATED);
- rewriteConfigYesNoOption(state,"aof-use-rdb-preamble",server.aof_use_rdb_preamble,CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE);
+ rewriteConfigNumericalOption(state,"hz",server.config_hz,CONFIG_DEFAULT_HZ);
rewriteConfigEnumOption(state,"supervised",server.supervised_mode,supervised_mode_enum,SUPERVISED_NONE);
- rewriteConfigYesNoOption(state,"lazyfree-lazy-eviction",server.lazyfree_lazy_eviction,CONFIG_DEFAULT_LAZYFREE_LAZY_EVICTION);
- rewriteConfigYesNoOption(state,"lazyfree-lazy-expire",server.lazyfree_lazy_expire,CONFIG_DEFAULT_LAZYFREE_LAZY_EXPIRE);
- rewriteConfigYesNoOption(state,"lazyfree-lazy-server-del",server.lazyfree_lazy_server_del,CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL);
- rewriteConfigYesNoOption(state,"slave-lazy-flush",server.repl_slave_lazy_flush,CONFIG_DEFAULT_SLAVE_LAZY_FLUSH);
+ rewriteConfigNumericalOption(state,"rdb-key-save-delay",server.rdb_key_save_delay,CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY);
+ rewriteConfigNumericalOption(state,"key-load-delay",server.key_load_delay,CONFIG_DEFAULT_KEY_LOAD_DELAY);
+#ifdef USE_OPENSSL
+ rewriteConfigYesNoOption(state,"tls-cluster",server.tls_cluster,0);
+ rewriteConfigYesNoOption(state,"tls-replication",server.tls_replication,0);
+ rewriteConfigYesNoOption(state,"tls-auth-clients",server.tls_auth_clients,1);
+ rewriteConfigStringOption(state,"tls-cert-file",server.tls_ctx_config.cert_file,NULL);
+ rewriteConfigStringOption(state,"tls-key-file",server.tls_ctx_config.key_file,NULL);
+ rewriteConfigStringOption(state,"tls-dh-params-file",server.tls_ctx_config.dh_params_file,NULL);
+ rewriteConfigStringOption(state,"tls-ca-cert-file",server.tls_ctx_config.ca_cert_file,NULL);
+ rewriteConfigStringOption(state,"tls-ca-cert-dir",server.tls_ctx_config.ca_cert_dir,NULL);
+ rewriteConfigStringOption(state,"tls-protocols",server.tls_ctx_config.protocols,NULL);
+ rewriteConfigStringOption(state,"tls-ciphers",server.tls_ctx_config.ciphers,NULL);
+ rewriteConfigStringOption(state,"tls-ciphersuites",server.tls_ctx_config.ciphersuites,NULL);
+ rewriteConfigYesNoOption(state,"tls-prefer-server-ciphers",server.tls_ctx_config.prefer_server_ciphers,0);
+#endif
/* Rewrite Sentinel config if in Sentinel mode. */
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
@@ -2096,10 +2405,10 @@ void configCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"get <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
-"set <parameter> <value> -- Set parameter to value.",
-"resetstat -- Reset statistics reported by INFO.",
-"rewrite -- Rewrite the configuration file.",
+"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.",
+"SET <parameter> <value> -- Set parameter to value.",
+"RESETSTAT -- Reset statistics reported by INFO.",
+"REWRITE -- Rewrite the configuration file.",
NULL
};
addReplyHelp(c, help);
@@ -2124,8 +2433,7 @@ NULL
addReply(c,shared.ok);
}
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CONFIG HELP",
- (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
return;
}
}
diff --git a/src/config.h b/src/config.h
index c23f1c789..efa9d11f2 100644
--- a/src/config.h
+++ b/src/config.h
@@ -62,7 +62,9 @@
#endif
/* Test for backtrace() */
-#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__))
+#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
+ defined(__FreeBSD__) || (defined(__OpenBSD__) && defined(USE_BACKTRACE))\
+ || defined(__DragonFly__)
#define HAVE_BACKTRACE 1
#endif
@@ -87,11 +89,11 @@
#endif
#endif
-/* Define aof_fsync to fdatasync() in Linux and fsync() for all the rest */
+/* Define redis_fsync to fdatasync() in Linux and fsync() for all the rest */
#ifdef __linux__
-#define aof_fsync fdatasync
+#define redis_fsync fdatasync
#else
-#define aof_fsync fsync
+#define redis_fsync fsync
#endif
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
diff --git a/src/connection.c b/src/connection.c
new file mode 100644
index 000000000..58d86c31b
--- /dev/null
+++ b/src/connection.c
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2019, Redis Labs
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "server.h"
+#include "connhelpers.h"
+
+/* The connections module provides a lean abstraction of network connections
+ * to avoid direct socket and async event management across the Redis code base.
+ *
+ * It does NOT provide advanced connection features commonly found in similar
+ * libraries such as complete in/out buffer management, throttling, etc. These
+ * functions remain in networking.c.
+ *
+ * The primary goal is to allow transparent handling of TCP and TLS based
+ * connections. To do so, connections have the following properties:
+ *
+ * 1. A connection may live before its corresponding socket exists. This
+ * allows various context and configuration setting to be handled before
+ * establishing the actual connection.
+ * 2. The caller may register/unregister logical read/write handlers to be
+ * called when the connection has data to read from/can accept writes.
+ * These logical handlers may or may not correspond to actual AE events,
+ * depending on the implementation (for TCP they are; for TLS they aren't).
+ */
+
+ConnectionType CT_Socket;
+
+/* When a connection is created we must know its type already, but the
+ * underlying socket may or may not exist:
+ *
+ * - For accepted connections, it exists as we do not model the listen/accept
+ * part; So caller calls connCreateSocket() followed by connAccept().
+ * - For outgoing connections, the socket is created by the connection module
+ * itself; So caller calls connCreateSocket() followed by connConnect(),
+ * which registers a connect callback that fires on connected/error state
+ * (and after any transport level handshake was done).
+ *
+ * NOTE: An earlier version relied on connections being part of other structs
+ * and not independently allocated. This could lead to further optimizations
+ * like using container_of(), etc. However it was discontinued in favor of
+ * this approach for these reasons:
+ *
+ * 1. In some cases conns are created/handled outside the context of the
+ * containing struct, in which case it gets a bit awkward to copy them.
+ * 2. Future implementations may wish to allocate arbitrary data for the
+ * connection.
+ * 3. The container_of() approach is anyway risky because connections may
+ * be embedded in different structs, not just client.
+ */
+
+connection *connCreateSocket() {
+ connection *conn = zcalloc(sizeof(connection));
+ conn->type = &CT_Socket;
+ conn->fd = -1;
+
+ return conn;
+}
+
+/* Create a new socket-type connection that is already associated with
+ * an accepted connection.
+ *
+ * The socket is not read for I/O until connAccept() was called and
+ * invoked the connection-level accept handler.
+ */
+connection *connCreateAcceptedSocket(int fd) {
+ connection *conn = connCreateSocket();
+ conn->fd = fd;
+ conn->state = CONN_STATE_ACCEPTING;
+ return conn;
+}
+
+static int connSocketConnect(connection *conn, const char *addr, int port, const char *src_addr,
+ ConnectionCallbackFunc connect_handler) {
+ int fd = anetTcpNonBlockBestEffortBindConnect(NULL,addr,port,src_addr);
+ if (fd == -1) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = errno;
+ return C_ERR;
+ }
+
+ conn->fd = fd;
+ conn->state = CONN_STATE_CONNECTING;
+
+ conn->conn_handler = connect_handler;
+ aeCreateFileEvent(server.el, conn->fd, AE_WRITABLE,
+ conn->type->ae_handler, conn);
+
+ return C_OK;
+}
+
+/* Returns true if a write handler is registered */
+int connHasWriteHandler(connection *conn) {
+ return conn->write_handler != NULL;
+}
+
+/* Returns true if a read handler is registered */
+int connHasReadHandler(connection *conn) {
+ return conn->read_handler != NULL;
+}
+
+/* Associate a private data pointer with the connection */
+void connSetPrivateData(connection *conn, void *data) {
+ conn->private_data = data;
+}
+
+/* Get the associated private data pointer */
+void *connGetPrivateData(connection *conn) {
+ return conn->private_data;
+}
+
+/* ------ Pure socket connections ------- */
+
+/* A very incomplete list of implementation-specific calls. Much of the above shall
+ * move here as we implement additional connection types.
+ */
+
+/* Close the connection and free resources. */
+static void connSocketClose(connection *conn) {
+ if (conn->fd != -1) {
+ aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
+ aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+ close(conn->fd);
+ conn->fd = -1;
+ }
+
+ /* If called from within a handler, schedule the close but
+ * keep the connection until the handler returns.
+ */
+ if (conn->flags & CONN_FLAG_IN_HANDLER) {
+ conn->flags |= CONN_FLAG_CLOSE_SCHEDULED;
+ return;
+ }
+
+ zfree(conn);
+}
+
+static int connSocketWrite(connection *conn, const void *data, size_t data_len) {
+ int ret = write(conn->fd, data, data_len);
+ if (ret < 0 && errno != EAGAIN) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ }
+
+ return ret;
+}
+
+static int connSocketRead(connection *conn, void *buf, size_t buf_len) {
+ int ret = read(conn->fd, buf, buf_len);
+ if (!ret) {
+ conn->state = CONN_STATE_CLOSED;
+ } else if (ret < 0 && errno != EAGAIN) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ }
+
+ return ret;
+}
+
+static int connSocketAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
+ if (conn->state != CONN_STATE_ACCEPTING) return C_ERR;
+ conn->state = CONN_STATE_CONNECTED;
+ if (!callHandler(conn, accept_handler)) return C_ERR;
+ return C_OK;
+}
+
+/* Register a write handler, to be called when the connection is writable.
+ * If NULL, the existing handler is removed.
+ *
+ * The barrier flag indicates a write barrier is requested, resulting with
+ * CONN_FLAG_WRITE_BARRIER set. This will ensure that the write handler is
+ * always called before and not after the read handler in a single event
+ * loop.
+ */
+static int connSocketSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
+ if (func == conn->write_handler) return C_OK;
+
+ conn->write_handler = func;
+ if (barrier)
+ conn->flags |= CONN_FLAG_WRITE_BARRIER;
+ else
+ conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
+ if (!conn->write_handler)
+ aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+ else
+ if (aeCreateFileEvent(server.el,conn->fd,AE_WRITABLE,
+ conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
+ return C_OK;
+}
+
+/* Register a read handler, to be called when the connection is readable.
+ * If NULL, the existing handler is removed.
+ */
+static int connSocketSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
+ if (func == conn->read_handler) return C_OK;
+
+ conn->read_handler = func;
+ if (!conn->read_handler)
+ aeDeleteFileEvent(server.el,conn->fd,AE_READABLE);
+ else
+ if (aeCreateFileEvent(server.el,conn->fd,
+ AE_READABLE,conn->type->ae_handler,conn) == AE_ERR) return C_ERR;
+ return C_OK;
+}
+
+static const char *connSocketGetLastError(connection *conn) {
+ return strerror(conn->last_errno);
+}
+
+static void connSocketEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask)
+{
+ UNUSED(el);
+ UNUSED(fd);
+ connection *conn = clientData;
+
+ if (conn->state == CONN_STATE_CONNECTING &&
+ (mask & AE_WRITABLE) && conn->conn_handler) {
+
+ if (connGetSocketError(conn)) {
+ conn->last_errno = errno;
+ conn->state = CONN_STATE_ERROR;
+ } else {
+ conn->state = CONN_STATE_CONNECTED;
+ }
+
+ if (!conn->write_handler) aeDeleteFileEvent(server.el,conn->fd,AE_WRITABLE);
+
+ if (!callHandler(conn, conn->conn_handler)) return;
+ conn->conn_handler = NULL;
+ }
+
+ /* Normally we execute the readable event first, and the writable
+ * event later. This is useful as sometimes we may be able
+ * to serve the reply of a query immediately after processing the
+ * query.
+ *
+ * However if WRITE_BARRIER is set in the mask, our application is
+ * asking us to do the reverse: never fire the writable event
+ * after the readable. In such a case, we invert the calls.
+ * This is useful when, for instance, we want to do things
+ * in the beforeSleep() hook, like fsync'ing a file to disk,
+ * before replying to a client. */
+ int invert = conn->flags & CONN_FLAG_WRITE_BARRIER;
+
+ int call_write = (mask & AE_WRITABLE) && conn->write_handler;
+ int call_read = (mask & AE_READABLE) && conn->read_handler;
+
+ /* Handle normal I/O flows */
+ if (!invert && call_read) {
+ if (!callHandler(conn, conn->read_handler)) return;
+ }
+ /* Fire the writable event. */
+ if (call_write) {
+ if (!callHandler(conn, conn->write_handler)) return;
+ }
+ /* If we have to invert the call, fire the readable event now
+ * after the writable one. */
+ if (invert && call_read) {
+ if (!callHandler(conn, conn->read_handler)) return;
+ }
+}
+
+static int connSocketBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
+ int fd = anetTcpNonBlockConnect(NULL,addr,port);
+ if (fd == -1) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = errno;
+ return C_ERR;
+ }
+
+ if ((aeWait(fd, AE_WRITABLE, timeout) & AE_WRITABLE) == 0) {
+ conn->state = CONN_STATE_ERROR;
+ conn->last_errno = ETIMEDOUT;
+ }
+
+ conn->fd = fd;
+ conn->state = CONN_STATE_CONNECTED;
+ return C_OK;
+}
+
+/* Connection-based versions of syncio.c functions.
+ * NOTE: This should ideally be refactored out in favor of pure async work.
+ */
+
+static ssize_t connSocketSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncWrite(conn->fd, ptr, size, timeout);
+}
+
+static ssize_t connSocketSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncRead(conn->fd, ptr, size, timeout);
+}
+
+static ssize_t connSocketSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return syncReadLine(conn->fd, ptr, size, timeout);
+}
+
+
+ConnectionType CT_Socket = {
+ .ae_handler = connSocketEventHandler,
+ .close = connSocketClose,
+ .write = connSocketWrite,
+ .read = connSocketRead,
+ .accept = connSocketAccept,
+ .connect = connSocketConnect,
+ .set_write_handler = connSocketSetWriteHandler,
+ .set_read_handler = connSocketSetReadHandler,
+ .get_last_error = connSocketGetLastError,
+ .blocking_connect = connSocketBlockingConnect,
+ .sync_write = connSocketSyncWrite,
+ .sync_read = connSocketSyncRead,
+ .sync_readline = connSocketSyncReadLine
+};
+
+
+int connGetSocketError(connection *conn) {
+ int sockerr = 0;
+ socklen_t errlen = sizeof(sockerr);
+
+ if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
+ sockerr = errno;
+ return sockerr;
+}
+
+int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
+ return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port);
+}
+
+int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
+ return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
+}
+
+int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
+ return anetSockName(conn->fd, ip, ip_len, port);
+}
+
+int connBlock(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetBlock(NULL, conn->fd);
+}
+
+int connNonBlock(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetNonBlock(NULL, conn->fd);
+}
+
+int connEnableTcpNoDelay(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetEnableTcpNoDelay(NULL, conn->fd);
+}
+
+int connDisableTcpNoDelay(connection *conn) {
+ if (conn->fd == -1) return C_ERR;
+ return anetDisableTcpNoDelay(NULL, conn->fd);
+}
+
+int connKeepAlive(connection *conn, int interval) {
+ if (conn->fd == -1) return C_ERR;
+ return anetKeepAlive(NULL, conn->fd, interval);
+}
+
+int connSendTimeout(connection *conn, long long ms) {
+ return anetSendTimeout(NULL, conn->fd, ms);
+}
+
+int connRecvTimeout(connection *conn, long long ms) {
+ return anetRecvTimeout(NULL, conn->fd, ms);
+}
+
+int connGetState(connection *conn) {
+ return conn->state;
+}
+
+/* Return a text that describes the connection, suitable for inclusion
+ * in CLIENT LIST and similar outputs.
+ *
+ * For sockets, we always return "fd=<fdnum>" to maintain compatibility.
+ */
+const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
+ snprintf(buf, buf_len-1, "fd=%i", conn->fd);
+ return buf;
+}
+
diff --git a/src/connection.h b/src/connection.h
new file mode 100644
index 000000000..97622f8d6
--- /dev/null
+++ b/src/connection.h
@@ -0,0 +1,220 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __REDIS_CONNECTION_H
+#define __REDIS_CONNECTION_H
+
+#define CONN_INFO_LEN 32
+
+struct aeEventLoop;
+typedef struct connection connection;
+
+typedef enum {
+ CONN_STATE_NONE = 0,
+ CONN_STATE_CONNECTING,
+ CONN_STATE_ACCEPTING,
+ CONN_STATE_CONNECTED,
+ CONN_STATE_CLOSED,
+ CONN_STATE_ERROR
+} ConnectionState;
+
+#define CONN_FLAG_IN_HANDLER (1<<0) /* A handler execution is in progress */
+#define CONN_FLAG_CLOSE_SCHEDULED (1<<1) /* Closed scheduled by a handler */
+#define CONN_FLAG_WRITE_BARRIER (1<<2) /* Write barrier requested */
+
+typedef void (*ConnectionCallbackFunc)(struct connection *conn);
+
+typedef struct ConnectionType {
+ void (*ae_handler)(struct aeEventLoop *el, int fd, void *clientData, int mask);
+ int (*connect)(struct connection *conn, const char *addr, int port, const char *source_addr, ConnectionCallbackFunc connect_handler);
+ int (*write)(struct connection *conn, const void *data, size_t data_len);
+ int (*read)(struct connection *conn, void *buf, size_t buf_len);
+ void (*close)(struct connection *conn);
+ int (*accept)(struct connection *conn, ConnectionCallbackFunc accept_handler);
+ int (*set_write_handler)(struct connection *conn, ConnectionCallbackFunc handler, int barrier);
+ int (*set_read_handler)(struct connection *conn, ConnectionCallbackFunc handler);
+ const char *(*get_last_error)(struct connection *conn);
+ int (*blocking_connect)(struct connection *conn, const char *addr, int port, long long timeout);
+ ssize_t (*sync_write)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+ ssize_t (*sync_read)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+ ssize_t (*sync_readline)(struct connection *conn, char *ptr, ssize_t size, long long timeout);
+} ConnectionType;
+
+struct connection {
+ ConnectionType *type;
+ ConnectionState state;
+ int flags;
+ int last_errno;
+ void *private_data;
+ ConnectionCallbackFunc conn_handler;
+ ConnectionCallbackFunc write_handler;
+ ConnectionCallbackFunc read_handler;
+ int fd;
+};
+
+/* The connection module does not deal with listening and accepting sockets,
+ * so we assume we have a socket when an incoming connection is created.
+ *
+ * The fd supplied should therefore be associated with an already accept()ed
+ * socket.
+ *
+ * connAccept() may directly call accept_handler(), or return and call it
+ * at a later time. This behavior is a bit awkward but aims to reduce the need
+ * to wait for the next event loop, if no additional handshake is required.
+ */
+
+static inline int connAccept(connection *conn, ConnectionCallbackFunc accept_handler) {
+ return conn->type->accept(conn, accept_handler);
+}
+
+/* Establish a connection. The connect_handler will be called when the connection
+ * is established, or if an error has occured.
+ *
+ * The connection handler will be responsible to set up any read/write handlers
+ * as needed.
+ *
+ * If C_ERR is returned, the operation failed and the connection handler shall
+ * not be expected.
+ */
+static inline int connConnect(connection *conn, const char *addr, int port, const char *src_addr,
+ ConnectionCallbackFunc connect_handler) {
+ return conn->type->connect(conn, addr, port, src_addr, connect_handler);
+}
+
+/* Blocking connect.
+ *
+ * NOTE: This is implemented in order to simplify the transition to the abstract
+ * connections, but should probably be refactored out of cluster.c and replication.c,
+ * in favor of a pure async implementation.
+ */
+static inline int connBlockingConnect(connection *conn, const char *addr, int port, long long timeout) {
+ return conn->type->blocking_connect(conn, addr, port, timeout);
+}
+
+/* Write to connection, behaves the same as write(2).
+ *
+ * Like write(2), a short write is possible. A -1 return indicates an error.
+ *
+ * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
+ * connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
+ */
+static inline int connWrite(connection *conn, const void *data, size_t data_len) {
+ return conn->type->write(conn, data, data_len);
+}
+
+/* Read from the connection, behaves the same as read(2).
+ *
+ * Like read(2), a short read is possible. A return value of 0 will indicate the
+ * connection was closed, and -1 will indicate an error.
+ *
+ * The caller should NOT rely on errno. Testing for an EAGAIN-like condition, use
+ * connGetState() to see if the connection state is still CONN_STATE_CONNECTED.
+ */
+static inline int connRead(connection *conn, void *buf, size_t buf_len) {
+ return conn->type->read(conn, buf, buf_len);
+}
+
+/* Register a write handler, to be called when the connection is writable.
+ * If NULL, the existing handler is removed.
+ */
+static inline int connSetWriteHandler(connection *conn, ConnectionCallbackFunc func) {
+ return conn->type->set_write_handler(conn, func, 0);
+}
+
+/* Register a read handler, to be called when the connection is readable.
+ * If NULL, the existing handler is removed.
+ */
+static inline int connSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
+ return conn->type->set_read_handler(conn, func);
+}
+
+/* Set a write handler, and possibly enable a write barrier, this flag is
+ * cleared when write handler is changed or removed.
+ * With barroer enabled, we never fire the event if the read handler already
+ * fired in the same event loop iteration. Useful when you want to persist
+ * things to disk before sending replies, and want to do that in a group fashion. */
+static inline int connSetWriteHandlerWithBarrier(connection *conn, ConnectionCallbackFunc func, int barrier) {
+ return conn->type->set_write_handler(conn, func, barrier);
+}
+
+static inline void connClose(connection *conn) {
+ conn->type->close(conn);
+}
+
+/* Returns the last error encountered by the connection, as a string. If no error,
+ * a NULL is returned.
+ */
+static inline const char *connGetLastError(connection *conn) {
+ return conn->type->get_last_error(conn);
+}
+
+static inline ssize_t connSyncWrite(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_write(conn, ptr, size, timeout);
+}
+
+static inline ssize_t connSyncRead(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_read(conn, ptr, size, timeout);
+}
+
+static inline ssize_t connSyncReadLine(connection *conn, char *ptr, ssize_t size, long long timeout) {
+ return conn->type->sync_readline(conn, ptr, size, timeout);
+}
+
+connection *connCreateSocket();
+connection *connCreateAcceptedSocket(int fd);
+
+connection *connCreateTLS();
+connection *connCreateAcceptedTLS(int fd, int require_auth);
+
+void connSetPrivateData(connection *conn, void *data);
+void *connGetPrivateData(connection *conn);
+int connGetState(connection *conn);
+int connHasWriteHandler(connection *conn);
+int connHasReadHandler(connection *conn);
+int connGetSocketError(connection *conn);
+
+/* anet-style wrappers to conns */
+int connBlock(connection *conn);
+int connNonBlock(connection *conn);
+int connEnableTcpNoDelay(connection *conn);
+int connDisableTcpNoDelay(connection *conn);
+int connKeepAlive(connection *conn, int interval);
+int connSendTimeout(connection *conn, long long ms);
+int connRecvTimeout(connection *conn, long long ms);
+int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port);
+int connFormatPeer(connection *conn, char *buf, size_t buf_len);
+int connSockName(connection *conn, char *ip, size_t ip_len, int *port);
+const char *connGetInfo(connection *conn, char *buf, size_t buf_len);
+
+/* Helpers for tls special considerations */
+int tlsHasPendingData();
+void tlsProcessPendingData();
+
+#endif /* __REDIS_CONNECTION_H */
diff --git a/src/connhelpers.h b/src/connhelpers.h
new file mode 100644
index 000000000..f237c9b1d
--- /dev/null
+++ b/src/connhelpers.h
@@ -0,0 +1,85 @@
+
+/*
+ * Copyright (c) 2019, Redis Labs
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __REDIS_CONNHELPERS_H
+#define __REDIS_CONNHELPERS_H
+
+#include "connection.h"
+
+/* These are helper functions that are common to different connection
+ * implementations (currently sockets in connection.c and TLS in tls.c).
+ *
+ * Currently helpers implement the mechanisms for invoking connection
+ * handlers, tracking in-handler states and dealing with deferred
+ * destruction (if invoked by a handler).
+ */
+
+/* Called whenever a handler is invoked on a connection and sets the
+ * CONN_FLAG_IN_HANDLER flag to indicate we're in a handler context.
+ *
+ * An attempt to close a connection while CONN_FLAG_IN_HANDLER is
+ * set will result with deferred close, i.e. setting the CONN_FLAG_CLOSE_SCHEDULED
+ * instead of destructing it.
+ */
+static inline void enterHandler(connection *conn) {
+ conn->flags |= CONN_FLAG_IN_HANDLER;
+}
+
+/* Called whenever a handler returns. This unsets the CONN_FLAG_IN_HANDLER
+ * flag and performs actual close/destruction if a deferred close was
+ * scheduled by the handler.
+ */
+static inline int exitHandler(connection *conn) {
+ conn->flags &= ~CONN_FLAG_IN_HANDLER;
+ if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
+ connClose(conn);
+ return 0;
+ }
+ return 1;
+}
+
+/* Helper for connection implementations to call handlers:
+ * 1. Mark the handler in use.
+ * 2. Execute the handler (if set).
+ * 3. Mark the handler as NOT in use and perform deferred close if was
+ * requested by the handler at any time.
+ */
+static inline int callHandler(connection *conn, ConnectionCallbackFunc handler) {
+ conn->flags |= CONN_FLAG_IN_HANDLER;
+ if (handler) handler(conn);
+ conn->flags &= ~CONN_FLAG_IN_HANDLER;
+ if (conn->flags & CONN_FLAG_CLOSE_SCHEDULED) {
+ connClose(conn);
+ return 0;
+ }
+ return 1;
+}
+
+#endif /* __REDIS_CONNHELPERS_H */
diff --git a/src/crc16_slottable.h b/src/crc16_slottable.h
new file mode 100644
index 000000000..652aea9e1
--- /dev/null
+++ b/src/crc16_slottable.h
@@ -0,0 +1,835 @@
+#ifndef _CRC16_TABLE_H__
+#define _CRC16_TABLE_H__
+
+/* A table of the shortest possible alphanumeric string that is mapped by redis' crc16
+ * to any given redis cluster slot.
+ *
+ * The array indexes are slot numbers, so that given a desired slot, this string is guaranteed
+ * to make redis cluster route a request to the shard holding this slot
+ */
+
+const char *crc16_slot_table[] = {
+"06S", "Qi", "5L5", "4Iu", "4gY", "460", "1Y7", "1LV", "0QG", "ru", "7Ok", "4ji", "4DE", "65n", "2JH", "I8", "F9", "SX", "7nF", "4KD",
+"4eh", "6PK", "2ke", "1Ng", "0Sv", "4L", "491", "4hX", "4Ft", "5C4", "2Hy", "09R", "021", "0cX", "4Xv", "6mU", "6Cy", "42R", "0Mt", "nF",
+"cv", "1Pe", "5kK", "6NI", "74L", "4UF", "0nh", "MZ", "2TJ", "0ai", "4ZG", "6od", "6AH", "40c", "0OE", "lw", "aG", "0Bu", "5iz", "6Lx",
+"5R7", "4Ww", "0lY", "Ok", "5n3", "4ks", "8YE", "7g", "2KR", "1nP", "714", "64t", "69D", "4Ho", "07I", "Ps", "2hN", "1ML", "4fC", "7CA",
+"avs", "4iB", "0Rl", "5V", "2Ic", "08H", "4Gn", "66E", "aUo", "b4e", "05x", "RB", "8f", "8VD", "4dr", "5a2", "4zp", "6OS", "bl", "355",
+"0or", "1j2", "75V", "bno", "4Yl", "6lO", "Ap", "0bB", "0Ln", "2yM", "6Bc", "43H", "4xA", "6Mb", "22D", "14", "0mC", "Nq", "6cN", "4Vm",
+"ban", "aDl", "CA", "14Z", "8GG", "mm", "549", "41y", "53t", "464", "1Y3", "1LR", "06W", "Qm", "5L1", "4Iq", "4DA", "65j", "2JL", "1oN",
+"0QC", "6y", "7Oo", "4jm", "4el", "6PO", "9x", "1Nc", "04f", "2EM", "7nB", "bqs", "4Fp", "5C0", "d6F", "09V", "0Sr", "4H", "495", "bRo",
+"aio", "42V", "0Mp", "nB", "025", "17u", "4Xr", "6mQ", "74H", "4UB", "0nl", "3Kn", "cr", "1Pa", "5kO", "6NM", "6AL", "40g", "0OA", "ls",
+"2TN", "0am", "4ZC", "aEr", "5R3", "4Ws", "18t", "Oo", "aC", "0Bq", "bCl", "afn", "2KV", "1nT", "5Uz", "64p", "5n7", "4kw", "0PY", "7c",
+"2hJ", "1MH", "4fG", "6Sd", "7mi", "4Hk", "07M", "Pw", "2Ig", "08L", "4Gj", "66A", "7LD", "4iF", "0Rh", "5R", "8b", "1Oy", "4dv", "5a6",
+"7oX", "4JZ", "0qt", "RF", "0ov", "LD", "4A9", "4TX", "4zt", "6OW", "bh", "0AZ", "z9", "oX", "6Bg", "43L", "4Yh", "6lK", "At", "0bF",
+"0mG", "Nu", "6cJ", "4Vi", "4xE", "6Mf", "2vH", "10", "8GC", "mi", "5p5", "4uu", "5Kx", "4N8", "CE", "1pV", "0QO", "6u", "7Oc", "4ja",
+"4DM", "65f", "3Za", "I0", "0rS", "Qa", "68V", "b7F", "4gQ", "468", "dSo", "285", "274", "4D", "499", "4hP", "b8G", "67W", "0h3", "09Z",
+"F1", "SP", "7nN", "4KL", "51I", "6PC", "9t", "1No", "21g", "1Pm", "5kC", "6NA", "74D", "4UN", "X3", "MR", "029", "0cP", "bbM", "79t",
+"4c3", "42Z", "8Dd", "nN", "aO", "8Ke", "4yS", "4l2", "76u", "635", "0lQ", "Oc", "BS", "W2", "4ZO", "6ol", "7Qa", "40k", "0OM", "2zn",
+"69L", "4Hg", "07A", "2Fj", "2hF", "k6", "4fK", "6Sh", "7Ny", "6K9", "0PU", "7o", "2KZ", "1nX", "4EW", "4P6", "7oT", "4JV", "05p", "RJ",
+"8n", "1Ou", "4dz", "6QY", "7LH", "4iJ", "d7", "qV", "2Ik", "1li", "4Gf", "66M", "4Yd", "6lG", "Ax", "0bJ", "z5", "oT", "6Bk", "4wH",
+"4zx", "aeI", "bd", "0AV", "0oz", "LH", "4A5", "4TT", "5Kt", "4N4", "CI", "14R", "0NW", "me", "541", "41q", "4xI", "6Mj", "22L", "u4",
+"0mK", "Ny", "6cF", "4Ve", "4DI", "65b", "2JD", "I4", "0QK", "6q", "7Og", "4je", "4gU", "4r4", "2iX", "1LZ", "0rW", "Qe", "5L9", "4Iy",
+"4Fx", "5C8", "0h7", "1mw", "0Sz", "pH", "7MV", "4hT", "4ed", "6PG", "9p", "1Nk", "F5", "ST", "7nJ", "4KH", "7pH", "4UJ", "X7", "MV",
+"cz", "1Pi", "5kG", "6NE", "4c7", "4vV", "0Mx", "nJ", "0v5", "0cT", "4Xz", "6mY", "6bX", "5GZ", "0lU", "Og", "aK", "0By", "4yW", "4l6",
+"6AD", "40o", "0OI", "2zj", "BW", "W6", "4ZK", "6oh", "2hB", "k2", "4fO", "6Sl", "69H", "4Hc", "07E", "2Fn", "d5e", "83m", "4ES", "4P2",
+"a0F", "bQL", "0PQ", "7k", "8j", "1Oq", "50W", "hbv", "7oP", "4JR", "05t", "RN", "2Io", "08D", "4Gb", "66I", "7LL", "4iN", "d3", "5Z",
+"z1", "oP", "6Bo", "43D", "5IA", "6lC", "2Wm", "0bN", "8ff", "LL", "4A1", "4TP", "cPn", "aeM", "0T3", "0AR", "0NS", "ma", "545", "41u",
+"5Kp", "4N0", "CM", "14V", "0mO", "2Xl", "6cB", "4Va", "4xM", "6Mn", "22H", "18", "04s", "SI", "7nW", "4KU", "4ey", "6PZ", "9m", "1Nv",
+"e4", "pU", "7MK", "4hI", "4Fe", "67N", "2Hh", "09C", "06B", "Qx", "68O", "4Id", "4gH", "6Rk", "2iE", "j5", "0QV", "6l", "5o8", "4jx",
+"4DT", "4Q5", "2JY", "82j", "BJ", "0ax", "4ZV", "4O7", "552", "40r", "0OT", "lf", "aV", "t7", "4yJ", "6Li", "6bE", "4Wf", "0lH", "Oz",
+"2Vj", "0cI", "4Xg", "6mD", "6Ch", "42C", "0Me", "nW", "cg", "1Pt", "5kZ", "6NX", "7pU", "4UW", "0ny", "MK", "7LQ", "4iS", "267", "5G",
+"0i0", "08Y", "b9D", "66T", "7oM", "4JO", "G2", "RS", "8w", "1Ol", "4dc", "7Aa", "atS", "4kb", "0PL", "7v", "2KC", "H3", "4EN", "64e",
+"69U", "b6E", "07X", "Pb", "dRl", "296", "4fR", "4s3", "4xP", "4m1", "22U", "8Jf", "0mR", "0x3", "77v", "626", "5Km", "6no", "CP", "V1",
+"0NN", "3kL", "7Pb", "41h", "4za", "6OB", "20d", "0AO", "Y0", "LQ", "6an", "4TM", "bcN", "78w", "Aa", "0bS", "8Eg", "oM", "4b0", "43Y",
+"51T", "azL", "9i", "1Nr", "04w", "SM", "7nS", "4KQ", "4Fa", "67J", "2Hl", "09G", "e0", "4Y", "7MO", "4hM", "4gL", "6Ro", "2iA", "j1",
+"06F", "2Gm", "68K", "5YA", "4DP", "4Q1", "d4f", "82n", "0QR", "6h", "a1E", "bPO", "556", "40v", "0OP", "lb", "BN", "15U", "4ZR", "4O3",
+"6bA", "4Wb", "0lL", "2Yo", "aR", "t3", "4yN", "6Lm", "6Cl", "42G", "0Ma", "nS", "2Vn", "0cM", "4Xc", "79i", "74Y", "4US", "8ge", "MO",
+"cc", "1Pp", "bAL", "adN", "0i4", "1lt", "5WZ", "66P", "7LU", "4iW", "0Ry", "5C", "8s", "1Oh", "4dg", "6QD", "7oI", "4JK", "G6", "RW",
+"2KG", "H7", "4EJ", "64a", "7Nd", "4kf", "0PH", "7r", "1X8", "1MY", "4fV", "4s7", "69Q", "4Hz", "0sT", "Pf", "0mV", "Nd", "5S8", "4Vx",
+"4xT", "4m5", "22Q", "0Cz", "0NJ", "mx", "7Pf", "41l", "5Ki", "6nk", "CT", "V5", "Y4", "LU", "6aj", "4TI", "4ze", "6OF", "by", "0AK",
+"2l9", "oI", "4b4", "4wU", "4Yy", "6lZ", "Ae", "0bW", "0So", "4U", "7MC", "4hA", "4Fm", "67F", "3XA", "09K", "0ps", "SA", "aTl", "b5f",
+"4eq", "6PR", "9e", "8WG", "8XF", "6d", "5o0", "4jp", "707", "65w", "1z2", "1oS", "06J", "Qp", "68G", "4Il", "53i", "6Rc", "2iM", "1LO",
+"23G", "07", "4yB", "6La", "6bM", "4Wn", "18i", "Or", "BB", "0ap", "c4D", "aEo", "5q2", "40z", "8FD", "ln", "co", "346", "5kR", "6NP",
+"74U", "bol", "0nq", "MC", "2Vb", "0cA", "4Xo", "6mL", "7SA", "42K", "0Mm", "2xN", "7oE", "4JG", "05a", "2DJ", "2jf", "1Od", "4dk", "6QH",
+"482", "5yz", "0Ru", "5O", "0i8", "08Q", "4Gw", "5B7", "5M6", "4Hv", "07P", "Pj", "1X4", "1MU", "4fZ", "473", "7Nh", "4kj", "0PD", "sv",
+"2KK", "1nI", "4EF", "64m", "5Ke", "6ng", "CX", "V9", "0NF", "mt", "7Pj", "4uh", "4xX", "4m9", "1F6", "0Cv", "0mZ", "Nh", "5S4", "4Vt",
+"4Yu", "6lV", "Ai", "16r", "0Lw", "oE", "4b8", "43Q", "4zi", "6OJ", "bu", "0AG", "Y8", "LY", "6af", "4TE", "4Fi", "67B", "2Hd", "09O",
+"e8", "4Q", "7MG", "4hE", "4eu", "6PV", "9a", "1Nz", "0pw", "SE", "aTh", "4KY", "4DX", "4Q9", "1z6", "1oW", "0QZ", "rh", "5o4", "4jt",
+"4gD", "6Rg", "2iI", "j9", "06N", "Qt", "68C", "4Ih", "6bI", "4Wj", "0lD", "Ov", "aZ", "03", "4yF", "6Le", "5q6", "4tv", "0OX", "lj",
+"BF", "0at", "4ZZ", "6oy", "74Q", "5Ez", "0nu", "MG", "ck", "1Px", "5kV", "6NT", "6Cd", "42O", "0Mi", "2xJ", "2Vf", "0cE", "4Xk", "6mH",
+"2jb", "8VY", "4do", "6QL", "7oA", "4JC", "05e", "2DN", "d7E", "08U", "4Gs", "5B3", "486", "bSl", "0Rq", "5K", "1X0", "1MQ", "52w", "477",
+"5M2", "4Hr", "07T", "Pn", "2KO", "1nM", "4EB", "64i", "7Nl", "4kn", "8YX", "7z", "0NB", "mp", "7Pn", "41d", "5Ka", "6nc", "2UM", "14G",
+"19w", "Nl", "5S0", "4Vp", "bBo", "agm", "1F2", "0Cr", "0Ls", "oA", "ahl", "43U", "4Yq", "6lR", "Am", "16v", "0oo", "2ZL", "6ab", "4TA",
+"4zm", "6ON", "bq", "0AC", "2VY", "0cz", "4XT", "4M5", "570", "42p", "0MV", "nd", "cT", "v5", "5ki", "6Nk", "74n", "4Ud", "0nJ", "Mx",
+"By", "0aK", "4Ze", "6oF", "6Aj", "40A", "y4", "lU", "ae", "0BW", "4yy", "581", "4B4", "4WU", "18R", "OI", "06q", "QK", "7lU", "4IW",
+"53R", "6RX", "0I4", "1Lt", "g6", "rW", "7OI", "4jK", "4Dg", "65L", "2Jj", "1oh", "0pH", "Sz", "7nd", "4Kf", "4eJ", "6Pi", "2kG", "h7",
+"0ST", "4n", "7Mx", "4hz", "4FV", "4S7", "1x8", "09p", "4zR", "4o3", "bN", "8Hd", "0oP", "Lb", "75t", "604", "4YN", "6lm", "AR", "T3",
+"0LL", "2yo", "6BA", "43j", "4xc", "agR", "22f", "0CM", "0ma", "NS", "6cl", "4VO", "baL", "aDN", "Cc", "14x", "8Ge", "mO", "7PQ", "4uS",
+"7NS", "4kQ", "245", "7E", "0k2", "1nr", "coo", "64V", "69f", "4HM", "E0", "PQ", "2hl", "1Mn", "4fa", "6SB", "7Lb", "5yA", "0RN", "5t",
+"2IA", "J1", "4GL", "66g", "aUM", "b4G", "05Z", "0d3", "8D", "8Vf", "4dP", "459", "574", "42t", "0MR", "0X3", "dln", "17W", "4XP", "4M1",
+"74j", "5EA", "0nN", "3KL", "cP", "29", "5km", "6No", "6An", "40E", "y0", "lQ", "2Tl", "0aO", "4Za", "6oB", "4B0", "4WQ", "18V", "OM",
+"aa", "0BS", "bCN", "585", "53V", "axN", "0I0", "1Lp", "06u", "QO", "68x", "4IS", "4Dc", "65H", "2Jn", "1ol", "g2", "rS", "7OM", "4jO",
+"4eN", "6Pm", "9Z", "h3", "04D", "2Eo", "aTS", "4Kb", "4FR", "4S3", "d6d", "09t", "0SP", "4j", "a3G", "bRM", "0oT", "Lf", "6aY", "4Tz",
+"4zV", "4o7", "bJ", "0Ax", "0LH", "oz", "6BE", "43n", "4YJ", "6li", "AV", "T7", "0me", "NW", "6ch", "4VK", "4xg", "6MD", "22b", "0CI",
+"0Ny", "mK", "7PU", "4uW", "5KZ", "6nX", "Cg", "1pt", "0k6", "1nv", "4Ey", "64R", "7NW", "4kU", "241", "7A", "2hh", "1Mj", "4fe", "6SF",
+"69b", "4HI", "E4", "PU", "2IE", "J5", "4GH", "66c", "7Lf", "4id", "0RJ", "5p", "2jY", "8Vb", "4dT", "4q5", "5O8", "4Jx", "0qV", "Rd",
+"21E", "25", "5ka", "6Nc", "74f", "4Ul", "0nB", "Mp", "1f2", "0cr", "bbo", "79V", "578", "42x", "395", "nl", "am", "364", "4yq", "589",
+"76W", "bmn", "0ls", "OA", "Bq", "0aC", "4Zm", "6oN", "6Ab", "40I", "0Oo", "2zL", "0Qm", "6W", "7OA", "4jC", "4Do", "65D", "2Jb", "82Q",
+"06y", "QC", "68t", "b7d", "4gs", "5b3", "dSM", "8UE", "8ZD", "4f", "5m2", "4hr", "725", "67u", "1x0", "09x", "04H", "Sr", "7nl", "4Kn",
+"4eB", "6Pa", "9V", "1NM", "4YF", "6le", "AZ", "0bh", "0LD", "ov", "6BI", "43b", "4zZ", "6Oy", "bF", "0At", "0oX", "Lj", "5Q6", "4Tv",
+"5KV", "6nT", "Ck", "14p", "0Nu", "mG", "7PY", "41S", "4xk", "6MH", "22n", "0CE", "0mi", "2XJ", "6cd", "4VG", "69n", "4HE", "E8", "PY",
+"2hd", "1Mf", "4fi", "6SJ", "ath", "4kY", "0Pw", "7M", "2Kx", "1nz", "4Eu", "6pV", "5O4", "4Jt", "05R", "Rh", "8L", "1OW", "4dX", "451",
+"7Lj", "4ih", "0RF", "qt", "2II", "J9", "4GD", "66o", "74b", "4Uh", "0nF", "Mt", "cX", "21", "5ke", "6Ng", "5s4", "4vt", "0MZ", "nh",
+"1f6", "0cv", "4XX", "4M9", "4B8", "4WY", "0lw", "OE", "ai", "1Rz", "4yu", "6LV", "6Af", "40M", "y8", "lY", "Bu", "0aG", "4Zi", "6oJ",
+"4Dk", "6qH", "2Jf", "1od", "0Qi", "6S", "7OE", "4jG", "4gw", "5b7", "0I8", "1Lx", "0ru", "QG", "68p", "5Yz", "4FZ", "67q", "1x4", "1mU",
+"0SX", "4b", "5m6", "4hv", "4eF", "6Pe", "9R", "1NI", "04L", "Sv", "7nh", "4Kj", "8EX", "or", "6BM", "43f", "4YB", "6la", "2WO", "0bl",
+"8fD", "Ln", "5Q2", "4Tr", "cPL", "aeo", "bB", "0Ap", "0Nq", "mC", "ajn", "41W", "5KR", "6nP", "Co", "14t", "0mm", "2XN", "77I", "4VC",
+"4xo", "6ML", "22j", "0CA", "3xA", "1Mb", "4fm", "6SN", "69j", "4HA", "07g", "2FL", "d5G", "83O", "4Eq", "64Z", "a0d", "bQn", "0Ps", "7I",
+"8H", "1OS", "50u", "455", "5O0", "4Jp", "05V", "Rl", "2IM", "08f", "5Wa", "66k", "7Ln", "4il", "0RB", "5x", "Bh", "0aZ", "4Zt", "6oW",
+"4a9", "40P", "0Ov", "lD", "at", "0BF", "4yh", "6LK", "6bg", "4WD", "Z9", "OX", "2VH", "U8", "4XE", "6mf", "6CJ", "42a", "0MG", "nu",
+"cE", "1PV", "5kx", "4n8", "5P5", "4Uu", "8gC", "Mi", "04Q", "Sk", "5N7", "4Kw", "51r", "442", "9O", "1NT", "0SE", "pw", "7Mi", "4hk",
+"4FG", "67l", "2HJ", "09a", "3", "QZ", "68m", "4IF", "4gj", "6RI", "2ig", "1Le", "0Qt", "6N", "7OX", "4jZ", "4Dv", "5A6", "0j9", "1oy",
+"4xr", "6MQ", "22w", "377", "0mp", "NB", "77T", "blm", "5KO", "6nM", "Cr", "14i", "0Nl", "3kn", "ajs", "41J", "4zC", "aer", "20F", "36",
+"0oA", "Ls", "6aL", "4To", "bcl", "78U", "AC", "0bq", "386", "oo", "5r3", "4ws", "5l1", "4iq", "9Kf", "5e", "1y3", "1lR", "736", "66v",
+"7oo", "4Jm", "05K", "Rq", "8U", "1ON", "4dA", "6Qb", "7NB", "bQs", "0Pn", "7T", "2Ka", "1nc", "4El", "64G", "69w", "b6g", "07z", "1v2",
+"dRN", "8TF", "4fp", "5c0", "akm", "40T", "0Or", "1J2", "Bl", "15w", "4Zp", "6oS", "6bc", "5Ga", "0ln", "2YM", "ap", "0BB", "4yl", "6LO",
+"6CN", "42e", "0MC", "nq", "2VL", "0co", "4XA", "6mb", "5P1", "4Uq", "8gG", "Mm", "cA", "1PR", "bAn", "adl", "51v", "446", "9K", "1NP",
+"04U", "So", "5N3", "4Ks", "4FC", "67h", "2HN", "09e", "0SA", "ps", "7Mm", "4ho", "4gn", "6RM", "2ic", "1La", "7", "2GO", "68i", "4IB",
+"4Dr", "5A2", "d4D", "82L", "0Qp", "6J", "a1g", "bPm", "0mt", "NF", "6cy", "4VZ", "4xv", "6MU", "0V9", "0CX", "0Nh", "mZ", "7PD", "41N",
+"5KK", "6nI", "Cv", "14m", "0oE", "Lw", "6aH", "4Tk", "4zG", "6Od", "20B", "32", "0LY", "ok", "5r7", "4ww", "5Iz", "6lx", "AG", "0bu",
+"1y7", "1lV", "4GY", "4R8", "5l5", "4iu", "1Bz", "5a", "8Q", "i8", "4dE", "6Qf", "7ok", "4Ji", "05O", "Ru", "2Ke", "1ng", "4Eh", "64C",
+"7NF", "4kD", "f9", "7P", "2hy", "3m9", "4ft", "5c4", "69s", "4HX", "0sv", "PD", "23e", "0BN", "5iA", "6LC", "6bo", "4WL", "Z1", "OP",
+"0t3", "0aR", "c4f", "aEM", "4a1", "40X", "8Ff", "lL", "cM", "8Ig", "5kp", "4n0", "74w", "617", "0nS", "Ma", "3Fa", "U0", "4XM", "6mn",
+"6CB", "42i", "0MO", "2xl", "0SM", "4w", "7Ma", "4hc", "4FO", "67d", "2HB", "K2", "04Y", "Sc", "aTN", "b5D", "4eS", "4p2", "9G", "8We",
+"256", "6F", "7OP", "4jR", "cnl", "65U", "0j1", "1oq", "D3", "QR", "68e", "4IN", "4gb", "6RA", "2io", "1Lm", "5KG", "6nE", "Cz", "14a",
+"x7", "mV", "7PH", "41B", "4xz", "592", "0V5", "0CT", "0mx", "NJ", "4C7", "4VV", "4YW", "4L6", "AK", "0by", "0LU", "og", "563", "43s",
+"4zK", "6Oh", "bW", "w6", "0oI", "2Zj", "6aD", "4Tg", "7og", "4Je", "05C", "Ry", "2jD", "i4", "4dI", "6Qj", "5l9", "4iy", "0RW", "5m",
+"2IX", "08s", "4GU", "4R4", "7mV", "4HT", "07r", "PH", "0H7", "1Mw", "4fx", "5c8", "7NJ", "4kH", "f5", "sT", "2Ki", "1nk", "4Ed", "64O",
+"6bk", "4WH", "Z5", "OT", "ax", "0BJ", "4yd", "6LG", "4a5", "4tT", "0Oz", "lH", "Bd", "0aV", "4Zx", "aEI", "5P9", "4Uy", "0nW", "Me",
+"cI", "1PZ", "5kt", "4n4", "6CF", "42m", "0MK", "ny", "2VD", "U4", "4XI", "6mj", "4FK", "6sh", "2HF", "K6", "0SI", "4s", "7Me", "4hg",
+"4eW", "4p6", "9C", "1NX", "0pU", "Sg", "7ny", "6k9", "4Dz", "65Q", "0j5", "1ou", "0Qx", "6B", "7OT", "4jV", "4gf", "6RE", "2ik", "1Li",
+"D7", "QV", "68a", "4IJ", "x3", "mR", "7PL", "41F", "5KC", "6nA", "2Uo", "14e", "19U", "NN", "4C3", "4VR", "bBM", "596", "0V1", "0CP",
+"0LQ", "oc", "567", "43w", "4YS", "4L2", "AO", "16T", "0oM", "2Zn", "75i", "4Tc", "4zO", "6Ol", "bS", "w2", "8Y", "i0", "4dM", "6Qn",
+"7oc", "4Ja", "05G", "2Dl", "d7g", "08w", "4GQ", "4R0", "a2D", "bSN", "0RS", "5i", "0H3", "1Ms", "52U", "ayM", "7mR", "4HP", "07v", "PL",
+"2Km", "1no", "5UA", "64K", "7NN", "4kL", "f1", "7X", "5nw", "4k7", "fJ", "0Ex", "0kT", "Hf", "6eY", "4Pz", "5Mk", "6hi", "EV", "P7",
+"0HH", "kz", "6FE", "47n", "48o", "6ID", "26b", "0GI", "0ie", "JW", "6gh", "4RK", "5OZ", "6jX", "Gg", "0dU", "0Jy", "iK", "4d6", "4qW",
+"4z4", "4oU", "1DZ", "3A", "Ye", "0zW", "4Ay", "5D9", "6yj", "4LI", "A4", "TU", "zy", "0YK", "4be", "6WF", "6XG", "4md", "0VJ", "1p",
+"2ME", "N5", "4CH", "62c", "5K8", "4Nx", "0uV", "Vd", "xH", "8Rb", "5pu", "4u5", "D", "13W", "5Lq", "4I1", "534", "46t", "0IR", "28y",
+"gP", "69", "5om", "6Jo", "6dC", "5AA", "0jN", "3OL", "2Pl", "0eO", "aT1", "6kB", "6En", "44E", "98", "hQ", "ea", "0FS", "49u", "abL",
+"4F0", "4SQ", "8ag", "KM", "02u", "UO", "4X2", "4MS", "57V", "a8F", "0M0", "0XQ", "c2", "vS", "7KM", "4nO", "5PB", "61H", "2Nn", "1kl",
+"00D", "2Ao", "6zA", "4Ob", "4aN", "6Tm", "yR", "l3", "0WP", "0j", "a7G", "58W", "4BR", "4W3", "ZN", "84l", "0kP", "Hb", "71t", "644",
+"5ns", "4k3", "fN", "8Ld", "0HL", "29g", "6FA", "47j", "5Mo", "6hm", "ER", "P3", "0ia", "JS", "6gl", "4RO", "48k", "7Ya", "26f", "0GM",
+"8Ce", "iO", "4d2", "4qS", "beL", "hYw", "Gc", "0dQ", "Ya", "0zS", "cko", "60V", "4z0", "4oQ", "205", "3E", "2ll", "0YO", "4ba", "6WB",
+"6yn", "4LM", "A0", "TQ", "2MA", "N1", "4CL", "62g", "6XC", "59I", "0VN", "1t", "xL", "8Rf", "54y", "419", "aQM", "b0G", "01Z", "3PP",
+"530", "46p", "0IV", "jd", "DH", "0gz", "5Lu", "4I5", "6dG", "4Qd", "0jJ", "Ix", "gT", "r5", "5oi", "6Jk", "6Ej", "44A", "0Kg", "hU",
+"Fy", "0eK", "5ND", "6kF", "4F4", "4SU", "1xZ", "KI", "ee", "0FW", "49q", "5x9", "57R", "6VX", "0M4", "0XU", "02q", "UK", "4X6", "4MW",
+"5PF", "61L", "2Nj", "1kh", "c6", "vW", "7KI", "4nK", "4aJ", "6Ti", "yV", "l7", "0tH", "Wz", "6zE", "4Of", "4BV", "4W7", "ZJ", "0yx",
+"0WT", "0n", "6YY", "4lz", "5Mc", "6ha", "2SO", "0fl", "1Xa", "kr", "6FM", "47f", "bDm", "aao", "fB", "0Ep", "8bD", "Hn", "5U2", "4Pr",
+"5OR", "5Z3", "Go", "10t", "0Jq", "iC", "ann", "45W", "48g", "6IL", "ds", "0GA", "0im", "3Lo", "73I", "4RC", "6yb", "4LA", "03g", "2BL",
+"zq", "0YC", "4bm", "6WN", "a4d", "bUn", "0Ts", "3I", "Ym", "87O", "4Aq", "5D1", "5K0", "4Np", "01V", "Vl", "2nQ", "1KS", "54u", "415",
+"6XO", "4ml", "0VB", "1x", "2MM", "0xn", "5Sa", "62k", "gX", "61", "5oe", "6Jg", "6dK", "4Qh", "0jF", "It", "L", "0gv", "5Ly", "4I9",
+"5w4", "4rt", "0IZ", "jh", "ei", "1Vz", "5mT", "5x5", "4F8", "4SY", "0hw", "KE", "Fu", "0eG", "5NH", "6kJ", "6Ef", "44M", "90", "hY",
+"0Ui", "2S", "7KE", "4nG", "5PJ", "6uH", "Xw", "1kd", "0vu", "UG", "6xx", "790", "4cw", "5f7", "0M8", "0XY", "0WX", "0b", "5i6", "4lv",
+"4BZ", "63q", "ZF", "0yt", "00L", "Wv", "6zI", "4Oj", "4aF", "6Te", "yZ", "0Zh", "0HD", "kv", "6FI", "47b", "5Mg", "6he", "EZ", "0fh",
+"0kX", "Hj", "5U6", "4Pv", "7N9", "6Ky", "fF", "0Et", "0Ju", "iG", "6Dx", "45S", "5OV", "5Z7", "Gk", "0dY", "0ii", "3Lk", "6gd", "4RG",
+"48c", "6IH", "dw", "0GE", "zu", "0YG", "4bi", "6WJ", "6yf", "4LE", "A8", "TY", "Yi", "1jz", "4Au", "5D5", "4z8", "4oY", "0Tw", "3M",
+"xD", "1KW", "54q", "411", "5K4", "4Nt", "01R", "Vh", "2MI", "N9", "4CD", "62o", "6XK", "4mh", "0VF", "ut", "6dO", "4Ql", "0jB", "Ip",
+"25E", "65", "5oa", "6Jc", "538", "46x", "9Pg", "jl", "H", "0gr", "bfo", "aCm", "72W", "bin", "0hs", "KA", "em", "324", "49y", "5x1",
+"6Eb", "44I", "94", "3nm", "Fq", "0eC", "5NL", "6kN", "5PN", "61D", "Xs", "86Q", "0Um", "2W", "7KA", "4nC", "4cs", "5f3", "39W", "8QE",
+"02y", "UC", "aRn", "794", "765", "63u", "ZB", "0yp", "9Ne", "0f", "5i2", "4lr", "4aB", "6Ta", "2oO", "0Zl", "00H", "Wr", "6zM", "4On",
+"5lW", "5y6", "dj", "0GX", "0it", "JF", "6gy", "4RZ", "5OK", "6jI", "Gv", "0dD", "83", "iZ", "6De", "45N", "5nf", "6Kd", "24B", "72",
+"0kE", "Hw", "6eH", "4Pk", "5Mz", "6hx", "EG", "0fu", "0HY", "kk", "5v7", "4sw", "5h5", "4mu", "1Fz", "1a", "2MT", "0xw", "4CY", "4V8",
+"7kk", "4Ni", "01O", "Vu", "xY", "m8", "54l", "6Uf", "6Zg", "4oD", "b9", "3P", "Yt", "0zF", "4Ah", "60C", "4Y9", "4LX", "0wv", "TD",
+"zh", "0YZ", "4bt", "5g4", "Fl", "11w", "5NQ", "6kS", "aom", "44T", "0Kr", "1N2", "ep", "0FB", "49d", "6HO", "6fc", "5Ca", "0hn", "3Ml",
+"U", "0go", "bfr", "6ib", "6GN", "46e", "0IC", "jq", "gA", "0Ds", "bEn", "hyU", "5T1", "4Qq", "8cG", "Im", "00U", "Wo", "5J3", "4Os",
+"55v", "406", "yC", "0Zq", "0WA", "ts", "6YL", "4lo", "4BC", "63h", "2LN", "0ym", "02d", "2CO", "6xa", "4MB", "4cn", "6VM", "2mc", "1Ha",
+"0Up", "2J", "a5g", "bTm", "5PS", "5E2", "Xn", "86L", "0ip", "JB", "73T", "bhm", "48z", "5y2", "dn", "337", "87", "3on", "6Da", "45J",
+"5OO", "6jM", "Gr", "10i", "0kA", "Hs", "6eL", "4Po", "5nb", "aar", "24F", "76", "8AE", "ko", "5v3", "4ss", "bgl", "aBn", "EC", "0fq",
+"2MP", "0xs", "776", "62v", "5h1", "4mq", "9Of", "1e", "2nL", "1KN", "54h", "6Ub", "7ko", "4Nm", "01K", "Vq", "Yp", "0zB", "4Al", "60G",
+"6Zc", "bUs", "0Tn", "3T", "zl", "8PF", "4bp", "5g0", "aSm", "787", "03z", "1r2", "4e9", "44P", "0Kv", "hD", "Fh", "0eZ", "5NU", "6kW",
+"6fg", "4SD", "0hj", "KX", "et", "0FF", "5mI", "6HK", "6GJ", "46a", "0IG", "ju", "Q", "Q8", "5Ld", "6if", "5T5", "4Qu", "1zz", "Ii",
+"gE", "0Dw", "5ox", "4j8", "55r", "402", "yG", "0Zu", "00Q", "Wk", "5J7", "4Ow", "4BG", "63l", "2LJ", "0yi", "0WE", "tw", "6YH", "4lk",
+"4cj", "6VI", "2mg", "0XD", "0vh", "UZ", "6xe", "4MF", "5PW", "5E6", "Xj", "1ky", "0Ut", "2N", "7KX", "4nZ", "5OC", "6jA", "2Qo", "0dL",
+"1ZA", "iR", "6Dm", "45F", "48v", "acO", "db", "0GP", "94M", "JN", "4G3", "4RR", "5Mr", "4H2", "EO", "12T", "0HQ", "kc", "527", "47w",
+"5nn", "6Kl", "fS", "s2", "0kM", "3NO", "71i", "4Pc", "7kc", "4Na", "01G", "3PM", "xQ", "m0", "54d", "6Un", "a6D", "59T", "0VS", "1i",
+"197", "85o", "4CQ", "4V0", "4Y1", "4LP", "03v", "TL", "0L3", "0YR", "56U", "a9E", "6Zo", "4oL", "b1", "3X", "2Om", "0zN", "5QA", "60K",
+"ex", "0FJ", "49l", "6HG", "6fk", "4SH", "0hf", "KT", "Fd", "0eV", "5NY", "aAI", "4e5", "4pT", "0Kz", "hH", "gI", "1TZ", "5ot", "4j4",
+"5T9", "4Qy", "0jW", "Ie", "DU", "Q4", "5Lh", "6ij", "6GF", "46m", "0IK", "jy", "0WI", "0s", "6YD", "4lg", "4BK", "6wh", "ZW", "O6",
+"0tU", "Wg", "6zX", "6o9", "4aW", "4t6", "yK", "0Zy", "0Ux", "2B", "7KT", "4nV", "bzI", "61Q", "Xf", "1ku", "02l", "UV", "6xi", "4MJ",
+"4cf", "6VE", "2mk", "0XH", "0Jd", "iV", "6Di", "45B", "5OG", "6jE", "Gz", "0dH", "0ix", "JJ", "4G7", "4RV", "48r", "6IY", "df", "0GT",
+"0HU", "kg", "523", "47s", "5Mv", "4H6", "EK", "0fy", "0kI", "3NK", "6eD", "4Pg", "5nj", "6Kh", "fW", "s6", "xU", "m4", "5ph", "6Uj",
+"7kg", "4Ne", "01C", "Vy", "193", "1hZ", "4CU", "4V4", "5h9", "4my", "0VW", "1m", "zd", "0YV", "4bx", "5g8", "4Y5", "4LT", "03r", "TH",
+"Yx", "0zJ", "4Ad", "60O", "6Zk", "4oH", "b5", "wT", "6fo", "4SL", "0hb", "KP", "27e", "0FN", "49h", "6HC", "4e1", "44X", "8Bf", "hL",
+"0p3", "0eR", "bdO", "aAM", "70w", "657", "0jS", "Ia", "gM", "8Mg", "5op", "4j0", "6GB", "46i", "0IO", "28d", "Y", "Q0", "5Ll", "6in",
+"4BO", "63d", "ZS", "O2", "0WM", "0w", "7Ia", "4lc", "4aS", "4t2", "yO", "8Se", "00Y", "Wc", "aPN", "b1D", "bzM", "61U", "Xb", "1kq",
+"216", "2F", "7KP", "4nR", "4cb", "6VA", "2mo", "0XL", "02h", "UR", "6xm", "4MN", "5j7", "4ow", "0TY", "3c", "YG", "0zu", "5Qz", "60p",
+"6yH", "4Lk", "03M", "Tw", "2lJ", "0Yi", "4bG", "6Wd", "6Xe", "4mF", "0Vh", "1R", "2Mg", "0xD", "4Cj", "62A", "7kX", "4NZ", "0ut", "VF",
+"xj", "1Ky", "5pW", "5e6", "5nU", "6KW", "fh", "0EZ", "0kv", "HD", "4E9", "4PX", "5MI", "6hK", "Et", "0fF", "0Hj", "kX", "6Fg", "47L",
+"48M", "6If", "dY", "50", "0iG", "Ju", "6gJ", "4Ri", "5Ox", "4J8", "GE", "0dw", "1Zz", "ii", "5t5", "4qu", "02W", "Um", "5H1", "4Mq",
+"57t", "424", "2mP", "0Xs", "0UC", "2y", "7Ko", "4nm", "bzr", "61j", "2NL", "1kN", "00f", "2AM", "6zc", "bus", "4al", "6TO", "yp", "0ZB",
+"0Wr", "0H", "a7e", "58u", "4Bp", "5G0", "Zl", "84N", "f", "13u", "5LS", "5Y2", "amo", "46V", "0Ip", "jB", "gr", "1Ta", "5oO", "6JM",
+"6da", "4QB", "0jl", "3On", "2PN", "0em", "5Nb", "aAr", "6EL", "44g", "0KA", "hs", "eC", "0Fq", "49W", "abn", "5V3", "4Ss", "8aE", "Ko",
+"YC", "0zq", "754", "60t", "5j3", "4os", "9Md", "3g", "2lN", "0Ym", "4bC", "7GA", "6yL", "4Lo", "03I", "Ts", "2Mc", "1ha", "4Cn", "62E",
+"6Xa", "4mB", "0Vl", "1V", "xn", "8RD", "5pS", "5e2", "aQo", "b0e", "01x", "VB", "0kr", "1n2", "71V", "bjo", "5nQ", "6KS", "fl", "315",
+"0Hn", "29E", "6Fc", "47H", "5MM", "6hO", "Ep", "0fB", "0iC", "Jq", "6gN", "4Rm", "48I", "6Ib", "26D", "54", "8CG", "im", "509", "45y",
+"ben", "hYU", "GA", "0ds", "4cY", "420", "2mT", "0Xw", "02S", "Ui", "5H5", "4Mu", "5Pd", "61n", "XY", "M8", "0UG", "vu", "7Kk", "4ni",
+"4ah", "6TK", "yt", "0ZF", "B9", "WX", "6zg", "4OD", "4Bt", "5G4", "Zh", "0yZ", "0Wv", "0L", "4y9", "4lX", "6Gy", "46R", "0It", "jF",
+"b", "0gX", "5LW", "5Y6", "6de", "4QF", "0jh", "IZ", "gv", "0DD", "5oK", "6JI", "6EH", "44c", "0KE", "hw", "2PJ", "0ei", "5Nf", "6kd",
+"5V7", "4Sw", "0hY", "Kk", "eG", "0Fu", "49S", "6Hx", "7ia", "4Lc", "03E", "2Bn", "zS", "o2", "4bO", "6Wl", "a4F", "bUL", "0TQ", "3k",
+"YO", "87m", "4AS", "4T2", "7kP", "4NR", "01t", "VN", "xb", "1Kq", "54W", "hfv", "6Xm", "4mN", "1FA", "1Z", "2Mo", "0xL", "4Cb", "62I",
+"5MA", "6hC", "2Sm", "0fN", "0Hb", "kP", "6Fo", "47D", "bDO", "aaM", "0P3", "0ER", "8bf", "HL", "4E1", "4PP", "5Op", "4J0", "GM", "10V",
+"0JS", "ia", "505", "45u", "48E", "6In", "dQ", "58", "0iO", "3LM", "6gB", "4Ra", "0UK", "2q", "7Kg", "4ne", "5Ph", "61b", "XU", "M4",
+"0vW", "Ue", "5H9", "4My", "4cU", "4v4", "2mX", "1HZ", "0Wz", "tH", "4y5", "4lT", "4Bx", "5G8", "Zd", "0yV", "B5", "WT", "6zk", "4OH",
+"4ad", "6TG", "yx", "0ZJ", "gz", "0DH", "5oG", "6JE", "6di", "4QJ", "0jd", "IV", "n", "0gT", "680", "6iY", "4g7", "4rV", "0Ix", "jJ",
+"eK", "0Fy", "5mv", "4h6", "6fX", "5CZ", "0hU", "Kg", "FW", "S6", "5Nj", "6kh", "6ED", "44o", "0KI", "3nK", "zW", "o6", "4bK", "6Wh",
+"6yD", "4Lg", "03A", "2Bj", "YK", "0zy", "4AW", "4T6", "6ZX", "6O9", "0TU", "3o", "xf", "1Ku", "54S", "6UY", "7kT", "4NV", "01p", "VJ",
+"2Mk", "0xH", "4Cf", "62M", "6Xi", "4mJ", "0Vd", "uV", "0Hf", "kT", "6Fk", "4sH", "5ME", "6hG", "Ex", "0fJ", "0kz", "HH", "4E5", "4PT",
+"5nY", "aaI", "fd", "0EV", "0JW", "ie", "501", "45q", "5Ot", "4J4", "GI", "10R", "0iK", "Jy", "6gF", "4Re", "48A", "6Ij", "dU", "q4",
+"5Pl", "61f", "XQ", "M0", "0UO", "2u", "7Kc", "4na", "4cQ", "428", "39u", "8Qg", "0vS", "Ua", "aRL", "b3F", "bxO", "63W", "0l3", "0yR",
+"234", "0D", "4y1", "4lP", "55I", "6TC", "2om", "0ZN", "B1", "WP", "6zo", "4OL", "6dm", "4QN", "1zA", "IR", "25g", "0DL", "5oC", "6JA",
+"4g3", "46Z", "9PE", "jN", "j", "0gP", "684", "aCO", "72u", "675", "0hQ", "Kc", "eO", "8Oe", "5mr", "4h2", "7Ua", "44k", "0KM", "3nO",
+"FS", "S2", "5Nn", "6kl", "4x6", "4mW", "0Vy", "1C", "0m4", "0xU", "5SZ", "62P", "7kI", "4NK", "C6", "VW", "2nj", "1Kh", "54N", "6UD",
+"6ZE", "4of", "0TH", "3r", "YV", "L7", "4AJ", "60a", "6yY", "4Lz", "0wT", "Tf", "zJ", "0Yx", "4bV", "4w7", "5lu", "4i5", "dH", "0Gz",
+"0iV", "Jd", "5W8", "4Rx", "5Oi", "6jk", "GT", "R5", "0JJ", "ix", "6DG", "45l", "5nD", "6KF", "fy", "0EK", "0kg", "HU", "6ej", "4PI",
+"5MX", "5X9", "Ee", "0fW", "1XZ", "kI", "4f4", "4sU", "00w", "WM", "4Z0", "4OQ", "55T", "hgu", "ya", "0ZS", "a0", "0Y", "6Yn", "4lM",
+"4Ba", "63J", "2Ll", "0yO", "02F", "2Cm", "6xC", "aG0", "4cL", "6Vo", "2mA", "n1", "0UR", "2h", "a5E", "bTO", "5Pq", "4U1", "XL", "86n",
+"FN", "11U", "5Ns", "4K3", "516", "44v", "0KP", "hb", "eR", "p3", "49F", "6Hm", "6fA", "4Sb", "0hL", "3MN", "w", "0gM", "5LB", "7ya",
+"6Gl", "46G", "0Ia", "jS", "gc", "0DQ", "bEL", "hyw", "4D2", "4QS", "8ce", "IO", "0m0", "0xQ", "byL", "62T", "4x2", "4mS", "227", "1G",
+"2nn", "1Kl", "54J", "7Ea", "7kM", "4NO", "C2", "VS", "YR", "L3", "4AN", "60e", "6ZA", "4ob", "0TL", "3v", "zN", "8Pd", "4bR", "4w3",
+"aSO", "b2E", "03X", "Tb", "0iR", "3LP", "73v", "666", "48X", "4i1", "dL", "8Nf", "0JN", "3oL", "6DC", "45h", "5Om", "6jo", "GP", "R1",
+"0kc", "HQ", "6en", "4PM", "a09", "6KB", "24d", "0EO", "8Ag", "kM", "4f0", "47Y", "697", "aBL", "Ea", "0fS", "4ay", "5d9", "ye", "0ZW",
+"00s", "WI", "4Z4", "4OU", "4Be", "63N", "Zy", "0yK", "a4", "tU", "6Yj", "4lI", "4cH", "6Vk", "2mE", "n5", "02B", "Ux", "6xG", "4Md",
+"5Pu", "4U5", "XH", "86j", "0UV", "2l", "5k8", "4nx", "512", "44r", "0KT", "hf", "FJ", "0ex", "5Nw", "4K7", "6fE", "4Sf", "0hH", "Kz",
+"eV", "p7", "49B", "6Hi", "6Gh", "46C", "0Ie", "jW", "s", "0gI", "5LF", "6iD", "4D6", "4QW", "0jy", "IK", "gg", "0DU", "5oZ", "6JX",
+"7kA", "4NC", "01e", "3Po", "xs", "8RY", "54F", "6UL", "a6f", "59v", "0Vq", "1K", "d3E", "85M", "4Cs", "5F3", "5I2", "4Lr", "03T", "Tn",
+"zB", "0Yp", "56w", "437", "6ZM", "4on", "1Da", "3z", "2OO", "0zl", "4AB", "60i", "5Oa", "6jc", "2QM", "0dn", "0JB", "ip", "6DO", "45d",
+"48T", "acm", "1B2", "0Gr", "94o", "Jl", "5W0", "4Rp", "5MP", "5X1", "Em", "12v", "0Hs", "kA", "all", "47U", "5nL", "6KN", "fq", "0EC",
+"0ko", "3Nm", "6eb", "4PA", "a8", "0Q", "6Yf", "4lE", "4Bi", "63B", "Zu", "0yG", "0tw", "WE", "4Z8", "4OY", "4au", "5d5", "yi", "1Jz",
+"0UZ", "vh", "5k4", "4nt", "5Py", "4U9", "XD", "1kW", "02N", "Ut", "6xK", "4Mh", "4cD", "6Vg", "2mI", "n9", "eZ", "43", "49N", "6He",
+"6fI", "4Sj", "0hD", "Kv", "FF", "0et", "7n9", "6ky", "5u6", "4pv", "0KX", "hj", "gk", "0DY", "5oV", "5z7", "6dx", "5Az", "0ju", "IG",
+"Dw", "0gE", "5LJ", "6iH", "6Gd", "46O", "0Ii", "28B", "xw", "1Kd", "54B", "6UH", "7kE", "4NG", "01a", "3Pk", "0m8", "0xY", "4Cw", "5F7",
+"6Xx", "59r", "0Vu", "1O", "zF", "0Yt", "4bZ", "433", "5I6", "4Lv", "03P", "Tj", "YZ", "0zh", "4AF", "60m", "6ZI", "4oj", "0TD", "wv",
+"0JF", "it", "6DK", "4qh", "5Oe", "6jg", "GX", "R9", "0iZ", "Jh", "5W4", "4Rt", "48P", "4i9", "dD", "0Gv", "0Hw", "kE", "4f8", "47Q",
+"5MT", "5X5", "Ei", "12r", "0kk", "HY", "6ef", "4PE", "5nH", "6KJ", "fu", "0EG", "4Bm", "63F", "Zq", "0yC", "0Wo", "0U", "6Yb", "4lA",
+"4aq", "5d1", "ym", "8SG", "0ts", "WA", "aPl", "b1f", "747", "61w", "2NQ", "1kS", "9Lg", "2d", "5k0", "4np", "57i", "6Vc", "2mM", "0Xn",
+"02J", "Up", "6xO", "4Ml", "6fM", "4Sn", "1xa", "Kr", "27G", "47", "49J", "6Ha", "5u2", "44z", "8BD", "hn", "FB", "0ep", "bdm", "aAo",
+"70U", "bkl", "0jq", "IC", "go", "306", "5oR", "5z3", "7WA", "46K", "0Im", "28F", "Ds", "0gA", "5LN", "6iL", "0cY", "020", "6mT", "4Xw",
+"42S", "6Cx", "nG", "0Mu", "1Pd", "cw", "6NH", "5kJ", "4UG", "74M", "3Kk", "0ni", "0ah", "BZ", "6oe", "4ZF", "40b", "6AI", "lv", "0OD",
+"0Bt", "aF", "6Ly", "4yZ", "4Wv", "5R6", "Oj", "0lX", "Qh", "06R", "4It", "5L4", "461", "4gX", "1LW", "1Y6", "rt", "0QF", "4jh", "7Oj",
+"65o", "4DD", "I9", "2JI", "SY", "F8", "4KE", "7nG", "6PJ", "4ei", "1Nf", "2kd", "4M", "0Sw", "4hY", "490", "5C5", "4Fu", "09S", "2Hx",
+"6OR", "4zq", "354", "bm", "LA", "0os", "bnn", "75W", "6lN", "4Ym", "0bC", "Aq", "2yL", "0Lo", "43I", "6Bb", "6Mc", "5ha", "15", "22E",
+"Np", "0mB", "4Vl", "6cO", "aDm", "bao", "1pS", "1e2", "ml", "8GF", "41x", "548", "4kr", "5n2", "7f", "8YD", "1nQ", "2KS", "64u", "715",
+"4Hn", "69E", "Pr", "07H", "1MM", "2hO", "6Sa", "4fB", "4iC", "7LA", "5W", "0Rm", "08I", "2Ib", "66D", "4Go", "b4d", "aUn", "RC", "05y",
+"8VE", "8g", "5a3", "4ds", "42W", "ain", "nC", "0Mq", "17t", "024", "6mP", "4Xs", "4UC", "74I", "3Ko", "0nm", "8IY", "cs", "6NL", "5kN",
+"40f", "6AM", "lr", "8FX", "0al", "2TO", "6oa", "4ZB", "4Wr", "5R2", "On", "18u", "0Bp", "aB", "afo", "bCm", "465", "53u", "1LS", "1Y2",
+"Ql", "06V", "4Ip", "5L0", "65k", "5Ta", "1oO", "2JM", "6x", "0QB", "4jl", "7On", "6PN", "4em", "1Nb", "9y", "2EL", "04g", "4KA", "7nC",
+"5C1", "4Fq", "09W", "d6G", "4I", "0Ss", "bRn", "494", "LE", "0ow", "4TY", "4A8", "6OV", "4zu", "1Qz", "bi", "oY", "z8", "43M", "6Bf",
+"6lJ", "4Yi", "0bG", "Au", "Nt", "0mF", "4Vh", "6cK", "6Mg", "4xD", "11", "22A", "mh", "0NZ", "4ut", "5p4", "4N9", "5Ky", "1pW", "CD",
+"1nU", "2KW", "64q", "4EZ", "4kv", "5n6", "7b", "0PX", "1MI", "2hK", "6Se", "4fF", "4Hj", "69A", "Pv", "07L", "08M", "2If", "6rH", "4Gk",
+"4iG", "7LE", "5S", "0Ri", "1Ox", "8c", "5a7", "4dw", "5Zz", "7oY", "RG", "0qu", "1Pl", "21f", "adR", "5kB", "4UO", "74E", "MS", "X2",
+"0cQ", "028", "79u", "bbL", "4vS", "4c2", "nO", "8De", "8Kd", "aN", "4l3", "4yR", "634", "76t", "Ob", "0lP", "W3", "BR", "6om", "4ZN",
+"40j", "6AA", "2zo", "0OL", "6t", "0QN", "5zA", "7Ob", "65g", "4DL", "I1", "2JA", "0g3", "06Z", "b7G", "68W", "469", "4gP", "284", "dSn",
+"4E", "275", "4hQ", "498", "67V", "b8F", "1mr", "0h2", "SQ", "F0", "4KM", "7nO", "6PB", "4ea", "1Nn", "9u", "6lF", "4Ye", "0bK", "Ay",
+"oU", "z4", "43A", "6Bj", "6OZ", "4zy", "0AW", "be", "LI", "2O9", "4TU", "4A4", "4N5", "5Ku", "14S", "CH", "md", "0NV", "41p", "540",
+"6Mk", "4xH", "u5", "22M", "Nx", "0mJ", "4Vd", "6cG", "4Hf", "69M", "Pz", "0sH", "k7", "2hG", "6Si", "4fJ", "4kz", "7Nx", "7n", "0PT",
+"1nY", "dqh", "4P7", "4EV", "4JW", "7oU", "RK", "05q", "1Ot", "8o", "6QX", "50R", "4iK", "7LI", "qW", "d6", "08A", "2Ij", "66L", "4Gg",
+"4UK", "74A", "MW", "X6", "1Ph", "21b", "6ND", "5kF", "4vW", "4c6", "nK", "0My", "0cU", "0v4", "6mX", "5HZ", "4Wz", "6bY", "Of", "0lT",
+"0Bx", "aJ", "4l7", "4yV", "40n", "6AE", "lz", "0OH", "W7", "BV", "6oi", "4ZJ", "65c", "4DH", "I5", "2JE", "6p", "0QJ", "4jd", "7Of",
+"4r5", "4gT", "280", "2iY", "Qd", "0rV", "4Ix", "5L8", "5C9", "4Fy", "1mv", "0h6", "4A", "1CZ", "4hU", "7MW", "6PF", "4ee", "1Nj", "9q",
+"SU", "F4", "4KI", "7nK", "oQ", "z0", "43E", "6Bn", "6lB", "4Ya", "0bO", "2Wl", "LM", "8fg", "4TQ", "4A0", "aeL", "cPo", "0AS", "ba",
+"3kP", "0NR", "41t", "544", "4N1", "5Kq", "14W", "CL", "2Xm", "0mN", "5FA", "6cC", "6Mo", "4xL", "19", "22I", "k3", "2hC", "6Sm", "4fN",
+"4Hb", "69I", "2Fo", "07D", "83l", "d5d", "4P3", "4ER", "bQM", "a0G", "7j", "0PP", "1Op", "8k", "hbw", "50V", "4JS", "7oQ", "RO", "05u",
+"08E", "2In", "66H", "4Gc", "4iO", "7LM", "qS", "d2", "0ay", "BK", "4O6", "4ZW", "40s", "553", "lg", "0OU", "t6", "aW", "6Lh", "4yK",
+"4Wg", "6bD", "2Yj", "0lI", "0cH", "2Vk", "6mE", "4Xf", "42B", "6Ci", "nV", "0Md", "1Pu", "cf", "6NY", "bAI", "4UV", "7pT", "MJ", "0nx",
+"SH", "04r", "4KT", "7nV", "azI", "4ex", "1Nw", "9l", "pT", "e5", "4hH", "7MJ", "67O", "4Fd", "09B", "2Hi", "Qy", "06C", "4Ie", "68N",
+"6Rj", "4gI", "j4", "2iD", "6m", "0QW", "4jy", "5o9", "4Q4", "4DU", "1oZ", "2JX", "4m0", "4xQ", "8Jg", "22T", "Na", "0mS", "627", "77w",
+"6nn", "5Kl", "V0", "CQ", "3kM", "0NO", "41i", "7Pc", "6OC", "5jA", "0AN", "20e", "LP", "Y1", "4TL", "6ao", "78v", "bcO", "0bR", "0w3",
+"oL", "8Ef", "43X", "4b1", "4iR", "7LP", "5F", "266", "08X", "0i1", "66U", "b9E", "4JN", "7oL", "RR", "G3", "1Om", "8v", "6QA", "4db",
+"4kc", "7Na", "7w", "0PM", "H2", "2KB", "64d", "4EO", "b6D", "69T", "Pc", "07Y", "297", "dRm", "4s2", "4fS", "40w", "557", "lc", "0OQ",
+"15T", "BO", "4O2", "4ZS", "4Wc", "76i", "2Yn", "0lM", "t2", "aS", "6Ll", "4yO", "42F", "6Cm", "nR", "8Dx", "0cL", "2Vo", "6mA", "4Xb",
+"4UR", "74X", "MN", "8gd", "1Pq", "cb", "adO", "bAM", "azM", "51U", "1Ns", "9h", "SL", "04v", "4KP", "7nR", "67K", "5VA", "09F", "2Hm",
+"4X", "e1", "4hL", "7MN", "6Rn", "4gM", "j0", "3ya", "2Gl", "06G", "4Ia", "68J", "4Q0", "4DQ", "82o", "d4g", "6i", "0QS", "bPN", "a1D",
+"Ne", "0mW", "4Vy", "5S9", "4m4", "4xU", "1SZ", "22P", "my", "0NK", "41m", "7Pg", "6nj", "5Kh", "V4", "CU", "LT", "Y5", "4TH", "6ak",
+"6OG", "4zd", "0AJ", "bx", "oH", "0Lz", "4wT", "4b5", "78r", "4Yx", "0bV", "Ad", "1lu", "0i5", "66Q", "4Gz", "4iV", "7LT", "5B", "0Rx",
+"1Oi", "8r", "6QE", "4df", "4JJ", "7oH", "RV", "G7", "H6", "2KF", "6ph", "4EK", "4kg", "7Ne", "7s", "0PI", "1MX", "1X9", "4s6", "4fW",
+"5XZ", "69P", "Pg", "0sU", "06", "23F", "afr", "4yC", "4Wo", "6bL", "Os", "0lA", "0aq", "BC", "aEn", "c4E", "4ts", "5q3", "lo", "8FE",
+"347", "cn", "6NQ", "5kS", "bom", "74T", "MB", "0np", "17i", "2Vc", "6mM", "4Xn", "42J", "6Ca", "2xO", "0Ml", "4T", "0Sn", "5xa", "7MB",
+"67G", "4Fl", "09J", "2Ha", "1u2", "04z", "b5g", "aTm", "6PS", "4ep", "8WF", "9d", "6e", "8XG", "4jq", "5o1", "65v", "706", "1oR", "1z3",
+"Qq", "06K", "4Im", "68F", "6Rb", "4gA", "1LN", "2iL", "6nf", "5Kd", "V8", "CY", "mu", "0NG", "41a", "7Pk", "4m8", "4xY", "0Cw", "1F7",
+"Ni", "19r", "4Vu", "5S5", "6lW", "4Yt", "0bZ", "Ah", "oD", "0Lv", "43P", "4b9", "6OK", "4zh", "0AF", "bt", "LX", "Y9", "4TD", "6ag",
+"4JF", "7oD", "RZ", "0qh", "1Oe", "2jg", "6QI", "4dj", "4iZ", "483", "5N", "0Rt", "08P", "0i9", "5B6", "4Gv", "4Hw", "5M7", "Pk", "07Q",
+"1MT", "1X5", "472", "52r", "4kk", "7Ni", "sw", "0PE", "1nH", "2KJ", "64l", "4EG", "4Wk", "6bH", "Ow", "0lE", "02", "23B", "6Ld", "4yG",
+"4tw", "5q7", "lk", "0OY", "0au", "BG", "6ox", "5Jz", "4UZ", "74P", "MF", "0nt", "1Py", "cj", "6NU", "5kW", "42N", "6Ce", "nZ", "0Mh",
+"0cD", "2Vg", "6mI", "4Xj", "67C", "4Fh", "09N", "2He", "4P", "e9", "4hD", "7MF", "6PW", "4et", "3n9", "2ky", "SD", "0pv", "4KX", "7nZ",
+"4Q8", "4DY", "1oV", "1z7", "6a", "1Az", "4ju", "5o5", "6Rf", "4gE", "j8", "2iH", "Qu", "06O", "4Ii", "68B", "mq", "0NC", "41e", "7Po",
+"6nb", "bar", "14F", "2UL", "Nm", "19v", "4Vq", "5S1", "agl", "bBn", "0Cs", "1F3", "1I2", "0Lr", "43T", "ahm", "6lS", "4Yp", "16w", "Al",
+"2ZM", "0on", "5Da", "6ac", "6OO", "4zl", "0AB", "bp", "1Oa", "8z", "6QM", "4dn", "4JB", "aUs", "2DO", "05d", "08T", "d7D", "5B2", "4Gr",
+"bSm", "487", "5J", "0Rp", "1MP", "1X1", "476", "52v", "4Hs", "5M3", "Po", "07U", "1nL", "2KN", "64h", "4EC", "4ko", "7Nm", "ss", "0PA",
+"QJ", "06p", "4IV", "7lT", "6RY", "4gz", "1Lu", "0I5", "rV", "g7", "4jJ", "7OH", "65M", "4Df", "1oi", "2Jk", "2Ej", "04A", "4Kg", "7ne",
+"6Ph", "4eK", "h6", "2kF", "4o", "0SU", "5xZ", "7My", "4S6", "4FW", "09q", "1x9", "17R", "2VX", "4M4", "4XU", "42q", "571", "ne", "0MW",
+"v4", "cU", "6Nj", "5kh", "4Ue", "74o", "My", "0nK", "0aJ", "Bx", "6oG", "4Zd", "4tH", "6Ak", "lT", "y5", "0BV", "ad", "580", "4yx",
+"4WT", "4B5", "OH", "0lz", "4kP", "7NR", "7D", "244", "1ns", "0k3", "64W", "con", "4HL", "69g", "PP", "E1", "1Mo", "2hm", "6SC", "52I",
+"4ia", "7Lc", "5u", "0RO", "J0", "3Ya", "66f", "4GM", "b4F", "aUL", "Ra", "0qS", "8Vg", "8E", "458", "4dQ", "4o2", "4zS", "8He", "bO",
+"Lc", "0oQ", "605", "75u", "6ll", "4YO", "T2", "AS", "2yn", "0LM", "43k", "7Ra", "6MA", "4xb", "0CL", "22g", "NR", "19I", "4VN", "6cm",
+"aDO", "baM", "14y", "Cb", "mN", "8Gd", "41Z", "7PP", "axO", "53W", "1Lq", "0I1", "QN", "06t", "4IR", "68y", "65I", "4Db", "1om", "2Jo",
+"6Z", "g3", "4jN", "7OL", "6Pl", "4eO", "h2", "2kB", "2En", "04E", "4Kc", "7na", "4S2", "4FS", "09u", "d6e", "4k", "0SQ", "bRL", "a3F",
+"42u", "575", "na", "0MS", "17V", "dlo", "4M0", "4XQ", "4Ua", "74k", "3KM", "0nO", "28", "cQ", "6Nn", "5kl", "40D", "6Ao", "lP", "y1",
+"0aN", "2Tm", "6oC", "5JA", "4WP", "4B1", "OL", "18W", "0BR", "0W3", "584", "bCO", "1nw", "0k7", "64S", "4Ex", "4kT", "7NV", "sH", "0Pz",
+"1Mk", "2hi", "6SG", "4fd", "4HH", "69c", "PT", "E5", "J4", "2ID", "66b", "4GI", "4ie", "7Lg", "5q", "0RK", "1OZ", "8A", "4q4", "4dU",
+"4Jy", "5O9", "Re", "0qW", "Lg", "0oU", "5DZ", "6aX", "4o6", "4zW", "0Ay", "bK", "2yj", "0LI", "43o", "6BD", "6lh", "4YK", "T6", "AW",
+"NV", "0md", "4VJ", "6ci", "6ME", "4xf", "0CH", "22c", "mJ", "0Nx", "4uV", "7PT", "6nY", "baI", "1pu", "Cf", "6V", "0Ql", "4jB", "aus",
+"65E", "4Dn", "1oa", "2Jc", "QB", "06x", "b7e", "68u", "5b2", "4gr", "8UD", "dSL", "4g", "8ZE", "4hs", "5m3", "67t", "724", "09y", "1x1",
+"Ss", "04I", "4Ko", "7nm", "azr", "4eC", "1NL", "9W", "24", "21D", "6Nb", "bAr", "4Um", "74g", "Mq", "0nC", "0cs", "1f3", "79W", "bbn",
+"42y", "579", "nm", "394", "365", "al", "588", "4yp", "bmo", "76V", "1i2", "0lr", "0aB", "Bp", "6oO", "4Zl", "40H", "6Ac", "2zM", "0On",
+"4HD", "69o", "PX", "E9", "1Mg", "2he", "6SK", "4fh", "4kX", "7NZ", "7L", "0Pv", "3N9", "2Ky", "6pW", "4Et", "4Ju", "5O5", "Ri", "05S",
+"1OV", "8M", "450", "4dY", "4ii", "7Lk", "qu", "0RG", "J8", "2IH", "66n", "4GE", "6ld", "4YG", "0bi", "2WJ", "ow", "0LE", "43c", "6BH",
+"6Ox", "5jz", "0Au", "bG", "Lk", "0oY", "4Tw", "5Q7", "6nU", "5KW", "14q", "Cj", "mF", "0Nt", "41R", "7PX", "6MI", "4xj", "0CD", "22o",
+"NZ", "0mh", "4VF", "6ce", "65A", "4Dj", "1oe", "2Jg", "6R", "0Qh", "4jF", "7OD", "5b6", "4gv", "1Ly", "0I9", "QF", "0rt", "4IZ", "68q",
+"67p", "5Vz", "1mT", "1x5", "4c", "0SY", "4hw", "5m7", "6Pd", "4eG", "1NH", "9S", "Sw", "04M", "4Kk", "7ni", "4Ui", "74c", "Mu", "0nG",
+"20", "cY", "6Nf", "5kd", "4vu", "5s5", "ni", "390", "0cw", "1f7", "4M8", "4XY", "4WX", "4B9", "OD", "0lv", "0BZ", "ah", "6LW", "4yt",
+"40L", "6Ag", "lX", "y9", "0aF", "Bt", "6oK", "4Zh", "1Mc", "2ha", "6SO", "4fl", "5Xa", "69k", "2FM", "07f", "83N", "d5F", "6pS", "4Ep",
+"bQo", "a0e", "7H", "0Pr", "1OR", "8I", "454", "50t", "4Jq", "5O1", "Rm", "05W", "08g", "2IL", "66j", "4GA", "4im", "7Lo", "5y", "0RC",
+"os", "0LA", "43g", "6BL", "78I", "4YC", "0bm", "2WN", "Lo", "8fE", "4Ts", "5Q3", "aen", "cPM", "0Aq", "bC", "mB", "0Np", "41V", "ajo",
+"6nQ", "5KS", "14u", "Cn", "2XO", "0ml", "4VB", "6ca", "6MM", "4xn", "1Sa", "22k", "Sj", "04P", "4Kv", "5N6", "443", "4eZ", "1NU", "9N",
+"pv", "0SD", "4hj", "7Mh", "67m", "4FF", "1mI", "2HK", "2GJ", "2", "4IG", "68l", "6RH", "4gk", "1Ld", "2if", "6O", "0Qu", "5zz", "7OY",
+"5A7", "4Dw", "1ox", "0j8", "15r", "Bi", "6oV", "4Zu", "40Q", "4a8", "lE", "0Ow", "0BG", "au", "6LJ", "4yi", "4WE", "6bf", "OY", "Z8",
+"U9", "2VI", "6mg", "4XD", "4vh", "6CK", "nt", "0MF", "1PW", "cD", "4n9", "5ky", "4Ut", "5P4", "Mh", "0nZ", "4ip", "5l0", "5d", "9Kg",
+"08z", "1y2", "66w", "737", "4Jl", "7on", "Rp", "05J", "1OO", "8T", "6Qc", "50i", "4kA", "7NC", "7U", "0Po", "1nb", "dqS", "64F", "4Em",
+"b6f", "69v", "PA", "0ss", "8TG", "dRO", "5c1", "4fq", "6MP", "4xs", "376", "22v", "NC", "0mq", "bll", "77U", "6nL", "5KN", "14h", "Cs",
+"3ko", "0Nm", "41K", "7PA", "6Oa", "4zB", "37", "20G", "Lr", "8fX", "4Tn", "6aM", "78T", "bcm", "0bp", "AB", "on", "387", "43z", "5r2",
+"447", "51w", "1NQ", "9J", "Sn", "04T", "4Kr", "5N2", "67i", "4FB", "09d", "2HO", "4z", "1Ca", "4hn", "7Ml", "6RL", "4go", "8UY", "2ib",
+"2GN", "6", "4IC", "68h", "5A3", "4Ds", "82M", "d4E", "6K", "0Qq", "bPl", "a1f", "40U", "akl", "lA", "0Os", "15v", "Bm", "6oR", "4Zq",
+"4WA", "6bb", "2YL", "0lo", "0BC", "aq", "6LN", "4ym", "42d", "6CO", "np", "0MB", "0cn", "2VM", "6mc", "5Ha", "4Up", "5P0", "Ml", "8gF",
+"1PS", "1E2", "adm", "bAo", "1lW", "1y6", "4R9", "4GX", "4it", "5l4", "qh", "0RZ", "i9", "8P", "6Qg", "4dD", "4Jh", "7oj", "Rt", "05N",
+"1nf", "2Kd", "64B", "4Ei", "4kE", "7NG", "7Q", "f8", "1Mz", "2hx", "5c5", "4fu", "4HY", "69r", "PE", "0sw", "NG", "0mu", "5Fz", "6cx",
+"6MT", "4xw", "0CY", "0V8", "3kk", "0Ni", "41O", "7PE", "6nH", "5KJ", "14l", "Cw", "Lv", "0oD", "4Tj", "6aI", "6Oe", "4zF", "33", "bZ",
+"oj", "0LX", "4wv", "5r6", "6ly", "4YZ", "0bt", "AF", "4v", "0SL", "4hb", "awS", "67e", "4FN", "K3", "2HC", "Sb", "04X", "b5E", "aTO",
+"4p3", "4eR", "8Wd", "9F", "6G", "257", "4jS", "7OQ", "65T", "cnm", "1op", "0j0", "QS", "D2", "4IO", "68d", "7Ba", "4gc", "1Ll", "2in",
+"0BO", "23d", "6LB", "4ya", "4WM", "6bn", "OQ", "Z0", "0aS", "Ba", "aEL", "c4g", "40Y", "4a0", "lM", "8Fg", "8If", "cL", "4n1", "5kq",
+"616", "74v", "3KP", "0nR", "U1", "2VA", "6mo", "4XL", "42h", "6CC", "2xm", "0MN", "4Jd", "7of", "Rx", "05B", "i5", "2jE", "6Qk", "4dH",
+"4ix", "5l8", "5l", "0RV", "08r", "2IY", "4R5", "4GT", "4HU", "7mW", "PI", "07s", "1Mv", "0H6", "5c9", "4fy", "4kI", "7NK", "sU", "f4",
+"1nj", "2Kh", "64N", "4Ee", "6nD", "5KF", "1ph", "2Uj", "mW", "x6", "41C", "7PI", "593", "5hZ", "0CU", "0V4", "NK", "0my", "4VW", "4C6",
+"4L7", "4YV", "0bx", "AJ", "of", "0LT", "43r", "562", "6Oi", "4zJ", "w7", "bV", "Lz", "0oH", "4Tf", "6aE", "67a", "4FJ", "K7", "2HG",
+"4r", "0SH", "4hf", "7Md", "4p7", "4eV", "1NY", "9B", "Sf", "0pT", "4Kz", "7nx", "65P", "5TZ", "1ot", "0j4", "6C", "0Qy", "4jW", "7OU",
+"6RD", "4gg", "1Lh", "2ij", "QW", "D6", "4IK", "7lI", "4WI", "6bj", "OU", "Z4", "0BK", "ay", "6LF", "4ye", "4tU", "4a4", "lI", "2o9",
+"0aW", "Be", "6oZ", "4Zy", "4Ux", "5P8", "Md", "0nV", "8Ib", "cH", "4n5", "5ku", "42l", "6CG", "nx", "0MJ", "U5", "2VE", "6mk", "4XH",
+"i1", "8X", "6Qo", "4dL", "5ZA", "7ob", "2Dm", "05F", "08v", "d7f", "4R1", "4GP", "bSO", "a2E", "5h", "0RR", "1Mr", "0H2", "ayL", "52T",
+"4HQ", "69z", "PM", "07w", "1nn", "2Kl", "64J", "4Ea", "4kM", "7NO", "7Y", "f0", "mS", "x2", "41G", "7PM", "aDR", "5KB", "14d", "2Un",
+"NO", "19T", "4VS", "4C2", "597", "bBL", "0CQ", "0V0", "ob", "0LP", "43v", "566", "4L3", "4YR", "16U", "AN", "2Zo", "0oL", "4Tb", "6aA",
+"6Om", "4zN", "w3", "bR", "4oT", "4z5", "wH", "0Tz", "0zV", "Yd", "5D8", "4Ax", "4LH", "6yk", "TT", "A5", "0YJ", "zx", "6WG", "4bd",
+"4me", "6XF", "1q", "0VK", "N4", "2MD", "62b", "4CI", "4Ny", "5K9", "Ve", "0uW", "1KZ", "xI", "4u4", "5pt", "4k6", "5nv", "0Ey", "fK",
+"Hg", "0kU", "641", "6eX", "6hh", "5Mj", "P6", "EW", "29b", "0HI", "47o", "6FD", "6IE", "48n", "0GH", "dz", "JV", "0id", "4RJ", "6gi",
+"6jY", "beI", "0dT", "Gf", "iJ", "0Jx", "4qV", "4d7", "UN", "02t", "4MR", "4X3", "a8G", "57W", "0XP", "0M1", "2Z", "c3", "4nN", "7KL",
+"61I", "5PC", "1km", "2No", "2An", "00E", "4Oc", "7ja", "6Tl", "4aO", "l2", "yS", "0k", "0WQ", "58V", "a7F", "4W2", "4BS", "84m", "ZO",
+"13V", "E", "4I0", "5Lp", "46u", "535", "ja", "0IS", "68", "gQ", "6Jn", "5ol", "4Qa", "6dB", "3OM", "0jO", "0eN", "2Pm", "6kC", "5NA",
+"44D", "6Eo", "hP", "99", "0FR", "0S3", "abM", "49t", "4SP", "4F1", "KL", "8af", "0zR", "0o3", "60W", "ckn", "4oP", "4z1", "3D", "204",
+"0YN", "2lm", "6WC", "56I", "4LL", "6yo", "TP", "A1", "N0", "903", "62f", "4CM", "4ma", "6XB", "1u", "0VO", "8Rg", "xM", "418", "54x",
+"b0F", "aQL", "Va", "0uS", "Hc", "0kQ", "645", "71u", "4k2", "5nr", "8Le", "fO", "29f", "0HM", "47k", "7Va", "6hl", "5Mn", "P2", "ES",
+"JR", "1yA", "4RN", "6gm", "6IA", "48j", "0GL", "26g", "iN", "8Cd", "45Z", "4d3", "hYv", "beM", "0dP", "Gb", "6VY", "4cz", "0XT", "0M5",
+"UJ", "02p", "4MV", "4X7", "61M", "5PG", "1ki", "Xz", "vV", "c7", "4nJ", "7KH", "6Th", "4aK", "l6", "yW", "2Aj", "00A", "4Og", "6zD",
+"4W6", "4BW", "0yy", "ZK", "0o", "0WU", "58R", "6YX", "46q", "531", "je", "0IW", "13R", "A", "4I4", "5Lt", "4Qe", "6dF", "Iy", "0jK",
+"r4", "gU", "6Jj", "5oh", "4pH", "6Ek", "hT", "0Kf", "0eJ", "Fx", "6kG", "5NE", "4ST", "4F5", "KH", "0hz", "0FV", "ed", "5x8", "49p",
+"bvs", "6yc", "2BM", "03f", "0YB", "zp", "6WO", "4bl", "bUo", "a4e", "3H", "0Tr", "87N", "Yl", "5D0", "4Ap", "4Nq", "5K1", "Vm", "01W",
+"1KR", "xA", "414", "54t", "4mm", "6XN", "1y", "0VC", "0xo", "2ML", "62j", "4CA", "7xA", "5Mb", "0fm", "2SN", "ks", "0HA", "47g", "6FL",
+"aan", "bDl", "0Eq", "fC", "Ho", "8bE", "4Ps", "5U3", "5Z2", "5OS", "10u", "Gn", "iB", "0Jp", "45V", "ano", "6IM", "48f", "1Wa", "dr",
+"3Ln", "0il", "4RB", "6ga", "2R", "0Uh", "4nF", "7KD", "61A", "5PK", "1ke", "Xv", "UF", "0vt", "4MZ", "6xy", "5f6", "4cv", "0XX", "0M9",
+"0c", "0WY", "4lw", "5i7", "63p", "5Rz", "0yu", "ZG", "Ww", "00M", "4Ok", "6zH", "6Td", "4aG", "0Zi", "2oJ", "60", "gY", "6Jf", "5od",
+"4Qi", "6dJ", "Iu", "0jG", "0gw", "M", "4I8", "5Lx", "4ru", "5w5", "ji", "1Yz", "0FZ", "eh", "5x4", "5mU", "4SX", "4F9", "KD", "0hv",
+"0eF", "Ft", "6kK", "5NI", "44L", "6Eg", "hX", "91", "0YF", "zt", "6WK", "4bh", "4LD", "6yg", "TX", "A9", "0zZ", "Yh", "5D4", "4At",
+"4oX", "4z9", "3L", "0Tv", "1KV", "xE", "410", "54p", "4Nu", "5K5", "Vi", "01S", "N8", "2MH", "62n", "4CE", "4mi", "6XJ", "uu", "0VG",
+"kw", "0HE", "47c", "6FH", "6hd", "5Mf", "0fi", "2SJ", "Hk", "0kY", "4Pw", "5U7", "6Kx", "5nz", "0Eu", "fG", "iF", "0Jt", "45R", "6Dy",
+"5Z6", "5OW", "0dX", "Gj", "JZ", "0ih", "4RF", "6ge", "6II", "48b", "0GD", "dv", "61E", "5PO", "1ka", "Xr", "2V", "0Ul", "4nB", "aqs",
+"5f2", "4cr", "8QD", "39V", "UB", "02x", "795", "aRo", "63t", "764", "0yq", "ZC", "0g", "9Nd", "4ls", "5i3", "7DA", "4aC", "0Zm", "2oN",
+"Ws", "00I", "4Oo", "6zL", "4Qm", "6dN", "Iq", "0jC", "64", "25D", "6Jb", "bEr", "46y", "539", "jm", "9Pf", "0gs", "I", "aCl", "bfn",
+"bio", "72V", "1m2", "0hr", "325", "el", "5x0", "49x", "44H", "6Ec", "3nl", "95", "0eB", "Fp", "6kO", "5NM", "4mt", "5h4", "uh", "0VZ",
+"0xv", "2MU", "4V9", "4CX", "4Nh", "7kj", "Vt", "01N", "m9", "xX", "6Ug", "54m", "4oE", "6Zf", "3Q", "b8", "0zG", "Yu", "60B", "4Ai",
+"4LY", "4Y8", "TE", "0ww", "1Iz", "zi", "5g5", "4bu", "5y7", "5lV", "0GY", "dk", "JG", "0iu", "5Bz", "6gx", "6jH", "5OJ", "0dE", "Gw",
+"3ok", "82", "45O", "6Dd", "6Ke", "5ng", "73", "fZ", "Hv", "0kD", "4Pj", "6eI", "6hy", "7m9", "0ft", "EF", "kj", "0HX", "4sv", "5v6",
+"Wn", "00T", "4Or", "5J2", "407", "55w", "0Zp", "yB", "0z", "1Ga", "4ln", "6YM", "63i", "4BB", "0yl", "2LO", "2CN", "02e", "4MC", "7hA",
+"6VL", "4co", "0XA", "2mb", "2K", "0Uq", "bTl", "a5f", "5E3", "5PR", "86M", "Xo", "11v", "Fm", "6kR", "5NP", "44U", "aol", "hA", "0Ks",
+"0FC", "eq", "6HN", "49e", "4SA", "6fb", "3Mm", "0ho", "0gn", "T", "6ic", "5La", "46d", "6GO", "jp", "0IB", "0Dr", "1A2", "hyT", "bEo",
+"4Qp", "5T0", "Il", "8cF", "0xr", "2MQ", "62w", "777", "4mp", "5h0", "1d", "9Og", "1KO", "2nM", "6Uc", "54i", "4Nl", "7kn", "Vp", "01J",
+"0zC", "Yq", "60F", "4Am", "4oA", "6Zb", "3U", "0To", "8PG", "zm", "5g1", "4bq", "786", "aSl", "TA", "0ws", "JC", "0iq", "bhl", "73U",
+"5y3", "5lR", "336", "do", "3oo", "86", "45K", "7TA", "6jL", "5ON", "0dA", "Gs", "Hr", "8bX", "4Pn", "6eM", "6Ka", "5nc", "77", "24G",
+"kn", "8AD", "47z", "5v2", "aBo", "bgm", "0fp", "EB", "403", "4aZ", "0Zt", "yF", "Wj", "00P", "4Ov", "5J6", "63m", "4BF", "0yh", "ZZ",
+"tv", "0WD", "4lj", "6YI", "6VH", "4ck", "0XE", "2mf", "2CJ", "02a", "4MG", "6xd", "5E7", "5PV", "1kx", "Xk", "2O", "0Uu", "bTh", "7KY",
+"44Q", "4e8", "hE", "0Kw", "11r", "Fi", "6kV", "5NT", "4SE", "6ff", "KY", "0hk", "0FG", "eu", "6HJ", "49a", "4rh", "6GK", "jt", "0IF",
+"Q9", "P", "6ig", "5Le", "4Qt", "5T4", "Ih", "0jZ", "0Dv", "gD", "4j9", "5oy", "aD0", "7kb", "3PL", "01F", "m1", "xP", "6Uo", "54e",
+"59U", "a6E", "1h", "0VR", "85n", "196", "4V1", "4CP", "4LQ", "4Y0", "TM", "03w", "0YS", "za", "a9D", "56T", "4oM", "6Zn", "3Y", "b0",
+"0zO", "2Ol", "60J", "4Aa", "7za", "5OB", "0dM", "2Qn", "iS", "0Ja", "45G", "6Dl", "acN", "48w", "0GQ", "dc", "JO", "94L", "4RS", "4G2",
+"4H3", "5Ms", "12U", "EN", "kb", "0HP", "47v", "526", "6Km", "5no", "s3", "fR", "3NN", "0kL", "4Pb", "6eA", "0r", "0WH", "4lf", "6YE",
+"63a", "4BJ", "O7", "ZV", "Wf", "0tT", "4Oz", "6zY", "4t7", "4aV", "0Zx", "yJ", "2C", "0Uy", "4nW", "7KU", "61P", "5PZ", "1kt", "Xg",
+"UW", "02m", "4MK", "6xh", "6VD", "4cg", "0XI", "2mj", "0FK", "ey", "6HF", "49m", "4SI", "6fj", "KU", "0hg", "0eW", "Fe", "6kZ", "5NX",
+"4pU", "4e4", "hI", "2k9", "0Dz", "gH", "4j5", "5ou", "4Qx", "5T8", "Id", "0jV", "Q5", "DT", "6ik", "5Li", "46l", "6GG", "jx", "0IJ",
+"m5", "xT", "6Uk", "54a", "4Nd", "7kf", "Vx", "01B", "0xz", "192", "4V5", "4CT", "4mx", "5h8", "1l", "0VV", "0YW", "ze", "5g9", "4by",
+"4LU", "4Y4", "TI", "03s", "0zK", "Yy", "60N", "4Ae", "4oI", "6Zj", "wU", "b4", "iW", "0Je", "45C", "6Dh", "6jD", "5OF", "0dI", "2Qj",
+"JK", "0iy", "4RW", "4G6", "6IX", "48s", "0GU", "dg", "kf", "0HT", "47r", "522", "4H7", "5Mw", "0fx", "EJ", "Hz", "0kH", "4Pf", "6eE",
+"6Ki", "5nk", "s7", "fV", "63e", "4BN", "O3", "ZR", "0v", "0WL", "4lb", "6YA", "4t3", "4aR", "8Sd", "yN", "Wb", "00X", "b1E", "aPO",
+"61T", "bzL", "1kp", "Xc", "2G", "217", "4nS", "7KQ", "7Fa", "4cc", "0XM", "2mn", "US", "02i", "4MO", "6xl", "4SM", "6fn", "KQ", "0hc",
+"0FO", "27d", "6HB", "49i", "44Y", "4e0", "hM", "8Bg", "0eS", "Fa", "aAL", "bdN", "656", "70v", "3OP", "0jR", "8Mf", "gL", "4j1", "5oq",
+"46h", "6GC", "28e", "0IN", "Q1", "X", "6io", "5Lm", "6KV", "5nT", "1Uz", "fi", "HE", "0kw", "4PY", "4E8", "6hJ", "5MH", "0fG", "Eu",
+"kY", "0Hk", "47M", "6Ff", "6Ig", "48L", "51", "dX", "Jt", "0iF", "4Rh", "6gK", "4J9", "5Oy", "0dv", "GD", "ih", "0JZ", "4qt", "5t4",
+"4ov", "5j6", "3b", "0TX", "0zt", "YF", "60q", "4AZ", "4Lj", "6yI", "Tv", "03L", "0Yh", "zZ", "6We", "4bF", "4mG", "6Xd", "1S", "0Vi",
+"0xE", "2Mf", "6vH", "4Ck", "bth", "7kY", "VG", "0uu", "1Kx", "xk", "5e7", "5pV", "13t", "g", "5Y3", "5LR", "46W", "amn", "jC", "0Iq",
+"0DA", "gs", "6JL", "5oN", "4QC", "70I", "3Oo", "0jm", "0el", "2PO", "6ka", "5Nc", "44f", "6EM", "hr", "8BX", "0Fp", "eB", "abo", "49V",
+"4Sr", "5V2", "Kn", "8aD", "Ul", "02V", "4Mp", "5H0", "425", "57u", "0Xr", "2mQ", "2x", "0UB", "4nl", "7Kn", "61k", "5Pa", "1kO", "2NM",
+"2AL", "00g", "4OA", "6zb", "6TN", "4am", "0ZC", "yq", "0I", "0Ws", "58t", "a7d", "5G1", "4Bq", "84O", "Zm", "HA", "0ks", "bjn", "71W",
+"6KR", "5nP", "314", "fm", "29D", "0Ho", "47I", "6Fb", "6hN", "5ML", "0fC", "Eq", "Jp", "0iB", "4Rl", "6gO", "6Ic", "48H", "55", "26E",
+"il", "8CF", "45x", "508", "hYT", "beo", "0dr", "1a2", "0zp", "YB", "60u", "755", "4or", "5j2", "3f", "9Me", "0Yl", "2lO", "6Wa", "4bB",
+"4Ln", "6yM", "Tr", "03H", "0xA", "2Mb", "62D", "4Co", "4mC", "7HA", "1W", "0Vm", "8RE", "xo", "5e3", "54Z", "b0d", "aQn", "VC", "01y",
+"46S", "6Gx", "jG", "0Iu", "0gY", "c", "5Y7", "5LV", "4QG", "6dd", "3Ok", "0ji", "0DE", "gw", "6JH", "5oJ", "44b", "6EI", "hv", "0KD",
+"0eh", "FZ", "6ke", "5Ng", "4Sv", "5V6", "Kj", "0hX", "0Ft", "eF", "6Hy", "49R", "421", "4cX", "0Xv", "2mU", "Uh", "02R", "4Mt", "5H4",
+"61o", "5Pe", "M9", "XX", "vt", "0UF", "4nh", "7Kj", "6TJ", "4ai", "0ZG", "yu", "WY", "B8", "4OE", "6zf", "5G5", "4Bu", "1iz", "Zi",
+"0M", "0Ww", "4lY", "4y8", "6hB", "aW1", "0fO", "2Sl", "kQ", "0Hc", "47E", "6Fn", "aaL", "bDN", "0ES", "fa", "HM", "8bg", "4PQ", "4E0",
+"4J1", "5Oq", "10W", "GL", "3oP", "0JR", "45t", "504", "6Io", "48D", "59", "dP", "3LL", "0iN", "5BA", "6gC", "4Lb", "6yA", "2Bo", "03D",
+"o3", "zR", "6Wm", "4bN", "bUM", "a4G", "3j", "0TP", "87l", "YN", "4T3", "4AR", "4NS", "7kQ", "VO", "01u", "1Kp", "xc", "hfw", "54V",
+"4mO", "6Xl", "uS", "0Va", "0xM", "2Mn", "62H", "4Cc", "0DI", "25b", "6JD", "5oF", "4QK", "6dh", "IW", "0je", "0gU", "o", "6iX", "5LZ",
+"4rW", "4g6", "jK", "0Iy", "0Fx", "eJ", "4h7", "5mw", "4Sz", "6fY", "Kf", "0hT", "S7", "FV", "6ki", "5Nk", "44n", "6EE", "hz", "0KH",
+"2p", "0UJ", "4nd", "7Kf", "61c", "5Pi", "M5", "XT", "Ud", "0vV", "4Mx", "5H8", "4v5", "4cT", "0Xz", "2mY", "0A", "1GZ", "4lU", "4y4",
+"5G9", "4By", "0yW", "Ze", "WU", "B4", "4OI", "6zj", "6TF", "4ae", "0ZK", "yy", "kU", "0Hg", "47A", "6Fj", "6hF", "5MD", "0fK", "Ey",
+"HI", "2K9", "4PU", "4E4", "6KZ", "5nX", "0EW", "fe", "id", "0JV", "45p", "500", "4J5", "5Ou", "0dz", "GH", "Jx", "0iJ", "4Rd", "6gG",
+"6Ik", "5li", "q5", "dT", "o7", "zV", "6Wi", "4bJ", "4Lf", "6yE", "Tz", "0wH", "0zx", "YJ", "4T7", "4AV", "4oz", "6ZY", "3n", "0TT",
+"1Kt", "xg", "6UX", "54R", "4NW", "7kU", "VK", "01q", "0xI", "2Mj", "62L", "4Cg", "4mK", "6Xh", "uW", "0Ve", "4QO", "6dl", "IS", "0ja",
+"0DM", "25f", "7Za", "5oB", "4rS", "4g2", "jO", "9PD", "0gQ", "k", "aCN", "685", "674", "72t", "Kb", "0hP", "8Od", "eN", "4h3", "49Z",
+"44j", "6EA", "3nN", "0KL", "S3", "FR", "6km", "5No", "61g", "5Pm", "M1", "XP", "2t", "0UN", "ad0", "7Kb", "429", "4cP", "8Qf", "39t",
+"0c3", "02Z", "b3G", "aRM", "63V", "bxN", "0yS", "Za", "0E", "235", "4lQ", "4y0", "6TB", "4aa", "0ZO", "2ol", "WQ", "B0", "4OM", "6zn",
+"4i4", "5lt", "1WZ", "dI", "Je", "0iW", "4Ry", "5W9", "6jj", "5Oh", "R4", "GU", "iy", "0JK", "45m", "6DF", "6KG", "5nE", "0EJ", "fx",
+"HT", "0kf", "4PH", "6ek", "5X8", "5MY", "0fV", "Ed", "kH", "0Hz", "4sT", "4f5", "4mV", "4x7", "1B", "0Vx", "0xT", "0m5", "62Q", "4Cz",
+"4NJ", "7kH", "VV", "C7", "1Ki", "xz", "6UE", "54O", "4og", "6ZD", "3s", "0TI", "L6", "YW", "6th", "4AK", "6l9", "6yX", "Tg", "0wU",
+"0Yy", "zK", "4w6", "4bW", "11T", "FO", "4K2", "5Nr", "44w", "517", "hc", "0KQ", "p2", "eS", "6Hl", "49G", "4Sc", "72i", "3MO", "0hM",
+"0gL", "v", "6iA", "5LC", "46F", "6Gm", "jR", "1YA", "0DP", "gb", "hyv", "bEM", "4QR", "4D3", "IN", "8cd", "WL", "00v", "4OP", "4Z1",
+"hgt", "55U", "0ZR", "0O3", "0X", "a1", "4lL", "6Yo", "63K", "5RA", "0yN", "2Lm", "2Cl", "02G", "4Ma", "6xB", "6Vn", "4cM", "n0", "39i",
+"2i", "0US", "bTN", "a5D", "4U0", "5Pp", "86o", "XM", "Ja", "0iS", "667", "73w", "4i0", "48Y", "8Ng", "dM", "3oM", "0JO", "45i", "6DB",
+"6jn", "5Ol", "R0", "GQ", "HP", "0kb", "4PL", "6eo", "6KC", "5nA", "0EN", "24e", "kL", "8Af", "47X", "4f1", "aBM", "696", "0fR", "0s3",
+"0xP", "0m1", "62U", "byM", "4mR", "4x3", "1F", "226", "1Km", "2no", "6UA", "54K", "4NN", "7kL", "VR", "C3", "L2", "YS", "60d", "4AO",
+"4oc", "7Ja", "3w", "0TM", "8Pe", "zO", "4w2", "4bS", "b2D", "aSN", "Tc", "03Y", "44s", "513", "hg", "0KU", "0ey", "FK", "4K6", "5Nv",
+"4Sg", "6fD", "3MK", "0hI", "p6", "eW", "6Hh", "49C", "46B", "6Gi", "jV", "0Id", "0gH", "r", "6iE", "5LG", "4QV", "4D7", "IJ", "0jx",
+"0DT", "gf", "6JY", "bEI", "5d8", "4ax", "0ZV", "yd", "WH", "00r", "4OT", "4Z5", "63O", "4Bd", "0yJ", "Zx", "tT", "a5", "4lH", "6Yk",
+"6Vj", "4cI", "n4", "2mD", "Uy", "02C", "4Me", "6xF", "4U4", "5Pt", "1kZ", "XI", "2m", "0UW", "4ny", "5k9", "6jb", "ber", "0do", "2QL",
+"iq", "0JC", "45e", "6DN", "acl", "48U", "0Gs", "dA", "Jm", "94n", "4Rq", "5W1", "5X0", "5MQ", "12w", "El", "1M2", "0Hr", "47T", "alm",
+"6KO", "5nM", "0EB", "fp", "3Nl", "0kn", "bjs", "6ec", "4NB", "aQs", "3Pn", "01d", "1Ka", "xr", "6UM", "54G", "59w", "a6g", "1J", "0Vp",
+"85L", "d3D", "5F2", "4Cr", "4Ls", "5I3", "To", "03U", "0Yq", "zC", "436", "56v", "4oo", "6ZL", "ws", "0TA", "0zm", "2ON", "60h", "4AC",
+"42", "27B", "6Hd", "49O", "4Sk", "6fH", "Kw", "0hE", "0eu", "FG", "6kx", "5Nz", "4pw", "5u7", "hk", "0KY", "0DX", "gj", "5z6", "5oW",
+"4QZ", "6dy", "IF", "0jt", "0gD", "Dv", "6iI", "5LK", "46N", "6Ge", "jZ", "0Ih", "0P", "a9", "4lD", "6Yg", "63C", "4Bh", "0yF", "Zt",
+"WD", "0tv", "4OX", "4Z9", "5d4", "4at", "0ZZ", "yh", "2a", "1Ez", "4nu", "5k5", "4U8", "5Px", "1kV", "XE", "Uu", "02O", "4Mi", "6xJ",
+"6Vf", "4cE", "n8", "2mH", "iu", "0JG", "45a", "6DJ", "6jf", "5Od", "R8", "GY", "Ji", "1yz", "4Ru", "5W5", "4i8", "48Q", "0Gw", "dE",
+"kD", "0Hv", "47P", "4f9", "5X4", "5MU", "0fZ", "Eh", "HX", "0kj", "4PD", "6eg", "6KK", "5nI", "0EF", "ft", "1Ke", "xv", "6UI", "54C",
+"4NF", "7kD", "VZ", "0uh", "0xX", "0m9", "5F6", "4Cv", "4mZ", "6Xy", "1N", "0Vt", "0Yu", "zG", "432", "56r", "4Lw", "5I7", "Tk", "03Q",
+"0zi", "2OJ", "60l", "4AG", "4ok", "6ZH", "ww", "0TE", "4So", "6fL", "Ks", "0hA", "46", "27F", "7XA", "49K", "4ps", "5u3", "ho", "8BE",
+"0eq", "FC", "aAn", "bdl", "bkm", "70T", "IB", "0jp", "307", "gn", "5z2", "5oS", "46J", "6Ga", "28G", "0Il", "13i", "z", "6iM", "5LO",
+"63G", "4Bl", "0yB", "Zp", "0T", "0Wn", "58i", "6Yc", "5d0", "4ap", "8SF", "yl", "1q2", "00z", "b1g", "aPm", "61v", "746", "1kR", "XA",
+"2e", "9Lf", "4nq", "5k1", "6Vb", "4cA", "0Xo", "2mL", "Uq", "02K", "4Mm", "6xN", "8YG", "7e", "5n1", "4kq", "716", "64v", "2KP", "1nR",
+"07K", "Pq", "69F", "4Hm", "4fA", "6Sb", "2hL", "1MN", "0Rn", "5T", "7LB", "5ya", "4Gl", "66G", "2Ia", "08J", "05z", "1t2", "aUm", "b4g",
+"4dp", "5a0", "8d", "8VF", "bn", "357", "4zr", "6OQ", "75T", "bnm", "0op", "LB", "Ar", "16i", "4Yn", "6lM", "6Ba", "43J", "0Ll", "2yO",
+"22F", "16", "4xC", "agr", "6cL", "4Vo", "0mA", "Ns", "CC", "14X", "bal", "aDn", "5p3", "4us", "8GE", "mo", "5L7", "4Iw", "06Q", "Qk",
+"1Y5", "1LT", "53r", "462", "7Oi", "4jk", "0QE", "rw", "2JJ", "1oH", "4DG", "65l", "7nD", "4KF", "0ph", "SZ", "2kg", "1Ne", "4ej", "6PI",
+"493", "4hZ", "0St", "4N", "0h9", "09P", "4Fv", "5C6", "4Xt", "6mW", "023", "0cZ", "0Mv", "nD", "4c9", "42P", "5kI", "6NK", "ct", "1Pg",
+"X9", "MX", "74N", "4UD", "4ZE", "6of", "BY", "W8", "0OG", "lu", "6AJ", "40a", "4yY", "4l8", "aE", "0Bw", "18r", "Oi", "5R5", "4Wu",
+"4EY", "4P8", "2KT", "1nV", "8YC", "7a", "5n5", "4ku", "4fE", "6Sf", "2hH", "k8", "07O", "Pu", "69B", "4Hi", "4Gh", "66C", "2Ie", "08N",
+"d9", "5P", "7LF", "4iD", "4dt", "5a4", "2jy", "3o9", "0qv", "RD", "7oZ", "4JX", "6ay", "4TZ", "0ot", "LF", "bj", "0AX", "4zv", "6OU",
+"6Be", "43N", "0Lh", "oZ", "Av", "0bD", "4Yj", "6lI", "6cH", "4Vk", "0mE", "Nw", "22B", "12", "4xG", "6Md", "5p7", "4uw", "0NY", "mk",
+"CG", "1pT", "5Kz", "6nx", "1Y1", "1LP", "53v", "466", "5L3", "4Is", "06U", "Qo", "2JN", "1oL", "4DC", "65h", "7Om", "4jo", "0QA", "rs",
+"9z", "1Na", "4en", "6PM", "aTs", "4KB", "04d", "2EO", "d6D", "09T", "4Fr", "5C2", "497", "bRm", "0Sp", "4J", "0Mr", "1H2", "aim", "42T",
+"4Xp", "6mS", "027", "17w", "0nn", "3Kl", "74J", "5Ea", "5kM", "6NO", "cp", "1Pc", "0OC", "lq", "6AN", "40e", "4ZA", "6ob", "2TL", "0ao",
+"18v", "Om", "5R1", "4Wq", "bCn", "afl", "aA", "0Bs", "07C", "Py", "69N", "4He", "4fI", "6Sj", "2hD", "k4", "0PW", "7m", "5n9", "4ky",
+"4EU", "4P4", "2KX", "1nZ", "05r", "RH", "7oV", "4JT", "4dx", "5a8", "8l", "1Ow", "d5", "qT", "7LJ", "4iH", "4Gd", "66O", "2Ii", "08B",
+"Az", "0bH", "4Yf", "6lE", "6Bi", "43B", "z7", "oV", "bf", "0AT", "4zz", "6OY", "4A7", "4TV", "0ox", "LJ", "CK", "14P", "5Kv", "4N6",
+"543", "41s", "0NU", "mg", "22N", "u6", "4xK", "6Mh", "6cD", "4Vg", "0mI", "2Xj", "7Oa", "4jc", "0QM", "6w", "2JB", "I2", "4DO", "65d",
+"68T", "b7D", "06Y", "Qc", "dSm", "287", "4gS", "4r2", "7MP", "4hR", "276", "4F", "0h1", "09X", "b8E", "67U", "7nL", "4KN", "F3", "SR",
+"9v", "1Nm", "4eb", "6PA", "5kA", "6NC", "21e", "1Po", "X1", "MP", "74F", "4UL", "bbO", "79v", "0v3", "0cR", "8Df", "nL", "4c1", "42X",
+"4yQ", "4l0", "aM", "8Kg", "0lS", "Oa", "76w", "637", "4ZM", "6on", "BQ", "W0", "0OO", "2zl", "6AB", "40i", "4fM", "6Sn", "3xa", "k0",
+"07G", "2Fl", "69J", "4Ha", "4EQ", "4P0", "d5g", "83o", "0PS", "7i", "a0D", "bQN", "50U", "hbt", "8h", "1Os", "05v", "RL", "7oR", "4JP",
+"5WA", "66K", "2Im", "08F", "d1", "5X", "7LN", "4iL", "6Bm", "43F", "z3", "oR", "2Wo", "0bL", "4Yb", "6lA", "4A3", "4TR", "8fd", "LN",
+"bb", "0AP", "cPl", "aeO", "547", "41w", "0NQ", "mc", "CO", "14T", "5Kr", "4N2", "77i", "4Vc", "0mM", "2Xn", "22J", "u2", "4xO", "6Ml",
+"2JF", "I6", "4DK", "6qh", "7Oe", "4jg", "0QI", "6s", "1Y9", "1LX", "4gW", "4r6", "68P", "5YZ", "0rU", "Qg", "0h5", "1mu", "4Fz", "67Q",
+"7MT", "4hV", "0Sx", "4B", "9r", "1Ni", "4ef", "6PE", "7nH", "4KJ", "F7", "SV", "X5", "MT", "74B", "4UH", "5kE", "6NG", "cx", "1Pk",
+"0Mz", "nH", "4c5", "4vT", "4Xx", "79r", "0v7", "0cV", "0lW", "Oe", "5R9", "4Wy", "4yU", "4l4", "aI", "1RZ", "0OK", "ly", "6AF", "40m",
+"4ZI", "6oj", "BU", "W4", "265", "5E", "488", "4iQ", "b9F", "66V", "0i2", "1lr", "G0", "RQ", "7oO", "4JM", "4da", "6QB", "8u", "1On",
+"0PN", "7t", "7Nb", "aa0", "4EL", "64g", "2KA", "H1", "07Z", "0f3", "69W", "b6G", "4fP", "479", "dRn", "294", "22W", "8Jd", "4xR", "4m3",
+"77t", "624", "0mP", "Nb", "CR", "V3", "5Ko", "6nm", "ajS", "41j", "0NL", "3kN", "20f", "0AM", "4zc", "aeR", "6al", "4TO", "Y2", "LS",
+"Ac", "0bQ", "bcL", "78u", "4b2", "4wS", "8Ee", "oO", "7nU", "4KW", "04q", "SK", "9o", "1Nt", "51R", "6PX", "7MI", "4hK", "e6", "pW",
+"2Hj", "09A", "4Fg", "67L", "68M", "4If", "0rH", "Qz", "2iG", "j7", "4gJ", "6Ri", "7Ox", "4jz", "0QT", "6n", "1z8", "1oY", "4DV", "4Q7",
+"4ZT", "4O5", "BH", "0az", "0OV", "ld", "550", "40p", "4yH", "6Lk", "aT", "t5", "0lJ", "Ox", "6bG", "4Wd", "4Xe", "6mF", "2Vh", "0cK",
+"0Mg", "nU", "6Cj", "42A", "5kX", "6NZ", "ce", "1Pv", "2N9", "MI", "7pW", "4UU", "4Gy", "5B9", "0i6", "1lv", "1BZ", "5A", "7LW", "4iU",
+"4de", "6QF", "8q", "1Oj", "G4", "RU", "7oK", "4JI", "4EH", "64c", "2KE", "H5", "0PJ", "7p", "7Nf", "4kd", "4fT", "4s5", "2hY", "290",
+"0sV", "Pd", "5M8", "4Hx", "6cY", "4Vz", "0mT", "Nf", "1F8", "0Cx", "4xV", "4m7", "7Pd", "41n", "0NH", "mz", "CV", "V7", "5Kk", "6ni",
+"6ah", "4TK", "Y6", "LW", "20b", "0AI", "4zg", "6OD", "4b6", "4wW", "0Ly", "oK", "Ag", "0bU", "5IZ", "6lX", "9k", "1Np", "51V", "azN",
+"7nQ", "4KS", "04u", "SO", "2Hn", "09E", "4Fc", "67H", "7MM", "4hO", "e2", "pS", "2iC", "j3", "4gN", "6Rm", "68I", "4Ib", "06D", "2Go",
+"d4d", "82l", "4DR", "4Q3", "a1G", "bPM", "0QP", "6j", "0OR", "0Z3", "554", "40t", "4ZP", "4O1", "BL", "15W", "0lN", "2Ym", "6bC", "5GA",
+"4yL", "6Lo", "aP", "09", "0Mc", "nQ", "6Cn", "42E", "4Xa", "6mB", "2Vl", "0cO", "8gg", "MM", "7pS", "4UQ", "bAN", "adL", "ca", "1Pr",
+"G8", "RY", "7oG", "4JE", "4di", "6QJ", "2jd", "1Of", "0Rw", "5M", "480", "4iY", "4Gu", "5B5", "2Ix", "08S", "07R", "Ph", "5M4", "4Ht",
+"4fX", "471", "1X6", "1MW", "0PF", "st", "7Nj", "4kh", "4ED", "64o", "2KI", "H9", "CZ", "14A", "5Kg", "6ne", "7Ph", "41b", "0ND", "mv",
+"1F4", "0Ct", "4xZ", "6My", "5S6", "4Vv", "0mX", "Nj", "Ak", "0bY", "4Yw", "6lT", "6Bx", "43S", "0Lu", "oG", "bw", "0AE", "4zk", "6OH",
+"6ad", "4TG", "0oi", "2ZJ", "7MA", "4hC", "0Sm", "4W", "2Hb", "09I", "4Fo", "67D", "aTn", "b5d", "04y", "SC", "9g", "8WE", "4es", "6PP",
+"5o2", "4jr", "8XD", "6f", "1z0", "1oQ", "705", "65u", "68E", "4In", "06H", "Qr", "2iO", "1LM", "4gB", "6Ra", "5ia", "6Lc", "23E", "05",
+"0lB", "Op", "6bO", "4Wl", "c4F", "aEm", "1d2", "0ar", "8FF", "ll", "558", "40x", "5kP", "6NR", "cm", "344", "0ns", "MA", "74W", "bon",
+"4Xm", "6mN", "3FA", "0cC", "0Mo", "2xL", "6Cb", "42I", "4dm", "6QN", "8y", "1Ob", "05g", "2DL", "7oC", "4JA", "4Gq", "5B1", "d7G", "08W",
+"0Rs", "5I", "484", "bSn", "52u", "475", "1X2", "1MS", "07V", "Pl", "5M0", "4Hp", "5Ua", "64k", "2KM", "1nO", "0PB", "7x", "7Nn", "4kl",
+"7Pl", "41f", "8GX", "mr", "2UO", "14E", "5Kc", "6na", "5S2", "4Vr", "19u", "Nn", "1F0", "0Cp", "bBm", "ago", "ahn", "43W", "0Lq", "oC",
+"Ao", "16t", "4Ys", "6lP", "75I", "4TC", "0om", "2ZN", "bs", "0AA", "4zo", "6OL", "2Hf", "09M", "4Fk", "6sH", "7ME", "4hG", "0Si", "4S",
+"9c", "1Nx", "4ew", "6PT", "7nY", "bqh", "0pu", "SG", "1z4", "1oU", "4DZ", "65q", "5o6", "4jv", "0QX", "6b", "2iK", "1LI", "4gF", "6Re",
+"68A", "4Ij", "06L", "Qv", "0lF", "Ot", "6bK", "4Wh", "4yD", "6Lg", "aX", "01", "0OZ", "lh", "5q4", "4tt", "4ZX", "4O9", "BD", "0av",
+"0nw", "ME", "74S", "4UY", "5kT", "6NV", "ci", "1Pz", "0Mk", "nY", "6Cf", "42M", "4Xi", "6mJ", "2Vd", "0cG", "bL", "8Hf", "4zP", "4o1",
+"75v", "606", "0oR", "0z3", "AP", "T1", "4YL", "6lo", "6BC", "43h", "0LN", "2ym", "22d", "0CO", "4xa", "6MB", "6cn", "4VM", "0mc", "NQ",
+"Ca", "14z", "baN", "aDL", "7PS", "41Y", "8Gg", "mM", "247", "7G", "7NQ", "4kS", "com", "64T", "0k0", "1np", "E2", "PS", "69d", "4HO",
+"4fc", "7Ca", "2hn", "1Ml", "0RL", "5v", "avS", "4ib", "4GN", "66e", "2IC", "J3", "05X", "Rb", "aUO", "b4E", "4dR", "4q3", "8F", "8Vd",
+"4XV", "4M7", "1f8", "0cx", "0MT", "nf", "572", "42r", "5kk", "6Ni", "cV", "v7", "0nH", "Mz", "74l", "4Uf", "4Zg", "6oD", "2Tj", "0aI",
+"y6", "lW", "6Ah", "40C", "5iZ", "583", "ag", "0BU", "0ly", "OK", "4B6", "4WW", "7lW", "4IU", "06s", "QI", "0I6", "1Lv", "4gy", "5b9",
+"7OK", "4jI", "g4", "rU", "2Jh", "1oj", "4De", "65N", "7nf", "4Kd", "04B", "Sx", "2kE", "h5", "4eH", "6Pk", "5m8", "4hx", "0SV", "4l",
+"2HY", "09r", "4FT", "4S5", "5Q8", "4Tx", "0oV", "Ld", "bH", "0Az", "4zT", "4o5", "6BG", "43l", "0LJ", "ox", "AT", "T5", "4YH", "6lk",
+"6cj", "4VI", "0mg", "NU", "2vh", "0CK", "4xe", "6MF", "7PW", "4uU", "2n9", "mI", "Ce", "1pv", "5KX", "6nZ", "5UZ", "64P", "0k4", "1nt",
+"0Py", "7C", "7NU", "4kW", "4fg", "6SD", "2hj", "1Mh", "E6", "PW", "7mI", "4HK", "4GJ", "66a", "2IG", "J7", "0RH", "5r", "7Ld", "4if",
+"4dV", "4q7", "8B", "1OY", "0qT", "Rf", "7ox", "4Jz", "0MP", "nb", "576", "42v", "4XR", "4M3", "dll", "17U", "0nL", "3KN", "74h", "4Ub",
+"5ko", "6Nm", "cR", "v3", "y2", "lS", "6Al", "40G", "4Zc", "aER", "2Tn", "0aM", "18T", "OO", "4B2", "4WS", "bCL", "587", "ac", "0BQ",
+"0I2", "1Lr", "53T", "axL", "68z", "4IQ", "06w", "QM", "2Jl", "1on", "4Da", "65J", "7OO", "4jM", "g0", "6Y", "9X", "h1", "4eL", "6Po",
+"7nb", "aA0", "04F", "2Em", "d6f", "09v", "4FP", "4S1", "a3E", "bRO", "0SR", "4h", "AX", "T9", "4YD", "6lg", "6BK", "4wh", "0LF", "ot",
+"bD", "0Av", "4zX", "4o9", "5Q4", "4Tt", "0oZ", "Lh", "Ci", "14r", "5KT", "6nV", "ajh", "41Q", "0Nw", "mE", "22l", "0CG", "4xi", "6MJ",
+"6cf", "4VE", "0mk", "NY", "07a", "2FJ", "69l", "4HG", "4fk", "6SH", "2hf", "1Md", "0Pu", "7O", "7NY", "bQh", "4Ew", "6pT", "0k8", "1nx",
+"05P", "Rj", "5O6", "4Jv", "4dZ", "453", "8N", "1OU", "0RD", "qv", "7Lh", "4ij", "4GF", "66m", "2IK", "1lI", "5kc", "6Na", "21G", "27",
+"8gX", "Mr", "74d", "4Un", "bbm", "79T", "1f0", "0cp", "397", "nn", "5s2", "42z", "4ys", "6LP", "ao", "366", "0lq", "OC", "76U", "bml",
+"4Zo", "6oL", "Bs", "0aA", "0Om", "2zN", "7QA", "40K", "7OC", "4jA", "0Qo", "6U", "3ZA", "1ob", "4Dm", "65F", "68v", "b7f", "0rs", "QA",
+"dSO", "8UG", "4gq", "5b1", "5m0", "4hp", "8ZF", "4d", "1x2", "09z", "727", "67w", "7nn", "4Kl", "04J", "Sp", "9T", "1NO", "51i", "6Pc",
+"6BO", "43d", "0LB", "op", "2WM", "0bn", "5Ia", "6lc", "5Q0", "4Tp", "8fF", "Ll", "1D2", "0Ar", "cPN", "aem", "ajl", "41U", "0Ns", "mA",
+"Cm", "14v", "5KP", "6nR", "6cb", "4VA", "0mo", "2XL", "22h", "0CC", "4xm", "6MN", "4fo", "6SL", "2hb", "8TY", "07e", "2FN", "69h", "4HC",
+"4Es", "64X", "d5E", "83M", "0Pq", "7K", "a0f", "bQl", "50w", "457", "8J", "1OQ", "05T", "Rn", "5O2", "4Jr", "4GB", "66i", "2IO", "08d",
+"1Ba", "5z", "7Ll", "4in", "0nD", "Mv", "7ph", "4Uj", "5kg", "6Ne", "cZ", "23", "0MX", "nj", "5s6", "4vv", "4XZ", "6my", "1f4", "0ct",
+"0lu", "OG", "6bx", "5Gz", "4yw", "6LT", "ak", "0BY", "0Oi", "2zJ", "6Ad", "40O", "4Zk", "6oH", "Bw", "0aE", "2Jd", "1of", "4Di", "65B",
+"7OG", "4jE", "g8", "6Q", "2ix", "1Lz", "4gu", "5b5", "68r", "4IY", "0rw", "QE", "1x6", "1mW", "4FX", "4S9", "5m4", "4ht", "0SZ", "ph",
+"9P", "h9", "4eD", "6Pg", "7nj", "4Kh", "04N", "St", "22u", "375", "4xp", "598", "77V", "blo", "0mr", "1h2", "Cp", "14k", "5KM", "6nO",
+"7PB", "41H", "0Nn", "3kl", "20D", "34", "4zA", "6Ob", "6aN", "4Tm", "0oC", "Lq", "AA", "0bs", "bcn", "78W", "569", "43y", "384", "om",
+"9Kd", "5g", "5l3", "4is", "734", "66t", "1y1", "08y", "05I", "Rs", "7om", "4Jo", "4dC", "7AA", "8W", "1OL", "0Pl", "7V", "ats", "4kB",
+"4En", "64E", "2Kc", "1na", "07x", "PB", "69u", "b6e", "4fr", "5c2", "dRL", "8TD", "4Zv", "6oU", "Bj", "0aX", "0Ot", "lF", "6Ay", "40R",
+"4yj", "6LI", "av", "0BD", "0lh", "OZ", "6be", "4WF", "4XG", "6md", "2VJ", "0ci", "0ME", "nw", "6CH", "42c", "5kz", "6Nx", "cG", "1PT",
+"0nY", "Mk", "5P7", "4Uw", "5N5", "4Ku", "04S", "Si", "9M", "1NV", "4eY", "440", "7Mk", "4hi", "0SG", "pu", "2HH", "K8", "4FE", "67n",
+"68o", "4ID", "1", "QX", "2ie", "1Lg", "4gh", "6RK", "7OZ", "4jX", "0Qv", "6L", "2Jy", "3O9", "4Dt", "5A4", "4C9", "4VX", "0mv", "ND",
+"22q", "0CZ", "4xt", "6MW", "7PF", "41L", "x9", "mX", "Ct", "14o", "5KI", "6nK", "6aJ", "4Ti", "0oG", "Lu", "bY", "30", "4zE", "6Of",
+"5r5", "4wu", "380", "oi", "AE", "0bw", "4YY", "4L8", "5Wz", "66p", "1y5", "1lT", "0RY", "5c", "5l7", "4iw", "4dG", "6Qd", "8S", "1OH",
+"05M", "Rw", "7oi", "4Jk", "4Ej", "64A", "2Kg", "1ne", "0Ph", "7R", "7ND", "4kF", "4fv", "5c6", "0H9", "1My", "0st", "PF", "69q", "4HZ",
+"0Op", "lB", "ako", "40V", "4Zr", "6oQ", "Bn", "15u", "0ll", "2YO", "6ba", "4WB", "4yn", "6LM", "ar", "1Ra", "0MA", "ns", "6CL", "42g",
+"4XC", "79I", "2VN", "0cm", "8gE", "Mo", "5P3", "4Us", "bAl", "adn", "cC", "1PP", "9I", "1NR", "51t", "444", "5N1", "4Kq", "04W", "Sm",
+"2HL", "09g", "4FA", "67j", "7Mo", "4hm", "0SC", "4y", "2ia", "1Lc", "4gl", "6RO", "68k", "5Ya", "5", "2GM", "d4F", "82N", "4Dp", "5A0",
+"a1e", "bPo", "0Qr", "6H", "Cx", "14c", "5KE", "6nG", "7PJ", "4uH", "x5", "mT", "0V7", "0CV", "4xx", "590", "4C5", "4VT", "0mz", "NH",
+"AI", "16R", "4YU", "4L4", "561", "43q", "0LW", "oe", "bU", "w4", "4zI", "6Oj", "6aF", "4Te", "0oK", "Ly", "05A", "2Dj", "7oe", "4Jg",
+"4dK", "6Qh", "2jF", "i6", "0RU", "5o", "7Ly", "5yZ", "4GW", "4R6", "1y9", "08q", "07p", "PJ", "7mT", "4HV", "4fz", "6SY", "0H5", "1Mu",
+"f7", "sV", "7NH", "4kJ", "4Ef", "64M", "2Kk", "1ni", "4yb", "6LA", "23g", "0BL", "Z3", "OR", "6bm", "4WN", "c4d", "aEO", "Bb", "0aP",
+"8Fd", "lN", "4a3", "40Z", "5kr", "4n2", "cO", "8Ie", "0nQ", "Mc", "74u", "615", "4XO", "6ml", "2VB", "U2", "0MM", "2xn", "7Sa", "42k",
+"7Mc", "4ha", "0SO", "4u", "3Xa", "K0", "4FM", "67f", "aTL", "b5F", "0pS", "Sa", "9E", "8Wg", "4eQ", "448", "7OR", "4jP", "254", "6D",
+"0j3", "1os", "cnn", "65W", "68g", "4IL", "9", "QP", "2im", "1Lo", "53I", "6RC", "7PN", "41D", "x1", "mP", "2Um", "14g", "5KA", "6nC",
+"4C1", "4VP", "19W", "NL", "0V3", "0CR", "bBO", "594", "565", "43u", "0LS", "oa", "AM", "16V", "4YQ", "4L0", "6aB", "4Ta", "0oO", "2Zl",
+"bQ", "38", "4zM", "6On", "4dO", "6Ql", "2jB", "i2", "05E", "2Dn", "7oa", "4Jc", "4GS", "4R2", "d7e", "08u", "0RQ", "5k", "a2F", "bSL",
+"52W", "ayO", "0H1", "1Mq", "07t", "PN", "69y", "4HR", "4Eb", "64I", "2Ko", "1nm", "f3", "7Z", "7NL", "4kN", "Z7", "OV", "6bi", "4WJ",
+"4yf", "6LE", "az", "0BH", "0Ox", "lJ", "4a7", "4tV", "4Zz", "6oY", "Bf", "0aT", "0nU", "Mg", "74q", "5EZ", "5kv", "4n6", "cK", "1PX",
+"0MI", "2xj", "6CD", "42o", "4XK", "6mh", "2VF", "U6", "2HD", "K4", "4FI", "67b", "7Mg", "4he", "0SK", "4q", "9A", "1NZ", "4eU", "4p4",
+"5N9", "4Ky", "0pW", "Se", "0j7", "1ow", "4Dx", "5A8", "7OV", "4jT", "0Qz", "rH", "2ii", "1Lk", "4gd", "6RG", "68c", "4IH", "D5", "QT",
+"5Ls", "4I3", "F", "13U", "0IP", "jb", "536", "46v", "5oo", "6Jm", "gR", "r3", "0jL", "3ON", "6dA", "4Qb", "5NB", "aAR", "2Pn", "0eM",
+"0Ka", "hS", "6El", "44G", "49w", "abN", "ec", "0FQ", "8ae", "KO", "4F2", "4SS", "4X0", "4MQ", "02w", "UM", "0M2", "0XS", "57T", "a8D",
+"7KO", "4nM", "c0", "2Y", "2Nl", "1kn", "aJ1", "61J", "6zC", "aE0", "00F", "2Am", "yP", "l1", "4aL", "6To", "a7E", "58U", "0WR", "0h",
+"ZL", "84n", "4BP", "4W1", "fH", "0Ez", "5nu", "4k5", "5U8", "4Px", "0kV", "Hd", "ET", "P5", "5Mi", "6hk", "6FG", "47l", "0HJ", "kx",
+"dy", "0GK", "48m", "6IF", "6gj", "4RI", "0ig", "JU", "Ge", "0dW", "5OX", "5Z9", "4d4", "4qU", "1ZZ", "iI", "0Ty", "3C", "4z6", "4oW",
+"5QZ", "60P", "Yg", "0zU", "A6", "TW", "6yh", "4LK", "4bg", "6WD", "2lj", "0YI", "0VH", "1r", "6XE", "4mf", "4CJ", "62a", "2MG", "N7",
+"0uT", "Vf", "7kx", "4Nz", "5pw", "4u7", "xJ", "1KY", "0IT", "jf", "532", "46r", "5Lw", "4I7", "B", "0gx", "0jH", "Iz", "6dE", "4Qf",
+"5ok", "6Ji", "gV", "r7", "0Ke", "hW", "6Eh", "44C", "5NF", "6kD", "2Pj", "0eI", "0hy", "KK", "4F6", "4SW", "49s", "6HX", "eg", "0FU",
+"0M6", "0XW", "4cy", "5f9", "4X4", "4MU", "02s", "UI", "Xy", "1kj", "5PD", "61N", "7KK", "4nI", "c4", "vU", "yT", "l5", "4aH", "6Tk",
+"6zG", "4Od", "00B", "Wx", "ZH", "0yz", "4BT", "4W5", "5i8", "4lx", "0WV", "0l", "71v", "646", "0kR", "3NP", "fL", "8Lf", "5nq", "4k1",
+"6FC", "47h", "0HN", "29e", "EP", "P1", "5Mm", "6ho", "6gn", "4RM", "0ic", "JQ", "26d", "0GO", "48i", "6IB", "4d0", "45Y", "8Cg", "iM",
+"Ga", "0dS", "beN", "hYu", "ckm", "60T", "Yc", "0zQ", "207", "3G", "4z2", "4oS", "4bc", "7Ga", "2ln", "0YM", "A2", "TS", "6yl", "4LO",
+"4CN", "62e", "2MC", "N3", "0VL", "1v", "6XA", "4mb", "5ps", "4u3", "xN", "8Rd", "01X", "Vb", "aQO", "b0E", "5og", "6Je", "gZ", "63",
+"0jD", "Iv", "6dI", "4Qj", "7l9", "6iy", "N", "0gt", "0IX", "jj", "5w6", "4rv", "5mV", "5x7", "ek", "0FY", "0hu", "KG", "6fx", "5Cz",
+"5NJ", "6kH", "Fw", "0eE", "92", "3nk", "6Ed", "44O", "7KG", "4nE", "c8", "2Q", "Xu", "1kf", "5PH", "61B", "4X8", "4MY", "0vw", "UE",
+"2mx", "1Hz", "4cu", "5f5", "5i4", "4lt", "0WZ", "th", "ZD", "0yv", "4BX", "4W9", "6zK", "4Oh", "00N", "Wt", "yX", "l9", "4aD", "6Tg",
+"2SM", "0fn", "5Ma", "6hc", "6FO", "47d", "0HB", "kp", "24Y", "0Er", "bDo", "aam", "5U0", "4Pp", "8bF", "Hl", "Gm", "10v", "5OP", "5Z1",
+"anl", "45U", "0Js", "iA", "dq", "0GC", "48e", "6IN", "6gb", "4RA", "0io", "3Lm", "03e", "2BN", "7iA", "4LC", "4bo", "6WL", "zs", "0YA",
+"0Tq", "3K", "a4f", "bUl", "4As", "5D3", "Yo", "87M", "01T", "Vn", "5K2", "4Nr", "54w", "417", "xB", "1KQ", "1Fa", "1z", "6XM", "4mn",
+"4CB", "62i", "2MO", "0xl", "1za", "Ir", "6dM", "4Qn", "5oc", "6Ja", "25G", "67", "9Pe", "jn", "5w2", "46z", "bfm", "aCo", "J", "0gp",
+"0hq", "KC", "72U", "bil", "5mR", "5x3", "eo", "326", "96", "3no", "7UA", "44K", "5NN", "6kL", "Fs", "0eA", "Xq", "1kb", "5PL", "61F",
+"7KC", "4nA", "0Uo", "2U", "39U", "8QG", "4cq", "5f1", "aRl", "796", "0vs", "UA", "2LQ", "0yr", "767", "63w", "5i0", "4lp", "9Ng", "0d",
+"2oM", "0Zn", "55i", "6Tc", "6zO", "4Ol", "00J", "Wp", "6FK", "4sh", "0HF", "kt", "EX", "P9", "5Me", "6hg", "5U4", "4Pt", "0kZ", "Hh",
+"fD", "0Ev", "5ny", "4k9", "4d8", "45Q", "0Jw", "iE", "Gi", "10r", "5OT", "5Z5", "6gf", "4RE", "0ik", "JY", "du", "0GG", "48a", "6IJ",
+"4bk", "6WH", "zw", "0YE", "03a", "2BJ", "6yd", "4LG", "4Aw", "5D7", "Yk", "0zY", "0Tu", "3O", "6Zx", "bUh", "54s", "413", "xF", "1KU",
+"01P", "Vj", "5K6", "4Nv", "4CF", "62m", "2MK", "0xh", "0VD", "uv", "6XI", "4mj", "5NS", "6kQ", "Fn", "11u", "0Kp", "hB", "aoo", "44V",
+"49f", "6HM", "er", "1Va", "0hl", "3Mn", "6fa", "4SB", "5Lb", "7yA", "W", "0gm", "0IA", "js", "6GL", "46g", "bEl", "hyW", "gC", "0Dq",
+"8cE", "Io", "5T3", "4Qs", "5J1", "4Oq", "00W", "Wm", "yA", "0Zs", "55t", "404", "6YN", "4lm", "0WC", "0y", "2LL", "0yo", "4BA", "63j",
+"6xc", "bws", "02f", "2CM", "2ma", "0XB", "4cl", "6VO", "a5e", "bTo", "0Ur", "2H", "Xl", "86N", "5PQ", "5E0", "dh", "0GZ", "5lU", "5y4",
+"4G9", "4RX", "0iv", "JD", "Gt", "0dF", "5OI", "6jK", "6Dg", "45L", "81", "iX", "fY", "70", "5nd", "6Kf", "6eJ", "4Pi", "0kG", "Hu",
+"EE", "0fw", "5Mx", "4H8", "5v5", "4su", "1Xz", "ki", "0VY", "1c", "5h7", "4mw", "5Sz", "62p", "2MV", "0xu", "01M", "Vw", "7ki", "4Nk",
+"54n", "6Ud", "2nJ", "1KH", "0Th", "3R", "6Ze", "4oF", "4Aj", "60A", "Yv", "0zD", "0wt", "TF", "6yy", "4LZ", "4bv", "5g6", "zj", "0YX",
+"0Kt", "hF", "6Ey", "44R", "5NW", "6kU", "Fj", "0eX", "0hh", "KZ", "6fe", "4SF", "49b", "6HI", "ev", "0FD", "0IE", "jw", "6GH", "46c",
+"5Lf", "6id", "S", "0gi", "0jY", "Ik", "5T7", "4Qw", "5oz", "6Jx", "gG", "0Du", "yE", "0Zw", "4aY", "400", "5J5", "4Ou", "00S", "Wi",
+"ZY", "O8", "4BE", "63n", "6YJ", "4li", "0WG", "tu", "2me", "0XF", "4ch", "6VK", "6xg", "4MD", "02b", "UX", "Xh", "3K9", "5PU", "5E4",
+"7KZ", "4nX", "0Uv", "2L", "73V", "bho", "0ir", "1l2", "dl", "335", "48x", "5y0", "6Dc", "45H", "85", "3ol", "Gp", "0dB", "5OM", "6jO",
+"6eN", "4Pm", "0kC", "Hq", "24D", "74", "bDr", "6Kb", "529", "47y", "8AG", "km", "EA", "0fs", "bgn", "aBl", "774", "62t", "199", "0xq",
+"9Od", "1g", "5h3", "4ms", "54j", "7EA", "2nN", "1KL", "01I", "Vs", "7km", "4No", "4An", "60E", "Yr", "1ja", "0Tl", "3V", "6Za", "4oB",
+"4br", "5g2", "zn", "8PD", "03x", "TB", "aSo", "785", "49n", "6HE", "ez", "0FH", "0hd", "KV", "6fi", "4SJ", "bdI", "6kY", "Ff", "0eT",
+"0Kx", "hJ", "4e7", "4pV", "5ov", "4j6", "gK", "0Dy", "0jU", "Ig", "6dX", "5AZ", "5Lj", "6ih", "DW", "Q6", "0II", "28b", "6GD", "46o",
+"6YF", "4le", "0WK", "0q", "ZU", "O4", "4BI", "63b", "5J9", "4Oy", "0tW", "We", "yI", "1JZ", "4aU", "4t4", "7KV", "4nT", "0Uz", "vH",
+"Xd", "1kw", "5PY", "5E8", "6xk", "4MH", "02n", "UT", "2mi", "0XJ", "4cd", "6VG", "2Qm", "0dN", "5OA", "6jC", "6Do", "45D", "89", "iP",
+"0R3", "0GR", "48t", "acM", "4G1", "4RP", "94O", "JL", "EM", "12V", "5Mp", "4H0", "525", "47u", "0HS", "ka", "fQ", "78", "5nl", "6Kn",
+"6eB", "4Pa", "0kO", "3NM", "01E", "3PO", "7ka", "4Nc", "54f", "6Ul", "xS", "m2", "0VQ", "1k", "a6F", "59V", "4CS", "4V2", "195", "85m",
+"03t", "TN", "4Y3", "4LR", "56W", "a9G", "zb", "0YP", "b3", "3Z", "6Zm", "4oN", "4Ab", "60I", "2Oo", "0zL", "1xA", "KR", "6fm", "4SN",
+"49j", "6HA", "27g", "0FL", "8Bd", "hN", "4e3", "44Z", "bdM", "aAO", "Fb", "0eP", "0jQ", "Ic", "70u", "655", "5or", "4j2", "gO", "8Me",
+"0IM", "28f", "7Wa", "46k", "5Ln", "6il", "DS", "Q2", "ZQ", "O0", "4BM", "63f", "6YB", "4la", "0WO", "0u", "yM", "8Sg", "4aQ", "408",
+"aPL", "b1F", "0tS", "Wa", "0n3", "1ks", "bzO", "61W", "7KR", "4nP", "214", "2D", "2mm", "0XN", "57I", "6VC", "6xo", "4ML", "02j", "UP",
+"6Dk", "4qH", "0Jf", "iT", "Gx", "0dJ", "5OE", "6jG", "4G5", "4RT", "0iz", "JH", "dd", "0GV", "48p", "5y8", "521", "47q", "0HW", "ke",
+"EI", "12R", "5Mt", "4H4", "6eF", "4Pe", "0kK", "Hy", "fU", "s4", "5nh", "6Kj", "54b", "6Uh", "xW", "m6", "01A", "3PK", "7ke", "4Ng",
+"4CW", "4V6", "191", "0xy", "0VU", "1o", "6XX", "59R", "4bz", "6WY", "zf", "0YT", "03p", "TJ", "4Y7", "4LV", "4Af", "60M", "Yz", "0zH",
+"b7", "wV", "6Zi", "4oJ", "5H3", "4Ms", "02U", "Uo", "2mR", "0Xq", "57v", "426", "7Km", "4no", "0UA", "vs", "2NN", "1kL", "5Pb", "61h",
+"6za", "4OB", "00d", "2AO", "yr", "1Ja", "4an", "6TM", "a7g", "58w", "0Wp", "0J", "Zn", "84L", "4Br", "5G2", "5LQ", "5Y0", "d", "13w",
+"0Ir", "1L2", "amm", "46T", "5oM", "6JO", "gp", "0DB", "0jn", "3Ol", "6dc", "5Aa", "bdr", "6kb", "2PL", "0eo", "0KC", "hq", "6EN", "44e",
+"49U", "abl", "eA", "0Fs", "8aG", "Km", "5V1", "4Sq", "1Dz", "3a", "5j5", "4ou", "4AY", "4T8", "YE", "0zw", "03O", "Tu", "6yJ", "4Li",
+"4bE", "6Wf", "zY", "o8", "0Vj", "1P", "6Xg", "4mD", "4Ch", "62C", "2Me", "0xF", "0uv", "VD", "7kZ", "4NX", "5pU", "5e4", "xh", "3k9",
+"fj", "0EX", "5nW", "6KU", "6ey", "4PZ", "0kt", "HF", "Ev", "0fD", "5MK", "6hI", "6Fe", "47N", "0Hh", "kZ", "26B", "52", "48O", "6Id",
+"6gH", "4Rk", "0iE", "Jw", "GG", "0du", "5Oz", "6jx", "5t7", "4qw", "0JY", "ik", "2mV", "0Xu", "57r", "422", "5H7", "4Mw", "02Q", "Uk",
+"2NJ", "1kH", "5Pf", "61l", "7Ki", "4nk", "0UE", "vw", "yv", "0ZD", "4aj", "6TI", "6ze", "4OF", "0th", "WZ", "Zj", "0yX", "4Bv", "5G6",
+"6Yy", "4lZ", "0Wt", "0N", "0Iv", "jD", "4g9", "46P", "5LU", "5Y4", "Dh", "0gZ", "0jj", "IX", "6dg", "4QD", "5oI", "6JK", "gt", "0DF",
+"0KG", "hu", "6EJ", "44a", "5Nd", "6kf", "FY", "S8", "1xz", "Ki", "5V5", "4Su", "49Q", "4h8", "eE", "0Fw", "756", "60v", "YA", "0zs",
+"9Mf", "3e", "5j1", "4oq", "4bA", "6Wb", "2lL", "0Yo", "03K", "Tq", "6yN", "4Lm", "4Cl", "62G", "2Ma", "0xB", "0Vn", "1T", "6Xc", "59i",
+"54Y", "5e0", "xl", "8RF", "01z", "1p2", "aQm", "b0g", "71T", "bjm", "0kp", "HB", "fn", "317", "5nS", "6KQ", "6Fa", "47J", "0Hl", "29G",
+"Er", "12i", "5MO", "6hM", "6gL", "4Ro", "0iA", "Js", "26F", "56", "48K", "7YA", "5t3", "4qs", "8CE", "io", "GC", "0dq", "bel", "hYW",
+"7Ke", "4ng", "0UI", "2s", "XW", "M6", "5Pj", "6uh", "6xX", "6m9", "0vU", "Ug", "2mZ", "0Xy", "4cW", "4v6", "4y7", "4lV", "0Wx", "0B",
+"Zf", "0yT", "4Bz", "63Q", "6zi", "4OJ", "B7", "WV", "yz", "0ZH", "4af", "6TE", "5oE", "6JG", "gx", "0DJ", "0jf", "IT", "6dk", "4QH",
+"5LY", "5Y8", "l", "0gV", "0Iz", "jH", "4g5", "4rT", "5mt", "4h4", "eI", "1VZ", "0hW", "Ke", "5V9", "4Sy", "5Nh", "6kj", "FU", "S4",
+"0KK", "hy", "6EF", "44m", "03G", "2Bl", "6yB", "4La", "4bM", "6Wn", "zQ", "o0", "0TS", "3i", "a4D", "bUN", "4AQ", "4T0", "YM", "87o",
+"01v", "VL", "7kR", "4NP", "54U", "hft", "0N3", "1Ks", "0Vb", "1X", "6Xo", "4mL", "5SA", "62K", "2Mm", "0xN", "2So", "0fL", "5MC", "6hA",
+"6Fm", "47F", "1XA", "kR", "fb", "0EP", "bDM", "aaO", "4E3", "4PR", "8bd", "HN", "GO", "10T", "5Or", "4J2", "507", "45w", "0JQ", "ic",
+"dS", "q2", "48G", "6Il", "73i", "4Rc", "0iM", "3LO", "XS", "M2", "5Pn", "61d", "7Ka", "4nc", "0UM", "2w", "39w", "8Qe", "4cS", "4v2",
+"aRN", "b3D", "02Y", "Uc", "Zb", "0yP", "bxM", "63U", "4y3", "4lR", "236", "0F", "2oo", "0ZL", "4ab", "6TA", "6zm", "4ON", "B3", "WR",
+"0jb", "IP", "6do", "4QL", "5oA", "6JC", "25e", "0DN", "9PG", "jL", "4g1", "46X", "686", "aCM", "h", "0gR", "0hS", "Ka", "72w", "677",
+"49Y", "4h0", "eM", "8Og", "0KO", "3nM", "6EB", "44i", "5Nl", "6kn", "FQ", "S0", "4bI", "6Wj", "zU", "o4", "03C", "Ty", "6yF", "4Le",
+"4AU", "4T4", "YI", "1jZ", "0TW", "3m", "5j9", "4oy", "54Q", "5e8", "xd", "1Kw", "01r", "VH", "7kV", "4NT", "4Cd", "62O", "2Mi", "0xJ",
+"0Vf", "uT", "6Xk", "4mH", "6Fi", "47B", "0Hd", "kV", "Ez", "0fH", "5MG", "6hE", "4E7", "4PV", "0kx", "HJ", "ff", "0ET", "bDI", "6KY",
+"503", "45s", "0JU", "ig", "GK", "0dy", "5Ov", "4J6", "6gD", "4Rg", "0iI", "3LK", "dW", "q6", "48C", "6Ih", "4Z2", "4OS", "00u", "WO",
+"yc", "0ZQ", "55V", "hgw", "6Yl", "4lO", "a2", "tS", "2Ln", "0yM", "4Bc", "63H", "6xA", "4Mb", "02D", "2Co", "2mC", "n3", "4cN", "6Vm",
+"a5G", "bTM", "0UP", "2j", "XN", "86l", "5Ps", "4U3", "5Nq", "4K1", "FL", "11W", "0KR", "3nP", "514", "44t", "49D", "6Ho", "eP", "49",
+"0hN", "3ML", "6fC", "5CA", "aV1", "6iB", "u", "0gO", "0Ic", "jQ", "6Gn", "46E", "bEN", "hyu", "ga", "0DS", "8cg", "IM", "4D0", "4QQ",
+"1FZ", "1A", "4x4", "4mU", "4Cy", "5F9", "0m6", "0xW", "C4", "VU", "7kK", "4NI", "54L", "6UF", "xy", "1Kj", "0TJ", "3p", "6ZG", "4od",
+"4AH", "60c", "YT", "L5", "0wV", "Td", "5I8", "4Lx", "4bT", "4w5", "zH", "0Yz", "dJ", "0Gx", "5lw", "4i7", "6gY", "4Rz", "0iT", "Jf",
+"GV", "R7", "5Ok", "6ji", "6DE", "45n", "0JH", "iz", "24b", "0EI", "5nF", "6KD", "6eh", "4PK", "0ke", "HW", "Eg", "0fU", "5MZ", "6hX",
+"4f6", "4sW", "0Hy", "kK", "yg", "0ZU", "55R", "6TX", "4Z6", "4OW", "00q", "WK", "2Lj", "0yI", "4Bg", "63L", "6Yh", "4lK", "a6", "tW",
+"2mG", "n7", "4cJ", "6Vi", "6xE", "4Mf", "0vH", "Uz", "XJ", "1kY", "5Pw", "4U7", "7Kx", "4nz", "0UT", "2n", "0KV", "hd", "510", "44p",
+"5Nu", "4K5", "FH", "0ez", "0hJ", "Kx", "6fG", "4Sd", "5mi", "6Hk", "eT", "p5", "0Ig", "jU", "6Gj", "46A", "5LD", "6iF", "q", "0gK",
+"1zZ", "II", "4D4", "4QU", "5oX", "5z9", "ge", "0DW", "byN", "62V", "0m2", "0xS", "225", "1E", "4x0", "4mQ", "54H", "6UB", "2nl", "1Kn",
+"C0", "VQ", "7kO", "4NM", "4AL", "60g", "YP", "L1", "0TN", "3t", "6ZC", "ae0", "4bP", "439", "zL", "8Pf", "03Z", "0b3", "aSM", "b2G",
+"73t", "664", "0iP", "Jb", "dN", "8Nd", "48Z", "4i3", "6DA", "45j", "0JL", "3oN", "GR", "R3", "5Oo", "6jm", "6el", "4PO", "0ka", "HS",
+"24f", "0EM", "5nB", "aaR", "4f2", "4sS", "8Ae", "kO", "Ec", "0fQ", "695", "aBN", "6Yd", "4lG", "0Wi", "0S", "Zw", "0yE", "4Bk", "6wH",
+"6zx", "buh", "0tu", "WG", "yk", "0ZY", "4aw", "5d7", "5k6", "4nv", "0UX", "2b", "XF", "1kU", "741", "61q", "6xI", "4Mj", "02L", "Uv",
+"2mK", "0Xh", "4cF", "6Ve", "49L", "6Hg", "eX", "41", "0hF", "Kt", "6fK", "4Sh", "5Ny", "4K9", "FD", "0ev", "0KZ", "hh", "5u4", "4pt",
+"5oT", "5z5", "gi", "1Tz", "0jw", "IE", "4D8", "4QY", "5LH", "6iJ", "Du", "0gG", "0Ik", "jY", "6Gf", "46M", "01g", "3Pm", "7kC", "4NA",
+"54D", "6UN", "xq", "1Kb", "0Vs", "1I", "a6d", "59t", "4Cq", "5F1", "d3G", "85O", "03V", "Tl", "5I0", "4Lp", "56u", "435", "2lQ", "0Yr",
+"0TB", "3x", "6ZO", "4ol", "5Qa", "60k", "2OM", "0zn", "2QO", "0dl", "5Oc", "6ja", "6DM", "45f", "1Za", "ir", "dB", "0Gp", "48V", "aco",
+"5W2", "4Rr", "94m", "Jn", "Eo", "12t", "5MR", "5X3", "aln", "47W", "0Hq", "kC", "fs", "0EA", "5nN", "6KL", "71I", "4PC", "0km", "3No",
+"Zs", "0yA", "4Bo", "63D", "7IA", "4lC", "0Wm", "0W", "yo", "8SE", "4as", "5d3", "aPn", "b1d", "00y", "WC", "XB", "1kQ", "745", "61u",
+"5k2", "4nr", "9Le", "2f", "2mO", "0Xl", "4cB", "6Va", "6xM", "4Mn", "02H", "Ur", "0hB", "Kp", "6fO", "4Sl", "49H", "6Hc", "27E", "45",
+"8BF", "hl", "518", "44x", "bdo", "aAm", "2PQ", "0er", "0js", "IA", "70W", "bkn", "5oP", "5z1", "gm", "304", "0Io", "28D", "6Gb", "46I",
+"5LL", "6iN", "y", "0gC", "5pH", "6UJ", "xu", "1Kf", "C8", "VY", "7kG", "4NE", "4Cu", "5F5", "2Mx", "1hz", "0Vw", "1M", "4x8", "4mY",
+"4bX", "431", "zD", "0Yv", "03R", "Th", "5I4", "4Lt", "4AD", "60o", "YX", "L9", "0TF", "wt", "6ZK", "4oh", "6DI", "45b", "0JD", "iv",
+"GZ", "0dh", "5Og", "6je", "5W6", "4Rv", "0iX", "Jj", "dF", "0Gt", "48R", "6Iy", "6Fx", "47S", "0Hu", "kG", "Ek", "0fY", "5MV", "5X7",
+"6ed", "4PG", "0ki", "3Nk", "fw", "0EE", "5nJ", "6KH", "356", "bo", "6OP", "4zs", "bnl", "75U", "LC", "0oq", "0bA", "As", "6lL", "4Yo",
+"43K", "7RA", "2yN", "0Lm", "17", "22G", "6Ma", "4xB", "4Vn", "6cM", "Nr", "19i", "14Y", "CB", "aDo", "bam", "41z", "5p2", "mn", "8GD",
+"7d", "8YF", "4kp", "5n0", "64w", "717", "1nS", "2KQ", "Pp", "07J", "4Hl", "69G", "6Sc", "52i", "1MO", "2hM", "5U", "0Ro", "4iA", "7LC",
+"66F", "4Gm", "08K", "3YA", "RA", "0qs", "b4f", "aUl", "5a1", "4dq", "8VG", "8e", "6mV", "4Xu", "17r", "022", "nE", "0Mw", "42Q", "4c8",
+"6NJ", "5kH", "1Pf", "cu", "MY", "X8", "4UE", "74O", "6og", "4ZD", "W9", "BX", "lt", "0OF", "4th", "6AK", "4l9", "4yX", "0Bv", "aD",
+"Oh", "0lZ", "4Wt", "5R4", "4Iv", "5L6", "Qj", "06P", "1LU", "1Y4", "463", "4gZ", "4jj", "7Oh", "rv", "0QD", "1oI", "2JK", "65m", "4DF",
+"4KG", "7nE", "2EJ", "04a", "1Nd", "2kf", "6PH", "4ek", "5xz", "492", "4O", "0Su", "09Q", "0h8", "5C7", "4Fw", "5Dz", "6ax", "LG", "0ou",
+"0AY", "bk", "6OT", "4zw", "43O", "6Bd", "2yJ", "0Li", "0bE", "Aw", "6lH", "4Yk", "4Vj", "6cI", "Nv", "0mD", "13", "22C", "6Me", "4xF",
+"4uv", "5p6", "mj", "0NX", "1pU", "CF", "6ny", "7k9", "4P9", "4EX", "1nW", "2KU", "sh", "0PZ", "4kt", "5n4", "6Sg", "4fD", "k9", "2hI",
+"Pt", "07N", "4Hh", "69C", "66B", "4Gi", "08O", "2Id", "5Q", "d8", "4iE", "7LG", "5a5", "4du", "1Oz", "8a", "RE", "0qw", "4JY", "aUh",
+"nA", "0Ms", "42U", "ail", "6mR", "4Xq", "17v", "026", "3Km", "0no", "4UA", "74K", "6NN", "5kL", "1Pb", "cq", "lp", "0OB", "40d", "6AO",
+"6oc", "5Ja", "0an", "2TM", "Ol", "18w", "4Wp", "5R0", "afm", "bCo", "0Br", "1G2", "1LQ", "1Y0", "467", "53w", "4Ir", "5L2", "Qn", "06T",
+"1oM", "2JO", "65i", "4DB", "4jn", "7Ol", "6z", "1Aa", "8WY", "2kb", "6PL", "4eo", "4KC", "7nA", "2EN", "04e", "09U", "d6E", "5C3", "4Fs",
+"bRl", "496", "4K", "0Sq", "0bI", "2Wj", "6lD", "4Yg", "43C", "6Bh", "oW", "z6", "0AU", "bg", "6OX", "5jZ", "4TW", "4A6", "LK", "0oy",
+"14Q", "CJ", "4N7", "5Kw", "41r", "542", "mf", "0NT", "u7", "22O", "6Mi", "4xJ", "4Vf", "6cE", "Nz", "0mH", "Px", "07B", "4Hd", "69O",
+"6Sk", "4fH", "k5", "2hE", "7l", "0PV", "4kx", "5n8", "4P5", "4ET", "83j", "2KY", "RI", "05s", "4JU", "7oW", "5a9", "4dy", "1Ov", "8m",
+"qU", "d4", "4iI", "7LK", "66N", "4Ge", "08C", "2Ih", "6NB", "a59", "1Pn", "21d", "MQ", "X0", "4UM", "74G", "79w", "bbN", "0cS", "0v2",
+"nM", "8Dg", "42Y", "4c0", "4l1", "4yP", "8Kf", "aL", "0y3", "0lR", "636", "76v", "6oo", "4ZL", "W1", "BP", "2zm", "0ON", "40h", "6AC",
+"4jb", "auS", "6v", "0QL", "I3", "2JC", "65e", "4DN", "b7E", "68U", "Qb", "06X", "286", "dSl", "4r3", "4gR", "4hS", "7MQ", "4G", "277",
+"09Y", "0h0", "67T", "b8D", "4KO", "7nM", "SS", "F2", "1Nl", "9w", "azR", "4ec", "43G", "6Bl", "oS", "z2", "0bM", "2Wn", "78i", "4Yc",
+"4TS", "4A2", "LO", "8fe", "0AQ", "bc", "aeN", "cPm", "41v", "546", "mb", "0NP", "14U", "CN", "4N3", "5Ks", "4Vb", "6cA", "2Xo", "0mL",
+"u3", "22K", "6Mm", "4xN", "6So", "4fL", "k1", "2hA", "2Fm", "07F", "5XA", "69K", "4P1", "4EP", "83n", "d5f", "7h", "0PR", "bQO", "a0E",
+"hbu", "50T", "1Or", "8i", "RM", "05w", "4JQ", "7oS", "66J", "4Ga", "08G", "2Il", "5Y", "d0", "4iM", "7LO", "MU", "X4", "4UI", "74C",
+"6NF", "5kD", "1Pj", "cy", "nI", "2m9", "4vU", "4c4", "6mZ", "4Xy", "0cW", "0v6", "Od", "0lV", "4Wx", "5R8", "4l5", "4yT", "0Bz", "aH",
+"lx", "0OJ", "40l", "6AG", "6ok", "4ZH", "W5", "BT", "I7", "2JG", "65a", "4DJ", "4jf", "7Od", "6r", "0QH", "1LY", "1Y8", "4r7", "4gV",
+"4Iz", "68Q", "Qf", "0rT", "1mt", "0h4", "67P", "5VZ", "4hW", "7MU", "4C", "0Sy", "1Nh", "9s", "6PD", "4eg", "4KK", "7nI", "SW", "F6",
+"8Je", "22V", "4m2", "4xS", "625", "77u", "Nc", "0mQ", "V2", "CS", "6nl", "5Kn", "41k", "7Pa", "3kO", "0NM", "0AL", "20g", "6OA", "4zb",
+"4TN", "6am", "LR", "Y3", "0bP", "Ab", "78t", "bcM", "43Z", "4b3", "oN", "8Ed", "5D", "264", "4iP", "489", "66W", "b9G", "08Z", "0i3",
+"RP", "G1", "4JL", "7oN", "6QC", "50I", "1Oo", "8t", "7u", "0PO", "4ka", "7Nc", "64f", "4EM", "H0", "963", "Pa", "0sS", "b6F", "69V",
+"478", "4fQ", "295", "dRo", "4O4", "4ZU", "15R", "BI", "le", "0OW", "40q", "551", "6Lj", "4yI", "t4", "aU", "Oy", "0lK", "4We", "6bF",
+"6mG", "4Xd", "0cJ", "2Vi", "nT", "0Mf", "4vH", "6Ck", "adI", "5kY", "1Pw", "cd", "MH", "0nz", "4UT", "7pV", "4KV", "7nT", "SJ", "04p",
+"1Nu", "9n", "6PY", "4ez", "4hJ", "7MH", "pV", "e7", "1mi", "2Hk", "67M", "4Ff", "4Ig", "68L", "2Gj", "06A", "j6", "2iF", "6Rh", "4gK",
+"5zZ", "7Oy", "6o", "0QU", "1oX", "1z9", "4Q6", "4DW", "5FZ", "6cX", "Ng", "0mU", "0Cy", "1F9", "4m6", "4xW", "41o", "7Pe", "3kK", "0NI",
+"V6", "CW", "6nh", "5Kj", "4TJ", "6ai", "LV", "Y7", "0AH", "bz", "6OE", "4zf", "4wV", "4b7", "oJ", "0Lx", "0bT", "Af", "6lY", "4Yz",
+"5B8", "4Gx", "1lw", "0i7", "qH", "0Rz", "4iT", "7LV", "6QG", "4dd", "1Ok", "8p", "RT", "G5", "4JH", "7oJ", "64b", "4EI", "H4", "2KD",
+"7q", "0PK", "4ke", "7Ng", "4s4", "4fU", "1MZ", "2hX", "Pe", "0sW", "4Hy", "5M9", "la", "0OS", "40u", "555", "4O0", "4ZQ", "15V", "BM",
+"2Yl", "0lO", "4Wa", "6bB", "6Ln", "4yM", "08", "aQ", "nP", "0Mb", "42D", "6Co", "6mC", "5HA", "0cN", "2Vm", "ML", "8gf", "4UP", "74Z",
+"adM", "bAO", "1Ps", "0U3", "1Nq", "9j", "azO", "51W", "4KR", "7nP", "SN", "04t", "09D", "2Ho", "67I", "4Fb", "4hN", "7ML", "4Z", "e3",
+"j2", "2iB", "6Rl", "4gO", "4Ic", "68H", "2Gn", "06E", "82m", "d4e", "4Q2", "4DS", "bPL", "a1F", "6k", "0QQ", "1pH", "2UJ", "6nd", "5Kf",
+"41c", "7Pi", "mw", "0NE", "0Cu", "1F5", "6Mx", "5hz", "4Vw", "5S7", "Nk", "0mY", "0bX", "Aj", "6lU", "4Yv", "43R", "6By", "oF", "0Lt",
+"0AD", "bv", "6OI", "4zj", "4TF", "6ae", "LZ", "0oh", "RX", "G9", "4JD", "7oF", "6QK", "4dh", "1Og", "2je", "5L", "0Rv", "4iX", "481",
+"5B4", "4Gt", "08R", "2Iy", "Pi", "07S", "4Hu", "5M5", "470", "4fY", "1MV", "1X7", "su", "0PG", "4ki", "7Nk", "64n", "4EE", "H8", "2KH",
+"6Lb", "4yA", "04", "23D", "Oq", "0lC", "4Wm", "6bN", "aEl", "c4G", "0as", "BA", "lm", "8FG", "40y", "559", "6NS", "5kQ", "345", "cl",
+"1k2", "0nr", "boo", "74V", "6mO", "4Xl", "0cB", "2Va", "2xM", "0Mn", "42H", "6Cc", "4hB", "aws", "4V", "0Sl", "09H", "2Hc", "67E", "4Fn",
+"b5e", "aTo", "SB", "04x", "8WD", "9f", "6PQ", "4er", "4js", "5o3", "6g", "8XE", "1oP", "1z1", "65t", "704", "4Io", "68D", "Qs", "06I",
+"1LL", "2iN", "7BA", "4gC", "41g", "7Pm", "ms", "0NA", "14D", "2UN", "aDr", "5Kb", "4Vs", "5S3", "No", "19t", "0Cq", "1F1", "agn", "bBl",
+"43V", "aho", "oB", "0Lp", "16u", "An", "6lQ", "4Yr", "4TB", "6aa", "2ZO", "0ol", "1Qa", "br", "6OM", "4zn", "6QO", "4dl", "1Oc", "8x",
+"2DM", "05f", "5Za", "7oB", "5B0", "4Gp", "08V", "d7F", "5H", "0Rr", "bSo", "485", "474", "52t", "1MR", "1X3", "Pm", "07W", "4Hq", "5M1",
+"64j", "4EA", "1nN", "2KL", "7y", "0PC", "4km", "7No", "Ou", "0lG", "4Wi", "6bJ", "6Lf", "4yE", "00", "aY", "li", "8FC", "4tu", "5q5",
+"4O8", "4ZY", "0aw", "BE", "MD", "0nv", "4UX", "74R", "6NW", "5kU", "341", "ch", "nX", "0Mj", "42L", "6Cg", "6mK", "4Xh", "0cF", "2Ve",
+"09L", "2Hg", "67A", "4Fj", "4hF", "7MD", "4R", "0Sh", "1Ny", "9b", "6PU", "4ev", "4KZ", "7nX", "SF", "0pt", "1oT", "1z5", "65p", "5Tz",
+"4jw", "5o7", "6c", "0QY", "1LH", "2iJ", "6Rd", "4gG", "4Ik", "7li", "Qw", "06M", "7F", "246", "4kR", "7NP", "64U", "col", "1nq", "0k1",
+"PR", "E3", "4HN", "69e", "6SA", "4fb", "1Mm", "2ho", "5w", "0RM", "4ic", "7La", "66d", "4GO", "J2", "2IB", "Rc", "05Y", "b4D", "aUN",
+"4q2", "4dS", "8Ve", "8G", "8Hg", "bM", "4o0", "4zQ", "607", "75w", "La", "0oS", "T0", "AQ", "6ln", "4YM", "43i", "6BB", "2yl", "0LO",
+"0CN", "22e", "6MC", "5hA", "4VL", "6co", "NP", "0mb", "1ps", "0u3", "aDM", "baO", "41X", "7PR", "mL", "8Gf", "4IT", "7lV", "QH", "06r",
+"1Lw", "0I7", "5b8", "4gx", "4jH", "7OJ", "rT", "g5", "1ok", "2Ji", "65O", "4Dd", "4Ke", "7ng", "Sy", "04C", "h4", "2kD", "6Pj", "4eI",
+"4hy", "5m9", "4m", "0SW", "09s", "2HX", "4S4", "4FU", "4M6", "4XW", "0cy", "1f9", "ng", "0MU", "42s", "573", "6Nh", "5kj", "v6", "cW",
+"3KK", "0nI", "4Ug", "74m", "6oE", "4Zf", "0aH", "Bz", "lV", "y7", "40B", "6Ai", "582", "4yz", "0BT", "af", "OJ", "0lx", "4WV", "4B7",
+"64Q", "4Ez", "1nu", "0k5", "7B", "0Px", "4kV", "7NT", "6SE", "4ff", "1Mi", "2hk", "PV", "E7", "4HJ", "69a", "6rh", "4GK", "J6", "2IF",
+"5s", "0RI", "4ig", "7Le", "4q6", "4dW", "1OX", "8C", "Rg", "0qU", "5ZZ", "7oy", "4Ty", "5Q9", "Le", "0oW", "1QZ", "bI", "4o4", "4zU",
+"43m", "6BF", "oy", "0LK", "T4", "AU", "6lj", "4YI", "4VH", "6ck", "NT", "0mf", "0CJ", "22a", "6MG", "4xd", "4uT", "7PV", "mH", "0Nz",
+"1pw", "Cd", "aDI", "5KY", "1Ls", "0I3", "axM", "53U", "4IP", "7lR", "QL", "06v", "1oo", "2Jm", "65K", "5TA", "4jL", "7ON", "6X", "g1",
+"h0", "9Y", "6Pn", "4eM", "4Ka", "7nc", "2El", "04G", "09w", "d6g", "4S0", "4FQ", "bRN", "a3D", "4i", "0SS", "nc", "0MQ", "42w", "577",
+"4M2", "4XS", "17T", "dlm", "3KO", "0nM", "4Uc", "74i", "6Nl", "5kn", "v2", "cS", "lR", "y3", "40F", "6Am", "6oA", "4Zb", "0aL", "2To",
+"ON", "18U", "4WR", "4B3", "586", "bCM", "0BP", "ab", "PZ", "0sh", "4HF", "69m", "6SI", "4fj", "1Me", "2hg", "7N", "0Pt", "4kZ", "7NX",
+"6pU", "4Ev", "1ny", "0k9", "Rk", "05Q", "4Jw", "5O7", "452", "50r", "1OT", "8O", "qw", "0RE", "4ik", "7Li", "66l", "4GG", "08a", "2IJ",
+"T8", "AY", "6lf", "4YE", "43a", "6BJ", "ou", "0LG", "0Aw", "bE", "4o8", "4zY", "4Tu", "5Q5", "Li", "8fC", "14s", "Ch", "6nW", "5KU",
+"41P", "7PZ", "mD", "0Nv", "0CF", "22m", "6MK", "4xh", "4VD", "6cg", "NX", "0mj", "5za", "7OB", "6T", "0Qn", "1oc", "2Ja", "65G", "4Dl",
+"b7g", "68w", "1w2", "06z", "8UF", "dSN", "5b0", "4gp", "4hq", "5m1", "4e", "8ZG", "1mR", "1x3", "67v", "726", "4Km", "7no", "Sq", "04K",
+"1NN", "9U", "6Pb", "4eA", "adr", "5kb", "26", "21F", "Ms", "0nA", "4Uo", "74e", "79U", "bbl", "0cq", "1f1", "no", "396", "4vs", "5s3",
+"6LQ", "4yr", "367", "an", "OB", "0lp", "bmm", "76T", "6oM", "4Zn", "15i", "Br", "2zO", "0Ol", "40J", "6Aa", "6SM", "4fn", "1Ma", "2hc",
+"2FO", "07d", "4HB", "69i", "64Y", "4Er", "83L", "d5D", "7J", "0Pp", "bQm", "a0g", "456", "50v", "1OP", "8K", "Ro", "05U", "4Js", "5O3",
+"66h", "4GC", "08e", "2IN", "qs", "0RA", "4io", "7Lm", "43e", "6BN", "oq", "0LC", "0bo", "2WL", "6lb", "4YA", "4Tq", "5Q1", "Lm", "8fG",
+"0As", "bA", "ael", "cPO", "41T", "ajm", "1K2", "0Nr", "14w", "Cl", "6nS", "5KQ", "5Fa", "6cc", "2XM", "0mn", "0CB", "22i", "6MO", "4xl",
+"1og", "2Je", "65C", "4Dh", "4jD", "7OF", "6P", "g9", "3l9", "2iy", "5b4", "4gt", "4IX", "68s", "QD", "0rv", "1mV", "1x7", "4S8", "4FY",
+"4hu", "5m5", "4a", "1Cz", "h8", "9Q", "6Pf", "4eE", "4Ki", "7nk", "Su", "04O", "Mw", "0nE", "4Uk", "74a", "6Nd", "5kf", "22", "21B",
+"nk", "0MY", "4vw", "5s7", "6mx", "5Hz", "0cu", "1f5", "OF", "0lt", "4WZ", "6by", "6LU", "4yv", "0BX", "aj", "lZ", "0Oh", "40N", "6Ae",
+"6oI", "4Zj", "0aD", "Bv", "5f", "9Ke", "4ir", "5l2", "66u", "735", "08x", "1y0", "Rr", "05H", "4Jn", "7ol", "6Qa", "4dB", "1OM", "8V",
+"7W", "0Pm", "4kC", "7NA", "64D", "4Eo", "83Q", "2Kb", "PC", "07y", "b6d", "69t", "5c3", "4fs", "8TE", "dRM", "374", "22t", "599", "4xq",
+"bln", "77W", "NA", "0ms", "14j", "Cq", "6nN", "5KL", "41I", "7PC", "3km", "0No", "35", "20E", "6Oc", "5ja", "4Tl", "6aO", "Lp", "0oB",
+"0br", "1g2", "78V", "bco", "43x", "568", "ol", "385", "4Kt", "5N4", "Sh", "04R", "1NW", "9L", "441", "4eX", "4hh", "7Mj", "pt", "0SF",
+"K9", "2HI", "67o", "4FD", "4IE", "68n", "QY", "0", "1Lf", "2id", "6RJ", "4gi", "4jY", "auh", "6M", "0Qw", "1oz", "2Jx", "5A5", "4Du",
+"6oT", "4Zw", "0aY", "Bk", "lG", "0Ou", "40S", "6Ax", "6LH", "4yk", "0BE", "aw", "2YJ", "0li", "4WG", "6bd", "6me", "4XF", "0ch", "2VK",
+"nv", "0MD", "42b", "6CI", "6Ny", "7K9", "1PU", "cF", "Mj", "0nX", "4Uv", "5P6", "66q", "4GZ", "1lU", "1y4", "5b", "0RX", "4iv", "5l6",
+"6Qe", "4dF", "1OI", "8R", "Rv", "05L", "4Jj", "7oh", "6pH", "4Ek", "1nd", "2Kf", "7S", "0Pi", "4kG", "7NE", "5c7", "4fw", "1Mx", "0H8",
+"PG", "0su", "5Xz", "69p", "4VY", "4C8", "NE", "0mw", "1Sz", "22p", "6MV", "4xu", "41M", "7PG", "mY", "x8", "14n", "Cu", "6nJ", "5KH",
+"4Th", "6aK", "Lt", "0oF", "31", "bX", "6Og", "4zD", "4wt", "5r4", "oh", "0LZ", "0bv", "AD", "4L9", "4YX", "1NS", "9H", "445", "51u",
+"4Kp", "5N0", "Sl", "04V", "09f", "2HM", "67k", "5Va", "4hl", "7Mn", "4x", "0SB", "1Lb", "3yA", "6RN", "4gm", "4IA", "68j", "2GL", "4",
+"82O", "d4G", "5A1", "4Dq", "bPn", "a1d", "6I", "0Qs", "lC", "0Oq", "40W", "akn", "6oP", "4Zs", "15t", "Bo", "2YN", "0lm", "4WC", "76I",
+"6LL", "4yo", "0BA", "as", "nr", "8DX", "42f", "6CM", "6ma", "4XB", "0cl", "2VO", "Mn", "8gD", "4Ur", "5P2", "ado", "bAm", "1PQ", "cB",
+"Rz", "0qH", "4Jf", "7od", "6Qi", "4dJ", "i7", "2jG", "5n", "0RT", "4iz", "7Lx", "4R7", "4GV", "08p", "1y8", "PK", "07q", "4HW", "7mU",
+"6SX", "52R", "1Mt", "0H4", "sW", "f6", "4kK", "7NI", "64L", "4Eg", "1nh", "2Kj", "14b", "Cy", "6nF", "5KD", "41A", "7PK", "mU", "x4",
+"0CW", "0V6", "591", "4xy", "4VU", "4C4", "NI", "19R", "0bz", "AH", "4L5", "4YT", "43p", "560", "od", "0LV", "w5", "bT", "6Ok", "4zH",
+"4Td", "6aG", "Lx", "0oJ", "5xA", "7Mb", "4t", "0SN", "K1", "2HA", "67g", "4FL", "b5G", "aTM", "0e3", "04Z", "8Wf", "9D", "449", "4eP",
+"4jQ", "7OS", "6E", "255", "1or", "0j2", "65V", "cno", "4IM", "68f", "QQ", "8", "1Ln", "2il", "6RB", "4ga", "afR", "4yc", "0BM", "23f",
+"OS", "Z2", "4WO", "6bl", "aEN", "c4e", "0aQ", "Bc", "lO", "8Fe", "4tS", "4a2", "4n3", "5ks", "8Id", "cN", "Mb", "0nP", "614", "74t",
+"6mm", "4XN", "U3", "2VC", "2xo", "0ML", "42j", "6CA", "6Qm", "4dN", "i3", "8Z", "2Do", "05D", "4Jb", "aUS", "4R3", "4GR", "08t", "d7d",
+"5j", "0RP", "bSM", "a2G", "ayN", "52V", "1Mp", "0H0", "PO", "07u", "4HS", "69x", "64H", "4Ec", "1nl", "2Kn", "sS", "f2", "4kO", "7NM",
+"41E", "7PO", "mQ", "x0", "14f", "2Ul", "6nB", "aQ1", "4VQ", "4C0", "NM", "19V", "0CS", "0V2", "595", "bBN", "43t", "564", "0Y3", "0LR",
+"16W", "AL", "4L1", "4YP", "5DA", "6aC", "2Zm", "0oN", "39", "bP", "6Oo", "4zL", "K5", "2HE", "67c", "4FH", "4hd", "7Mf", "4p", "0SJ",
+"8Wb", "2kY", "4p5", "4eT", "4Kx", "5N8", "Sd", "0pV", "1ov", "0j6", "5A9", "4Dy", "4jU", "7OW", "6A", "1AZ", "1Lj", "2ih", "6RF", "4ge",
+"4II", "68b", "QU", "D4", "OW", "Z6", "4WK", "6bh", "6LD", "4yg", "0BI", "23b", "lK", "0Oy", "4tW", "4a6", "6oX", "5JZ", "0aU", "Bg",
+"Mf", "0nT", "4Uz", "74p", "4n7", "5kw", "1PY", "cJ", "nz", "0MH", "42n", "6CE", "6mi", "4XJ", "U7", "2VG", "4MP", "4X1", "UL", "02v",
+"0XR", "0M3", "a8E", "57U", "4nL", "7KN", "2X", "c1", "1ko", "2Nm", "61K", "5PA", "4Oa", "6zB", "2Al", "00G", "l0", "yQ", "6Tn", "4aM",
+"58T", "a7D", "0i", "0WS", "84o", "ZM", "4W0", "4BQ", "4I2", "5Lr", "13T", "G", "jc", "0IQ", "46w", "537", "6Jl", "5on", "r2", "gS",
+"3OO", "0jM", "4Qc", "70i", "6kA", "5NC", "0eL", "2Po", "hR", "8Bx", "44F", "6Em", "abO", "49v", "0FP", "eb", "KN", "8ad", "4SR", "4F3",
+"3B", "0Tx", "4oV", "4z7", "60Q", "4Az", "0zT", "Yf", "TV", "A7", "4LJ", "6yi", "6WE", "4bf", "0YH", "zz", "1s", "0VI", "4mg", "6XD",
+"6vh", "4CK", "N6", "2MF", "Vg", "0uU", "6n9", "7ky", "4u6", "5pv", "1KX", "xK", "1UZ", "fI", "4k4", "5nt", "4Py", "5U9", "He", "0kW",
+"P4", "EU", "6hj", "5Mh", "47m", "6FF", "ky", "0HK", "0GJ", "dx", "6IG", "48l", "4RH", "6gk", "JT", "0if", "0dV", "Gd", "5Z8", "5OY",
+"4qT", "4d5", "iH", "0Jz", "0XV", "0M7", "5f8", "4cx", "4MT", "4X5", "UH", "02r", "1kk", "Xx", "61O", "5PE", "4nH", "7KJ", "vT", "c5",
+"l4", "yU", "6Tj", "4aI", "4Oe", "6zF", "Wy", "00C", "1iZ", "ZI", "4W4", "4BU", "4ly", "5i9", "0m", "0WW", "jg", "0IU", "46s", "533",
+"4I6", "5Lv", "0gy", "C", "3OK", "0jI", "4Qg", "6dD", "6Jh", "5oj", "r6", "gW", "hV", "0Kd", "44B", "6Ei", "6kE", "5NG", "0eH", "Fz",
+"KJ", "0hx", "4SV", "4F7", "6HY", "49r", "0FT", "ef", "60U", "ckl", "0zP", "Yb", "3F", "206", "4oR", "4z3", "6WA", "4bb", "0YL", "2lo",
+"TR", "A3", "4LN", "6ym", "62d", "4CO", "N2", "2MB", "1w", "0VM", "4mc", "7Ha", "4u2", "54z", "8Re", "xO", "Vc", "01Y", "b0D", "aQN",
+"647", "71w", "Ha", "0kS", "8Lg", "fM", "4k0", "5np", "47i", "6FB", "29d", "0HO", "P0", "EQ", "6hn", "5Ml", "4RL", "6go", "JP", "0ib",
+"0GN", "26e", "6IC", "48h", "45X", "4d1", "iL", "8Cf", "0dR", "0q3", "hYt", "beO", "4nD", "7KF", "2P", "c9", "1kg", "Xt", "61C", "5PI",
+"4MX", "4X9", "UD", "0vv", "0XZ", "2my", "5f4", "4ct", "4lu", "5i5", "0a", "1Gz", "0yw", "ZE", "4W8", "4BY", "4Oi", "6zJ", "Wu", "00O",
+"l8", "yY", "6Tf", "4aE", "6Jd", "5of", "62", "25B", "Iw", "0jE", "4Qk", "6dH", "6ix", "5Lz", "0gu", "O", "jk", "0IY", "4rw", "5w7",
+"5x6", "5mW", "0FX", "ej", "KF", "0ht", "4SZ", "6fy", "6kI", "5NK", "0eD", "Fv", "hZ", "93", "44N", "6Ee", "2BO", "03d", "4LB", "6ya",
+"6WM", "4bn", "1Ia", "zr", "3J", "0Tp", "bUm", "a4g", "5D2", "4Ar", "87L", "Yn", "Vo", "01U", "4Ns", "5K3", "416", "54v", "1KP", "xC",
+"us", "0VA", "4mo", "6XL", "62h", "4CC", "0xm", "2MN", "0fo", "2SL", "6hb", "bgr", "47e", "6FN", "kq", "0HC", "0Es", "fA", "aal", "bDn",
+"4Pq", "5U1", "Hm", "8bG", "10w", "Gl", "5Z0", "5OQ", "45T", "anm", "1O2", "0Jr", "0GB", "dp", "6IO", "48d", "5Ba", "6gc", "3Ll", "0in",
+"1kc", "Xp", "61G", "5PM", "bTs", "7KB", "2T", "0Un", "8QF", "39T", "5f0", "4cp", "797", "aRm", "1s2", "02z", "0ys", "ZA", "63v", "766",
+"4lq", "5i1", "0e", "9Nf", "0Zo", "2oL", "6Tb", "4aA", "4Om", "6zN", "Wq", "00K", "Is", "0jA", "4Qo", "6dL", "7ZA", "5ob", "66", "25F",
+"jo", "9Pd", "4rs", "5w3", "aCn", "bfl", "0gq", "K", "KB", "0hp", "bim", "72T", "5x2", "49z", "327", "en", "3nn", "97", "44J", "6Ea",
+"6kM", "5NO", "11i", "Fr", "6WI", "4bj", "0YD", "zv", "TZ", "0wh", "4LF", "6ye", "5D6", "4Av", "0zX", "Yj", "3N", "0Tt", "4oZ", "6Zy",
+"412", "54r", "1KT", "xG", "Vk", "01Q", "4Nw", "5K7", "62l", "4CG", "0xi", "2MJ", "uw", "0VE", "4mk", "6XH", "47a", "6FJ", "ku", "0HG",
+"P8", "EY", "6hf", "5Md", "4Pu", "5U5", "Hi", "8bC", "0Ew", "fE", "4k8", "5nx", "45P", "4d9", "iD", "0Jv", "0dZ", "Gh", "5Z4", "5OU",
+"4RD", "6gg", "JX", "0ij", "0GF", "dt", "6IK", "5lI", "4Op", "5J0", "Wl", "00V", "0Zr", "2oQ", "405", "55u", "4ll", "6YO", "0x", "0WB",
+"0yn", "2LM", "63k", "5Ra", "4MA", "6xb", "2CL", "02g", "0XC", "39I", "6VN", "4cm", "bTn", "a5d", "2I", "0Us", "86O", "Xm", "5E1", "5PP",
+"6kP", "5NR", "11t", "Fo", "hC", "0Kq", "44W", "aon", "6HL", "49g", "0FA", "es", "3Mo", "0hm", "4SC", "72I", "6ia", "5Lc", "0gl", "V",
+"jr", "1Ya", "46f", "6GM", "hyV", "bEm", "0Dp", "gB", "In", "8cD", "4Qr", "5T2", "1b", "0VX", "4mv", "5h6", "62q", "4CZ", "0xt", "2MW",
+"Vv", "01L", "4Nj", "7kh", "6Ue", "54o", "1KI", "xZ", "3S", "0Ti", "4oG", "6Zd", "6tH", "4Ak", "0zE", "Yw", "TG", "0wu", "780", "6yx",
+"5g7", "4bw", "0YY", "zk", "1Wz", "di", "5y5", "5lT", "4RY", "4G8", "JE", "0iw", "0dG", "Gu", "6jJ", "5OH", "45M", "6Df", "iY", "80",
+"71", "fX", "6Kg", "5ne", "4Ph", "6eK", "Ht", "0kF", "0fv", "ED", "4H9", "5My", "4st", "5v4", "kh", "0HZ", "0Zv", "yD", "401", "4aX",
+"4Ot", "5J4", "Wh", "00R", "O9", "ZX", "63o", "4BD", "4lh", "6YK", "tt", "0WF", "0XG", "2md", "6VJ", "4ci", "4ME", "6xf", "UY", "02c",
+"1kz", "Xi", "5E5", "5PT", "4nY", "aqh", "2M", "0Uw", "hG", "0Ku", "44S", "6Ex", "6kT", "5NV", "0eY", "Fk", "3Mk", "0hi", "4SG", "6fd",
+"6HH", "49c", "0FE", "ew", "jv", "0ID", "46b", "6GI", "6ie", "5Lg", "0gh", "R", "Ij", "0jX", "4Qv", "5T6", "6Jy", "7O9", "0Dt", "gF",
+"62u", "775", "0xp", "198", "1f", "9Oe", "4mr", "5h2", "6Ua", "54k", "1KM", "2nO", "Vr", "01H", "4Nn", "7kl", "60D", "4Ao", "0zA", "Ys",
+"3W", "0Tm", "4oC", "7JA", "5g3", "4bs", "8PE", "zo", "TC", "03y", "784", "aSn", "bhn", "73W", "JA", "0is", "334", "dm", "5y1", "48y",
+"45I", "6Db", "3om", "84", "0dC", "Gq", "6jN", "5OL", "4Pl", "6eO", "Hp", "0kB", "75", "24E", "6Kc", "5na", "47x", "528", "kl", "8AF",
+"0fr", "1c2", "aBm", "bgo", "4ld", "6YG", "0p", "0WJ", "O5", "ZT", "63c", "4BH", "4Ox", "5J8", "Wd", "0tV", "0Zz", "yH", "4t5", "4aT",
+"4nU", "7KW", "2A", "1EZ", "1kv", "Xe", "5E9", "5PX", "4MI", "6xj", "UU", "02o", "0XK", "2mh", "6VF", "4ce", "6HD", "49o", "0FI", "27b",
+"KW", "0he", "4SK", "6fh", "6kX", "5NZ", "0eU", "Fg", "hK", "0Ky", "4pW", "4e6", "4j7", "5ow", "0Dx", "gJ", "If", "0jT", "4Qz", "6dY",
+"6ii", "5Lk", "Q7", "DV", "jz", "0IH", "46n", "6GE", "3PN", "01D", "4Nb", "aQS", "6Um", "54g", "m3", "xR", "1j", "0VP", "59W", "a6G",
+"4V3", "4CR", "85l", "194", "TO", "03u", "4LS", "4Y2", "a9F", "56V", "0YQ", "zc", "wS", "b2", "4oO", "6Zl", "60H", "4Ac", "0zM", "2On",
+"0dO", "2Ql", "6jB", "aU1", "45E", "6Dn", "iQ", "88", "0GS", "da", "acL", "48u", "4RQ", "4G0", "JM", "94N", "12W", "EL", "4H1", "5Mq",
+"47t", "524", "29y", "0HR", "79", "fP", "6Ko", "5nm", "aZ0", "6eC", "3NL", "0kN", "O1", "ZP", "63g", "4BL", "58I", "6YC", "0t", "0WN",
+"8Sf", "yL", "409", "4aP", "b1G", "aPM", "0a3", "00Z", "1kr", "Xa", "61V", "bzN", "4nQ", "7KS", "2E", "215", "0XO", "2ml", "6VB", "4ca",
+"4MM", "6xn", "UQ", "02k", "KS", "0ha", "4SO", "6fl", "7Xa", "49k", "0FM", "27f", "hO", "8Be", "4pS", "4e2", "aAN", "bdL", "0eQ", "Fc",
+"Ib", "0jP", "654", "70t", "4j3", "5os", "8Md", "gN", "28g", "0IL", "46j", "6GA", "6im", "5Lo", "Q3", "Z", "6Ui", "54c", "m7", "xV",
+"Vz", "0uH", "4Nf", "7kd", "4V7", "4CV", "0xx", "190", "1n", "0VT", "4mz", "6XY", "6WX", "56R", "0YU", "zg", "TK", "03q", "4LW", "4Y6",
+"60L", "4Ag", "0zI", "2Oj", "wW", "b6", "4oK", "6Zh", "45A", "6Dj", "iU", "0Jg", "0dK", "Gy", "6jF", "5OD", "4RU", "4G4", "JI", "1yZ",
+"0GW", "de", "5y9", "48q", "47p", "520", "kd", "0HV", "0fz", "EH", "4H5", "5Mu", "4Pd", "6eG", "Hx", "0kJ", "s5", "fT", "6Kk", "5ni",
+"5Y1", "5LP", "13v", "e", "jA", "0Is", "46U", "aml", "6JN", "5oL", "0DC", "gq", "3Om", "0jo", "4QA", "6db", "6kc", "5Na", "0en", "2PM",
+"hp", "0KB", "44d", "6EO", "abm", "49T", "0Fr", "1C2", "Kl", "8aF", "4Sp", "5V0", "4Mr", "5H2", "Un", "02T", "0Xp", "2mS", "427", "57w",
+"4nn", "7Kl", "2z", "1Ea", "1kM", "2NO", "61i", "5Pc", "4OC", "7jA", "2AN", "00e", "0ZA", "ys", "6TL", "4ao", "58v", "a7f", "0K", "0Wq",
+"84M", "Zo", "5G3", "4Bs", "0EY", "fk", "6KT", "5nV", "bjh", "6ex", "HG", "0ku", "0fE", "Ew", "6hH", "5MJ", "47O", "6Fd", "29B", "0Hi",
+"53", "dZ", "6Ie", "48N", "4Rj", "6gI", "Jv", "0iD", "0dt", "GF", "6jy", "7o9", "4qv", "5t6", "ij", "0JX", "wh", "0TZ", "4ot", "5j4",
+"4T9", "4AX", "0zv", "YD", "Tt", "03N", "4Lh", "6yK", "6Wg", "4bD", "o9", "zX", "1Q", "0Vk", "4mE", "6Xf", "62B", "4Ci", "0xG", "2Md",
+"VE", "0uw", "4NY", "aQh", "5e5", "5pT", "1Kz", "xi", "jE", "0Iw", "46Q", "4g8", "5Y5", "5LT", "13r", "a", "IY", "0jk", "4QE", "6df",
+"6JJ", "5oH", "0DG", "gu", "ht", "0KF", "4ph", "6EK", "6kg", "5Ne", "S9", "FX", "Kh", "0hZ", "4St", "5V4", "4h9", "49P", "0Fv", "eD",
+"0Xt", "2mW", "423", "4cZ", "4Mv", "5H6", "Uj", "02P", "1kI", "XZ", "61m", "5Pg", "4nj", "7Kh", "vv", "0UD", "0ZE", "yw", "6TH", "4ak",
+"4OG", "6zd", "2AJ", "00a", "0yY", "Zk", "5G7", "4Bw", "58r", "6Yx", "0O", "0Wu", "bjl", "71U", "HC", "0kq", "316", "fo", "6KP", "5nR",
+"47K", "7VA", "29F", "0Hm", "0fA", "Es", "6hL", "5MN", "4Rn", "6gM", "Jr", "1ya", "57", "26G", "6Ia", "48J", "45z", "5t2", "in", "8CD",
+"0dp", "GB", "hYV", "bem", "60w", "757", "0zr", "2OQ", "3d", "9Mg", "4op", "5j0", "6Wc", "56i", "0Yn", "2lM", "Tp", "03J", "4Ll", "6yO",
+"62F", "4Cm", "0xC", "dwS", "1U", "0Vo", "4mA", "6Xb", "5e1", "54X", "8RG", "xm", "VA", "0us", "b0f", "aQl", "6JF", "5oD", "0DK", "gy",
+"IU", "0jg", "4QI", "6dj", "5Y9", "5LX", "0gW", "m", "jI", "1YZ", "4rU", "4g4", "4h5", "5mu", "0Fz", "eH", "Kd", "0hV", "4Sx", "5V8",
+"6kk", "5Ni", "S5", "FT", "hx", "0KJ", "44l", "6EG", "4nf", "7Kd", "2r", "0UH", "M7", "XV", "61a", "5Pk", "4Mz", "6xY", "Uf", "0vT",
+"0Xx", "39r", "4v7", "4cV", "4lW", "4y6", "0C", "0Wy", "0yU", "Zg", "63P", "5RZ", "4OK", "6zh", "WW", "B6", "0ZI", "2oj", "6TD", "4ag",
+"0fM", "2Sn", "7xa", "5MB", "47G", "6Fl", "kS", "0Ha", "0EQ", "fc", "aaN", "bDL", "4PS", "4E2", "HO", "8be", "10U", "GN", "4J3", "5Os",
+"45v", "506", "ib", "0JP", "q3", "dR", "6Im", "48F", "4Rb", "6gA", "3LN", "0iL", "2Bm", "03F", "aF0", "6yC", "6Wo", "4bL", "o1", "zP",
+"3h", "0TR", "bUO", "a4E", "4T1", "4AP", "87n", "YL", "VM", "01w", "4NQ", "7kS", "hfu", "54T", "1Kr", "xa", "1Y", "0Vc", "4mM", "6Xn",
+"62J", "4Ca", "0xO", "2Ml", "IQ", "0jc", "4QM", "6dn", "6JB", "a19", "0DO", "25d", "jM", "9PF", "46Y", "4g0", "aCL", "687", "0gS", "i",
+"3MP", "0hR", "676", "72v", "4h1", "49X", "8Of", "eL", "3nL", "0KN", "44h", "6EC", "6ko", "5Nm", "S1", "FP", "M3", "XR", "61e", "5Po",
+"4nb", "aqS", "2v", "0UL", "8Qd", "39v", "4v3", "4cR", "b3E", "aRO", "Ub", "02X", "0yQ", "Zc", "63T", "bxL", "4lS", "4y2", "0G", "237",
+"0ZM", "2on", "7Da", "4ac", "4OO", "6zl", "WS", "B2", "47C", "6Fh", "kW", "0He", "0fI", "2Sj", "6hD", "5MF", "4PW", "4E6", "HK", "0ky",
+"0EU", "fg", "6KX", "5nZ", "45r", "502", "if", "0JT", "0dx", "GJ", "4J7", "5Ow", "4Rf", "6gE", "Jz", "0iH", "q7", "dV", "6Ii", "48B",
+"6Wk", "4bH", "o5", "zT", "Tx", "03B", "4Ld", "6yG", "4T5", "4AT", "0zz", "YH", "3l", "0TV", "4ox", "5j8", "5e9", "54P", "1Kv", "xe",
+"VI", "01s", "4NU", "7kW", "62N", "4Ce", "0xK", "2Mh", "uU", "0Vg", "4mI", "6Xj", "4K0", "5Np", "11V", "FM", "ha", "0KS", "44u", "515",
+"6Hn", "49E", "48", "eQ", "3MM", "0hO", "4Sa", "6fB", "6iC", "5LA", "0gN", "t", "jP", "0Ib", "46D", "6Go", "hyt", "bEO", "0DR", "0Q3",
+"IL", "8cf", "4QP", "4D1", "4OR", "4Z3", "WN", "00t", "0ZP", "yb", "hgv", "55W", "4lN", "6Ym", "0Z", "a3", "0yL", "2Lo", "63I", "4Bb",
+"4Mc", "7ha", "2Cn", "02E", "n2", "2mB", "6Vl", "4cO", "bTL", "a5F", "2k", "0UQ", "86m", "XO", "4U2", "5Pr", "0Gy", "dK", "4i6", "5lv",
+"5BZ", "6gX", "Jg", "0iU", "R6", "GW", "6jh", "5Oj", "45o", "6DD", "3oK", "0JI", "0EH", "fz", "6KE", "5nG", "4PJ", "6ei", "HV", "0kd",
+"0fT", "Ef", "6hY", "690", "4sV", "4f7", "kJ", "0Hx", "uH", "0Vz", "4mT", "4x5", "5F8", "4Cx", "0xV", "0m7", "VT", "C5", "4NH", "7kJ",
+"6UG", "54M", "1Kk", "xx", "3q", "0TK", "4oe", "6ZF", "60b", "4AI", "L4", "YU", "Te", "0wW", "4Ly", "5I9", "4w4", "4bU", "1IZ", "zI",
+"he", "0KW", "44q", "511", "4K4", "5Nt", "11R", "FI", "Ky", "0hK", "4Se", "6fF", "6Hj", "49A", "p4", "eU", "jT", "0If", "4rH", "6Gk",
+"6iG", "5LE", "0gJ", "p", "IH", "0jz", "4QT", "4D5", "5z8", "5oY", "0DV", "gd", "0ZT", "yf", "6TY", "4az", "4OV", "4Z7", "WJ", "00p",
+"0yH", "Zz", "63M", "4Bf", "4lJ", "6Yi", "tV", "a7", "n6", "2mF", "6Vh", "4cK", "4Mg", "6xD", "2Cj", "02A", "1kX", "XK", "4U6", "5Pv",
+"6N9", "7Ky", "2o", "0UU", "665", "73u", "Jc", "0iQ", "8Ne", "dO", "4i2", "5lr", "45k", "7Ta", "3oO", "0JM", "R2", "GS", "6jl", "5On",
+"4PN", "6em", "HR", "8bx", "0EL", "24g", "6KA", "5nC", "47Z", "4f3", "kN", "8Ad", "0fP", "Eb", "aBO", "694", "62W", "byO", "0xR", "0m3",
+"1D", "224", "4mP", "4x1", "6UC", "54I", "1Ko", "2nm", "VP", "C1", "4NL", "7kN", "60f", "4AM", "L0", "YQ", "3u", "0TO", "4oa", "6ZB",
+"438", "4bQ", "8Pg", "zM", "Ta", "0wS", "b2F", "aSL", "6Hf", "49M", "40", "eY", "Ku", "0hG", "4Si", "6fJ", "4K8", "5Nx", "0ew", "FE",
+"hi", "8BC", "4pu", "5u5", "5z4", "5oU", "0DZ", "gh", "ID", "0jv", "4QX", "4D9", "6iK", "5LI", "0gF", "Dt", "jX", "0Ij", "46L", "6Gg",
+"4lF", "6Ye", "0R", "0Wh", "0yD", "Zv", "63A", "4Bj", "4OZ", "6zy", "WF", "0tt", "0ZX", "yj", "5d6", "4av", "4nw", "5k7", "2c", "0UY",
+"1kT", "XG", "61p", "5Pz", "4Mk", "6xH", "Uw", "02M", "0Xi", "2mJ", "6Vd", "4cG", "0dm", "2QN", "7zA", "5Ob", "45g", "6DL", "is", "0JA",
+"0Gq", "dC", "acn", "48W", "4Rs", "5W3", "Jo", "94l", "12u", "En", "5X2", "5MS", "47V", "alo", "kB", "0Hp", "1Ua", "fr", "6KM", "5nO",
+"4PB", "6ea", "3Nn", "0kl", "3Pl", "01f", "bts", "7kB", "6UO", "54E", "1Kc", "xp", "1H", "0Vr", "59u", "a6e", "5F0", "4Cp", "85N", "d3F",
+"Tm", "03W", "4Lq", "5I1", "434", "56t", "0Ys", "zA", "3y", "0TC", "4om", "6ZN", "60j", "4AA", "0zo", "2OL", "Kq", "0hC", "4Sm", "6fN",
+"6Hb", "49I", "44", "27D", "hm", "8BG", "44y", "519", "aAl", "bdn", "0es", "FA", "1o2", "0jr", "bko", "70V", "5z0", "5oQ", "305", "gl",
+"28E", "0In", "46H", "6Gc", "6iO", "5LM", "0gB", "x", "1ia", "Zr", "63E", "4Bn", "4lB", "6Ya", "0V", "0Wl", "8SD", "yn", "5d2", "4ar",
+"b1e", "aPo", "WB", "00x", "1kP", "XC", "61t", "744", "4ns", "5k3", "2g", "9Ld", "0Xm", "2mN", "7FA", "4cC", "4Mo", "6xL", "Us", "02I",
+"45c", "6DH", "iw", "0JE", "0di", "2QJ", "6jd", "5Of", "4Rw", "5W7", "Jk", "0iY", "0Gu", "dG", "6Ix", "48S", "47R", "6Fy", "kF", "0Ht",
+"0fX", "Ej", "5X6", "5MW", "4PF", "6ee", "HZ", "0kh", "0ED", "fv", "6KI", "5nK", "6UK", "54A", "1Kg", "xt", "VX", "C9", "4ND", "7kF",
+"5F4", "4Ct", "0xZ", "2My", "1L", "0Vv", "4mX", "4x9", "430", "4bY", "0Yw", "zE", "Ti", "03S", "4Lu", "5I5", "60n", "4AE", "L8", "YY",
+"wu", "0TG", "4oi", "6ZJ" };
+
+
+#endif
+
diff --git a/src/db.c b/src/db.c
index 9086f9a6c..2c0a0cdd3 100644
--- a/src/db.c
+++ b/src/db.c
@@ -38,6 +38,8 @@
* C-level DB API
*----------------------------------------------------------------------------*/
+int keyIsExpired(redisDb *db, robj *key);
+
/* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */
@@ -58,10 +60,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
/* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
- if (server.rdb_child_pid == -1 &&
- server.aof_child_pid == -1 &&
- !(flags & LOOKUP_NOTOUCH))
- {
+ if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val);
} else {
@@ -81,6 +80,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* 1. A key gets expired if it reached it's TTL.
* 2. The key last access time is updated.
* 3. The global keys hits/misses stats are updated (reported in INFO).
+ * 4. If keyspace notifications are enabled, a "keymiss" notification is fired.
*
* This API should not be used when we write to the key after obtaining
* the object linked to the key, but only for read only operations.
@@ -90,7 +90,7 @@ robj *lookupKey(redisDb *db, robj *key, int flags) {
* LOOKUP_NONE (or zero): no special flags are passed.
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
*
- * Note: this function also returns NULL is the key is logically expired
+ * Note: this function also returns NULL if the key is logically expired
* but still existing, in case this is a slave, since this API is called only
* for read operations. Even if the key expiry is master-driven, we can
* correctly report a key is expired on slaves even if the master is lagging
@@ -102,7 +102,11 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
/* Key expired. If we are in the context of a master, expireIfNeeded()
* returns 0 only when the key does not exist at all, so it's safe
* to return NULL ASAP. */
- if (server.masterhost == NULL) return NULL;
+ if (server.masterhost == NULL) {
+ server.stat_keyspace_misses++;
+ notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
+ return NULL;
+ }
/* However if we are in the context of a slave, expireIfNeeded() will
* not really try to expire the key, it only returns information
@@ -113,7 +117,7 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
* safety measure, the command invoked is a read-only command, we can
* safely return NULL here, and provide a more consistent behavior
* to clients accessign expired values in a read-only fashion, that
- * will say the key as non exisitng.
+ * will say the key as non existing.
*
* Notably this covers GETs when slaves are used to scale reads. */
if (server.current_client &&
@@ -121,12 +125,16 @@ robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
server.current_client->cmd &&
server.current_client->cmd->flags & CMD_READONLY)
{
+ server.stat_keyspace_misses++;
+ notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
return NULL;
}
}
val = lookupKey(db,key,flags);
- if (val == NULL)
+ if (val == NULL) {
server.stat_keyspace_misses++;
+ notifyKeyspaceEvent(NOTIFY_KEY_MISS, "keymiss", key, db->id);
+ }
else
server.stat_keyspace_hits++;
return val;
@@ -169,7 +177,9 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
- if (val->type == OBJ_LIST) signalKeyAsReady(db, key);
+ if (val->type == OBJ_LIST ||
+ val->type == OBJ_ZSET)
+ signalKeyAsReady(db, key);
if (server.cluster_enabled) slotToKeyAdd(key);
}
@@ -182,17 +192,19 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
dictEntry *de = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,de != NULL);
+ dictEntry auxentry = *de;
+ robj *old = dictGetVal(de);
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
- robj *old = dictGetVal(de);
- int saved_lru = old->lru;
- dictReplace(db->dict, key->ptr, val);
- val->lru = saved_lru;
- /* LFU should be not only copied but also updated
- * when a key is overwritten. */
- updateLFU(val);
- } else {
- dictReplace(db->dict, key->ptr, val);
+ val->lru = old->lru;
+ }
+ dictSetVal(db->dict, de, val);
+
+ if (server.lazyfree_lazy_server_del) {
+ freeObjAsync(old);
+ dictSetVal(db->dict, &auxentry, NULL);
}
+
+ dictFreeVal(db->dict, &auxentry);
}
/* High level Set operation. This function can be used in order to set
@@ -202,7 +214,7 @@ void dbOverwrite(redisDb *db, robj *key, robj *val) {
* 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent).
*
- * All the new keys in the database should be craeted via this interface. */
+ * All the new keys in the database should be created via this interface. */
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
@@ -224,17 +236,30 @@ int dbExists(redisDb *db, robj *key) {
* The function makes sure to return keys not already expired. */
robj *dbRandomKey(redisDb *db) {
dictEntry *de;
+ int maxtries = 100;
+ int allvolatile = dictSize(db->dict) == dictSize(db->expires);
while(1) {
sds key;
robj *keyobj;
- de = dictGetRandomKey(db->dict);
+ de = dictGetFairRandomKey(db->dict);
if (de == NULL) return NULL;
key = dictGetKey(de);
keyobj = createStringObject(key,sdslen(key));
if (dictFind(db->expires,key)) {
+ if (allvolatile && server.masterhost && --maxtries == 0) {
+ /* If the DB is composed only of keys with an expire set,
+ * it could happen that all the keys are already logically
+ * expired in the slave, so the function cannot stop because
+ * expireIfNeeded() is false, nor it can stop because
+ * dictGetRandomKey() returns NULL (there are keys to return).
+ * To prevent the infinite loop we do some tries, but if there
+ * are the conditions for an infinite loop, eventually we
+ * return a key name that may be already expired. */
+ return keyobj;
+ }
if (expireIfNeeded(db,keyobj)) {
decrRefCount(keyobj);
continue; /* search for another key. This expired. */
@@ -306,7 +331,7 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* If callback is given the function is called from time to time to
* signal that work is in progress.
*
- * The dbnum can be -1 if all teh DBs should be flushed, or the specified
+ * The dbnum can be -1 if all the DBs should be flushed, or the specified
* DB number if we want to flush only a single Redis database number.
*
* Flags are be EMPTYDB_NO_FLAGS if no special flags are specified or
@@ -316,8 +341,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {
* On success the fuction returns the number of keys removed from the
* database(s). Otherwise -1 is returned in the specific case the
* DB number is out of range, and errno is set to EINVAL. */
-long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
- int j, async = (flags & EMPTYDB_ASYNC);
+long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*)) {
+ int async = (flags & EMPTYDB_ASYNC);
long long removed = 0;
if (dbnum < -1 || dbnum >= server.dbnum) {
@@ -325,14 +350,32 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
return -1;
}
- for (j = 0; j < server.dbnum; j++) {
- if (dbnum != -1 && dbnum != j) continue;
- removed += dictSize(server.db[j].dict);
+ /* Fire the flushdb modules event. */
+ RedisModuleFlushInfoV1 fi = {REDISMODULE_FLUSHINFO_VERSION,!async,dbnum};
+ moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
+ REDISMODULE_SUBEVENT_FLUSHDB_START,
+ &fi);
+
+ /* Make sure the WATCHed keys are affected by the FLUSH* commands.
+ * Note that we need to call the function while the keys are still
+ * there. */
+ signalFlushedDb(dbnum);
+
+ int startdb, enddb;
+ if (dbnum == -1) {
+ startdb = 0;
+ enddb = server.dbnum-1;
+ } else {
+ startdb = enddb = dbnum;
+ }
+
+ for (int j = startdb; j <= enddb; j++) {
+ removed += dictSize(dbarray[j].dict);
if (async) {
- emptyDbAsync(&server.db[j]);
+ emptyDbAsync(&dbarray[j]);
} else {
- dictEmpty(server.db[j].dict,callback);
- dictEmpty(server.db[j].expires,callback);
+ dictEmpty(dbarray[j].dict,callback);
+ dictEmpty(dbarray[j].expires,callback);
}
}
if (server.cluster_enabled) {
@@ -343,9 +386,20 @@ long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
}
}
if (dbnum == -1) flushSlaveKeysWithExpireList();
+
+ /* Also fire the end event. Note that this event will fire almost
+ * immediately after the start event if the flush is asynchronous. */
+ moduleFireServerEvent(REDISMODULE_EVENT_FLUSHDB,
+ REDISMODULE_SUBEVENT_FLUSHDB_END,
+ &fi);
+
return removed;
}
+long long emptyDb(int dbnum, int flags, void(callback)(void*)) {
+ return emptyDbGeneric(server.db, dbnum, flags, callback);
+}
+
int selectDb(client *c, int id) {
if (id < 0 || id >= server.dbnum)
return C_ERR;
@@ -353,6 +407,15 @@ int selectDb(client *c, int id) {
return C_OK;
}
+long long dbTotalServerKeyCount() {
+ long long total = 0;
+ int j;
+ for (j = 0; j < server.dbnum; j++) {
+ total += dictSize(server.db[j].dict);
+ }
+ return total;
+}
+
/*-----------------------------------------------------------------------------
* Hooks for key space changes.
*
@@ -364,10 +427,12 @@ int selectDb(client *c, int id) {
void signalModifiedKey(redisDb *db, robj *key) {
touchWatchedKey(db,key);
+ trackingInvalidateKey(key);
}
void signalFlushedDb(int dbid) {
touchWatchedKeysOnFlush(dbid);
+ trackingInvalidateKeysOnFlush(dbid);
}
/*-----------------------------------------------------------------------------
@@ -403,9 +468,15 @@ void flushdbCommand(client *c) {
int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
- signalFlushedDb(c->db->id);
server.dirty += emptyDb(c->db->id,flags,NULL);
addReply(c,shared.ok);
+#if defined(USE_JEMALLOC)
+ /* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
+ * for large databases, flushdb blocks for long anyway, so a bit more won't
+ * harm and this way the flush and purge will be synchroneus. */
+ if (!(flags & EMPTYDB_ASYNC))
+ jemalloc_purge();
+#endif
}
/* FLUSHALL [ASYNC]
@@ -415,13 +486,9 @@ void flushallCommand(client *c) {
int flags;
if (getFlushCommandFlags(c,&flags) == C_ERR) return;
- signalFlushedDb(-1);
server.dirty += emptyDb(-1,flags,NULL);
addReply(c,shared.ok);
- if (server.rdb_child_pid != -1) {
- kill(server.rdb_child_pid,SIGUSR1);
- rdbRemoveTempFile(server.rdb_child_pid);
- }
+ if (server.rdb_child_pid != -1) killRDBChild();
if (server.saveparamslen > 0) {
/* Normally rdbSave() will reset dirty, but we don't want this here
* as otherwise FLUSHALL will not be replicated nor put into the AOF. */
@@ -432,6 +499,13 @@ void flushallCommand(client *c) {
server.dirty = saved_dirty;
}
server.dirty++;
+#if defined(USE_JEMALLOC)
+ /* jemalloc 5 doesn't release pages back to the OS when there's no traffic.
+ * for large databases, flushdb blocks for long anyway, so a bit more won't
+ * harm and this way the flush and purge will be synchroneus. */
+ if (!(flags & EMPTYDB_ASYNC))
+ jemalloc_purge();
+#endif
}
/* This command implements DEL and LAZYDEL. */
@@ -468,8 +542,7 @@ void existsCommand(client *c) {
int j;
for (j = 1; j < c->argc; j++) {
- expireIfNeeded(c->db,c->argv[j]);
- if (dbExists(c->db,c->argv[j])) count++;
+ if (lookupKeyRead(c->db,c->argv[j])) count++;
}
addReplyLongLong(c,count);
}
@@ -496,7 +569,7 @@ void randomkeyCommand(client *c) {
robj *key;
if ((key = dbRandomKey(c->db)) == NULL) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
return;
}
@@ -510,7 +583,7 @@ void keysCommand(client *c) {
sds pattern = c->argv[1]->ptr;
int plen = sdslen(pattern), allkeys;
unsigned long numkeys = 0;
- void *replylen = addDeferredMultiBulkLength(c);
+ void *replylen = addReplyDeferredLen(c);
di = dictGetSafeIterator(c->db->dict);
allkeys = (pattern[0] == '*' && pattern[1] == '\0');
@@ -520,7 +593,7 @@ void keysCommand(client *c) {
if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) {
keyobj = createStringObject(key,sdslen(key));
- if (expireIfNeeded(c->db,keyobj) == 0) {
+ if (!keyIsExpired(c->db,keyobj)) {
addReplyBulk(c,keyobj);
numkeys++;
}
@@ -528,7 +601,7 @@ void keysCommand(client *c) {
}
}
dictReleaseIterator(di);
- setDeferredMultiBulkLength(c,replylen,numkeys);
+ setDeferredArrayLen(c,replylen,numkeys);
}
/* This callback is used by scanGenericCommand in order to collect elements
@@ -582,7 +655,7 @@ int parseScanCursorOrReply(client *c, robj *o, unsigned long *cursor) {
}
/* This command implements SCAN, HSCAN and SSCAN commands.
- * If object 'o' is passed, then it must be a Hash or Set object, otherwise
+ * If object 'o' is passed, then it must be a Hash, Set or Zset object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with
* the current database.
*
@@ -598,6 +671,7 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
listNode *node, *nextnode;
long count = 10;
sds pat = NULL;
+ sds typename = NULL;
int patlen = 0, use_pattern = 0;
dict *ht;
@@ -634,6 +708,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
+ } else if (!strcasecmp(c->argv[i]->ptr, "type") && o == NULL && j >= 2) {
+ /* SCAN for a particular type only applies to the db dict */
+ typename = c->argv[i+1]->ptr;
+ i+= 2;
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
@@ -728,6 +806,13 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
}
}
+ /* Filter an element if it isn't the type we want. */
+ if (!filter && o == NULL && typename){
+ robj* typecheck = lookupKeyReadWithFlags(c->db, kobj, LOOKUP_NOTOUCH);
+ char* type = getObjectTypeName(typecheck);
+ if (strcasecmp((char*) typename, type)) filter = 1;
+ }
+
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
@@ -753,10 +838,10 @@ void scanGenericCommand(client *c, robj *o, unsigned long cursor) {
}
/* Step 4: Reply to the client. */
- addReplyMultiBulkLen(c, 2);
+ addReplyArrayLen(c, 2);
addReplyBulkLongLong(c,cursor);
- addReplyMultiBulkLen(c, listLength(keys));
+ addReplyArrayLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj);
@@ -784,11 +869,8 @@ void lastsaveCommand(client *c) {
addReplyLongLong(c,server.lastsave);
}
-void typeCommand(client *c) {
- robj *o;
- char *type;
-
- o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
+char* getObjectTypeName(robj *o) {
+ char* type;
if (o == NULL) {
type = "none";
} else {
@@ -806,7 +888,13 @@ void typeCommand(client *c) {
default: type = "unknown"; break;
}
}
- addReplyStatus(c,type);
+ return type;
+}
+
+void typeCommand(client *c) {
+ robj *o;
+ o = lookupKeyReadWithFlags(c->db,c->argv[1],LOOKUP_NOTOUCH);
+ addReplyStatus(c, getObjectTypeName(o));
}
void shutdownCommand(client *c) {
@@ -943,16 +1031,18 @@ void moveCommand(client *c) {
}
/* Helper function for dbSwapDatabases(): scans the list of keys that have
- * one or more blocked clients for B[LR]POP or other list blocking commands
- * and signal the keys are ready if they are lists. See the comment where
- * the function is used for more info. */
+ * one or more blocked clients for B[LR]POP or other blocking commands
+ * and signal the keys as ready if they are of the right type. See the comment
+ * where the function is used for more info. */
void scanDatabaseForReadyLists(redisDb *db) {
dictEntry *de;
dictIterator *di = dictGetSafeIterator(db->blocking_keys);
while((de = dictNext(di)) != NULL) {
robj *key = dictGetKey(de);
robj *value = lookupKey(db,key,LOOKUP_NOTOUCH);
- if (value && (value->type == OBJ_LIST || value->type == OBJ_STREAM))
+ if (value && (value->type == OBJ_LIST ||
+ value->type == OBJ_STREAM ||
+ value->type == OBJ_ZSET))
signalKeyAsReady(db, key);
}
dictReleaseIterator(di);
@@ -966,7 +1056,7 @@ void scanDatabaseForReadyLists(redisDb *db) {
*
* Returns C_ERR if at least one of the DB ids are out of range, otherwise
* C_OK is returned. */
-int dbSwapDatabases(int id1, int id2) {
+int dbSwapDatabases(long id1, long id2) {
if (id1 < 0 || id1 >= server.dbnum ||
id2 < 0 || id2 >= server.dbnum) return C_ERR;
if (id1 == id2) return C_OK;
@@ -1095,6 +1185,25 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
decrRefCount(argv[1]);
}
+/* Check if the key is expired. */
+int keyIsExpired(redisDb *db, robj *key) {
+ mstime_t when = getExpire(db,key);
+
+ if (when < 0) return 0; /* No expire for this key */
+
+ /* Don't expire anything while loading. It will be done later. */
+ if (server.loading) return 0;
+
+ /* If we are in the context of a Lua script, we pretend that time is
+ * blocked to when the Lua script started. This way a key can expire
+ * only the first time it is accessed and not in the middle of the
+ * script execution, making propagation to slaves / AOF consistent.
+ * See issue #1525 on Github for more information. */
+ mstime_t now = server.lua_caller ? server.lua_time_start : mstime();
+
+ return now > when;
+}
+
/* This function is called when we are going to perform some operation
* in a given key, but such key may be already logically expired even if
* it still exists in the database. The main way this function is called
@@ -1115,32 +1224,17 @@ void propagateExpire(redisDb *db, robj *key, int lazy) {
* The return value of the function is 0 if the key is still valid,
* otherwise the function returns 1 if the key is expired. */
int expireIfNeeded(redisDb *db, robj *key) {
- mstime_t when = getExpire(db,key);
- mstime_t now;
-
- if (when < 0) return 0; /* No expire for this key */
+ if (!keyIsExpired(db,key)) return 0;
- /* Don't expire anything while loading. It will be done later. */
- if (server.loading) return 0;
-
- /* If we are in the context of a Lua script, we pretend that time is
- * blocked to when the Lua script started. This way a key can expire
- * only the first time it is accessed and not in the middle of the
- * script execution, making propagation to slaves / AOF consistent.
- * See issue #1525 on Github for more information. */
- now = server.lua_caller ? server.lua_time_start : mstime();
-
- /* If we are running in the context of a slave, return ASAP:
+ /* If we are running in the context of a slave, instead of
+ * evicting the expired key from the database, we return ASAP:
* the slave key expiration is controlled by the master that will
* send us synthesized DEL operations for expired keys.
*
* Still we try to return the right information to the caller,
* that is, 0 if we think the key should be still valid, 1 if
* we think the key is expired at this time. */
- if (server.masterhost != NULL) return now > when;
-
- /* Return when this key has not expired */
- if (now <= when) return 0;
+ if (server.masterhost != NULL) return 1;
/* Delete the key */
server.stat_expiredkeys++;
@@ -1172,7 +1266,7 @@ int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, in
for (j = cmd->firstkey; j <= last; j += cmd->keystep) {
if (j >= argc) {
/* Modules commands, and standard commands with a not fixed number
- * of arugments (negative arity parameter) do not have dispatch
+ * of arguments (negative arity parameter) do not have dispatch
* time arity checks, so we need to handle the case where the user
* passed an invalid number of arguments here. In this case we
* return no keys and expect the command implementation to report
@@ -1227,7 +1321,7 @@ int *zunionInterGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *nu
num = atoi(argv[2]->ptr);
/* Sanity check. Don't return any key if the command is going to
* reply with syntax error. */
- if (num > (argc-3)) {
+ if (num < 1 || num > (argc-3)) {
*numkeys = 0;
return NULL;
}
@@ -1256,7 +1350,7 @@ int *evalGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
num = atoi(argv[2]->ptr);
/* Sanity check. Don't return any key if the command is going to
* reply with syntax error. */
- if (num > (argc-3)) {
+ if (num <= 0 || num > (argc-3)) {
*numkeys = 0;
return NULL;
}
@@ -1359,7 +1453,7 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
for (i = 5; i < argc; i++) {
char *arg = argv[i]->ptr;
/* For the case when user specifies both "store" and "storedist" options, the
- * second key specified would override the first key. This behavior is kept
+ * second key specified would override the first key. This behavior is kept
* the same as in georadiusCommand method.
*/
if ((!strcasecmp(arg, "store") || !strcasecmp(arg, "storedist")) && ((i+1) < argc)) {
@@ -1380,28 +1474,42 @@ int *georadiusGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numk
if(num > 1) {
keys[1] = stored_key;
}
- *numkeys = num;
+ *numkeys = num;
return keys;
}
/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
- * [RETRY <milliseconds> <ttl>] STREAMS key_1 key_2 ... key_N
- * ID_1 ID_2 ... ID_N */
+ * STREAMS key_1 key_2 ... key_N ID_1 ID_2 ... ID_N */
int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys) {
- int i, num, *keys;
+ int i, num = 0, *keys;
UNUSED(cmd);
- /* We need to seek the last argument that contains "STREAMS", because other
- * arguments before may contain it (for example the group name). */
+ /* We need to parse the options of the command in order to seek the first
+ * "STREAMS" string which is actually the option. This is needed because
+ * "STREAMS" could also be the name of the consumer group and even the
+ * name of the stream key. */
int streams_pos = -1;
for (i = 1; i < argc; i++) {
char *arg = argv[i]->ptr;
- if (!strcasecmp(arg, "streams")) streams_pos = i;
+ if (!strcasecmp(arg, "block")) {
+ i++; /* Skip option argument. */
+ } else if (!strcasecmp(arg, "count")) {
+ i++; /* Skip option argument. */
+ } else if (!strcasecmp(arg, "group")) {
+ i += 2; /* Skip option argument. */
+ } else if (!strcasecmp(arg, "noack")) {
+ /* Nothing to do. */
+ } else if (!strcasecmp(arg, "streams")) {
+ streams_pos = i;
+ break;
+ } else {
+ break; /* Syntax error. */
+ }
}
if (streams_pos != -1) num = argc - streams_pos - 1;
/* Syntax error. */
- if (streams_pos == -1 || num % 2 != 0) {
+ if (streams_pos == -1 || num == 0 || num % 2 != 0) {
*numkeys = 0;
return NULL;
}
@@ -1409,7 +1517,7 @@ int *xreadGetKeys(struct redisCommand *cmd, robj **argv, int argc, int *numkeys)
there are also the IDs, one per key. */
keys = zmalloc(sizeof(int) * num);
- for (i = streams_pos+1; i < argc; i++) keys[i-streams_pos-1] = i;
+ for (i = streams_pos+1; i < argc-num; i++) keys[i-streams_pos-1] = i;
*numkeys = num;
return keys;
}
diff --git a/src/debug.c b/src/debug.c
index 293dbe36e..179f6d2c9 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -37,7 +37,11 @@
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
+#ifndef __OpenBSD__
#include <ucontext.h>
+#else
+typedef ucontext_t sigcontext_t;
+#endif
#include <fcntl.h>
#include "bio.h"
#include <unistd.h>
@@ -70,7 +74,7 @@ void xorDigest(unsigned char *digest, void *ptr, size_t len) {
digest[j] ^= hash[j];
}
-void xorObjectDigest(unsigned char *digest, robj *o) {
+void xorStringObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o);
xorDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o);
@@ -100,12 +104,151 @@ void mixDigest(unsigned char *digest, void *ptr, size_t len) {
SHA1Final(digest,&ctx);
}
-void mixObjectDigest(unsigned char *digest, robj *o) {
+void mixStringObjectDigest(unsigned char *digest, robj *o) {
o = getDecodedObject(o);
mixDigest(digest,o->ptr,sdslen(o->ptr));
decrRefCount(o);
}
+/* This function computes the digest of a data structure stored in the
+ * object 'o'. It is the core of the DEBUG DIGEST command: when taking the
+ * digest of a whole dataset, we take the digest of the key and the value
+ * pair, and xor all those together.
+ *
+ * Note that this function does not reset the initial 'digest' passed, it
+ * will continue mixing this object digest to anything that was already
+ * present. */
+void xorObjectDigest(redisDb *db, robj *keyobj, unsigned char *digest, robj *o) {
+ uint32_t aux = htonl(o->type);
+ mixDigest(digest,&aux,sizeof(aux));
+ long long expiretime = getExpire(db,keyobj);
+ char buf[128];
+
+ /* Save the key and associated value */
+ if (o->type == OBJ_STRING) {
+ mixStringObjectDigest(digest,o);
+ } else if (o->type == OBJ_LIST) {
+ listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
+ listTypeEntry entry;
+ while(listTypeNext(li,&entry)) {
+ robj *eleobj = listTypeGet(&entry);
+ mixStringObjectDigest(digest,eleobj);
+ decrRefCount(eleobj);
+ }
+ listTypeReleaseIterator(li);
+ } else if (o->type == OBJ_SET) {
+ setTypeIterator *si = setTypeInitIterator(o);
+ sds sdsele;
+ while((sdsele = setTypeNextObject(si)) != NULL) {
+ xorDigest(digest,sdsele,sdslen(sdsele));
+ sdsfree(sdsele);
+ }
+ setTypeReleaseIterator(si);
+ } else if (o->type == OBJ_ZSET) {
+ unsigned char eledigest[20];
+
+ if (o->encoding == OBJ_ENCODING_ZIPLIST) {
+ unsigned char *zl = o->ptr;
+ unsigned char *eptr, *sptr;
+ unsigned char *vstr;
+ unsigned int vlen;
+ long long vll;
+ double score;
+
+ eptr = ziplistIndex(zl,0);
+ serverAssert(eptr != NULL);
+ sptr = ziplistNext(zl,eptr);
+ serverAssert(sptr != NULL);
+
+ while (eptr != NULL) {
+ serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
+ score = zzlGetScore(sptr);
+
+ memset(eledigest,0,20);
+ if (vstr != NULL) {
+ mixDigest(eledigest,vstr,vlen);
+ } else {
+ ll2string(buf,sizeof(buf),vll);
+ mixDigest(eledigest,buf,strlen(buf));
+ }
+
+ snprintf(buf,sizeof(buf),"%.17g",score);
+ mixDigest(eledigest,buf,strlen(buf));
+ xorDigest(digest,eledigest,20);
+ zzlNext(zl,&eptr,&sptr);
+ }
+ } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
+ zset *zs = o->ptr;
+ dictIterator *di = dictGetIterator(zs->dict);
+ dictEntry *de;
+
+ while((de = dictNext(di)) != NULL) {
+ sds sdsele = dictGetKey(de);
+ double *score = dictGetVal(de);
+
+ snprintf(buf,sizeof(buf),"%.17g",*score);
+ memset(eledigest,0,20);
+ mixDigest(eledigest,sdsele,sdslen(sdsele));
+ mixDigest(eledigest,buf,strlen(buf));
+ xorDigest(digest,eledigest,20);
+ }
+ dictReleaseIterator(di);
+ } else {
+ serverPanic("Unknown sorted set encoding");
+ }
+ } else if (o->type == OBJ_HASH) {
+ hashTypeIterator *hi = hashTypeInitIterator(o);
+ while (hashTypeNext(hi) != C_ERR) {
+ unsigned char eledigest[20];
+ sds sdsele;
+
+ memset(eledigest,0,20);
+ sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
+ mixDigest(eledigest,sdsele,sdslen(sdsele));
+ sdsfree(sdsele);
+ sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
+ mixDigest(eledigest,sdsele,sdslen(sdsele));
+ sdsfree(sdsele);
+ xorDigest(digest,eledigest,20);
+ }
+ hashTypeReleaseIterator(hi);
+ } else if (o->type == OBJ_STREAM) {
+ streamIterator si;
+ streamIteratorStart(&si,o->ptr,NULL,NULL,0);
+ streamID id;
+ int64_t numfields;
+
+ while(streamIteratorGetID(&si,&id,&numfields)) {
+ sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
+ mixDigest(digest,itemid,sdslen(itemid));
+ sdsfree(itemid);
+
+ while(numfields--) {
+ unsigned char *field, *value;
+ int64_t field_len, value_len;
+ streamIteratorGetField(&si,&field,&value,
+ &field_len,&value_len);
+ mixDigest(digest,field,field_len);
+ mixDigest(digest,value,value_len);
+ }
+ }
+ streamIteratorStop(&si);
+ } else if (o->type == OBJ_MODULE) {
+ RedisModuleDigest md;
+ moduleValue *mv = o->ptr;
+ moduleType *mt = mv->type;
+ moduleInitDigestContext(md);
+ if (mt->digest) {
+ mt->digest(&md,mv->value);
+ xorDigest(digest,md.x,sizeof(md.x));
+ }
+ } else {
+ serverPanic("Unknown object type");
+ }
+ /* If the key has an expire, add it to the mix */
+ if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
+}
+
/* Compute the dataset digest. Since keys, sets elements, hashes elements
* are not ordered, we use a trick: every aggregate digest is the xor
* of the digests of their elements. This way the order will not change
@@ -114,7 +257,6 @@ void mixObjectDigest(unsigned char *digest, robj *o) {
* a different digest. */
void computeDatasetDigest(unsigned char *final) {
unsigned char digest[20];
- char buf[128];
dictIterator *di = NULL;
dictEntry *de;
int j;
@@ -137,7 +279,6 @@ void computeDatasetDigest(unsigned char *final) {
while((de = dictNext(di)) != NULL) {
sds key;
robj *keyobj, *o;
- long long expiretime;
memset(digest,0,20); /* This key-val digest */
key = dictGetKey(de);
@@ -146,134 +287,8 @@ void computeDatasetDigest(unsigned char *final) {
mixDigest(digest,key,sdslen(key));
o = dictGetVal(de);
+ xorObjectDigest(db,keyobj,digest,o);
- aux = htonl(o->type);
- mixDigest(digest,&aux,sizeof(aux));
- expiretime = getExpire(db,keyobj);
-
- /* Save the key and associated value */
- if (o->type == OBJ_STRING) {
- mixObjectDigest(digest,o);
- } else if (o->type == OBJ_LIST) {
- listTypeIterator *li = listTypeInitIterator(o,0,LIST_TAIL);
- listTypeEntry entry;
- while(listTypeNext(li,&entry)) {
- robj *eleobj = listTypeGet(&entry);
- mixObjectDigest(digest,eleobj);
- decrRefCount(eleobj);
- }
- listTypeReleaseIterator(li);
- } else if (o->type == OBJ_SET) {
- setTypeIterator *si = setTypeInitIterator(o);
- sds sdsele;
- while((sdsele = setTypeNextObject(si)) != NULL) {
- xorDigest(digest,sdsele,sdslen(sdsele));
- sdsfree(sdsele);
- }
- setTypeReleaseIterator(si);
- } else if (o->type == OBJ_ZSET) {
- unsigned char eledigest[20];
-
- if (o->encoding == OBJ_ENCODING_ZIPLIST) {
- unsigned char *zl = o->ptr;
- unsigned char *eptr, *sptr;
- unsigned char *vstr;
- unsigned int vlen;
- long long vll;
- double score;
-
- eptr = ziplistIndex(zl,0);
- serverAssert(eptr != NULL);
- sptr = ziplistNext(zl,eptr);
- serverAssert(sptr != NULL);
-
- while (eptr != NULL) {
- serverAssert(ziplistGet(eptr,&vstr,&vlen,&vll));
- score = zzlGetScore(sptr);
-
- memset(eledigest,0,20);
- if (vstr != NULL) {
- mixDigest(eledigest,vstr,vlen);
- } else {
- ll2string(buf,sizeof(buf),vll);
- mixDigest(eledigest,buf,strlen(buf));
- }
-
- snprintf(buf,sizeof(buf),"%.17g",score);
- mixDigest(eledigest,buf,strlen(buf));
- xorDigest(digest,eledigest,20);
- zzlNext(zl,&eptr,&sptr);
- }
- } else if (o->encoding == OBJ_ENCODING_SKIPLIST) {
- zset *zs = o->ptr;
- dictIterator *di = dictGetIterator(zs->dict);
- dictEntry *de;
-
- while((de = dictNext(di)) != NULL) {
- sds sdsele = dictGetKey(de);
- double *score = dictGetVal(de);
-
- snprintf(buf,sizeof(buf),"%.17g",*score);
- memset(eledigest,0,20);
- mixDigest(eledigest,sdsele,sdslen(sdsele));
- mixDigest(eledigest,buf,strlen(buf));
- xorDigest(digest,eledigest,20);
- }
- dictReleaseIterator(di);
- } else {
- serverPanic("Unknown sorted set encoding");
- }
- } else if (o->type == OBJ_HASH) {
- hashTypeIterator *hi = hashTypeInitIterator(o);
- while (hashTypeNext(hi) != C_ERR) {
- unsigned char eledigest[20];
- sds sdsele;
-
- memset(eledigest,0,20);
- sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_KEY);
- mixDigest(eledigest,sdsele,sdslen(sdsele));
- sdsfree(sdsele);
- sdsele = hashTypeCurrentObjectNewSds(hi,OBJ_HASH_VALUE);
- mixDigest(eledigest,sdsele,sdslen(sdsele));
- sdsfree(sdsele);
- xorDigest(digest,eledigest,20);
- }
- hashTypeReleaseIterator(hi);
- } else if (o->type == OBJ_STREAM) {
- streamIterator si;
- streamIteratorStart(&si,o->ptr,NULL,NULL,0);
- streamID id;
- int64_t numfields;
-
- while(streamIteratorGetID(&si,&id,&numfields)) {
- sds itemid = sdscatfmt(sdsempty(),"%U.%U",id.ms,id.seq);
- mixDigest(digest,itemid,sdslen(itemid));
- sdsfree(itemid);
-
- while(numfields--) {
- unsigned char *field, *value;
- int64_t field_len, value_len;
- streamIteratorGetField(&si,&field,&value,
- &field_len,&value_len);
- mixDigest(digest,field,field_len);
- mixDigest(digest,value,value_len);
- }
- }
- streamIteratorStop(&si);
- } else if (o->type == OBJ_MODULE) {
- RedisModuleDigest md;
- moduleValue *mv = o->ptr;
- moduleType *mt = mv->type;
- moduleInitDigestContext(md);
- if (mt->digest) {
- mt->digest(&md,mv->value);
- xorDigest(digest,md.x,sizeof(md.x));
- }
- } else {
- serverPanic("Unknown object type");
- }
- /* If the key has an expire, add it to the mix */
- if (expiretime != -1) xorDigest(digest,"!!expire!!",10);
/* We can finally xor the key-val digest to the final digest */
xorDigest(final,digest,20);
decrRefCount(keyobj);
@@ -282,28 +297,87 @@ void computeDatasetDigest(unsigned char *final) {
}
}
+#ifdef USE_JEMALLOC
+void mallctl_int(client *c, robj **argv, int argc) {
+ int ret;
+ /* start with the biggest size (int64), and if that fails, try smaller sizes (int32, bool) */
+ int64_t old = 0, val;
+ if (argc > 1) {
+ long long ll;
+ if (getLongLongFromObjectOrReply(c, argv[1], &ll, NULL) != C_OK)
+ return;
+ val = ll;
+ }
+ size_t sz = sizeof(old);
+ while (sz > 0) {
+ if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, argc > 1? &val: NULL, argc > 1?sz: 0))) {
+ if (ret==EINVAL) {
+ /* size might be wrong, try a smaller one */
+ sz /= 2;
+#if BYTE_ORDER == BIG_ENDIAN
+ val <<= 8*sz;
+#endif
+ continue;
+ }
+ addReplyErrorFormat(c,"%s", strerror(ret));
+ return;
+ } else {
+#if BYTE_ORDER == BIG_ENDIAN
+ old >>= 64 - 8*sz;
+#endif
+ addReplyLongLong(c, old);
+ return;
+ }
+ }
+ addReplyErrorFormat(c,"%s", strerror(EINVAL));
+}
+
+void mallctl_string(client *c, robj **argv, int argc) {
+ int ret;
+ char *old;
+ size_t sz = sizeof(old);
+ /* for strings, it seems we need to first get the old value, before overriding it. */
+ if ((ret=je_mallctl(argv[0]->ptr, &old, &sz, NULL, 0))) {
+ addReplyErrorFormat(c,"%s", strerror(ret));
+ return;
+ }
+ addReplyBulkCString(c, old);
+ if(argc > 1)
+ je_mallctl(argv[0]->ptr, NULL, 0, &argv[1]->ptr, sizeof(char*));
+}
+#endif
+
void debugCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"assert -- Crash by assertion failed.",
-"change-repl-id -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
-"crash-and-recovery <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
-"digest -- Outputs an hex signature representing the current DB content.",
-"htstats <dbid> -- Return hash table statistics of the specified Redis database.",
-"loadaof -- Flush the AOF buffers on disk and reload the AOF in memory.",
-"lua-always-replicate-commands (0|1) -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
-"object <key> -- Show low level info about key and associated value.",
-"panic -- Crash the server simulating a panic.",
-"populate <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
-"reload -- Save the RDB on disk and reload it back in memory.",
-"restart -- Graceful restart: save config, db, restart.",
-"sdslen <key> -- Show low level SDS string info representing key and value.",
-"segfault -- Crash the server with sigsegv.",
-"set-active-expire (0|1) -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
-"sleep <seconds> -- Stop the server for <seconds>. Decimals allowed.",
-"structsize -- Return the size of different Redis core C structures.",
-"ziplist <key> -- Show low level info about the ziplist encoding.",
-"error <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
+"ASSERT -- Crash by assertion failed.",
+"CHANGE-REPL-ID -- Change the replication IDs of the instance. Dangerous, should be used only for testing the replication subsystem.",
+"CRASH-AND-RECOVER <milliseconds> -- Hard crash and restart after <milliseconds> delay.",
+"DIGEST -- Output a hex signature representing the current DB content.",
+"DIGEST-VALUE <key-1> ... <key-N>-- Output a hex signature of the values of all the specified keys.",
+"ERROR <string> -- Return a Redis protocol error with <string> as message. Useful for clients unit tests to simulate Redis errors.",
+"LOG <message> -- write message to the server log.",
+"HTSTATS <dbid> -- Return hash table statistics of the specified Redis database.",
+"HTSTATS-KEY <key> -- Like htstats but for the hash table stored as key's value.",
+"LOADAOF -- Flush the AOF buffers on disk and reload the AOF in memory.",
+"LUA-ALWAYS-REPLICATE-COMMANDS <0|1> -- Setting it to 1 makes Lua replication defaulting to replicating single commands, without the script having to enable effects replication.",
+"OBJECT <key> -- Show low level info about key and associated value.",
+"PANIC -- Crash the server simulating a panic.",
+"POPULATE <count> [prefix] [size] -- Create <count> string keys named key:<num>. If a prefix is specified is used instead of the 'key' prefix.",
+"RELOAD -- Save the RDB on disk and reload it back in memory.",
+"RESTART -- Graceful restart: save config, db, restart.",
+"SDSLEN <key> -- Show low level SDS string info representing key and value.",
+"SEGFAULT -- Crash the server with sigsegv.",
+"SET-ACTIVE-EXPIRE <0|1> -- Setting it to 0 disables expiring keys in background when they are not accessed (otherwise the Redis behavior). Setting it to 1 reenables back the default.",
+"AOF-FLUSH-SLEEP <microsec> -- Server will sleep before flushing the AOF, this is used for testing",
+"SLEEP <seconds> -- Stop the server for <seconds>. Decimals allowed.",
+"STRUCTSIZE -- Return the size of different Redis core C structures.",
+"ZIPLIST <key> -- Show low level info about the ziplist encoding.",
+"STRINGMATCH-TEST -- Run a fuzz tester against the stringmatchlen() function.",
+#ifdef USE_JEMALLOC
+"MALLCTL <key> [<val>] -- Get or set a malloc tunning integer.",
+"MALLCTL-STR <key> [<val>] -- Get or set a malloc tunning string.",
+#endif
NULL
};
addReplyHelp(c, help);
@@ -330,8 +404,10 @@ NULL
zfree(ptr);
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"assert")) {
- if (c->argc >= 3) c->argv[2] = tryObjectEncoding(c->argv[2]);
serverAssertWithInfo(c,c->argv[0],1 == 2);
+ } else if (!strcasecmp(c->argv[1]->ptr,"log") && c->argc == 3) {
+ serverLog(LL_WARNING, "DEBUG LOG: %s", (char*)c->argv[2]->ptr);
+ addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"reload")) {
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
@@ -340,16 +416,22 @@ NULL
return;
}
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
- if (rdbLoad(server.rdb_filename,NULL) != C_OK) {
+ protectClient(c);
+ int ret = rdbLoad(server.rdb_filename,NULL);
+ unprotectClient(c);
+ if (ret != C_OK) {
addReplyError(c,"Error trying to load the RDB dump");
return;
}
serverLog(LL_WARNING,"DB reloaded by DEBUG RELOAD");
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"loadaof")) {
- if (server.aof_state == AOF_ON) flushAppendOnlyFile(1);
+ if (server.aof_state != AOF_OFF) flushAppendOnlyFile(1);
emptyDb(-1,EMPTYDB_NO_FLAGS,NULL);
- if (loadAppendOnlyFile(server.aof_filename) != C_OK) {
+ protectClient(c);
+ int ret = loadAppendOnlyFile(server.aof_filename);
+ unprotectClient(c);
+ if (ret != C_OK) {
addReply(c,shared.err);
return;
}
@@ -480,15 +562,80 @@ NULL
}
addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"digest") && c->argc == 2) {
+ /* DEBUG DIGEST (form without keys specified) */
unsigned char digest[20];
sds d = sdsempty();
- int j;
computeDatasetDigest(digest);
- for (j = 0; j < 20; j++)
- d = sdscatprintf(d, "%02x",digest[j]);
+ for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
addReplyStatus(c,d);
sdsfree(d);
+ } else if (!strcasecmp(c->argv[1]->ptr,"digest-value") && c->argc >= 2) {
+ /* DEBUG DIGEST-VALUE key key key ... key. */
+ addReplyArrayLen(c,c->argc-2);
+ for (int j = 2; j < c->argc; j++) {
+ unsigned char digest[20];
+ memset(digest,0,20); /* Start with a clean result */
+ robj *o = lookupKeyReadWithFlags(c->db,c->argv[j],LOOKUP_NOTOUCH);
+ if (o) xorObjectDigest(c->db,c->argv[j],digest,o);
+
+ sds d = sdsempty();
+ for (int i = 0; i < 20; i++) d = sdscatprintf(d, "%02x",digest[i]);
+ addReplyStatus(c,d);
+ sdsfree(d);
+ }
+ } else if (!strcasecmp(c->argv[1]->ptr,"protocol") && c->argc == 3) {
+ /* DEBUG PROTOCOL [string|integer|double|bignum|null|array|set|map|
+ * attrib|push|verbatim|true|false|state|err|bloberr] */
+ char *name = c->argv[2]->ptr;
+ if (!strcasecmp(name,"string")) {
+ addReplyBulkCString(c,"Hello World");
+ } else if (!strcasecmp(name,"integer")) {
+ addReplyLongLong(c,12345);
+ } else if (!strcasecmp(name,"double")) {
+ addReplyDouble(c,3.14159265359);
+ } else if (!strcasecmp(name,"bignum")) {
+ addReplyProto(c,"(1234567999999999999999999999999999999\r\n",40);
+ } else if (!strcasecmp(name,"null")) {
+ addReplyNull(c);
+ } else if (!strcasecmp(name,"array")) {
+ addReplyArrayLen(c,3);
+ for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
+ } else if (!strcasecmp(name,"set")) {
+ addReplySetLen(c,3);
+ for (int j = 0; j < 3; j++) addReplyLongLong(c,j);
+ } else if (!strcasecmp(name,"map")) {
+ addReplyMapLen(c,3);
+ for (int j = 0; j < 3; j++) {
+ addReplyLongLong(c,j);
+ addReplyBool(c, j == 1);
+ }
+ } else if (!strcasecmp(name,"attrib")) {
+ addReplyAttributeLen(c,1);
+ addReplyBulkCString(c,"key-popularity");
+ addReplyArrayLen(c,2);
+ addReplyBulkCString(c,"key:123");
+ addReplyLongLong(c,90);
+ /* Attributes are not real replies, so a well formed reply should
+ * also have a normal reply type after the attribute. */
+ addReplyBulkCString(c,"Some real reply following the attribute");
+ } else if (!strcasecmp(name,"push")) {
+ addReplyPushLen(c,2);
+ addReplyBulkCString(c,"server-cpu-usage");
+ addReplyLongLong(c,42);
+ /* Push replies are not synchronous replies, so we emit also a
+ * normal reply in order for blocking clients just discarding the
+ * push reply, to actually consume the reply and continue. */
+ addReplyBulkCString(c,"Some real reply following the push reply");
+ } else if (!strcasecmp(name,"true")) {
+ addReplyBool(c,1);
+ } else if (!strcasecmp(name,"false")) {
+ addReplyBool(c,0);
+ } else if (!strcasecmp(name,"verbatim")) {
+ addReplyVerbatim(c,"This is a verbatim\nstring",25,"txt");
+ } else {
+ addReplyError(c,"Wrong protocol type name. Please use one of the following: string|integer|double|bignum|null|array|set|map|attrib|push|verbatim|true|false|state|err|bloberr");
+ }
} else if (!strcasecmp(c->argv[1]->ptr,"sleep") && c->argc == 3) {
double dtime = strtod(c->argv[2]->ptr,NULL);
long long utime = dtime*1000000;
@@ -503,6 +650,11 @@ NULL
{
server.active_expire_enabled = atoi(c->argv[2]->ptr);
addReply(c,shared.ok);
+ } else if (!strcasecmp(c->argv[1]->ptr,"aof-flush-sleep") &&
+ c->argc == 3)
+ {
+ server.aof_flush_sleep = atoi(c->argv[2]->ptr);
+ addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"lua-always-replicate-commands") &&
c->argc == 3)
{
@@ -546,15 +698,55 @@ NULL
dictGetStats(buf,sizeof(buf),server.db[dbid].expires);
stats = sdscat(stats,buf);
- addReplyBulkSds(c,stats);
+ addReplyVerbatim(c,stats,sdslen(stats),"txt");
+ sdsfree(stats);
+ } else if (!strcasecmp(c->argv[1]->ptr,"htstats-key") && c->argc == 3) {
+ robj *o;
+ dict *ht = NULL;
+
+ if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nokeyerr))
+ == NULL) return;
+
+ /* Get the hash table reference from the object, if possible. */
+ switch (o->encoding) {
+ case OBJ_ENCODING_SKIPLIST:
+ {
+ zset *zs = o->ptr;
+ ht = zs->dict;
+ }
+ break;
+ case OBJ_ENCODING_HT:
+ ht = o->ptr;
+ break;
+ }
+
+ if (ht == NULL) {
+ addReplyError(c,"The value stored at the specified key is not "
+ "represented using an hash table");
+ } else {
+ char buf[4096];
+ dictGetStats(buf,sizeof(buf),ht);
+ addReplyVerbatim(c,buf,strlen(buf),"txt");
+ }
} else if (!strcasecmp(c->argv[1]->ptr,"change-repl-id") && c->argc == 2) {
serverLog(LL_WARNING,"Changing replication IDs after receiving DEBUG change-repl-id");
changeReplicationId();
clearReplicationId2();
addReply(c,shared.ok);
+ } else if (!strcasecmp(c->argv[1]->ptr,"stringmatch-test") && c->argc == 2)
+ {
+ stringmatchlen_fuzz_test();
+ addReplyStatus(c,"Apparently Redis did not crash: test passed");
+#ifdef USE_JEMALLOC
+ } else if(!strcasecmp(c->argv[1]->ptr,"mallctl") && c->argc >= 3) {
+ mallctl_int(c, c->argv+2, c->argc-2);
+ return;
+ } else if(!strcasecmp(c->argv[1]->ptr,"mallctl-str") && c->argc >= 3) {
+ mallctl_string(c, c->argv+2, c->argc-2);
+ return;
+#endif
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try DEBUG HELP",
- (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
return;
}
}
@@ -576,11 +768,12 @@ void _serverAssert(const char *estr, const char *file, int line) {
void _serverAssertPrintClientInfo(const client *c) {
int j;
+ char conninfo[CONN_INFO_LEN];
bugReportStart();
serverLog(LL_WARNING,"=== ASSERTION FAILED CLIENT CONTEXT ===");
- serverLog(LL_WARNING,"client->flags = %d", c->flags);
- serverLog(LL_WARNING,"client->fd = %d", c->fd);
+ serverLog(LL_WARNING,"client->flags = %llu", (unsigned long long) c->flags);
+ serverLog(LL_WARNING,"client->conn = %s", connGetInfo(c->conn, conninfo, sizeof(conninfo)));
serverLog(LL_WARNING,"client->argc = %d", c->argc);
for (j=0; j < c->argc; j++) {
char buf[128];
@@ -680,7 +873,7 @@ static void *getMcontextEip(ucontext_t *uc) {
#endif
#elif defined(__linux__)
/* Linux */
- #if defined(__i386__)
+ #if defined(__i386__) || defined(__ILP32__)
return (void*) uc->uc_mcontext.gregs[14]; /* Linux 32 */
#elif defined(__X86_64__) || defined(__x86_64__)
return (void*) uc->uc_mcontext.gregs[16]; /* Linux 64 */
@@ -688,7 +881,25 @@ static void *getMcontextEip(ucontext_t *uc) {
return (void*) uc->uc_mcontext.sc_ip;
#elif defined(__arm__) /* Linux ARM */
return (void*) uc->uc_mcontext.arm_pc;
+ #elif defined(__aarch64__) /* Linux AArch64 */
+ return (void*) uc->uc_mcontext.pc;
+ #endif
+#elif defined(__FreeBSD__)
+ /* FreeBSD */
+ #if defined(__i386__)
+ return (void*) uc->uc_mcontext.mc_eip;
+ #elif defined(__x86_64__)
+ return (void*) uc->uc_mcontext.mc_rip;
#endif
+#elif defined(__OpenBSD__)
+ /* OpenBSD */
+ #if defined(__i386__)
+ return (void*) uc->sc_eip;
+ #elif defined(__x86_64__)
+ return (void*) uc->sc_rip;
+ #endif
+#elif defined(__DragonFly__)
+ return (void*) uc->uc_mcontext.mc_rip;
#else
return NULL;
#endif
@@ -774,7 +985,7 @@ void logRegisters(ucontext_t *uc) {
/* Linux */
#elif defined(__linux__)
/* Linux x86 */
- #if defined(__i386__)
+ #if defined(__i386__) || defined(__ILP32__)
serverLog(LL_WARNING,
"\n"
"EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
@@ -830,6 +1041,172 @@ void logRegisters(ucontext_t *uc) {
);
logStackContent((void**)uc->uc_mcontext.gregs[15]);
#endif
+#elif defined(__FreeBSD__)
+ #if defined(__x86_64__)
+ serverLog(LL_WARNING,
+ "\n"
+ "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
+ "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
+ "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
+ "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
+ "RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
+ (unsigned long) uc->uc_mcontext.mc_rax,
+ (unsigned long) uc->uc_mcontext.mc_rbx,
+ (unsigned long) uc->uc_mcontext.mc_rcx,
+ (unsigned long) uc->uc_mcontext.mc_rdx,
+ (unsigned long) uc->uc_mcontext.mc_rdi,
+ (unsigned long) uc->uc_mcontext.mc_rsi,
+ (unsigned long) uc->uc_mcontext.mc_rbp,
+ (unsigned long) uc->uc_mcontext.mc_rsp,
+ (unsigned long) uc->uc_mcontext.mc_r8,
+ (unsigned long) uc->uc_mcontext.mc_r9,
+ (unsigned long) uc->uc_mcontext.mc_r10,
+ (unsigned long) uc->uc_mcontext.mc_r11,
+ (unsigned long) uc->uc_mcontext.mc_r12,
+ (unsigned long) uc->uc_mcontext.mc_r13,
+ (unsigned long) uc->uc_mcontext.mc_r14,
+ (unsigned long) uc->uc_mcontext.mc_r15,
+ (unsigned long) uc->uc_mcontext.mc_rip,
+ (unsigned long) uc->uc_mcontext.mc_rflags,
+ (unsigned long) uc->uc_mcontext.mc_cs
+ );
+ logStackContent((void**)uc->uc_mcontext.mc_rsp);
+ #elif defined(__i386__)
+ serverLog(LL_WARNING,
+ "\n"
+ "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
+ "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
+ "SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
+ "DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
+ (unsigned long) uc->uc_mcontext.mc_eax,
+ (unsigned long) uc->uc_mcontext.mc_ebx,
+ (unsigned long) uc->uc_mcontext.mc_ebx,
+ (unsigned long) uc->uc_mcontext.mc_edx,
+ (unsigned long) uc->uc_mcontext.mc_edi,
+ (unsigned long) uc->uc_mcontext.mc_esi,
+ (unsigned long) uc->uc_mcontext.mc_ebp,
+ (unsigned long) uc->uc_mcontext.mc_esp,
+ (unsigned long) uc->uc_mcontext.mc_ss,
+ (unsigned long) uc->uc_mcontext.mc_eflags,
+ (unsigned long) uc->uc_mcontext.mc_eip,
+ (unsigned long) uc->uc_mcontext.mc_cs,
+ (unsigned long) uc->uc_mcontext.mc_es,
+ (unsigned long) uc->uc_mcontext.mc_fs,
+ (unsigned long) uc->uc_mcontext.mc_gs
+ );
+ logStackContent((void**)uc->uc_mcontext.mc_esp);
+ #endif
+#elif defined(__OpenBSD__)
+ #if defined(__x86_64__)
+ serverLog(LL_WARNING,
+ "\n"
+ "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
+ "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
+ "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
+ "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
+ "RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
+ (unsigned long) uc->sc_rax,
+ (unsigned long) uc->sc_rbx,
+ (unsigned long) uc->sc_rcx,
+ (unsigned long) uc->sc_rdx,
+ (unsigned long) uc->sc_rdi,
+ (unsigned long) uc->sc_rsi,
+ (unsigned long) uc->sc_rbp,
+ (unsigned long) uc->sc_rsp,
+ (unsigned long) uc->sc_r8,
+ (unsigned long) uc->sc_r9,
+ (unsigned long) uc->sc_r10,
+ (unsigned long) uc->sc_r11,
+ (unsigned long) uc->sc_r12,
+ (unsigned long) uc->sc_r13,
+ (unsigned long) uc->sc_r14,
+ (unsigned long) uc->sc_r15,
+ (unsigned long) uc->sc_rip,
+ (unsigned long) uc->sc_rflags,
+ (unsigned long) uc->sc_cs
+ );
+ logStackContent((void**)uc->sc_rsp);
+ #elif defined(__i386__)
+ serverLog(LL_WARNING,
+ "\n"
+ "EAX:%08lx EBX:%08lx ECX:%08lx EDX:%08lx\n"
+ "EDI:%08lx ESI:%08lx EBP:%08lx ESP:%08lx\n"
+ "SS :%08lx EFL:%08lx EIP:%08lx CS:%08lx\n"
+ "DS :%08lx ES :%08lx FS :%08lx GS:%08lx",
+ (unsigned long) uc->sc_eax,
+ (unsigned long) uc->sc_ebx,
+ (unsigned long) uc->sc_ebx,
+ (unsigned long) uc->sc_edx,
+ (unsigned long) uc->sc_edi,
+ (unsigned long) uc->sc_esi,
+ (unsigned long) uc->sc_ebp,
+ (unsigned long) uc->sc_esp,
+ (unsigned long) uc->sc_ss,
+ (unsigned long) uc->sc_eflags,
+ (unsigned long) uc->sc_eip,
+ (unsigned long) uc->sc_cs,
+ (unsigned long) uc->sc_es,
+ (unsigned long) uc->sc_fs,
+ (unsigned long) uc->sc_gs
+ );
+ logStackContent((void**)uc->sc_esp);
+ #endif
+#elif defined(__DragonFly__)
+ serverLog(LL_WARNING,
+ "\n"
+ "RAX:%016lx RBX:%016lx\nRCX:%016lx RDX:%016lx\n"
+ "RDI:%016lx RSI:%016lx\nRBP:%016lx RSP:%016lx\n"
+ "R8 :%016lx R9 :%016lx\nR10:%016lx R11:%016lx\n"
+ "R12:%016lx R13:%016lx\nR14:%016lx R15:%016lx\n"
+ "RIP:%016lx EFL:%016lx\nCSGSFS:%016lx",
+ (unsigned long) uc->uc_mcontext.mc_rax,
+ (unsigned long) uc->uc_mcontext.mc_rbx,
+ (unsigned long) uc->uc_mcontext.mc_rcx,
+ (unsigned long) uc->uc_mcontext.mc_rdx,
+ (unsigned long) uc->uc_mcontext.mc_rdi,
+ (unsigned long) uc->uc_mcontext.mc_rsi,
+ (unsigned long) uc->uc_mcontext.mc_rbp,
+ (unsigned long) uc->uc_mcontext.mc_rsp,
+ (unsigned long) uc->uc_mcontext.mc_r8,
+ (unsigned long) uc->uc_mcontext.mc_r9,
+ (unsigned long) uc->uc_mcontext.mc_r10,
+ (unsigned long) uc->uc_mcontext.mc_r11,
+ (unsigned long) uc->uc_mcontext.mc_r12,
+ (unsigned long) uc->uc_mcontext.mc_r13,
+ (unsigned long) uc->uc_mcontext.mc_r14,
+ (unsigned long) uc->uc_mcontext.mc_r15,
+ (unsigned long) uc->uc_mcontext.mc_rip,
+ (unsigned long) uc->uc_mcontext.mc_rflags,
+ (unsigned long) uc->uc_mcontext.mc_cs
+ );
+ logStackContent((void**)uc->uc_mcontext.mc_rsp);
+#elif defined(__aarch64__) /* Linux AArch64 */
+ serverLog(LL_WARNING,
+ "\n"
+ "X18:%016lx X19:%016lx\nX20:%016lx X21:%016lx\n"
+ "X22:%016lx X23:%016lx\nX24:%016lx X25:%016lx\n"
+ "X26:%016lx X27:%016lx\nX28:%016lx X29:%016lx\n"
+ "X30:%016lx\n"
+ "pc:%016lx sp:%016lx\npstate:%016lx fault_address:%016lx\n",
+ (unsigned long) uc->uc_mcontext.regs[18],
+ (unsigned long) uc->uc_mcontext.regs[19],
+ (unsigned long) uc->uc_mcontext.regs[20],
+ (unsigned long) uc->uc_mcontext.regs[21],
+ (unsigned long) uc->uc_mcontext.regs[22],
+ (unsigned long) uc->uc_mcontext.regs[23],
+ (unsigned long) uc->uc_mcontext.regs[24],
+ (unsigned long) uc->uc_mcontext.regs[25],
+ (unsigned long) uc->uc_mcontext.regs[26],
+ (unsigned long) uc->uc_mcontext.regs[27],
+ (unsigned long) uc->uc_mcontext.regs[28],
+ (unsigned long) uc->uc_mcontext.regs[29],
+ (unsigned long) uc->uc_mcontext.regs[30],
+ (unsigned long) uc->uc_mcontext.pc,
+ (unsigned long) uc->uc_mcontext.sp,
+ (unsigned long) uc->uc_mcontext.pstate,
+ (unsigned long) uc->uc_mcontext.fault_address
+ );
+ logStackContent((void**)uc->uc_mcontext.sp);
#else
serverLog(LL_WARNING,
" Dumping of registers not supported for this OS/arch");
@@ -1046,7 +1423,7 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
infostring = genRedisInfoString("all");
serverLogRaw(LL_WARNING|LL_RAW, infostring);
serverLogRaw(LL_WARNING|LL_RAW, "\n------ CLIENT LIST OUTPUT ------\n");
- clients = getAllClientsInfoString();
+ clients = getAllClientsInfoString(-1);
serverLogRaw(LL_WARNING|LL_RAW, clients);
sdsfree(infostring);
sdsfree(clients);
@@ -1057,6 +1434,12 @@ void sigsegvHandler(int sig, siginfo_t *info, void *secret) {
/* Log dump of processor registers */
logRegisters(uc);
+ /* Log Modules INFO */
+ serverLogRaw(LL_WARNING|LL_RAW, "\n------ MODULES INFO OUTPUT ------\n");
+ infostring = modulesCollectInfo(sdsempty(), NULL, 1, 0);
+ serverLogRaw(LL_WARNING|LL_RAW, infostring);
+ sdsfree(infostring);
+
#if defined(HAVE_PROC_MAPS)
/* Test memory */
serverLogRaw(LL_WARNING|LL_RAW, "\n------ FAST MEMORY TEST ------\n");
@@ -1149,6 +1532,8 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len) {
void watchdogSignalHandler(int sig, siginfo_t *info, void *secret) {
#ifdef HAVE_BACKTRACE
ucontext_t *uc = (ucontext_t*) secret;
+#else
+ (void)secret;
#endif
UNUSED(info);
UNUSED(sig);
diff --git a/src/defrag.c b/src/defrag.c
index 3f0e6627c..e794c8e41 100644
--- a/src/defrag.c
+++ b/src/defrag.c
@@ -45,6 +45,10 @@
* pointers are worthwhile moving and which aren't */
int je_get_defrag_hint(void* ptr, int *bin_util, int *run_util);
+/* forward declarations*/
+void defragDictBucketCallback(void *privdata, dictEntry **bucketref);
+dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged);
+
/* Defrag helper for generic allocations.
*
* returns NULL in case the allocatoin wasn't moved.
@@ -96,7 +100,7 @@ sds activeDefragSds(sds sdsptr) {
* returns NULL in case the allocatoin wasn't moved.
* when it returns a non-null value, the old pointer was already released
* and should NOT be accessed. */
-robj *activeDefragStringOb(robj* ob, int *defragged) {
+robj *activeDefragStringOb(robj* ob, long *defragged) {
robj *ret = NULL;
if (ob->refcount!=1)
return NULL;
@@ -134,11 +138,11 @@ robj *activeDefragStringOb(robj* ob, int *defragged) {
/* Defrag helper for dictEntries to be used during dict iteration (called on
* each step). Teturns a stat of how many pointers were moved. */
-int dictIterDefragEntry(dictIterator *iter) {
+long dictIterDefragEntry(dictIterator *iter) {
/* This function is a little bit dirty since it messes with the internals
* of the dict and it's iterator, but the benefit is that it is very easy
* to use, and require no other chagnes in the dict. */
- int defragged = 0;
+ long defragged = 0;
dictht *ht;
/* Handle the next entry (if there is one), and update the pointer in the
* current entry. */
@@ -166,14 +170,9 @@ int dictIterDefragEntry(dictIterator *iter) {
/* Defrag helper for dict main allocations (dict struct, and hash tables).
* receives a pointer to the dict* and implicitly updates it when the dict
* struct itself was moved. Returns a stat of how many pointers were moved. */
-int dictDefragTables(dict** dictRef) {
- dict *d = *dictRef;
+long dictDefragTables(dict* d) {
dictEntry **newtable;
- int defragged = 0;
- /* handle the dict struct */
- dict *newd = activeDefragAlloc(d);
- if (newd)
- defragged++, *dictRef = d = newd;
+ long defragged = 0;
/* handle the first hash table */
newtable = activeDefragAlloc(d->ht[0].table);
if (newtable)
@@ -246,6 +245,146 @@ double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
return NULL;
}
+/* Defrag helpler for sorted set.
+ * Defrag a single dict entry key name, and corresponding skiplist struct */
+long activeDefragZsetEntry(zset *zs, dictEntry *de) {
+ sds newsds;
+ double* newscore;
+ long defragged = 0;
+ sds sdsele = dictGetKey(de);
+ if ((newsds = activeDefragSds(sdsele)))
+ defragged++, de->key = newsds;
+ newscore = zslDefrag(zs->zsl, *(double*)dictGetVal(de), sdsele, newsds);
+ if (newscore) {
+ dictSetVal(zs->dict, de, newscore);
+ defragged++;
+ }
+ return defragged;
+}
+
+#define DEFRAG_SDS_DICT_NO_VAL 0
+#define DEFRAG_SDS_DICT_VAL_IS_SDS 1
+#define DEFRAG_SDS_DICT_VAL_IS_STROB 2
+#define DEFRAG_SDS_DICT_VAL_VOID_PTR 3
+
+/* Defrag a dict with sds key and optional value (either ptr, sds or robj string) */
+long activeDefragSdsDict(dict* d, int val_type) {
+ dictIterator *di;
+ dictEntry *de;
+ long defragged = 0;
+ di = dictGetIterator(d);
+ while((de = dictNext(di)) != NULL) {
+ sds sdsele = dictGetKey(de), newsds;
+ if ((newsds = activeDefragSds(sdsele)))
+ de->key = newsds, defragged++;
+ /* defrag the value */
+ if (val_type == DEFRAG_SDS_DICT_VAL_IS_SDS) {
+ sdsele = dictGetVal(de);
+ if ((newsds = activeDefragSds(sdsele)))
+ de->v.val = newsds, defragged++;
+ } else if (val_type == DEFRAG_SDS_DICT_VAL_IS_STROB) {
+ robj *newele, *ele = dictGetVal(de);
+ if ((newele = activeDefragStringOb(ele, &defragged)))
+ de->v.val = newele;
+ } else if (val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
+ void *newptr, *ptr = dictGetVal(de);
+ if ((newptr = activeDefragAlloc(ptr)))
+ de->v.val = newptr, defragged++;
+ }
+ defragged += dictIterDefragEntry(di);
+ }
+ dictReleaseIterator(di);
+ return defragged;
+}
+
+/* Defrag a list of ptr, sds or robj string values */
+long activeDefragList(list *l, int val_type) {
+ long defragged = 0;
+ listNode *ln, *newln;
+ for (ln = l->head; ln; ln = ln->next) {
+ if ((newln = activeDefragAlloc(ln))) {
+ if (newln->prev)
+ newln->prev->next = newln;
+ else
+ l->head = newln;
+ if (newln->next)
+ newln->next->prev = newln;
+ else
+ l->tail = newln;
+ ln = newln;
+ defragged++;
+ }
+ if (val_type == DEFRAG_SDS_DICT_VAL_IS_SDS) {
+ sds newsds, sdsele = ln->value;
+ if ((newsds = activeDefragSds(sdsele)))
+ ln->value = newsds, defragged++;
+ } else if (val_type == DEFRAG_SDS_DICT_VAL_IS_STROB) {
+ robj *newele, *ele = ln->value;
+ if ((newele = activeDefragStringOb(ele, &defragged)))
+ ln->value = newele;
+ } else if (val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
+ void *newptr, *ptr = ln->value;
+ if ((newptr = activeDefragAlloc(ptr)))
+ ln->value = newptr, defragged++;
+ }
+ }
+ return defragged;
+}
+
+/* Defrag a list of sds values and a dict with the same sds keys */
+long activeDefragSdsListAndDict(list *l, dict *d, int dict_val_type) {
+ long defragged = 0;
+ sds newsds, sdsele;
+ listNode *ln, *newln;
+ dictIterator *di;
+ dictEntry *de;
+ /* Defrag the list and it's sds values */
+ for (ln = l->head; ln; ln = ln->next) {
+ if ((newln = activeDefragAlloc(ln))) {
+ if (newln->prev)
+ newln->prev->next = newln;
+ else
+ l->head = newln;
+ if (newln->next)
+ newln->next->prev = newln;
+ else
+ l->tail = newln;
+ ln = newln;
+ defragged++;
+ }
+ sdsele = ln->value;
+ if ((newsds = activeDefragSds(sdsele))) {
+ /* When defragging an sds value, we need to update the dict key */
+ uint64_t hash = dictGetHash(d, sdsele);
+ replaceSateliteDictKeyPtrAndOrDefragDictEntry(d, sdsele, newsds, hash, &defragged);
+ ln->value = newsds;
+ defragged++;
+ }
+ }
+
+ /* Defrag the dict values (keys were already handled) */
+ di = dictGetIterator(d);
+ while((de = dictNext(di)) != NULL) {
+ if (dict_val_type == DEFRAG_SDS_DICT_VAL_IS_SDS) {
+ sds newsds, sdsele = dictGetVal(de);
+ if ((newsds = activeDefragSds(sdsele)))
+ de->v.val = newsds, defragged++;
+ } else if (dict_val_type == DEFRAG_SDS_DICT_VAL_IS_STROB) {
+ robj *newele, *ele = dictGetVal(de);
+ if ((newele = activeDefragStringOb(ele, &defragged)))
+ de->v.val = newele, defragged++;
+ } else if (dict_val_type == DEFRAG_SDS_DICT_VAL_VOID_PTR) {
+ void *newptr, *ptr = dictGetVal(de);
+ if ((newptr = activeDefragAlloc(ptr)))
+ ln->value = newptr, defragged++;
+ }
+ defragged += dictIterDefragEntry(di);
+ }
+ dictReleaseIterator(di);
+
+ return defragged;
+}
+
/* Utility function that replaces an old key pointer in the dictionary with a
* new pointer. Additionally, we try to defrag the dictEntry in that dict.
* Oldkey mey be a dead pointer and should not be accessed (we get a
@@ -253,7 +392,7 @@ double *zslDefrag(zskiplist *zsl, double score, sds oldele, sds newele) {
* moved. Return value is the the dictEntry if found, or NULL if not found.
* NOTE: this is very ugly code, but it let's us avoid the complication of
* doing a scan on another dict. */
-dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, unsigned int hash, int *defragged) {
+dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sds newkey, uint64_t hash, long *defragged) {
dictEntry **deref = dictFindEntryRefByPtrAndHash(d, oldkey, hash);
if (deref) {
dictEntry *de = *deref;
@@ -269,16 +408,363 @@ dictEntry* replaceSateliteDictKeyPtrAndOrDefragDictEntry(dict *d, sds oldkey, sd
return NULL;
}
+long activeDefragQuickListNodes(quicklist *ql) {
+ quicklistNode *node = ql->head, *newnode;
+ long defragged = 0;
+ unsigned char *newzl;
+ while (node) {
+ if ((newnode = activeDefragAlloc(node))) {
+ if (newnode->prev)
+ newnode->prev->next = newnode;
+ else
+ ql->head = newnode;
+ if (newnode->next)
+ newnode->next->prev = newnode;
+ else
+ ql->tail = newnode;
+ node = newnode;
+ defragged++;
+ }
+ if ((newzl = activeDefragAlloc(node->zl)))
+ defragged++, node->zl = newzl;
+ node = node->next;
+ }
+ return defragged;
+}
+
+/* when the value has lots of elements, we want to handle it later and not as
+ * oart of the main dictionary scan. this is needed in order to prevent latency
+ * spikes when handling large items */
+void defragLater(redisDb *db, dictEntry *kde) {
+ sds key = sdsdup(dictGetKey(kde));
+ listAddNodeTail(db->defrag_later, key);
+}
+
+long scanLaterList(robj *ob) {
+ quicklist *ql = ob->ptr;
+ if (ob->type != OBJ_LIST || ob->encoding != OBJ_ENCODING_QUICKLIST)
+ return 0;
+ server.stat_active_defrag_scanned+=ql->len;
+ return activeDefragQuickListNodes(ql);
+}
+
+typedef struct {
+ zset *zs;
+ long defragged;
+} scanLaterZsetData;
+
+void scanLaterZsetCallback(void *privdata, const dictEntry *_de) {
+ dictEntry *de = (dictEntry*)_de;
+ scanLaterZsetData *data = privdata;
+ data->defragged += activeDefragZsetEntry(data->zs, de);
+ server.stat_active_defrag_scanned++;
+}
+
+long scanLaterZset(robj *ob, unsigned long *cursor) {
+ if (ob->type != OBJ_ZSET || ob->encoding != OBJ_ENCODING_SKIPLIST)
+ return 0;
+ zset *zs = (zset*)ob->ptr;
+ dict *d = zs->dict;
+ scanLaterZsetData data = {zs, 0};
+ *cursor = dictScan(d, *cursor, scanLaterZsetCallback, defragDictBucketCallback, &data);
+ return data.defragged;
+}
+
+void scanLaterSetCallback(void *privdata, const dictEntry *_de) {
+ dictEntry *de = (dictEntry*)_de;
+ long *defragged = privdata;
+ sds sdsele = dictGetKey(de), newsds;
+ if ((newsds = activeDefragSds(sdsele)))
+ (*defragged)++, de->key = newsds;
+ server.stat_active_defrag_scanned++;
+}
+
+long scanLaterSet(robj *ob, unsigned long *cursor) {
+ long defragged = 0;
+ if (ob->type != OBJ_SET || ob->encoding != OBJ_ENCODING_HT)
+ return 0;
+ dict *d = ob->ptr;
+ *cursor = dictScan(d, *cursor, scanLaterSetCallback, defragDictBucketCallback, &defragged);
+ return defragged;
+}
+
+void scanLaterHashCallback(void *privdata, const dictEntry *_de) {
+ dictEntry *de = (dictEntry*)_de;
+ long *defragged = privdata;
+ sds sdsele = dictGetKey(de), newsds;
+ if ((newsds = activeDefragSds(sdsele)))
+ (*defragged)++, de->key = newsds;
+ sdsele = dictGetVal(de);
+ if ((newsds = activeDefragSds(sdsele)))
+ (*defragged)++, de->v.val = newsds;
+ server.stat_active_defrag_scanned++;
+}
+
+long scanLaterHash(robj *ob, unsigned long *cursor) {
+ long defragged = 0;
+ if (ob->type != OBJ_HASH || ob->encoding != OBJ_ENCODING_HT)
+ return 0;
+ dict *d = ob->ptr;
+ *cursor = dictScan(d, *cursor, scanLaterHashCallback, defragDictBucketCallback, &defragged);
+ return defragged;
+}
+
+long defragQuicklist(redisDb *db, dictEntry *kde) {
+ robj *ob = dictGetVal(kde);
+ long defragged = 0;
+ quicklist *ql = ob->ptr, *newql;
+ serverAssert(ob->type == OBJ_LIST && ob->encoding == OBJ_ENCODING_QUICKLIST);
+ if ((newql = activeDefragAlloc(ql)))
+ defragged++, ob->ptr = ql = newql;
+ if (ql->len > server.active_defrag_max_scan_fields)
+ defragLater(db, kde);
+ else
+ defragged += activeDefragQuickListNodes(ql);
+ return defragged;
+}
+
+long defragZsetSkiplist(redisDb *db, dictEntry *kde) {
+ robj *ob = dictGetVal(kde);
+ long defragged = 0;
+ zset *zs = (zset*)ob->ptr;
+ zset *newzs;
+ zskiplist *newzsl;
+ dict *newdict;
+ dictEntry *de;
+ struct zskiplistNode *newheader;
+ serverAssert(ob->type == OBJ_ZSET && ob->encoding == OBJ_ENCODING_SKIPLIST);
+ if ((newzs = activeDefragAlloc(zs)))
+ defragged++, ob->ptr = zs = newzs;
+ if ((newzsl = activeDefragAlloc(zs->zsl)))
+ defragged++, zs->zsl = newzsl;
+ if ((newheader = activeDefragAlloc(zs->zsl->header)))
+ defragged++, zs->zsl->header = newheader;
+ if (dictSize(zs->dict) > server.active_defrag_max_scan_fields)
+ defragLater(db, kde);
+ else {
+ dictIterator *di = dictGetIterator(zs->dict);
+ while((de = dictNext(di)) != NULL) {
+ defragged += activeDefragZsetEntry(zs, de);
+ }
+ dictReleaseIterator(di);
+ }
+ /* handle the dict struct */
+ if ((newdict = activeDefragAlloc(zs->dict)))
+ defragged++, zs->dict = newdict;
+ /* defrag the dict tables */
+ defragged += dictDefragTables(zs->dict);
+ return defragged;
+}
+
+long defragHash(redisDb *db, dictEntry *kde) {
+ long defragged = 0;
+ robj *ob = dictGetVal(kde);
+ dict *d, *newd;
+ serverAssert(ob->type == OBJ_HASH && ob->encoding == OBJ_ENCODING_HT);
+ d = ob->ptr;
+ if (dictSize(d) > server.active_defrag_max_scan_fields)
+ defragLater(db, kde);
+ else
+ defragged += activeDefragSdsDict(d, DEFRAG_SDS_DICT_VAL_IS_SDS);
+ /* handle the dict struct */
+ if ((newd = activeDefragAlloc(ob->ptr)))
+ defragged++, ob->ptr = newd;
+ /* defrag the dict tables */
+ defragged += dictDefragTables(ob->ptr);
+ return defragged;
+}
+
+long defragSet(redisDb *db, dictEntry *kde) {
+ long defragged = 0;
+ robj *ob = dictGetVal(kde);
+ dict *d, *newd;
+ serverAssert(ob->type == OBJ_SET && ob->encoding == OBJ_ENCODING_HT);
+ d = ob->ptr;
+ if (dictSize(d) > server.active_defrag_max_scan_fields)
+ defragLater(db, kde);
+ else
+ defragged += activeDefragSdsDict(d, DEFRAG_SDS_DICT_NO_VAL);
+ /* handle the dict struct */
+ if ((newd = activeDefragAlloc(ob->ptr)))
+ defragged++, ob->ptr = newd;
+ /* defrag the dict tables */
+ defragged += dictDefragTables(ob->ptr);
+ return defragged;
+}
+
+/* Defrag callback for radix tree iterator, called for each node,
+ * used in order to defrag the nodes allocations. */
+int defragRaxNode(raxNode **noderef) {
+ raxNode *newnode = activeDefragAlloc(*noderef);
+ if (newnode) {
+ *noderef = newnode;
+ return 1;
+ }
+ return 0;
+}
+
+/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
+int scanLaterStraemListpacks(robj *ob, unsigned long *cursor, long long endtime, long long *defragged) {
+ static unsigned char last[sizeof(streamID)];
+ raxIterator ri;
+ long iterations = 0;
+ if (ob->type != OBJ_STREAM || ob->encoding != OBJ_ENCODING_STREAM) {
+ *cursor = 0;
+ return 0;
+ }
+
+ stream *s = ob->ptr;
+ raxStart(&ri,s->rax);
+ if (*cursor == 0) {
+ /* if cursor is 0, we start new iteration */
+ defragRaxNode(&s->rax->head);
+ /* assign the iterator node callback before the seek, so that the
+ * initial nodes that are processed till the first item are covered */
+ ri.node_cb = defragRaxNode;
+ raxSeek(&ri,"^",NULL,0);
+ } else {
+ /* if cursor is non-zero, we seek to the static 'last' */
+ if (!raxSeek(&ri,">", last, sizeof(last))) {
+ *cursor = 0;
+ return 0;
+ }
+ /* assign the iterator node callback after the seek, so that the
+ * initial nodes that are processed till now aren't covered */
+ ri.node_cb = defragRaxNode;
+ }
+
+ (*cursor)++;
+ while (raxNext(&ri)) {
+ void *newdata = activeDefragAlloc(ri.data);
+ if (newdata)
+ raxSetData(ri.node, ri.data=newdata), (*defragged)++;
+ if (++iterations > 16) {
+ if (ustime() > endtime) {
+ serverAssert(ri.key_len==sizeof(last));
+ memcpy(last,ri.key,ri.key_len);
+ raxStop(&ri);
+ return 1;
+ }
+ iterations = 0;
+ }
+ }
+ raxStop(&ri);
+ *cursor = 0;
+ return 0;
+}
+
+/* optional callback used defrag each rax element (not including the element pointer itself) */
+typedef void *(raxDefragFunction)(raxIterator *ri, void *privdata, long *defragged);
+
+/* defrag radix tree including:
+ * 1) rax struct
+ * 2) rax nodes
+ * 3) rax entry data (only if defrag_data is specified)
+ * 4) call a callback per element, and allow the callback to return a new pointer for the element */
+long defragRadixTree(rax **raxref, int defrag_data, raxDefragFunction *element_cb, void *element_cb_data) {
+ long defragged = 0;
+ raxIterator ri;
+ rax* rax;
+ if ((rax = activeDefragAlloc(*raxref)))
+ defragged++, *raxref = rax;
+ rax = *raxref;
+ raxStart(&ri,rax);
+ ri.node_cb = defragRaxNode;
+ defragRaxNode(&rax->head);
+ raxSeek(&ri,"^",NULL,0);
+ while (raxNext(&ri)) {
+ void *newdata = NULL;
+ if (element_cb)
+ newdata = element_cb(&ri, element_cb_data, &defragged);
+ if (defrag_data && !newdata)
+ newdata = activeDefragAlloc(ri.data);
+ if (newdata)
+ raxSetData(ri.node, ri.data=newdata), defragged++;
+ }
+ raxStop(&ri);
+ return defragged;
+}
+
+typedef struct {
+ streamCG *cg;
+ streamConsumer *c;
+} PendingEntryContext;
+
+void* defragStreamConsumerPendingEntry(raxIterator *ri, void *privdata, long *defragged) {
+ UNUSED(defragged);
+ PendingEntryContext *ctx = privdata;
+ streamNACK *nack = ri->data, *newnack;
+ nack->consumer = ctx->c; /* update nack pointer to consumer */
+ newnack = activeDefragAlloc(nack);
+ if (newnack) {
+ /* update consumer group pointer to the nack */
+ void *prev;
+ raxInsert(ctx->cg->pel, ri->key, ri->key_len, newnack, &prev);
+ serverAssert(prev==nack);
+ /* note: we don't increment 'defragged' that's done by the caller */
+ }
+ return newnack;
+}
+
+void* defragStreamConsumer(raxIterator *ri, void *privdata, long *defragged) {
+ streamConsumer *c = ri->data;
+ streamCG *cg = privdata;
+ void *newc = activeDefragAlloc(c);
+ if (newc) {
+ /* note: we don't increment 'defragged' that's done by the caller */
+ c = newc;
+ }
+ sds newsds = activeDefragSds(c->name);
+ if (newsds)
+ (*defragged)++, c->name = newsds;
+ if (c->pel) {
+ PendingEntryContext pel_ctx = {cg, c};
+ *defragged += defragRadixTree(&c->pel, 0, defragStreamConsumerPendingEntry, &pel_ctx);
+ }
+ return newc; /* returns NULL if c was not defragged */
+}
+
+void* defragStreamConsumerGroup(raxIterator *ri, void *privdata, long *defragged) {
+ streamCG *cg = ri->data;
+ UNUSED(privdata);
+ if (cg->consumers)
+ *defragged += defragRadixTree(&cg->consumers, 0, defragStreamConsumer, cg);
+ if (cg->pel)
+ *defragged += defragRadixTree(&cg->pel, 0, NULL, NULL);
+ return NULL;
+}
+
+long defragStream(redisDb *db, dictEntry *kde) {
+ long defragged = 0;
+ robj *ob = dictGetVal(kde);
+ serverAssert(ob->type == OBJ_STREAM && ob->encoding == OBJ_ENCODING_STREAM);
+ stream *s = ob->ptr, *news;
+
+ /* handle the main struct */
+ if ((news = activeDefragAlloc(s)))
+ defragged++, ob->ptr = s = news;
+
+ if (raxSize(s->rax) > server.active_defrag_max_scan_fields) {
+ rax *newrax = activeDefragAlloc(s->rax);
+ if (newrax)
+ defragged++, s->rax = newrax;
+ defragLater(db, kde);
+ } else
+ defragged += defragRadixTree(&s->rax, 1, NULL, NULL);
+
+ if (s->cgroups)
+ defragged += defragRadixTree(&s->cgroups, 1, defragStreamConsumerGroup, NULL);
+ return defragged;
+}
+
/* for each key we scan in the main dict, this function will attempt to defrag
* all the various pointers it has. Returns a stat of how many pointers were
* moved. */
-int defragKey(redisDb *db, dictEntry *de) {
+long defragKey(redisDb *db, dictEntry *de) {
sds keysds = dictGetKey(de);
robj *newob, *ob;
unsigned char *newzl;
- dict *d;
- dictIterator *di;
- int defragged = 0;
+ long defragged = 0;
sds newsds;
/* Try to defrag the key name. */
@@ -304,27 +790,7 @@ int defragKey(redisDb *db, dictEntry *de) {
/* Already handled in activeDefragStringOb. */
} else if (ob->type == OBJ_LIST) {
if (ob->encoding == OBJ_ENCODING_QUICKLIST) {
- quicklist *ql = ob->ptr, *newql;
- quicklistNode *node = ql->head, *newnode;
- if ((newql = activeDefragAlloc(ql)))
- defragged++, ob->ptr = ql = newql;
- while (node) {
- if ((newnode = activeDefragAlloc(node))) {
- if (newnode->prev)
- newnode->prev->next = newnode;
- else
- ql->head = newnode;
- if (newnode->next)
- newnode->next->prev = newnode;
- else
- ql->tail = newnode;
- node = newnode;
- defragged++;
- }
- if ((newzl = activeDefragAlloc(node->zl)))
- defragged++, node->zl = newzl;
- node = node->next;
- }
+ defragged += defragQuicklist(db, de);
} else if (ob->encoding == OBJ_ENCODING_ZIPLIST) {
if ((newzl = activeDefragAlloc(ob->ptr)))
defragged++, ob->ptr = newzl;
@@ -333,20 +799,10 @@ int defragKey(redisDb *db, dictEntry *de) {
}
} else if (ob->type == OBJ_SET) {
if (ob->encoding == OBJ_ENCODING_HT) {
- d = ob->ptr;
- di = dictGetIterator(d);
- while((de = dictNext(di)) != NULL) {
- sds sdsele = dictGetKey(de);
- if ((newsds = activeDefragSds(sdsele)))
- defragged++, de->key = newsds;
- defragged += dictIterDefragEntry(di);
- }
- dictReleaseIterator(di);
- dictDefragTables((dict**)&ob->ptr);
+ defragged += defragSet(db, de);
} else if (ob->encoding == OBJ_ENCODING_INTSET) {
- intset *is = ob->ptr;
- intset *newis = activeDefragAlloc(is);
- if (newis)
+ intset *newis, *is = ob->ptr;
+ if ((newis = activeDefragAlloc(is)))
defragged++, ob->ptr = newis;
} else {
serverPanic("Unknown set encoding");
@@ -356,32 +812,7 @@ int defragKey(redisDb *db, dictEntry *de) {
if ((newzl = activeDefragAlloc(ob->ptr)))
defragged++, ob->ptr = newzl;
} else if (ob->encoding == OBJ_ENCODING_SKIPLIST) {
- zset *zs = (zset*)ob->ptr;
- zset *newzs;
- zskiplist *newzsl;
- struct zskiplistNode *newheader;
- if ((newzs = activeDefragAlloc(zs)))
- defragged++, ob->ptr = zs = newzs;
- if ((newzsl = activeDefragAlloc(zs->zsl)))
- defragged++, zs->zsl = newzsl;
- if ((newheader = activeDefragAlloc(zs->zsl->header)))
- defragged++, zs->zsl->header = newheader;
- d = zs->dict;
- di = dictGetIterator(d);
- while((de = dictNext(di)) != NULL) {
- double* newscore;
- sds sdsele = dictGetKey(de);
- if ((newsds = activeDefragSds(sdsele)))
- defragged++, de->key = newsds;
- newscore = zslDefrag(zs->zsl, *(double*)dictGetVal(de), sdsele, newsds);
- if (newscore) {
- dictSetVal(d, de, newscore);
- defragged++;
- }
- defragged += dictIterDefragEntry(di);
- }
- dictReleaseIterator(di);
- dictDefragTables(&zs->dict);
+ defragged += defragZsetSkiplist(db, de);
} else {
serverPanic("Unknown sorted set encoding");
}
@@ -390,22 +821,12 @@ int defragKey(redisDb *db, dictEntry *de) {
if ((newzl = activeDefragAlloc(ob->ptr)))
defragged++, ob->ptr = newzl;
} else if (ob->encoding == OBJ_ENCODING_HT) {
- d = ob->ptr;
- di = dictGetIterator(d);
- while((de = dictNext(di)) != NULL) {
- sds sdsele = dictGetKey(de);
- if ((newsds = activeDefragSds(sdsele)))
- defragged++, de->key = newsds;
- sdsele = dictGetVal(de);
- if ((newsds = activeDefragSds(sdsele)))
- defragged++, de->v.val = newsds;
- defragged += dictIterDefragEntry(di);
- }
- dictReleaseIterator(di);
- dictDefragTables((dict**)&ob->ptr);
+ defragged += defragHash(db, de);
} else {
serverPanic("Unknown hash encoding");
}
+ } else if (ob->type == OBJ_STREAM) {
+ defragged += defragStream(db, de);
} else if (ob->type == OBJ_MODULE) {
/* Currently defragmenting modules private data types
* is not supported. */
@@ -417,18 +838,19 @@ int defragKey(redisDb *db, dictEntry *de) {
/* Defrag scan callback for the main db dictionary. */
void defragScanCallback(void *privdata, const dictEntry *de) {
- int defragged = defragKey((redisDb*)privdata, (dictEntry*)de);
+ long defragged = defragKey((redisDb*)privdata, (dictEntry*)de);
server.stat_active_defrag_hits += defragged;
if(defragged)
server.stat_active_defrag_key_hits++;
else
server.stat_active_defrag_key_misses++;
+ server.stat_active_defrag_scanned++;
}
-/* Defrag scan callback for for each hash table bicket,
+/* Defrag scan callback for each hash table bicket,
* used in order to defrag the dictEntry allocations. */
void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
- UNUSED(privdata);
+ UNUSED(privdata); /* NOTE: this function is also used by both activeDefragCycle and scanLaterHash, etc. don't use privdata */
while(*bucketref) {
dictEntry *de = *bucketref, *newde;
if ((newde = activeDefragAlloc(de))) {
@@ -439,24 +861,14 @@ void defragDictBucketCallback(void *privdata, dictEntry **bucketref) {
}
/* Utility function to get the fragmentation ratio from jemalloc.
- * It is critical to do that by comparing only heap maps that belown to
+ * It is critical to do that by comparing only heap maps that belong to
* jemalloc, and skip ones the jemalloc keeps as spare. Since we use this
* fragmentation ratio in order to decide if a defrag action should be taken
* or not, a false detection can cause the defragmenter to waste a lot of CPU
* without the possibility of getting any results. */
float getAllocatorFragmentation(size_t *out_frag_bytes) {
- size_t epoch = 1, allocated = 0, resident = 0, active = 0, sz = sizeof(size_t);
- /* Update the statistics cached by mallctl. */
- je_mallctl("epoch", &epoch, &sz, &epoch, sz);
- /* Unlike RSS, this does not include RSS from shared libraries and other non
- * heap mappings. */
- je_mallctl("stats.resident", &resident, &sz, NULL, 0);
- /* Unlike resident, this doesn't not include the pages jemalloc reserves
- * for re-use (purge will clean that). */
- je_mallctl("stats.active", &active, &sz, NULL, 0);
- /* Unlike zmalloc_used_memory, this matches the stats.resident by taking
- * into account all allocations done by this process (not only zmalloc). */
- je_mallctl("stats.allocated", &allocated, &sz, NULL, 0);
+ size_t resident, active, allocated;
+ zmalloc_get_allocator_info(&allocated, &active, &resident);
float frag_pct = ((float)active / allocated)*100 - 100;
size_t frag_bytes = active - allocated;
float rss_pct = ((float)resident / allocated)*100 - 100;
@@ -464,14 +876,154 @@ float getAllocatorFragmentation(size_t *out_frag_bytes) {
if(out_frag_bytes)
*out_frag_bytes = frag_bytes;
serverLog(LL_DEBUG,
- "allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu%% rss)",
+ "allocated=%zu, active=%zu, resident=%zu, frag=%.0f%% (%.0f%% rss), frag_bytes=%zu (%zu rss)",
allocated, active, resident, frag_pct, rss_pct, frag_bytes, rss_bytes);
return frag_pct;
}
+/* We may need to defrag other globals, one small allcation can hold a full allocator run.
+ * so although small, it is still important to defrag these */
+long defragOtherGlobals() {
+ long defragged = 0;
+
+ /* there are many more pointers to defrag (e.g. client argv, output / aof buffers, etc.
+ * but we assume most of these are short lived, we only need to defrag allocations
+ * that remain static for a long time */
+ defragged += activeDefragSdsDict(server.lua_scripts, DEFRAG_SDS_DICT_VAL_IS_STROB);
+ defragged += activeDefragSdsListAndDict(server.repl_scriptcache_fifo, server.repl_scriptcache_dict, DEFRAG_SDS_DICT_NO_VAL);
+ return defragged;
+}
+
+/* returns 0 more work may or may not be needed (see non-zero cursor),
+ * and 1 if time is up and more work is needed. */
+int defragLaterItem(dictEntry *de, unsigned long *cursor, long long endtime) {
+ if (de) {
+ robj *ob = dictGetVal(de);
+ if (ob->type == OBJ_LIST) {
+ server.stat_active_defrag_hits += scanLaterList(ob);
+ *cursor = 0; /* list has no scan, we must finish it in one go */
+ } else if (ob->type == OBJ_SET) {
+ server.stat_active_defrag_hits += scanLaterSet(ob, cursor);
+ } else if (ob->type == OBJ_ZSET) {
+ server.stat_active_defrag_hits += scanLaterZset(ob, cursor);
+ } else if (ob->type == OBJ_HASH) {
+ server.stat_active_defrag_hits += scanLaterHash(ob, cursor);
+ } else if (ob->type == OBJ_STREAM) {
+ return scanLaterStraemListpacks(ob, cursor, endtime, &server.stat_active_defrag_hits);
+ } else {
+ *cursor = 0; /* object type may have changed since we schedule it for later */
+ }
+ } else {
+ *cursor = 0; /* object may have been deleted already */
+ }
+ return 0;
+}
+
+/* returns 0 if no more work needs to be been done, and 1 if time is up and more work is needed. */
+int defragLaterStep(redisDb *db, long long endtime) {
+ static sds current_key = NULL;
+ static unsigned long cursor = 0;
+ unsigned int iterations = 0;
+ unsigned long long prev_defragged = server.stat_active_defrag_hits;
+ unsigned long long prev_scanned = server.stat_active_defrag_scanned;
+ long long key_defragged;
+
+ do {
+ /* if we're not continuing a scan from the last call or loop, start a new one */
+ if (!cursor) {
+ listNode *head = listFirst(db->defrag_later);
+
+ /* Move on to next key */
+ if (current_key) {
+ serverAssert(current_key == head->value);
+ sdsfree(head->value);
+ listDelNode(db->defrag_later, head);
+ cursor = 0;
+ current_key = NULL;
+ }
+
+ /* stop if we reached the last one. */
+ head = listFirst(db->defrag_later);
+ if (!head)
+ return 0;
+
+ /* start a new key */
+ current_key = head->value;
+ cursor = 0;
+ }
+
+ /* each time we enter this function we need to fetch the key from the dict again (if it still exists) */
+ dictEntry *de = dictFind(db->dict, current_key);
+ key_defragged = server.stat_active_defrag_hits;
+ do {
+ int quit = 0;
+ if (defragLaterItem(de, &cursor, endtime))
+ quit = 1; /* time is up, we didn't finish all the work */
+
+ /* Don't start a new BIG key in this loop, this is because the
+ * next key can be a list, and scanLaterList must be done in once cycle */
+ if (!cursor)
+ quit = 1;
+
+ /* Once in 16 scan iterations, 512 pointer reallocations, or 64 fields
+ * (if we have a lot of pointers in one hash bucket, or rehashing),
+ * check if we reached the time limit. */
+ if (quit || (++iterations > 16 ||
+ server.stat_active_defrag_hits - prev_defragged > 512 ||
+ server.stat_active_defrag_scanned - prev_scanned > 64)) {
+ if (quit || ustime() > endtime) {
+ if(key_defragged != server.stat_active_defrag_hits)
+ server.stat_active_defrag_key_hits++;
+ else
+ server.stat_active_defrag_key_misses++;
+ return 1;
+ }
+ iterations = 0;
+ prev_defragged = server.stat_active_defrag_hits;
+ prev_scanned = server.stat_active_defrag_scanned;
+ }
+ } while(cursor);
+ if(key_defragged != server.stat_active_defrag_hits)
+ server.stat_active_defrag_key_hits++;
+ else
+ server.stat_active_defrag_key_misses++;
+ } while(1);
+}
+
#define INTERPOLATE(x, x1, x2, y1, y2) ( (y1) + ((x)-(x1)) * ((y2)-(y1)) / ((x2)-(x1)) )
#define LIMIT(y, min, max) ((y)<(min)? min: ((y)>(max)? max: (y)))
+/* decide if defrag is needed, and at what CPU effort to invest in it */
+void computeDefragCycles() {
+ size_t frag_bytes;
+ float frag_pct = getAllocatorFragmentation(&frag_bytes);
+ /* If we're not already running, and below the threshold, exit. */
+ if (!server.active_defrag_running) {
+ if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
+ return;
+ }
+
+ /* Calculate the adaptive aggressiveness of the defrag */
+ int cpu_pct = INTERPOLATE(frag_pct,
+ server.active_defrag_threshold_lower,
+ server.active_defrag_threshold_upper,
+ server.active_defrag_cycle_min,
+ server.active_defrag_cycle_max);
+ cpu_pct = LIMIT(cpu_pct,
+ server.active_defrag_cycle_min,
+ server.active_defrag_cycle_max);
+ /* We allow increasing the aggressiveness during a scan, but don't
+ * reduce it. */
+ if (!server.active_defrag_running ||
+ cpu_pct > server.active_defrag_running)
+ {
+ server.active_defrag_running = cpu_pct;
+ serverLog(LL_VERBOSE,
+ "Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
+ frag_pct, frag_bytes, cpu_pct);
+ }
+}
+
/* Perform incremental defragmentation work from the serverCron.
* This works in a similar way to activeExpireCycle, in the sense that
* we do incremental work across calls. */
@@ -481,42 +1033,19 @@ void activeDefragCycle(void) {
static redisDb *db = NULL;
static long long start_scan, start_stat;
unsigned int iterations = 0;
- unsigned long long defragged = server.stat_active_defrag_hits;
- long long start, timelimit;
+ unsigned long long prev_defragged = server.stat_active_defrag_hits;
+ unsigned long long prev_scanned = server.stat_active_defrag_scanned;
+ long long start, timelimit, endtime;
+ mstime_t latency;
+ int quit = 0;
- if (server.aof_child_pid!=-1 || server.rdb_child_pid!=-1)
+ if (hasActiveChildProcess())
return; /* Defragging memory while there's a fork will just do damage. */
/* Once a second, check if we the fragmentation justfies starting a scan
* or making it more aggressive. */
run_with_period(1000) {
- size_t frag_bytes;
- float frag_pct = getAllocatorFragmentation(&frag_bytes);
- /* If we're not already running, and below the threshold, exit. */
- if (!server.active_defrag_running) {
- if(frag_pct < server.active_defrag_threshold_lower || frag_bytes < server.active_defrag_ignore_bytes)
- return;
- }
-
- /* Calculate the adaptive aggressiveness of the defrag */
- int cpu_pct = INTERPOLATE(frag_pct,
- server.active_defrag_threshold_lower,
- server.active_defrag_threshold_upper,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- cpu_pct = LIMIT(cpu_pct,
- server.active_defrag_cycle_min,
- server.active_defrag_cycle_max);
- /* We allow increasing the aggressiveness during a scan, but don't
- * reduce it. */
- if (!server.active_defrag_running ||
- cpu_pct > server.active_defrag_running)
- {
- server.active_defrag_running = cpu_pct;
- serverLog(LL_VERBOSE,
- "Starting active defrag, frag=%.0f%%, frag_bytes=%zu, cpu=%d%%",
- frag_pct, frag_bytes, cpu_pct);
- }
+ computeDefragCycles();
}
if (!server.active_defrag_running)
return;
@@ -525,11 +1054,23 @@ void activeDefragCycle(void) {
start = ustime();
timelimit = 1000000*server.active_defrag_running/server.hz/100;
if (timelimit <= 0) timelimit = 1;
+ endtime = start + timelimit;
+ latencyStartMonitor(latency);
do {
+ /* if we're not continuing a scan from the last call or loop, start a new one */
if (!cursor) {
+ /* finish any leftovers from previous db before moving to the next one */
+ if (db && defragLaterStep(db, endtime)) {
+ quit = 1; /* time is up, we didn't finish all the work */
+ break; /* this will exit the function and we'll continue on the next cycle */
+ }
+
/* Move on to next database, and stop if we reached the last one. */
if (++current_db >= server.dbnum) {
+ /* defrag other items not part of the db / keys */
+ defragOtherGlobals();
+
long long now = ustime();
size_t frag_bytes;
float frag_pct = getAllocatorFragmentation(&frag_bytes);
@@ -542,7 +1083,11 @@ void activeDefragCycle(void) {
cursor = 0;
db = NULL;
server.active_defrag_running = 0;
- return;
+
+ computeDefragCycles(); /* if another scan is needed, start it right away */
+ if (server.active_defrag_running != 0 && ustime() < endtime)
+ continue;
+ break;
}
else if (current_db==0) {
/* Start a scan from the first database. */
@@ -555,19 +1100,35 @@ void activeDefragCycle(void) {
}
do {
+ /* before scanning the next bucket, see if we have big keys left from the previous bucket to scan */
+ if (defragLaterStep(db, endtime)) {
+ quit = 1; /* time is up, we didn't finish all the work */
+ break; /* this will exit the function and we'll continue on the next cycle */
+ }
+
cursor = dictScan(db->dict, cursor, defragScanCallback, defragDictBucketCallback, db);
- /* Once in 16 scan iterations, or 1000 pointer reallocations
- * (if we have a lot of pointers in one hash bucket), check if we
- * reached the tiem limit. */
- if (cursor && (++iterations > 16 || server.stat_active_defrag_hits - defragged > 1000)) {
- if ((ustime() - start) > timelimit) {
- return;
+
+ /* Once in 16 scan iterations, 512 pointer reallocations. or 64 keys
+ * (if we have a lot of pointers in one hash bucket or rehasing),
+ * check if we reached the time limit.
+ * But regardless, don't start a new db in this loop, this is because after
+ * the last db we call defragOtherGlobals, which must be done in once cycle */
+ if (!cursor || (++iterations > 16 ||
+ server.stat_active_defrag_hits - prev_defragged > 512 ||
+ server.stat_active_defrag_scanned - prev_scanned > 64)) {
+ if (!cursor || ustime() > endtime) {
+ quit = 1;
+ break;
}
iterations = 0;
- defragged = server.stat_active_defrag_hits;
+ prev_defragged = server.stat_active_defrag_hits;
+ prev_scanned = server.stat_active_defrag_scanned;
}
- } while(cursor);
- } while(1);
+ } while(cursor && !quit);
+ } while(!quit);
+
+ latencyEndMonitor(latency);
+ latencyAddSampleIfNeeded("active-defrag-cycle",latency);
}
#else /* HAVE_DEFRAG */
diff --git a/src/dict.c b/src/dict.c
index 97e636805..106467ef7 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -146,14 +146,14 @@ int dictResize(dict *d)
/* Expand or create the hash table */
int dictExpand(dict *d, unsigned long size)
{
- dictht n; /* the new hash table */
- unsigned long realsize = _dictNextPower(size);
-
/* the size is invalid if it is smaller than the number of
* elements already inside the hash table */
if (dictIsRehashing(d) || d->ht[0].used > size)
return DICT_ERR;
+ dictht n; /* the new hash table */
+ unsigned long realsize = _dictNextPower(size);
+
/* Rehashing to the same table size is not useful. */
if (realsize == d->ht[0].size) return DICT_ERR;
@@ -327,7 +327,7 @@ int dictReplace(dict *d, void *key, void *val)
dictEntry *entry, *existing, auxentry;
/* Try to add the element. If the key
- * does not exists dictAdd will suceed. */
+ * does not exists dictAdd will succeed. */
entry = dictAddRaw(d,key,&existing);
if (entry) {
dictSetVal(d, entry, val);
@@ -705,8 +705,10 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
* table, there will be no elements in both tables up to
* the current rehashing index, so we jump if possible.
* (this happens when going from big to small table). */
- if (i >= d->ht[1].size) i = d->rehashidx;
- continue;
+ if (i >= d->ht[1].size)
+ i = d->rehashidx;
+ else
+ continue;
}
if (i >= d->ht[j].size) continue; /* Out of range for this table. */
dictEntry *he = d->ht[j].table[i];
@@ -737,6 +739,30 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
return stored;
}
+/* This is like dictGetRandomKey() from the POV of the API, but will do more
+ * work to ensure a better distribution of the returned element.
+ *
+ * This function improves the distribution because the dictGetRandomKey()
+ * problem is that it selects a random bucket, then it selects a random
+ * element from the chain in the bucket. However elements being in different
+ * chain lengths will have different probabilities of being reported. With
+ * this function instead what we do is to consider a "linear" range of the table
+ * that may be constituted of N buckets with chains of different lengths
+ * appearing one after the other. Then we report a random element in the range.
+ * In this way we smooth away the problem of different chain lenghts. */
+#define GETFAIR_NUM_ENTRIES 15
+dictEntry *dictGetFairRandomKey(dict *d) {
+ dictEntry *entries[GETFAIR_NUM_ENTRIES];
+ unsigned int count = dictGetSomeKeys(d,entries,GETFAIR_NUM_ENTRIES);
+ /* Note that dictGetSomeKeys() may return zero elements in an unlucky
+ * run() even if there are actually elements inside the hash table. So
+ * when we get zero, we call the true dictGetRandomKey() that will always
+ * yeld the element if the hash table has at least one. */
+ if (count == 0) return dictGetRandomKey(d);
+ unsigned int idx = rand() % count;
+ return entries[idx];
+}
+
/* Function to reverse bits. Algorithm from:
* http://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel */
static unsigned long rev(unsigned long v) {
@@ -858,6 +884,15 @@ unsigned long dictScan(dict *d,
de = next;
}
+ /* Set unmasked bits so incrementing the reversed cursor
+ * operates on the masked bits */
+ v |= ~m0;
+
+ /* Increment the reverse cursor */
+ v = rev(v);
+ v++;
+ v = rev(v);
+
} else {
t0 = &d->ht[0];
t1 = &d->ht[1];
@@ -892,22 +927,16 @@ unsigned long dictScan(dict *d,
de = next;
}
- /* Increment bits not covered by the smaller mask */
- v = (((v | m0) + 1) & ~m0) | (v & m0);
+ /* Increment the reverse cursor not covered by the smaller mask.*/
+ v |= ~m1;
+ v = rev(v);
+ v++;
+ v = rev(v);
/* Continue while bits covered by mask difference is non-zero */
} while (v & (m0 ^ m1));
}
- /* Set unmasked bits so incrementing the reversed cursor
- * operates on the masked bits of the smaller table */
- v |= ~m0;
-
- /* Increment the reverse cursor */
- v = rev(v);
- v++;
- v = rev(v);
-
return v;
}
diff --git a/src/dict.h b/src/dict.h
index 62018cc44..dec60f637 100644
--- a/src/dict.h
+++ b/src/dict.h
@@ -166,6 +166,7 @@ dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
+dictEntry *dictGetFairRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, int len);
diff --git a/src/endianconv.h b/src/endianconv.h
index 08f553136..475f72b08 100644
--- a/src/endianconv.h
+++ b/src/endianconv.h
@@ -43,12 +43,12 @@ uint16_t intrev16(uint16_t v);
uint32_t intrev32(uint32_t v);
uint64_t intrev64(uint64_t v);
-/* variants of the function doing the actual convertion only if the target
+/* variants of the function doing the actual conversion only if the target
* host is big endian */
#if (BYTE_ORDER == LITTLE_ENDIAN)
-#define memrev16ifbe(p)
-#define memrev32ifbe(p)
-#define memrev64ifbe(p)
+#define memrev16ifbe(p) ((void)(0))
+#define memrev32ifbe(p) ((void)(0))
+#define memrev64ifbe(p) ((void)(0))
#define intrev16ifbe(v) (v)
#define intrev32ifbe(v) (v)
#define intrev64ifbe(v) (v)
diff --git a/src/evict.c b/src/evict.c
index bf485ddc5..71260c040 100644
--- a/src/evict.c
+++ b/src/evict.c
@@ -78,7 +78,7 @@ unsigned int getLRUClock(void) {
unsigned int LRU_CLOCK(void) {
unsigned int lruclock;
if (1000/server.hz <= LRU_CLOCK_RESOLUTION) {
- atomicGet(server.lruclock,lruclock);
+ lruclock = server.lruclock;
} else {
lruclock = getLRUClock();
}
@@ -364,26 +364,46 @@ size_t freeMemoryGetNotCountedMemory(void) {
}
}
if (server.aof_state != AOF_OFF) {
- overhead += sdslen(server.aof_buf)+aofRewriteBufferSize();
+ overhead += sdsalloc(server.aof_buf)+aofRewriteBufferSize();
}
return overhead;
}
-int freeMemoryIfNeeded(void) {
- size_t mem_reported, mem_used, mem_tofree, mem_freed;
- mstime_t latency, eviction_latency;
- long long delta;
- int slaves = listLength(server.slaves);
-
- /* When clients are paused the dataset should be static not just from the
- * POV of clients not being able to write, but also from the POV of
- * expires and evictions of keys not being performed. */
- if (clientsArePaused()) return C_OK;
+/* Get the memory status from the point of view of the maxmemory directive:
+ * if the memory used is under the maxmemory setting then C_OK is returned.
+ * Otherwise, if we are over the memory limit, the function returns
+ * C_ERR.
+ *
+ * The function may return additional info via reference, only if the
+ * pointers to the respective arguments is not NULL. Certain fields are
+ * populated only when C_ERR is returned:
+ *
+ * 'total' total amount of bytes used.
+ * (Populated both for C_ERR and C_OK)
+ *
+ * 'logical' the amount of memory used minus the slaves/AOF buffers.
+ * (Populated when C_ERR is returned)
+ *
+ * 'tofree' the amount of memory that should be released
+ * in order to return back into the memory limits.
+ * (Populated when C_ERR is returned)
+ *
+ * 'level' this usually ranges from 0 to 1, and reports the amount of
+ * memory currently used. May be > 1 if we are over the memory
+ * limit.
+ * (Populated both for C_ERR and C_OK)
+ */
+int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level) {
+ size_t mem_reported, mem_used, mem_tofree;
/* Check if we are over the memory usage limit. If we are not, no need
* to subtract the slaves output buffers. We can just return ASAP. */
mem_reported = zmalloc_used_memory();
- if (mem_reported <= server.maxmemory) return C_OK;
+ if (total) *total = mem_reported;
+
+ /* We may return ASAP if there is no need to compute the level. */
+ int return_ok_asap = !server.maxmemory || mem_reported <= server.maxmemory;
+ if (return_ok_asap && !level) return C_OK;
/* Remove the size of slaves output buffers and AOF buffer from the
* count of used memory. */
@@ -391,11 +411,56 @@ int freeMemoryIfNeeded(void) {
size_t overhead = freeMemoryGetNotCountedMemory();
mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
+ /* Compute the ratio of memory usage. */
+ if (level) {
+ if (!server.maxmemory) {
+ *level = 0;
+ } else {
+ *level = (float)mem_used / (float)server.maxmemory;
+ }
+ }
+
+ if (return_ok_asap) return C_OK;
+
/* Check if we are still over the memory limit. */
if (mem_used <= server.maxmemory) return C_OK;
/* Compute how much memory we need to free. */
mem_tofree = mem_used - server.maxmemory;
+
+ if (logical) *logical = mem_used;
+ if (tofree) *tofree = mem_tofree;
+
+ return C_ERR;
+}
+
+/* This function is periodically called to see if there is memory to free
+ * according to the current "maxmemory" settings. In case we are over the
+ * memory limit, the function will try to free some memory to return back
+ * under the limit.
+ *
+ * The function returns C_OK if we are under the memory limit or if we
+ * were over the limit, but the attempt to free memory was successful.
+ * Otehrwise if we are over the memory limit, but not enough memory
+ * was freed to return back under the limit, the function returns C_ERR. */
+int freeMemoryIfNeeded(void) {
+ int keys_freed = 0;
+ /* By default replicas should ignore maxmemory
+ * and just be masters exact copies. */
+ if (server.masterhost && server.repl_slave_ignore_maxmemory) return C_OK;
+
+ size_t mem_reported, mem_tofree, mem_freed;
+ mstime_t latency, eviction_latency;
+ long long delta;
+ int slaves = listLength(server.slaves);
+
+ /* When clients are paused the dataset should be static not just from the
+ * POV of clients not being able to write, but also from the POV of
+ * expires and evictions of keys not being performed. */
+ if (clientsArePaused()) return C_OK;
+ if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL) == C_OK)
+ return C_OK;
+
mem_freed = 0;
if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)
@@ -403,8 +468,8 @@ int freeMemoryIfNeeded(void) {
latencyStartMonitor(latency);
while (mem_freed < mem_tofree) {
- int j, k, i, keys_freed = 0;
- static int next_db = 0;
+ int j, k, i;
+ static unsigned int next_db = 0;
sds bestkey = NULL;
int bestdbid;
redisDb *db;
@@ -529,16 +594,12 @@ int freeMemoryIfNeeded(void) {
* across the dbAsyncDelete() call, while the thread can
* release the memory all the time. */
if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {
- overhead = freeMemoryGetNotCountedMemory();
- mem_used = zmalloc_used_memory();
- mem_used = (mem_used > overhead) ? mem_used-overhead : 0;
- if (mem_used <= server.maxmemory) {
+ if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
+ /* Let's satisfy our stop condition. */
mem_freed = mem_tofree;
}
}
- }
-
- if (!keys_freed) {
+ } else {
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("eviction-cycle",latency);
goto cant_free; /* nothing to free... */
@@ -560,3 +621,14 @@ cant_free:
return C_ERR;
}
+/* This is a wrapper for freeMemoryIfNeeded() that only really calls the
+ * function if right now there are the conditions to do so safely:
+ *
+ * - There must be no script in timeout condition.
+ * - Nor we are loading data right now.
+ *
+ */
+int freeMemoryIfNeededAndSafe(void) {
+ if (server.lua_timedout || server.loading) return C_OK;
+ return freeMemoryIfNeeded();
+}
diff --git a/src/expire.c b/src/expire.c
index ce7882e4c..598b27f96 100644
--- a/src/expire.c
+++ b/src/expire.c
@@ -64,6 +64,7 @@ int activeExpireCycleTryExpire(redisDb *db, dictEntry *de, long long now) {
dbSyncDelete(db,keyobj);
notifyKeyspaceEvent(NOTIFY_EXPIRED,
"expired",keyobj,db->id);
+ trackingInvalidateKey(keyobj);
decrRefCount(keyobj);
server.stat_expiredkeys++;
return 1;
@@ -112,7 +113,7 @@ void activeExpireCycle(int type) {
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
/* Don't start a fast cycle if the previous cycle did not exit
- * for time limt. Also don't repeat a fast cycle for the same period
+ * for time limit. Also don't repeat a fast cycle for the same period
* as the fast cycle total duration itself. */
if (!timelimit_exit) return;
if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;
diff --git a/src/geo.c b/src/geo.c
index 90216e7dd..049335a4f 100644
--- a/src/geo.c
+++ b/src/geo.c
@@ -145,7 +145,7 @@ double extractUnitOrReply(client *c, robj *unit) {
/* Input Argument Helper.
* Extract the dinstance from the specified two arguments starting at 'argv'
* that shouldbe in the form: <number> <unit> and return the dinstance in the
- * specified unit on success. *conversino is populated with the coefficient
+ * specified unit on success. *conversions is populated with the coefficient
* to use in order to convert meters to the unit.
*
* On error a value less than zero is returned. */
@@ -466,7 +466,7 @@ void georadiusGeneric(client *c, int flags) {
/* Look up the requested zset */
robj *zobj = NULL;
- if ((zobj = lookupKeyReadOrReply(c, key, shared.emptymultibulk)) == NULL ||
+ if ((zobj = lookupKeyReadOrReply(c, key, shared.emptyarray)) == NULL ||
checkType(c, zobj, OBJ_ZSET)) {
return;
}
@@ -566,7 +566,7 @@ void georadiusGeneric(client *c, int flags) {
/* If no matching results, the user gets an empty reply. */
if (ga->used == 0 && storekey == NULL) {
- addReply(c, shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
geoArrayFree(ga);
return;
}
@@ -597,11 +597,11 @@ void georadiusGeneric(client *c, int flags) {
if (withhash)
option_length++;
- /* The multibulk len we send is exactly result_length. The result is
+ /* The array len we send is exactly result_length. The result is
* either all strings of just zset members *or* a nested multi-bulk
* reply containing the zset member string _and_ all the additional
* options the user enabled for this request. */
- addReplyMultiBulkLen(c, returned_items);
+ addReplyArrayLen(c, returned_items);
/* Finally send results back to the caller */
int i;
@@ -613,7 +613,7 @@ void georadiusGeneric(client *c, int flags) {
* as a nested multi-bulk. Add 1 to account for result value
* itself. */
if (option_length)
- addReplyMultiBulkLen(c, option_length + 1);
+ addReplyArrayLen(c, option_length + 1);
addReplyBulkSds(c,gp->member);
gp->member = NULL;
@@ -625,7 +625,7 @@ void georadiusGeneric(client *c, int flags) {
addReplyLongLong(c, gp->score);
if (withcoords) {
- addReplyMultiBulkLen(c, 2);
+ addReplyArrayLen(c, 2);
addReplyHumanLongDouble(c, gp->longitude);
addReplyHumanLongDouble(c, gp->latitude);
}
@@ -659,7 +659,7 @@ void georadiusGeneric(client *c, int flags) {
zsetConvertToZiplistIfNeeded(zobj,maxelelen);
setKey(c->db,storekey,zobj);
decrRefCount(zobj);
- notifyKeyspaceEvent(NOTIFY_LIST,"georadiusstore",storekey,
+ notifyKeyspaceEvent(NOTIFY_ZSET,"georadiusstore",storekey,
c->db->id);
server.dirty += returned_items;
} else if (dbDelete(c->db,storekey)) {
@@ -706,11 +706,11 @@ void geohashCommand(client *c) {
/* Geohash elements one after the other, using a null bulk reply for
* missing elements. */
- addReplyMultiBulkLen(c,c->argc-2);
+ addReplyArrayLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) {
double score;
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
/* The internal format we use for geocoding is a bit different
* than the standard, since we use as initial latitude range
@@ -721,7 +721,7 @@ void geohashCommand(client *c) {
/* Decode... */
double xy[2];
if (!decodeGeohash(score,xy)) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
continue;
}
@@ -734,14 +734,14 @@ void geohashCommand(client *c) {
r[1].max = 90;
geohashEncode(&r[0],&r[1],xy[0],xy[1],26,&hash);
- char buf[12];
+ char buf[11];
int i;
- for (i = 0; i < 11; i++) {
+ for (i = 0; i < 10; i++) {
int idx = (hash.bits >> (52-((i+1)*5))) & 0x1f;
buf[i] = geoalphabet[idx];
}
- buf[11] = '\0';
- addReplyBulkCBuffer(c,buf,11);
+ buf[10] = '\0';
+ addReplyBulkCBuffer(c,buf,10);
}
}
}
@@ -759,19 +759,19 @@ void geoposCommand(client *c) {
/* Report elements one after the other, using a null bulk reply for
* missing elements. */
- addReplyMultiBulkLen(c,c->argc-2);
+ addReplyArrayLen(c,c->argc-2);
for (j = 2; j < c->argc; j++) {
double score;
if (!zobj || zsetScore(zobj, c->argv[j]->ptr, &score) == C_ERR) {
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
} else {
/* Decode... */
double xy[2];
if (!decodeGeohash(score,xy)) {
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
continue;
}
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyHumanLongDouble(c,xy[0]);
addReplyHumanLongDouble(c,xy[1]);
}
@@ -797,7 +797,7 @@ void geodistCommand(client *c) {
/* Look up the requested zset */
robj *zobj = NULL;
- if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.nullbulk))
+ if ((zobj = lookupKeyReadOrReply(c, c->argv[1], shared.null[c->resp]))
== NULL || checkType(c, zobj, OBJ_ZSET)) return;
/* Get the scores. We need both otherwise NULL is returned. */
@@ -805,13 +805,13 @@ void geodistCommand(client *c) {
if (zsetScore(zobj, c->argv[2]->ptr, &score1) == C_ERR ||
zsetScore(zobj, c->argv[3]->ptr, &score2) == C_ERR)
{
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
return;
}
/* Decode & compute the distance. */
if (!decodeGeohash(score1,xyxy) || !decodeGeohash(score2,xyxy+2))
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
else
addReplyDoubleDistance(c,
geohashGetDistance(xyxy[0],xyxy[1],xyxy[2],xyxy[3]) / to_meter);
diff --git a/src/geohash.c b/src/geohash.c
index 1ae7a7e05..db5ae025a 100644
--- a/src/geohash.c
+++ b/src/geohash.c
@@ -127,8 +127,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
/* Return an error when trying to index outside the supported
* constraints. */
- if (longitude > 180 || longitude < -180 ||
- latitude > 85.05112878 || latitude < -85.05112878) return 0;
+ if (longitude > GEO_LONG_MAX || longitude < GEO_LONG_MIN ||
+ latitude > GEO_LAT_MAX || latitude < GEO_LAT_MIN) return 0;
hash->bits = 0;
hash->step = step;
@@ -144,8 +144,8 @@ int geohashEncode(const GeoHashRange *long_range, const GeoHashRange *lat_range,
(longitude - long_range->min) / (long_range->max - long_range->min);
/* convert to fixed point based on the step size */
- lat_offset *= (1 << step);
- long_offset *= (1 << step);
+ lat_offset *= (1ULL << step);
+ long_offset *= (1ULL << step);
hash->bits = interleave64(lat_offset, long_offset);
return 1;
}
diff --git a/src/gopher.c b/src/gopher.c
new file mode 100644
index 000000000..38e44f754
--- /dev/null
+++ b/src/gopher.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "server.h"
+
+/* Emit an item in Gopher directory listing format:
+ * <type><descr><TAB><selector><TAB><hostname><TAB><port>
+ * If descr or selector are NULL, then the "(NULL)" string is used instead. */
+void addReplyGopherItem(client *c, const char *type, const char *descr,
+ const char *selector, const char *hostname, int port)
+{
+ sds item = sdscatfmt(sdsempty(),"%s%s\t%s\t%s\t%i\r\n",
+ type, descr,
+ selector ? selector : "(NULL)",
+ hostname ? hostname : "(NULL)",
+ port);
+ addReplyProto(c,item,sdslen(item));
+ sdsfree(item);
+}
+
+/* This is called by processInputBuffer() when an inline request is processed
+ * with Gopher mode enabled, and the request happens to have zero or just one
+ * argument. In such case we get the relevant key and reply using the Gopher
+ * protocol. */
+void processGopherRequest(client *c) {
+ robj *keyname = c->argc == 0 ? createStringObject("/",1) : c->argv[0];
+ robj *o = lookupKeyRead(c->db,keyname);
+
+ /* If there is no such key, return with a Gopher error. */
+ if (o == NULL || o->type != OBJ_STRING) {
+ char *errstr;
+ if (o == NULL)
+ errstr = "Error: no content at the specified key";
+ else
+ errstr = "Error: selected key type is invalid "
+ "for Gopher output";
+ addReplyGopherItem(c,"i",errstr,NULL,NULL,0);
+ addReplyGopherItem(c,"i","Redis Gopher server",NULL,NULL,0);
+ } else {
+ addReply(c,o);
+ }
+
+ /* Cleanup, also make sure to emit the final ".CRLF" line. Note that
+ * the connection will be closed immediately after this because the client
+ * will be flagged with CLIENT_CLOSE_AFTER_REPLY, in accordance with the
+ * Gopher protocol. */
+ if (c->argc == 0) decrRefCount(keyname);
+
+ /* Note that in theory we should terminate the Gopher request with
+ * ".<CR><LF>" (called Lastline in the RFC) like that:
+ *
+ * addReplyProto(c,".\r\n",3);
+ *
+ * However after examining the current clients landscape, it's probably
+ * going to do more harm than good for several reasons:
+ *
+ * 1. Clients should not have any issue with missing .<CR><LF> as for
+ * specification, and in the real world indeed certain servers
+ * implementations never used to send the terminator.
+ *
+ * 2. Redis does not know if it's serving a text file or a binary file:
+ * at the same time clients will not remove the ".<CR><LF>" bytes at
+ * tne end when downloading a binary file from the server, so adding
+ * the "Lastline" terminator without knowing the content is just
+ * dangerous.
+ *
+ * 3. The utility gopher2redis.rb that we provide for Redis, and any
+ * other similar tool you may use as Gopher authoring system for
+ * Redis, can just add the "Lastline" when needed.
+ */
+}
diff --git a/src/help.h b/src/help.h
index 5f927c303..184d76724 100644
--- a/src/help.h
+++ b/src/help.h
@@ -1,4 +1,4 @@
-/* Automatically generated by utils/generate-command-help.rb, do not edit. */
+/* Automatically generated by generate-command-help.rb, do not edit. */
#ifndef __REDIS_HELP_H
#define __REDIS_HELP_H
@@ -17,7 +17,8 @@ static char *commandGroups[] = {
"scripting",
"hyperloglog",
"cluster",
- "geo"
+ "geo",
+ "stream"
};
struct commandHelp {
@@ -82,11 +83,26 @@ struct commandHelp {
"Pop a value from a list, push it to another list and return it; or block until one is available",
2,
"2.2.0" },
+ { "BZPOPMAX",
+ "key [key ...] timeout",
+ "Remove and return the member with the highest score from one or more sorted sets, or block until one is available",
+ 4,
+ "5.0.0" },
+ { "BZPOPMIN",
+ "key [key ...] timeout",
+ "Remove and return the member with the lowest score from one or more sorted sets, or block until one is available",
+ 4,
+ "5.0.0" },
{ "CLIENT GETNAME",
"-",
"Get the current connection name",
9,
"2.6.9" },
+ { "CLIENT ID",
+ "-",
+ "Returns the client ID for the current connection",
+ 9,
+ "5.0.0" },
{ "CLIENT KILL",
"[ip:port] [ID client-id] [TYPE normal|master|slave|pubsub] [ADDR ip:port] [SKIPME yes/no]",
"Kill the connection of a client",
@@ -112,6 +128,11 @@ struct commandHelp {
"Set the current connection name",
9,
"2.6.9" },
+ { "CLIENT UNBLOCK",
+ "client-id [TIMEOUT|ERROR]",
+ "Unblock a client blocked in a blocking command from a different connection",
+ 9,
+ "5.0.0" },
{ "CLUSTER ADDSLOTS",
"slot [slot ...]",
"Assign new hash slots to receiving node",
@@ -134,7 +155,7 @@ struct commandHelp {
"3.0.0" },
{ "CLUSTER FAILOVER",
"[FORCE|TAKEOVER]",
- "Forces a slave to perform a manual failover of its master.",
+ "Forces a replica to perform a manual failover of its master.",
12,
"3.0.0" },
{ "CLUSTER FORGET",
@@ -167,9 +188,14 @@ struct commandHelp {
"Get Cluster config for the node",
12,
"3.0.0" },
+ { "CLUSTER REPLICAS",
+ "node-id",
+ "List replica nodes of the specified master node",
+ 12,
+ "5.0.0" },
{ "CLUSTER REPLICATE",
"node-id",
- "Reconfigure a node as a slave of the specified master node",
+ "Reconfigure a node as a replica of the specified master node",
12,
"3.0.0" },
{ "CLUSTER RESET",
@@ -194,7 +220,7 @@ struct commandHelp {
"3.0.0" },
{ "CLUSTER SLAVES",
"node-id",
- "List slave nodes of the specified master node",
+ "List replica nodes of the specified master node",
12,
"3.0.0" },
{ "CLUSTER SLOTS",
@@ -318,12 +344,12 @@ struct commandHelp {
0,
"1.2.0" },
{ "FLUSHALL",
- "-",
+ "[ASYNC]",
"Remove all keys from all databases",
9,
"1.0.0" },
{ "FLUSHDB",
- "-",
+ "[ASYNC]",
"Remove all keys from the current database",
9,
"1.0.0" },
@@ -532,6 +558,36 @@ struct commandHelp {
"Trim a list to the specified range",
2,
"1.0.0" },
+ { "MEMORY DOCTOR",
+ "-",
+ "Outputs memory problems report",
+ 9,
+ "4.0.0" },
+ { "MEMORY HELP",
+ "-",
+ "Show helpful text about the different subcommands",
+ 9,
+ "4.0.0" },
+ { "MEMORY MALLOC-STATS",
+ "-",
+ "Show allocator internal stats",
+ 9,
+ "4.0.0" },
+ { "MEMORY PURGE",
+ "-",
+ "Ask the allocator to release memory",
+ 9,
+ "4.0.0" },
+ { "MEMORY STATS",
+ "-",
+ "Show memory usage details",
+ 9,
+ "4.0.0" },
+ { "MEMORY USAGE",
+ "key [SAMPLES count]",
+ "Estimate the memory usage of a key",
+ 9,
+ "4.0.0" },
{ "MGET",
"key [key ...]",
"Get the values of all the given keys",
@@ -649,12 +705,12 @@ struct commandHelp {
"1.0.0" },
{ "READONLY",
"-",
- "Enables read queries for a connection to a cluster slave node",
+ "Enables read queries for a connection to a cluster replica node",
12,
"3.0.0" },
{ "READWRITE",
"-",
- "Disables read queries for a connection to a cluster slave node",
+ "Disables read queries for a connection to a cluster replica node",
12,
"3.0.0" },
{ "RENAME",
@@ -667,6 +723,11 @@ struct commandHelp {
"Rename a key, only if the new key does not exist",
0,
"1.0.0" },
+ { "REPLICAOF",
+ "host port",
+ "Make the server a replica of another instance, or promote it as master.",
+ 9,
+ "5.0.0" },
{ "RESTORE",
"key ttl serialized-value [REPLACE]",
"Create a key using the provided serialized value, previously obtained using DUMP.",
@@ -723,7 +784,7 @@ struct commandHelp {
10,
"3.2.0" },
{ "SCRIPT EXISTS",
- "script [script ...]",
+ "sha1 [sha1 ...]",
"Check existence of scripts in the script cache.",
10,
"2.6.0" },
@@ -758,7 +819,7 @@ struct commandHelp {
8,
"1.0.0" },
{ "SET",
- "key value [EX seconds] [PX milliseconds] [NX|XX]",
+ "key value [expiration EX seconds|PX milliseconds] [NX|XX]",
"Set the string value of a key",
1,
"1.0.0" },
@@ -804,7 +865,7 @@ struct commandHelp {
"1.0.0" },
{ "SLAVEOF",
"host port",
- "Make the server a slave of another instance, or promote it as master",
+ "Make the server a replica of another instance, or promote it as master. Deprecated starting with Redis 5. Use REPLICAOF instead.",
9,
"1.0.0" },
{ "SLOWLOG",
@@ -867,6 +928,11 @@ struct commandHelp {
"Add multiple sets and store the resulting set in a key",
3,
"1.0.0" },
+ { "SWAPDB",
+ "index index",
+ "Swaps two Redis databases",
+ 8,
+ "4.0.0" },
{ "SYNC",
"-",
"Internal command used for replication",
@@ -877,6 +943,11 @@ struct commandHelp {
"Return the current server time",
9,
"2.6.0" },
+ { "TOUCH",
+ "key [key ...]",
+ "Alters the last access time of a key(s). Returns the number of existing keys specified.",
+ 0,
+ "3.2.1" },
{ "TTL",
"key",
"Get the time to live for a key",
@@ -887,6 +958,11 @@ struct commandHelp {
"Determine the type stored at key",
0,
"1.0.0" },
+ { "UNLINK",
+ "key [key ...]",
+ "Delete a key asynchronously in another thread. Otherwise it is just as DEL, but non blocking.",
+ 0,
+ "4.0.0" },
{ "UNSUBSCRIBE",
"[channel [channel ...]]",
"Stop listening for messages posted to the given channels",
@@ -898,7 +974,7 @@ struct commandHelp {
7,
"2.2.0" },
{ "WAIT",
- "numslaves timeout",
+ "numreplicas timeout",
"Wait for the synchronous replication of all the write commands sent in the context of the current connection",
0,
"3.0.0" },
@@ -907,6 +983,71 @@ struct commandHelp {
"Watch the given keys to determine execution of the MULTI/EXEC block",
7,
"2.2.0" },
+ { "XACK",
+ "key group ID [ID ...]",
+ "Marks a pending message as correctly processed, effectively removing it from the pending entries list of the consumer group. Return value of the command is the number of messages successfully acknowledged, that is, the IDs we were actually able to resolve in the PEL.",
+ 14,
+ "5.0.0" },
+ { "XADD",
+ "key ID field string [field string ...]",
+ "Appends a new entry to a stream",
+ 14,
+ "5.0.0" },
+ { "XCLAIM",
+ "key group consumer min-idle-time ID [ID ...] [IDLE ms] [TIME ms-unix-time] [RETRYCOUNT count] [force] [justid]",
+ "Changes (or acquires) ownership of a message in a consumer group, as if the message was delivered to the specified consumer.",
+ 14,
+ "5.0.0" },
+ { "XDEL",
+ "key ID [ID ...]",
+ "Removes the specified entries from the stream. Returns the number of items actually deleted, that may be different from the number of IDs passed in case certain IDs do not exist.",
+ 14,
+ "5.0.0" },
+ { "XGROUP",
+ "[CREATE key groupname id-or-$] [SETID key id-or-$] [DESTROY key groupname] [DELCONSUMER key groupname consumername]",
+ "Create, destroy, and manage consumer groups.",
+ 14,
+ "5.0.0" },
+ { "XINFO",
+ "[CONSUMERS key groupname] [GROUPS key] [STREAM key] [HELP]",
+ "Get information on streams and consumer groups",
+ 14,
+ "5.0.0" },
+ { "XLEN",
+ "key",
+ "Return the number of entires in a stream",
+ 14,
+ "5.0.0" },
+ { "XPENDING",
+ "key group [start end count] [consumer]",
+ "Return information and entries from a stream consumer group pending entries list, that are messages fetched but never acknowledged.",
+ 14,
+ "5.0.0" },
+ { "XRANGE",
+ "key start end [COUNT count]",
+ "Return a range of elements in a stream, with IDs matching the specified IDs interval",
+ 14,
+ "5.0.0" },
+ { "XREAD",
+ "[COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
+ "Return never seen elements in multiple streams, with IDs greater than the ones reported by the caller for each stream. Can block.",
+ 14,
+ "5.0.0" },
+ { "XREADGROUP",
+ "GROUP group consumer [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]",
+ "Return new entries from a stream using a consumer group, or access the history of the pending entries for a given consumer. Can block.",
+ 14,
+ "5.0.0" },
+ { "XREVRANGE",
+ "key end start [COUNT count]",
+ "Return a range of elements in a stream, with IDs matching the specified IDs interval, in reverse order (from greater to smaller IDs) compared to XRANGE",
+ 14,
+ "5.0.0" },
+ { "XTRIM",
+ "key MAXLEN [~] count",
+ "Trims the stream to (approximately if '~' is passed) a certain size",
+ 14,
+ "5.0.0" },
{ "ZADD",
"key [NX|XX] [CH] [INCR] score member [score member ...]",
"Add one or more members to a sorted set, or update its score if it already exists",
@@ -937,6 +1078,16 @@ struct commandHelp {
"Count the number of members in a sorted set between a given lexicographical range",
4,
"2.8.9" },
+ { "ZPOPMAX",
+ "key [count]",
+ "Remove and return members with the highest scores in a sorted set",
+ 4,
+ "5.0.0" },
+ { "ZPOPMIN",
+ "key [count]",
+ "Remove and return members with the lowest scores in a sorted set",
+ 4,
+ "5.0.0" },
{ "ZRANGE",
"key start stop [WITHSCORES]",
"Return a range of members in a sorted set, by index",
diff --git a/src/hyperloglog.c b/src/hyperloglog.c
index ef33979a7..a44d15646 100644
--- a/src/hyperloglog.c
+++ b/src/hyperloglog.c
@@ -192,6 +192,8 @@ struct hllhdr {
#define HLL_VALID_CACHE(hdr) (((hdr)->card[7] & (1<<7)) == 0)
#define HLL_P 14 /* The greater is P, the smaller the error. */
+#define HLL_Q (64-HLL_P) /* The number of bits of the hash value used for
+ determining the number of leading zeros. */
#define HLL_REGISTERS (1<<HLL_P) /* With P=14, 16384 registers. */
#define HLL_P_MASK (HLL_REGISTERS-1) /* Mask to index register. */
#define HLL_BITS 6 /* Enough to count up to 63 leading zeroes. */
@@ -384,6 +386,7 @@ static char *invalid_hll_err = "-INVALIDOBJ Corrupted HLL object detected\r\n";
*(p) = (_l>>8) | HLL_SPARSE_XZERO_BIT; \
*((p)+1) = (_l&0xff); \
} while(0)
+#define HLL_ALPHA_INF 0.721347520444481703680 /* constant for 0.5/ln(2) */
/* ========================= HyperLogLog algorithm ========================= */
@@ -401,11 +404,11 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
uint64_t k;
#if (BYTE_ORDER == LITTLE_ENDIAN)
- #ifdef USE_ALIGNED_ACCESS
- memcpy(&k,data,sizeof(uint64_t));
- #else
+ #ifdef USE_ALIGNED_ACCESS
+ memcpy(&k,data,sizeof(uint64_t));
+ #else
k = *((uint64_t*)data);
- #endif
+ #endif
#else
k = (uint64_t) data[0];
k |= (uint64_t) data[1] << 8;
@@ -426,14 +429,14 @@ uint64_t MurmurHash64A (const void * key, int len, unsigned int seed) {
}
switch(len & 7) {
- case 7: h ^= (uint64_t)data[6] << 48;
- case 6: h ^= (uint64_t)data[5] << 40;
- case 5: h ^= (uint64_t)data[4] << 32;
- case 4: h ^= (uint64_t)data[3] << 24;
- case 3: h ^= (uint64_t)data[2] << 16;
- case 2: h ^= (uint64_t)data[1] << 8;
+ case 7: h ^= (uint64_t)data[6] << 48; /* fall-thru */
+ case 6: h ^= (uint64_t)data[5] << 40; /* fall-thru */
+ case 5: h ^= (uint64_t)data[4] << 32; /* fall-thru */
+ case 4: h ^= (uint64_t)data[3] << 24; /* fall-thru */
+ case 3: h ^= (uint64_t)data[2] << 16; /* fall-thru */
+ case 2: h ^= (uint64_t)data[1] << 8; /* fall-thru */
case 1: h ^= (uint64_t)data[0];
- h *= m;
+ h *= m; /* fall-thru */
};
h ^= h >> r;
@@ -451,7 +454,7 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
/* Count the number of zeroes starting from bit HLL_REGISTERS
* (that is a power of two corresponding to the first bit we don't use
- * as index). The max run can be 64-P+1 bits.
+ * as index). The max run can be 64-P+1 = Q+1 bits.
*
* Note that the final "1" ending the sequence of zeroes must be
* included in the count, so if we find "001" the count is 3, and
@@ -462,8 +465,10 @@ int hllPatLen(unsigned char *ele, size_t elesize, long *regp) {
* there are high probabilities to find a 1 after a few iterations. */
hash = MurmurHash64A(ele,elesize,0xadc83b19ULL);
index = hash & HLL_P_MASK; /* Register index. */
- hash |= ((uint64_t)1<<63); /* Make sure the loop terminates. */
- bit = HLL_REGISTERS; /* First bit not used to address the register. */
+ hash >>= HLL_P; /* Remove bits used to address the register. */
+ hash |= ((uint64_t)1<<HLL_Q); /* Make sure the loop terminates
+ and count will be <= Q+1. */
+ bit = 1;
count = 1; /* Initialized to 1 since we count the "00000...1" pattern. */
while((hash & bit) == 0) {
count++;
@@ -510,13 +515,9 @@ int hllDenseAdd(uint8_t *registers, unsigned char *ele, size_t elesize) {
return hllDenseSet(registers,index,count);
}
-/* Compute SUM(2^-reg) in the dense representation.
- * PE is an array with a pre-computer table of values 2^-reg indexed by reg.
- * As a side effect the integer pointed by 'ezp' is set to the number
- * of zero registers. */
-double hllDenseSum(uint8_t *registers, double *PE, int *ezp) {
- double E = 0;
- int j, ez = 0;
+/* Compute the register histogram in the dense representation. */
+void hllDenseRegHisto(uint8_t *registers, int* reghisto) {
+ int j;
/* Redis default is to use 16384 registers 6 bits each. The code works
* with other values by modifying the defines, but for our target value
@@ -527,47 +528,49 @@ double hllDenseSum(uint8_t *registers, double *PE, int *ezp) {
r10, r11, r12, r13, r14, r15;
for (j = 0; j < 1024; j++) {
/* Handle 16 registers per iteration. */
- r0 = r[0] & 63; if (r0 == 0) ez++;
- r1 = (r[0] >> 6 | r[1] << 2) & 63; if (r1 == 0) ez++;
- r2 = (r[1] >> 4 | r[2] << 4) & 63; if (r2 == 0) ez++;
- r3 = (r[2] >> 2) & 63; if (r3 == 0) ez++;
- r4 = r[3] & 63; if (r4 == 0) ez++;
- r5 = (r[3] >> 6 | r[4] << 2) & 63; if (r5 == 0) ez++;
- r6 = (r[4] >> 4 | r[5] << 4) & 63; if (r6 == 0) ez++;
- r7 = (r[5] >> 2) & 63; if (r7 == 0) ez++;
- r8 = r[6] & 63; if (r8 == 0) ez++;
- r9 = (r[6] >> 6 | r[7] << 2) & 63; if (r9 == 0) ez++;
- r10 = (r[7] >> 4 | r[8] << 4) & 63; if (r10 == 0) ez++;
- r11 = (r[8] >> 2) & 63; if (r11 == 0) ez++;
- r12 = r[9] & 63; if (r12 == 0) ez++;
- r13 = (r[9] >> 6 | r[10] << 2) & 63; if (r13 == 0) ez++;
- r14 = (r[10] >> 4 | r[11] << 4) & 63; if (r14 == 0) ez++;
- r15 = (r[11] >> 2) & 63; if (r15 == 0) ez++;
-
- /* Additional parens will allow the compiler to optimize the
- * code more with a loss of precision that is not very relevant
- * here (floating point math is not commutative!). */
- E += (PE[r0] + PE[r1]) + (PE[r2] + PE[r3]) + (PE[r4] + PE[r5]) +
- (PE[r6] + PE[r7]) + (PE[r8] + PE[r9]) + (PE[r10] + PE[r11]) +
- (PE[r12] + PE[r13]) + (PE[r14] + PE[r15]);
+ r0 = r[0] & 63;
+ r1 = (r[0] >> 6 | r[1] << 2) & 63;
+ r2 = (r[1] >> 4 | r[2] << 4) & 63;
+ r3 = (r[2] >> 2) & 63;
+ r4 = r[3] & 63;
+ r5 = (r[3] >> 6 | r[4] << 2) & 63;
+ r6 = (r[4] >> 4 | r[5] << 4) & 63;
+ r7 = (r[5] >> 2) & 63;
+ r8 = r[6] & 63;
+ r9 = (r[6] >> 6 | r[7] << 2) & 63;
+ r10 = (r[7] >> 4 | r[8] << 4) & 63;
+ r11 = (r[8] >> 2) & 63;
+ r12 = r[9] & 63;
+ r13 = (r[9] >> 6 | r[10] << 2) & 63;
+ r14 = (r[10] >> 4 | r[11] << 4) & 63;
+ r15 = (r[11] >> 2) & 63;
+
+ reghisto[r0]++;
+ reghisto[r1]++;
+ reghisto[r2]++;
+ reghisto[r3]++;
+ reghisto[r4]++;
+ reghisto[r5]++;
+ reghisto[r6]++;
+ reghisto[r7]++;
+ reghisto[r8]++;
+ reghisto[r9]++;
+ reghisto[r10]++;
+ reghisto[r11]++;
+ reghisto[r12]++;
+ reghisto[r13]++;
+ reghisto[r14]++;
+ reghisto[r15]++;
+
r += 12;
}
} else {
- for (j = 0; j < HLL_REGISTERS; j++) {
+ for(j = 0; j < HLL_REGISTERS; j++) {
unsigned long reg;
-
HLL_DENSE_GET_REGISTER(reg,registers,j);
- if (reg == 0) {
- ez++;
- /* Increment E at the end of the loop. */
- } else {
- E += PE[reg]; /* Precomputed 2^(-reg[j]). */
- }
+ reghisto[reg]++;
}
- E += ez; /* Add 2^0 'ez' times. */
}
- *ezp = ez;
- return E;
}
/* ================== Sparse representation implementation ================= */
@@ -611,6 +614,7 @@ int hllSparseToDense(robj *o) {
} else {
runlen = HLL_SPARSE_VAL_LEN(p);
regval = HLL_SPARSE_VAL_VALUE(p);
+ if ((runlen + idx) > HLL_REGISTERS) break; /* Overflow. */
while(runlen--) {
HLL_DENSE_SET_REGISTER(hdr->registers,idx,regval);
idx++;
@@ -670,7 +674,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
end = p + sdslen(o->ptr) - HLL_HDR_SIZE;
first = 0;
- prev = NULL; /* Points to previos opcode at the end of the loop. */
+ prev = NULL; /* Points to previous opcode at the end of the loop. */
next = NULL; /* Points to the next opcode at the end of the loop. */
span = 0;
while(p < end) {
@@ -696,7 +700,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
p += oplen;
first += span;
}
- if (span == 0) return -1; /* Invalid format. */
+ if (span == 0 || p >= end) return -1; /* Invalid format. */
next = HLL_SPARSE_IS_XZERO(p) ? p+2 : p+1;
if (next >= end) next = NULL;
@@ -761,7 +765,7 @@ int hllSparseSet(robj *o, long index, uint8_t count) {
* and is either currently represented by a VAL opcode with len > 1,
* by a ZERO opcode with len > 1, or by an XZERO opcode.
*
- * In those cases the original opcode must be split into muliple
+ * In those cases the original opcode must be split into multiple
* opcodes. The worst case is an XZERO split in the middle resuling into
* XZERO - VAL - XZERO, so the resulting sequence max length is
* 5 bytes.
@@ -884,7 +888,7 @@ promote: /* Promote to dense representation. */
*
* Note that this in turn means that PFADD will make sure the command
* is propagated to slaves / AOF, so if there is a sparse -> dense
- * convertion, it will be performed in all the slaves as well. */
+ * conversion, it will be performed in all the slaves as well. */
int dense_retval = hllDenseSet(hdr->registers,index,count);
serverAssert(dense_retval == 1);
return dense_retval;
@@ -903,76 +907,96 @@ int hllSparseAdd(robj *o, unsigned char *ele, size_t elesize) {
return hllSparseSet(o,index,count);
}
-/* Compute SUM(2^-reg) in the sparse representation.
- * PE is an array with a pre-computer table of values 2^-reg indexed by reg.
- * As a side effect the integer pointed by 'ezp' is set to the number
- * of zero registers. */
-double hllSparseSum(uint8_t *sparse, int sparselen, double *PE, int *ezp, int *invalid) {
- double E = 0;
- int ez = 0, idx = 0, runlen, regval;
+/* Compute the register histogram in the sparse representation. */
+void hllSparseRegHisto(uint8_t *sparse, int sparselen, int *invalid, int* reghisto) {
+ int idx = 0, runlen, regval;
uint8_t *end = sparse+sparselen, *p = sparse;
while(p < end) {
if (HLL_SPARSE_IS_ZERO(p)) {
runlen = HLL_SPARSE_ZERO_LEN(p);
idx += runlen;
- ez += runlen;
- /* Increment E at the end of the loop. */
+ reghisto[0] += runlen;
p++;
} else if (HLL_SPARSE_IS_XZERO(p)) {
runlen = HLL_SPARSE_XZERO_LEN(p);
idx += runlen;
- ez += runlen;
- /* Increment E at the end of the loop. */
+ reghisto[0] += runlen;
p += 2;
} else {
runlen = HLL_SPARSE_VAL_LEN(p);
regval = HLL_SPARSE_VAL_VALUE(p);
idx += runlen;
- E += PE[regval]*runlen;
+ reghisto[regval] += runlen;
p++;
}
}
if (idx != HLL_REGISTERS && invalid) *invalid = 1;
- E += ez; /* Add 2^0 'ez' times. */
- *ezp = ez;
- return E;
}
/* ========================= HyperLogLog Count ==============================
* This is the core of the algorithm where the approximated count is computed.
- * The function uses the lower level hllDenseSum() and hllSparseSum() functions
- * as helpers to compute the SUM(2^-reg) part of the computation, which is
- * representation-specific, while all the rest is common. */
-
-/* Implements the SUM operation for uint8_t data type which is only used
- * internally as speedup for PFCOUNT with multiple keys. */
-double hllRawSum(uint8_t *registers, double *PE, int *ezp) {
- double E = 0;
- int j, ez = 0;
+ * The function uses the lower level hllDenseRegHisto() and hllSparseRegHisto()
+ * functions as helpers to compute histogram of register values part of the
+ * computation, which is representation-specific, while all the rest is common. */
+
+/* Implements the register histogram calculation for uint8_t data type
+ * which is only used internally as speedup for PFCOUNT with multiple keys. */
+void hllRawRegHisto(uint8_t *registers, int* reghisto) {
uint64_t *word = (uint64_t*) registers;
uint8_t *bytes;
+ int j;
for (j = 0; j < HLL_REGISTERS/8; j++) {
if (*word == 0) {
- ez += 8;
+ reghisto[0] += 8;
} else {
bytes = (uint8_t*) word;
- if (bytes[0]) E += PE[bytes[0]]; else ez++;
- if (bytes[1]) E += PE[bytes[1]]; else ez++;
- if (bytes[2]) E += PE[bytes[2]]; else ez++;
- if (bytes[3]) E += PE[bytes[3]]; else ez++;
- if (bytes[4]) E += PE[bytes[4]]; else ez++;
- if (bytes[5]) E += PE[bytes[5]]; else ez++;
- if (bytes[6]) E += PE[bytes[6]]; else ez++;
- if (bytes[7]) E += PE[bytes[7]]; else ez++;
+ reghisto[bytes[0]]++;
+ reghisto[bytes[1]]++;
+ reghisto[bytes[2]]++;
+ reghisto[bytes[3]]++;
+ reghisto[bytes[4]]++;
+ reghisto[bytes[5]]++;
+ reghisto[bytes[6]]++;
+ reghisto[bytes[7]]++;
}
word++;
}
- E += ez; /* 2^(-reg[j]) is 1 when m is 0, add it 'ez' times for every
- zero register in the HLL. */
- *ezp = ez;
- return E;
+}
+
+/* Helper function sigma as defined in
+ * "New cardinality estimation algorithms for HyperLogLog sketches"
+ * Otmar Ertl, arXiv:1702.01284 */
+double hllSigma(double x) {
+ if (x == 1.) return INFINITY;
+ double zPrime;
+ double y = 1;
+ double z = x;
+ do {
+ x *= x;
+ zPrime = z;
+ z += x * y;
+ y += y;
+ } while(zPrime != z);
+ return z;
+}
+
+/* Helper function tau as defined in
+ * "New cardinality estimation algorithms for HyperLogLog sketches"
+ * Otmar Ertl, arXiv:1702.01284 */
+double hllTau(double x) {
+ if (x == 0. || x == 1.) return 0.;
+ double zPrime;
+ double y = 1.0;
+ double z = 1 - x;
+ do {
+ x = sqrt(x);
+ zPrime = z;
+ y *= 0.5;
+ z -= pow(1 - x, 2)*y;
+ } while(zPrime != z);
+ return z / 3;
}
/* Return the approximated cardinality of the set based on the harmonic
@@ -988,49 +1012,38 @@ double hllRawSum(uint8_t *registers, double *PE, int *ezp) {
* keys (no need to work with 6-bit integers encoding). */
uint64_t hllCount(struct hllhdr *hdr, int *invalid) {
double m = HLL_REGISTERS;
- double E, alpha = 0.7213/(1+1.079/m);
- int j, ez; /* Number of registers equal to 0. */
-
- /* We precompute 2^(-reg[j]) in a small table in order to
- * speedup the computation of SUM(2^-register[0..i]). */
- static int initialized = 0;
- static double PE[64];
- if (!initialized) {
- PE[0] = 1; /* 2^(-reg[j]) is 1 when m is 0. */
- for (j = 1; j < 64; j++) {
- /* 2^(-reg[j]) is the same as 1/2^reg[j]. */
- PE[j] = 1.0/(1ULL << j);
- }
- initialized = 1;
- }
-
- /* Compute SUM(2^-register[0..i]). */
+ double E;
+ int j;
+ /* Note that reghisto size could be just HLL_Q+2, becuase HLL_Q+1 is
+ * the maximum frequency of the "000...1" sequence the hash function is
+ * able to return. However it is slow to check for sanity of the
+ * input: instead we history array at a safe size: overflows will
+ * just write data to wrong, but correctly allocated, places. */
+ int reghisto[64] = {0};
+
+ /* Compute register histogram */
if (hdr->encoding == HLL_DENSE) {
- E = hllDenseSum(hdr->registers,PE,&ez);
+ hllDenseRegHisto(hdr->registers,reghisto);
} else if (hdr->encoding == HLL_SPARSE) {
- E = hllSparseSum(hdr->registers,
- sdslen((sds)hdr)-HLL_HDR_SIZE,PE,&ez,invalid);
+ hllSparseRegHisto(hdr->registers,
+ sdslen((sds)hdr)-HLL_HDR_SIZE,invalid,reghisto);
} else if (hdr->encoding == HLL_RAW) {
- E = hllRawSum(hdr->registers,PE,&ez);
+ hllRawRegHisto(hdr->registers,reghisto);
} else {
serverPanic("Unknown HyperLogLog encoding in hllCount()");
}
- /* Apply loglog-beta to the raw estimate. See:
- * "LogLog-Beta and More: A New Algorithm for Cardinality Estimation
- * Based on LogLog Counting" Jason Qin, Denys Kim, Yumei Tung
- * arXiv:1612.02284 */
- double zl = log(ez + 1);
- double beta = -0.370393911*ez +
- 0.070471823*zl +
- 0.17393686*pow(zl,2) +
- 0.16339839*pow(zl,3) +
- -0.09237745*pow(zl,4) +
- 0.03738027*pow(zl,5) +
- -0.005384159*pow(zl,6) +
- 0.00042419*pow(zl,7);
-
- E = llroundl(alpha*m*(m-ez)*(1/(E+beta)));
+ /* Estimate cardinality form register histogram. See:
+ * "New cardinality estimation algorithms for HyperLogLog sketches"
+ * Otmar Ertl, arXiv:1702.01284 */
+ double z = m * hllTau((m-reghisto[HLL_Q+1])/(double)m);
+ for (j = HLL_Q; j >= 1; --j) {
+ z += reghisto[j];
+ z *= 0.5;
+ }
+ z += m * hllSigma(reghisto[0]/(double)m);
+ E = llroundl(HLL_ALPHA_INF*m*m/z);
+
return (uint64_t) E;
}
@@ -1081,6 +1094,7 @@ int hllMerge(uint8_t *max, robj *hll) {
} else {
runlen = HLL_SPARSE_VAL_LEN(p);
regval = HLL_SPARSE_VAL_VALUE(p);
+ if ((runlen + i) > HLL_REGISTERS) break; /* Overflow. */
while(runlen--) {
if (regval > max[i]) max[i] = regval;
i++;
@@ -1228,7 +1242,7 @@ void pfcountCommand(client *c) {
if (o == NULL) continue; /* Assume empty HLL for non existing var.*/
if (isHLLObjectOrReply(c,o) != C_OK) return;
- /* Merge with this HLL with our 'max' HHL by setting max[i]
+ /* Merge with this HLL with our 'max' HLL by setting max[i]
* to MAX(max[i],hll[i]). */
if (hllMerge(registers,o) == C_ERR) {
addReplySds(c,sdsnew(invalid_hll_err));
@@ -1315,7 +1329,7 @@ void pfmergeCommand(client *c) {
hdr = o->ptr;
if (hdr->encoding == HLL_DENSE) use_dense = 1;
- /* Merge with this HLL with our 'max' HHL by setting max[i]
+ /* Merge with this HLL with our 'max' HLL by setting max[i]
* to MAX(max[i],hll[i]). */
if (hllMerge(max,o) == C_ERR) {
addReplySds(c,sdsnew(invalid_hll_err));
@@ -1505,7 +1519,7 @@ void pfdebugCommand(client *c) {
}
hdr = o->ptr;
- addReplyMultiBulkLen(c,HLL_REGISTERS);
+ addReplyArrayLen(c,HLL_REGISTERS);
for (j = 0; j < HLL_REGISTERS; j++) {
uint8_t val;
diff --git a/src/intset.c b/src/intset.c
index 198c90aa1..4445a5ca6 100644
--- a/src/intset.c
+++ b/src/intset.c
@@ -123,7 +123,7 @@ static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
} else {
/* Check for the case where we know we cannot find the value,
* but do know the insert position. */
- if (value > _intsetGet(is,intrev32ifbe(is->length)-1)) {
+ if (value > _intsetGet(is,max)) {
if (pos) *pos = intrev32ifbe(is->length);
return 0;
} else if (value < _intsetGet(is,0)) {
diff --git a/src/latency.c b/src/latency.c
index 292720aa0..b834da5c7 100644
--- a/src/latency.c
+++ b/src/latency.c
@@ -152,7 +152,7 @@ int latencyResetEvent(char *event_to_reset) {
/* ------------------------ Latency reporting (doctor) ---------------------- */
-/* Analyze the samples avaialble for a given event and return a structure
+/* Analyze the samples available for a given event and return a structure
* populate with different metrics, average, MAD, min, max, and so forth.
* Check latency.h definition of struct latenctStat for more info.
* If the specified event has no elements the structure is populate with
@@ -294,7 +294,7 @@ sds createLatencyReport(void) {
/* Potentially commands. */
if (!strcasecmp(event,"command")) {
- if (server.slowlog_log_slower_than == 0) {
+ if (server.slowlog_log_slower_than < 0) {
advise_slowlog_enabled = 1;
advices++;
} else if (server.slowlog_log_slower_than/1000 >
@@ -476,19 +476,19 @@ sds createLatencyReport(void) {
/* latencyCommand() helper to produce a time-delay reply for all the samples
* in memory for the specified time series. */
void latencyCommandReplyWithSamples(client *c, struct latencyTimeSeries *ts) {
- void *replylen = addDeferredMultiBulkLength(c);
+ void *replylen = addReplyDeferredLen(c);
int samples = 0, j;
for (j = 0; j < LATENCY_TS_LEN; j++) {
int i = (ts->idx + j) % LATENCY_TS_LEN;
if (ts->samples[i].time == 0) continue;
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyLongLong(c,ts->samples[i].time);
addReplyLongLong(c,ts->samples[i].latency);
samples++;
}
- setDeferredMultiBulkLength(c,replylen,samples);
+ setDeferredArrayLen(c,replylen,samples);
}
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
@@ -497,14 +497,14 @@ void latencyCommandReplyWithLatestEvents(client *c) {
dictIterator *di;
dictEntry *de;
- addReplyMultiBulkLen(c,dictSize(server.latency_events));
+ addReplyArrayLen(c,dictSize(server.latency_events));
di = dictGetIterator(server.latency_events);
while((de = dictNext(di)) != NULL) {
char *event = dictGetKey(de);
struct latencyTimeSeries *ts = dictGetVal(de);
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
- addReplyMultiBulkLen(c,4);
+ addReplyArrayLen(c,4);
addReplyBulkCString(c,event);
addReplyLongLong(c,ts->samples[last].time);
addReplyLongLong(c,ts->samples[last].latency);
@@ -560,19 +560,30 @@ sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
/* LATENCY command implementations.
*
- * LATENCY SAMPLES: return time-latency samples for the specified event.
+ * LATENCY HISTORY: return time-latency samples for the specified event.
* LATENCY LATEST: return the latest latency for all the events classes.
- * LATENCY DOCTOR: returns an human readable analysis of instance latency.
+ * LATENCY DOCTOR: returns a human readable analysis of instance latency.
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
+ * LATENCY RESET: reset data of a specified event or all the data if no event provided.
*/
void latencyCommand(client *c) {
+ const char *help[] = {
+"DOCTOR -- Returns a human readable latency analysis report.",
+"GRAPH <event> -- Returns an ASCII latency graph for the event class.",
+"HISTORY <event> -- Returns time-latency samples for the event class.",
+"LATEST -- Returns the latest latency samples for all events.",
+"RESET [event ...] -- Resets latency data of one or more event classes.",
+" (default: reset all data for all event classes)",
+"HELP -- Prints this help.",
+NULL
+ };
struct latencyTimeSeries *ts;
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
/* LATENCY HISTORY <event> */
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
if (ts == NULL) {
- addReplyMultiBulkLen(c,0);
+ addReplyArrayLen(c,0);
} else {
latencyCommandReplyWithSamples(c,ts);
}
@@ -588,7 +599,7 @@ void latencyCommand(client *c) {
event = dictGetKey(de);
graph = latencyCommandGenSparkeline(event,ts);
- addReplyBulkCString(c,graph);
+ addReplyVerbatim(c,graph,sdslen(graph),"txt");
sdsfree(graph);
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
/* LATENCY LATEST */
@@ -597,7 +608,7 @@ void latencyCommand(client *c) {
/* LATENCY DOCTOR */
sds report = createLatencyReport();
- addReplyBulkCBuffer(c,report,sdslen(report));
+ addReplyVerbatim(c,report,sdslen(report),"txt");
sdsfree(report);
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
/* LATENCY RESET */
@@ -610,8 +621,10 @@ void latencyCommand(client *c) {
resets += latencyResetEvent(c->argv[j]->ptr);
addReplyLongLong(c,resets);
}
+ } else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc >= 2) {
+ addReplyHelp(c, help);
} else {
- addReply(c,shared.syntaxerr);
+ addReplySubcommandSyntaxError(c);
}
return;
diff --git a/src/lazyfree.c b/src/lazyfree.c
index f1de0c898..3d3159c90 100644
--- a/src/lazyfree.c
+++ b/src/lazyfree.c
@@ -23,10 +23,10 @@ size_t lazyfreeGetPendingObjectsCount(void) {
* the function just returns the number of elements the object is composed of.
*
* Objects composed of single allocations are always reported as having a
- * single item even if they are actaully logical composed of multiple
+ * single item even if they are actually logical composed of multiple
* elements.
*
- * For lists the funciton returns the number of elements in the quicklist
+ * For lists the function returns the number of elements in the quicklist
* representing the list. */
size_t lazyfreeGetFreeEffort(robj *obj) {
if (obj->type == OBJ_LIST) {
@@ -90,6 +90,17 @@ int dbAsyncDelete(redisDb *db, robj *key) {
}
}
+/* Free an object, if the object is huge enough, free it in async way. */
+void freeObjAsync(robj *o) {
+ size_t free_effort = lazyfreeGetFreeEffort(o);
+ if (free_effort > LAZYFREE_THRESHOLD && o->refcount == 1) {
+ atomicIncr(lazyfree_objects,1);
+ bioCreateBackgroundJob(BIO_LAZY_FREE,o,NULL,NULL);
+ } else {
+ decrRefCount(o);
+ }
+}
+
/* Empty a Redis DB asynchronously. What the function does actually is to
* create a new empty set of hash tables and scheduling the old ones for
* lazy freeing. */
diff --git a/src/listpack.c b/src/listpack.c
index 30ea34690..e1f4d9a02 100644
--- a/src/listpack.c
+++ b/src/listpack.c
@@ -291,7 +291,7 @@ int lpEncodeGetType(unsigned char *ele, uint32_t size, unsigned char *intenc, ui
/* Store a reverse-encoded variable length field, representing the length
* of the previous element of size 'l', in the target buffer 'buf'.
* The function returns the number of bytes used to encode it, from
- * 1 to 5. If 'buf' is NULL the funciton just returns the number of bytes
+ * 1 to 5. If 'buf' is NULL the function just returns the number of bytes
* needed in order to encode the backlen. */
unsigned long lpEncodeBacklen(unsigned char *buf, uint64_t l) {
if (l <= 127) {
@@ -568,7 +568,7 @@ unsigned char *lpGet(unsigned char *p, int64_t *count, unsigned char *intbuf) {
}
}
-/* Insert, delete or replace the specified element 'ele' of lenght 'len' at
+/* Insert, delete or replace the specified element 'ele' of length 'len' at
* the specified position 'p', with 'p' being a listpack element pointer
* obtained with lpFirst(), lpLast(), lpIndex(), lpNext(), lpPrev() or
* lpSeek().
@@ -707,10 +707,30 @@ unsigned char *lpInsert(unsigned char *lp, unsigned char *ele, uint32_t size, un
}
}
lpSetTotalBytes(lp,new_listpack_bytes);
+
+#if 0
+ /* This code path is normally disabled: what it does is to force listpack
+ * to return *always* a new pointer after performing some modification to
+ * the listpack, even if the previous allocation was enough. This is useful
+ * in order to spot bugs in code using listpacks: by doing so we can find
+ * if the caller forgets to set the new pointer where the listpack reference
+ * is stored, after an update. */
+ unsigned char *oldlp = lp;
+ lp = lp_malloc(new_listpack_bytes);
+ memcpy(lp,oldlp,new_listpack_bytes);
+ if (newp) {
+ unsigned long offset = (*newp)-oldlp;
+ *newp = lp + offset;
+ }
+ /* Make sure the old allocation contains garbage. */
+ memset(oldlp,'A',new_listpack_bytes);
+ lp_free(oldlp);
+#endif
+
return lp;
}
-/* Append the specified element 'ele' of lenght 'len' at the end of the
+/* Append the specified element 'ele' of length 'len' at the end of the
* listpack. It is implemented in terms of lpInsert(), so the return value is
* the same as lpInsert(). */
unsigned char *lpAppend(unsigned char *lp, unsigned char *ele, uint32_t size) {
diff --git a/src/localtime.c b/src/localtime.c
new file mode 100644
index 000000000..3f59a3331
--- /dev/null
+++ b/src/localtime.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <time.h>
+
+/* This is a safe version of localtime() which contains no locks and is
+ * fork() friendly. Even the _r version of localtime() cannot be used safely
+ * in Redis. Another thread may be calling localtime() while the main thread
+ * forks(). Later when the child process calls localtime() again, for instance
+ * in order to log something to the Redis log, it may deadlock: in the copy
+ * of the address space of the forked process the lock will never be released.
+ *
+ * This function takes the timezone 'tz' as argument, and the 'dst' flag is
+ * used to check if daylight saving time is currently in effect. The caller
+ * of this function should obtain such information calling tzset() ASAP in the
+ * main() function to obtain the timezone offset from the 'timezone' global
+ * variable. To obtain the daylight information, if it is currently active or not,
+ * one trick is to call localtime() in main() ASAP as well, and get the
+ * information from the tm_isdst field of the tm structure. However the daylight
+ * time may switch in the future for long running processes, so this information
+ * should be refreshed at safe times.
+ *
+ * Note that this function does not work for dates < 1/1/1970, it is solely
+ * designed to work with what time(NULL) may return, and to support Redis
+ * logging of the dates, it's not really a complete implementation. */
+static int is_leap_year(time_t year) {
+ if (year % 4) return 0; /* A year not divisible by 4 is not leap. */
+ else if (year % 100) return 1; /* If div by 4 and not 100 is surely leap. */
+ else if (year % 400) return 0; /* If div by 100 *and* 400 is not leap. */
+ else return 1; /* If div by 100 and not by 400 is leap. */
+}
+
+void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst) {
+ const time_t secs_min = 60;
+ const time_t secs_hour = 3600;
+ const time_t secs_day = 3600*24;
+
+ t -= tz; /* Adjust for timezone. */
+ t += 3600*dst; /* Adjust for daylight time. */
+ time_t days = t / secs_day; /* Days passed since epoch. */
+ time_t seconds = t % secs_day; /* Remaining seconds. */
+
+ tmp->tm_isdst = dst;
+ tmp->tm_hour = seconds / secs_hour;
+ tmp->tm_min = (seconds % secs_hour) / secs_min;
+ tmp->tm_sec = (seconds % secs_hour) % secs_min;
+
+ /* 1/1/1970 was a Thursday, that is, day 4 from the POV of the tm structure
+ * where sunday = 0, so to calculate the day of the week we have to add 4
+ * and take the modulo by 7. */
+ tmp->tm_wday = (days+4)%7;
+
+ /* Calculate the current year. */
+ tmp->tm_year = 1970;
+ while(1) {
+ /* Leap years have one day more. */
+ time_t days_this_year = 365 + is_leap_year(tmp->tm_year);
+ if (days_this_year > days) break;
+ days -= days_this_year;
+ tmp->tm_year++;
+ }
+ tmp->tm_yday = days; /* Number of day of the current year. */
+
+ /* We need to calculate in which month and day of the month we are. To do
+ * so we need to skip days according to how many days there are in each
+ * month, and adjust for the leap year that has one more day in February. */
+ int mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ mdays[1] += is_leap_year(tmp->tm_year);
+
+ tmp->tm_mon = 0;
+ while(days >= mdays[tmp->tm_mon]) {
+ days -= mdays[tmp->tm_mon];
+ tmp->tm_mon++;
+ }
+
+ tmp->tm_mday = days+1; /* Add 1 since our 'days' is zero-based. */
+ tmp->tm_year -= 1900; /* Surprisingly tm_year is year-1900. */
+}
+
+#ifdef LOCALTIME_TEST_MAIN
+#include <stdio.h>
+
+int main(void) {
+ /* Obtain timezone and daylight info. */
+ tzset(); /* Now 'timezome' global is populated. */
+ time_t t = time(NULL);
+ struct tm *aux = localtime(&t);
+ int daylight_active = aux->tm_isdst;
+
+ struct tm tm;
+ char buf[1024];
+
+ nolocks_localtime(&tm,t,timezone,daylight_active);
+ strftime(buf,sizeof(buf),"%d %b %H:%M:%S",&tm);
+ printf("[timezone: %d, dl: %d] %s\n", (int)timezone, (int)daylight_active, buf);
+}
+#endif
diff --git a/src/lolwut.c b/src/lolwut.c
new file mode 100644
index 000000000..0e1552ba0
--- /dev/null
+++ b/src/lolwut.c
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------------
+ *
+ * This file implements the LOLWUT command. The command should do something
+ * fun and interesting, and should be replaced by a new implementation at
+ * each new version of Redis.
+ */
+
+#include "server.h"
+#include "lolwut.h"
+#include <math.h>
+
+void lolwut5Command(client *c);
+void lolwut6Command(client *c);
+
+/* The default target for LOLWUT if no matching version was found.
+ * This is what unstable versions of Redis will display. */
+void lolwutUnstableCommand(client *c) {
+ sds rendered = sdsnew("Redis ver. ");
+ rendered = sdscat(rendered,REDIS_VERSION);
+ rendered = sdscatlen(rendered,"\n",1);
+ addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
+ sdsfree(rendered);
+}
+
+/* LOLWUT [VERSION <version>] [... version specific arguments ...] */
+void lolwutCommand(client *c) {
+ char *v = REDIS_VERSION;
+ char verstr[64];
+
+ if (c->argc >= 3 && !strcasecmp(c->argv[1]->ptr,"version")) {
+ long ver;
+ if (getLongFromObjectOrReply(c,c->argv[2],&ver,NULL) != C_OK) return;
+ snprintf(verstr,sizeof(verstr),"%u.0.0",(unsigned int)ver);
+ v = verstr;
+
+ /* Adjust argv/argc to filter the "VERSION ..." option, since the
+ * specific LOLWUT version implementations don't know about it
+ * and expect their arguments. */
+ c->argv += 2;
+ c->argc -= 2;
+ }
+
+ if ((v[0] == '5' && v[1] == '.' && v[2] != '9') ||
+ (v[0] == '4' && v[1] == '.' && v[2] == '9'))
+ lolwut5Command(c);
+ else if ((v[0] == '6' && v[1] == '.' && v[2] != '9') ||
+ (v[0] == '5' && v[1] == '.' && v[2] == '9'))
+ lolwut6Command(c);
+ else
+ lolwutUnstableCommand(c);
+
+ /* Fix back argc/argv in case of VERSION argument. */
+ if (v == verstr) {
+ c->argv -= 2;
+ c->argc += 2;
+ }
+}
+
+/* ========================== LOLWUT Canvase ===============================
+ * Many LOWUT versions will likely print some computer art to the screen.
+ * This is the case with LOLWUT 5 and LOLWUT 6, so here there is a generic
+ * canvas implementation that can be reused. */
+
+/* Allocate and return a new canvas of the specified size. */
+lwCanvas *lwCreateCanvas(int width, int height, int bgcolor) {
+ lwCanvas *canvas = zmalloc(sizeof(*canvas));
+ canvas->width = width;
+ canvas->height = height;
+ canvas->pixels = zmalloc(width*height);
+ memset(canvas->pixels,bgcolor,width*height);
+ return canvas;
+}
+
+/* Free the canvas created by lwCreateCanvas(). */
+void lwFreeCanvas(lwCanvas *canvas) {
+ zfree(canvas->pixels);
+ zfree(canvas);
+}
+
+/* Set a pixel to the specified color. Color is 0 or 1, where zero means no
+ * dot will be displyed, and 1 means dot will be displayed.
+ * Coordinates are arranged so that left-top corner is 0,0. You can write
+ * out of the size of the canvas without issues. */
+void lwDrawPixel(lwCanvas *canvas, int x, int y, int color) {
+ if (x < 0 || x >= canvas->width ||
+ y < 0 || y >= canvas->height) return;
+ canvas->pixels[x+y*canvas->width] = color;
+}
+
+/* Return the value of the specified pixel on the canvas. */
+int lwGetPixel(lwCanvas *canvas, int x, int y) {
+ if (x < 0 || x >= canvas->width ||
+ y < 0 || y >= canvas->height) return 0;
+ return canvas->pixels[x+y*canvas->width];
+}
+
+/* Draw a line from x1,y1 to x2,y2 using the Bresenham algorithm. */
+void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color) {
+ int dx = abs(x2-x1);
+ int dy = abs(y2-y1);
+ int sx = (x1 < x2) ? 1 : -1;
+ int sy = (y1 < y2) ? 1 : -1;
+ int err = dx-dy, e2;
+
+ while(1) {
+ lwDrawPixel(canvas,x1,y1,color);
+ if (x1 == x2 && y1 == y2) break;
+ e2 = err*2;
+ if (e2 > -dy) {
+ err -= dy;
+ x1 += sx;
+ }
+ if (e2 < dx) {
+ err += dx;
+ y1 += sy;
+ }
+ }
+}
+
+/* Draw a square centered at the specified x,y coordinates, with the specified
+ * rotation angle and size. In order to write a rotated square, we use the
+ * trivial fact that the parametric equation:
+ *
+ * x = sin(k)
+ * y = cos(k)
+ *
+ * Describes a circle for values going from 0 to 2*PI. So basically if we start
+ * at 45 degrees, that is k = PI/4, with the first point, and then we find
+ * the other three points incrementing K by PI/2 (90 degrees), we'll have the
+ * points of the square. In order to rotate the square, we just start with
+ * k = PI/4 + rotation_angle, and we are done.
+ *
+ * Of course the vanilla equations above will describe the square inside a
+ * circle of radius 1, so in order to draw larger squares we'll have to
+ * multiply the obtained coordinates, and then translate them. However this
+ * is much simpler than implementing the abstract concept of 2D shape and then
+ * performing the rotation/translation transformation, so for LOLWUT it's
+ * a good approach. */
+void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color) {
+ int px[4], py[4];
+
+ /* Adjust the desired size according to the fact that the square inscribed
+ * into a circle of radius 1 has the side of length SQRT(2). This way
+ * size becomes a simple multiplication factor we can use with our
+ * coordinates to magnify them. */
+ size /= 1.4142135623;
+ size = round(size);
+
+ /* Compute the four points. */
+ float k = M_PI/4 + angle;
+ for (int j = 0; j < 4; j++) {
+ px[j] = round(sin(k) * size + x);
+ py[j] = round(cos(k) * size + y);
+ k += M_PI/2;
+ }
+
+ /* Draw the square. */
+ for (int j = 0; j < 4; j++)
+ lwDrawLine(canvas,px[j],py[j],px[(j+1)%4],py[(j+1)%4],color);
+}
diff --git a/src/lolwut.h b/src/lolwut.h
new file mode 100644
index 000000000..38c0de423
--- /dev/null
+++ b/src/lolwut.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2018-2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* This structure represents our canvas. Drawing functions will take a pointer
+ * to a canvas to write to it. Later the canvas can be rendered to a string
+ * suitable to be printed on the screen, using unicode Braille characters. */
+
+/* This represents a very simple generic canvas in order to draw stuff.
+ * It's up to each LOLWUT versions to translate what they draw to the
+ * screen, depending on the result to accomplish. */
+typedef struct lwCanvas {
+ int width;
+ int height;
+ char *pixels;
+} lwCanvas;
+
+/* Drawing functions implemented inside lolwut.c. */
+lwCanvas *lwCreateCanvas(int width, int height, int bgcolor);
+void lwFreeCanvas(lwCanvas *canvas);
+void lwDrawPixel(lwCanvas *canvas, int x, int y, int color);
+int lwGetPixel(lwCanvas *canvas, int x, int y);
+void lwDrawLine(lwCanvas *canvas, int x1, int y1, int x2, int y2, int color);
+void lwDrawSquare(lwCanvas *canvas, int x, int y, float size, float angle, int color);
diff --git a/src/lolwut5.c b/src/lolwut5.c
new file mode 100644
index 000000000..5a9348800
--- /dev/null
+++ b/src/lolwut5.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------------
+ *
+ * This file implements the LOLWUT command. The command should do something
+ * fun and interesting, and should be replaced by a new implementation at
+ * each new version of Redis.
+ */
+
+#include "server.h"
+#include "lolwut.h"
+#include <math.h>
+
+/* Translate a group of 8 pixels (2x4 vertical rectangle) to the corresponding
+ * braille character. The byte should correspond to the pixels arranged as
+ * follows, where 0 is the least significant bit, and 7 the most significant
+ * bit:
+ *
+ * 0 3
+ * 1 4
+ * 2 5
+ * 6 7
+ *
+ * The corresponding utf8 encoded character is set into the three bytes
+ * pointed by 'output'.
+ */
+#include <stdio.h>
+void lwTranslatePixelsGroup(int byte, char *output) {
+ int code = 0x2800 + byte;
+ /* Convert to unicode. This is in the U0800-UFFFF range, so we need to
+ * emit it like this in three bytes:
+ * 1110xxxx 10xxxxxx 10xxxxxx. */
+ output[0] = 0xE0 | (code >> 12); /* 1110-xxxx */
+ output[1] = 0x80 | ((code >> 6) & 0x3F); /* 10-xxxxxx */
+ output[2] = 0x80 | (code & 0x3F); /* 10-xxxxxx */
+}
+
+/* Schotter, the output of LOLWUT of Redis 5, is a computer graphic art piece
+ * generated by Georg Nees in the 60s. It explores the relationship between
+ * caos and order.
+ *
+ * The function creates the canvas itself, depending on the columns available
+ * in the output display and the number of squares per row and per column
+ * requested by the caller. */
+lwCanvas *lwDrawSchotter(int console_cols, int squares_per_row, int squares_per_col) {
+ /* Calculate the canvas size. */
+ int canvas_width = console_cols*2;
+ int padding = canvas_width > 4 ? 2 : 0;
+ float square_side = (float)(canvas_width-padding*2) / squares_per_row;
+ int canvas_height = square_side * squares_per_col + padding*2;
+ lwCanvas *canvas = lwCreateCanvas(canvas_width, canvas_height, 0);
+
+ for (int y = 0; y < squares_per_col; y++) {
+ for (int x = 0; x < squares_per_row; x++) {
+ int sx = x * square_side + square_side/2 + padding;
+ int sy = y * square_side + square_side/2 + padding;
+ /* Rotate and translate randomly as we go down to lower
+ * rows. */
+ float angle = 0;
+ if (y > 1) {
+ float r1 = (float)rand() / RAND_MAX / squares_per_col * y;
+ float r2 = (float)rand() / RAND_MAX / squares_per_col * y;
+ float r3 = (float)rand() / RAND_MAX / squares_per_col * y;
+ if (rand() % 2) r1 = -r1;
+ if (rand() % 2) r2 = -r2;
+ if (rand() % 2) r3 = -r3;
+ angle = r1;
+ sx += r2*square_side/3;
+ sy += r3*square_side/3;
+ }
+ lwDrawSquare(canvas,sx,sy,square_side,angle,1);
+ }
+ }
+
+ return canvas;
+}
+
+/* Converts the canvas to an SDS string representing the UTF8 characters to
+ * print to the terminal in order to obtain a graphical representaiton of the
+ * logical canvas. The actual returned string will require a terminal that is
+ * width/2 large and height/4 tall in order to hold the whole image without
+ * overflowing or scrolling, since each Barille character is 2x4. */
+static sds renderCanvas(lwCanvas *canvas) {
+ sds text = sdsempty();
+ for (int y = 0; y < canvas->height; y += 4) {
+ for (int x = 0; x < canvas->width; x += 2) {
+ /* We need to emit groups of 8 bits according to a specific
+ * arrangement. See lwTranslatePixelsGroup() for more info. */
+ int byte = 0;
+ if (lwGetPixel(canvas,x,y)) byte |= (1<<0);
+ if (lwGetPixel(canvas,x,y+1)) byte |= (1<<1);
+ if (lwGetPixel(canvas,x,y+2)) byte |= (1<<2);
+ if (lwGetPixel(canvas,x+1,y)) byte |= (1<<3);
+ if (lwGetPixel(canvas,x+1,y+1)) byte |= (1<<4);
+ if (lwGetPixel(canvas,x+1,y+2)) byte |= (1<<5);
+ if (lwGetPixel(canvas,x,y+3)) byte |= (1<<6);
+ if (lwGetPixel(canvas,x+1,y+3)) byte |= (1<<7);
+ char unicode[3];
+ lwTranslatePixelsGroup(byte,unicode);
+ text = sdscatlen(text,unicode,3);
+ }
+ if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
+ }
+ return text;
+}
+
+/* The LOLWUT command:
+ *
+ * LOLWUT [terminal columns] [squares-per-row] [squares-per-col]
+ *
+ * By default the command uses 66 columns, 8 squares per row, 12 squares
+ * per column.
+ */
+void lolwut5Command(client *c) {
+ long cols = 66;
+ long squares_per_row = 8;
+ long squares_per_col = 12;
+
+ /* Parse the optional arguments if any. */
+ if (c->argc > 1 &&
+ getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
+ return;
+
+ if (c->argc > 2 &&
+ getLongFromObjectOrReply(c,c->argv[2],&squares_per_row,NULL) != C_OK)
+ return;
+
+ if (c->argc > 3 &&
+ getLongFromObjectOrReply(c,c->argv[3],&squares_per_col,NULL) != C_OK)
+ return;
+
+ /* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
+ * so we have maximum number of columns, rows, and output resulution. */
+ if (cols < 1) cols = 1;
+ if (cols > 1000) cols = 1000;
+ if (squares_per_row < 1) squares_per_row = 1;
+ if (squares_per_row > 200) squares_per_row = 200;
+ if (squares_per_col < 1) squares_per_col = 1;
+ if (squares_per_col > 200) squares_per_col = 200;
+
+ /* Generate some computer art and reply. */
+ lwCanvas *canvas = lwDrawSchotter(cols,squares_per_row,squares_per_col);
+ sds rendered = renderCanvas(canvas);
+ rendered = sdscat(rendered,
+ "\nGeorg Nees - schotter, plotter on paper, 1968. Redis ver. ");
+ rendered = sdscat(rendered,REDIS_VERSION);
+ rendered = sdscatlen(rendered,"\n",1);
+ addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
+ sdsfree(rendered);
+ lwFreeCanvas(canvas);
+}
diff --git a/src/lolwut6.c b/src/lolwut6.c
new file mode 100644
index 000000000..b76d80690
--- /dev/null
+++ b/src/lolwut6.c
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * ----------------------------------------------------------------------------
+ *
+ * This file implements the LOLWUT command. The command should do something
+ * fun and interesting, and should be replaced by a new implementation at
+ * each new version of Redis.
+ *
+ * Thanks to Michele Hiki Falcone for the original image that ispired
+ * the image, part of his game, Plaguemon.
+ *
+ * Thanks to the Shhh computer art collective for the help in tuning the
+ * output to have a better artistic effect.
+ */
+
+#include "server.h"
+#include "lolwut.h"
+
+/* Render the canvas using the four gray levels of the standard color
+ * terminal: they match very well to the grayscale display of the gameboy. */
+static sds renderCanvas(lwCanvas *canvas) {
+ sds text = sdsempty();
+ for (int y = 0; y < canvas->height; y++) {
+ for (int x = 0; x < canvas->width; x++) {
+ int color = lwGetPixel(canvas,x,y);
+ char *ce; /* Color escape sequence. */
+
+ /* Note that we set both the foreground and background color.
+ * This way we are able to get a more consistent result among
+ * different terminals implementations. */
+ switch(color) {
+ case 0: ce = "0;30;40m"; break; /* Black */
+ case 1: ce = "0;90;100m"; break; /* Gray 1 */
+ case 2: ce = "0;37;47m"; break; /* Gray 2 */
+ case 3: ce = "0;97;107m"; break; /* White */
+ }
+ text = sdscatprintf(text,"\033[%s \033[0m",ce);
+ }
+ if (y != canvas->height-1) text = sdscatlen(text,"\n",1);
+ }
+ return text;
+}
+
+/* Draw a skyscraper on the canvas, according to the parameters in the
+ * 'skyscraper' structure. Window colors are random and are always one
+ * of the two grays. */
+struct skyscraper {
+ int xoff; /* X offset. */
+ int width; /* Pixels width. */
+ int height; /* Pixels height. */
+ int windows; /* Draw windows if true. */
+ int color; /* Color of the skyscraper. */
+};
+
+void generateSkyscraper(lwCanvas *canvas, struct skyscraper *si) {
+ int starty = canvas->height-1;
+ int endy = starty - si->height + 1;
+ for (int y = starty; y >= endy; y--) {
+ for (int x = si->xoff; x < si->xoff+si->width; x++) {
+ /* The roof is four pixels less wide. */
+ if (y == endy && (x <= si->xoff+1 || x >= si->xoff+si->width-2))
+ continue;
+ int color = si->color;
+ /* Alter the color if this is a place where we want to
+ * draw a window. We check that we are in the inner part of the
+ * skyscraper, so that windows are far from the borders. */
+ if (si->windows &&
+ x > si->xoff+1 &&
+ x < si->xoff+si->width-2 &&
+ y > endy+1 &&
+ y < starty-1)
+ {
+ /* Calculate the x,y position relative to the start of
+ * the window area. */
+ int relx = x - (si->xoff+1);
+ int rely = y - (endy+1);
+
+ /* Note that we want the windows to be two pixels wide
+ * but just one pixel tall, because terminal "pixels"
+ * (characters) are not square. */
+ if (relx/2 % 2 && rely % 2) {
+ do {
+ color = 1 + rand() % 2;
+ } while (color == si->color);
+ /* Except we want adjacent pixels creating the same
+ * window to be the same color. */
+ if (relx % 2) color = lwGetPixel(canvas,x-1,y);
+ }
+ }
+ lwDrawPixel(canvas,x,y,color);
+ }
+ }
+}
+
+/* Generate a skyline inspired by the parallax backgrounds of 8 bit games. */
+void generateSkyline(lwCanvas *canvas) {
+ struct skyscraper si;
+
+ /* First draw the background skyscraper without windows, using the
+ * two different grays. We use two passes to make sure that the lighter
+ * ones are always in the background. */
+ for (int color = 2; color >= 1; color--) {
+ si.color = color;
+ for (int offset = -10; offset < canvas->width;) {
+ offset += rand() % 8;
+ si.xoff = offset;
+ si.width = 10 + rand()%9;
+ if (color == 2)
+ si.height = canvas->height/2 + rand()%canvas->height/2;
+ else
+ si.height = canvas->height/2 + rand()%canvas->height/3;
+ si.windows = 0;
+ generateSkyscraper(canvas, &si);
+ if (color == 2)
+ offset += si.width/2;
+ else
+ offset += si.width+1;
+ }
+ }
+
+ /* Now draw the foreground skyscraper with the windows. */
+ si.color = 0;
+ for (int offset = -10; offset < canvas->width;) {
+ offset += rand() % 8;
+ si.xoff = offset;
+ si.width = 5 + rand()%14;
+ if (si.width % 4) si.width += (si.width % 3);
+ si.height = canvas->height/3 + rand()%canvas->height/2;
+ si.windows = 1;
+ generateSkyscraper(canvas, &si);
+ offset += si.width+5;
+ }
+}
+
+/* The LOLWUT 6 command:
+ *
+ * LOLWUT [columns] [rows]
+ *
+ * By default the command uses 80 columns, 40 squares per row
+ * per column.
+ */
+void lolwut6Command(client *c) {
+ long cols = 80;
+ long rows = 20;
+
+ /* Parse the optional arguments if any. */
+ if (c->argc > 1 &&
+ getLongFromObjectOrReply(c,c->argv[1],&cols,NULL) != C_OK)
+ return;
+
+ if (c->argc > 2 &&
+ getLongFromObjectOrReply(c,c->argv[2],&rows,NULL) != C_OK)
+ return;
+
+ /* Limits. We want LOLWUT to be always reasonably fast and cheap to execute
+ * so we have maximum number of columns, rows, and output resulution. */
+ if (cols < 1) cols = 1;
+ if (cols > 1000) cols = 1000;
+ if (rows < 1) rows = 1;
+ if (rows > 1000) rows = 1000;
+
+ /* Generate the city skyline and reply. */
+ lwCanvas *canvas = lwCreateCanvas(cols,rows,3);
+ generateSkyline(canvas);
+ sds rendered = renderCanvas(canvas);
+ rendered = sdscat(rendered,
+ "\nDedicated to the 8 bit game developers of past and present.\n"
+ "Original 8 bit image from Plaguemon by hikikomori. Redis ver. ");
+ rendered = sdscat(rendered,REDIS_VERSION);
+ rendered = sdscatlen(rendered,"\n",1);
+ addReplyVerbatim(c,rendered,sdslen(rendered),"txt");
+ sdsfree(rendered);
+ lwFreeCanvas(canvas);
+}
diff --git a/src/lzf_d.c b/src/lzf_d.c
index c32be8e87..d44bfcc8d 100644
--- a/src/lzf_d.c
+++ b/src/lzf_d.c
@@ -52,6 +52,10 @@
#endif
#endif
+#if defined(__GNUC__) && __GNUC__ >= 5
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
+#endif
unsigned int
lzf_decompress (const void *const in_data, unsigned int in_len,
void *out_data, unsigned int out_len)
@@ -163,17 +167,17 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
break;
- case 9: *op++ = *ref++;
- case 8: *op++ = *ref++;
- case 7: *op++ = *ref++;
- case 6: *op++ = *ref++;
- case 5: *op++ = *ref++;
- case 4: *op++ = *ref++;
- case 3: *op++ = *ref++;
- case 2: *op++ = *ref++;
- case 1: *op++ = *ref++;
+ case 9: *op++ = *ref++; /* fall-thru */
+ case 8: *op++ = *ref++; /* fall-thru */
+ case 7: *op++ = *ref++; /* fall-thru */
+ case 6: *op++ = *ref++; /* fall-thru */
+ case 5: *op++ = *ref++; /* fall-thru */
+ case 4: *op++ = *ref++; /* fall-thru */
+ case 3: *op++ = *ref++; /* fall-thru */
+ case 2: *op++ = *ref++; /* fall-thru */
+ case 1: *op++ = *ref++; /* fall-thru */
case 0: *op++ = *ref++; /* two octets more */
- *op++ = *ref++;
+ *op++ = *ref++; /* fall-thru */
}
#endif
}
@@ -182,4 +186,6 @@ lzf_decompress (const void *const in_data, unsigned int in_len,
return op - (u8 *)out_data;
}
-
+#if defined(__GNUC__) && __GNUC__ >= 5
+#pragma GCC diagnostic pop
+#endif
diff --git a/src/mkreleasehdr.sh b/src/mkreleasehdr.sh
index 1ae95886b..e6d558b17 100755
--- a/src/mkreleasehdr.sh
+++ b/src/mkreleasehdr.sh
@@ -2,6 +2,9 @@
GIT_SHA1=`(git show-ref --head --hash=8 2> /dev/null || echo 00000000) | head -n1`
GIT_DIRTY=`git diff --no-ext-diff 2> /dev/null | wc -l`
BUILD_ID=`uname -n`"-"`date +%s`
+if [ -n "$SOURCE_DATE_EPOCH" ]; then
+ BUILD_ID=$(date -u -d "@$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u -r "$SOURCE_DATE_EPOCH" +%s 2>/dev/null || date -u %s)
+fi
test -f release.h || touch release.h
(cat release.h | grep SHA1 | grep $GIT_SHA1) && \
(cat release.h | grep DIRTY | grep $GIT_DIRTY) && exit 0 # Already up-to-date
diff --git a/src/module.c b/src/module.c
index 8eb3f8aa5..cabb37e08 100644
--- a/src/module.c
+++ b/src/module.c
@@ -29,10 +29,9 @@
#include "server.h"
#include "cluster.h"
+#include "rdb.h"
#include <dlfcn.h>
-
-#define REDISMODULE_CORE 1
-#include "redismodule.h"
+#include <sys/wait.h>
/* --------------------------------------------------------------------------
* Private data structures used by the modules system. Those are data
@@ -40,6 +39,17 @@
* pointers that have an API the module can call with them)
* -------------------------------------------------------------------------- */
+typedef struct RedisModuleInfoCtx {
+ struct RedisModule *module;
+ sds requested_section;
+ sds info; /* info string we collected so far */
+ int sections; /* number of sections we collected so far */
+ int in_section; /* indication if we're in an active section or not */
+ int in_dict_field; /* indication that we're curreintly appending to a dict */
+} RedisModuleInfoCtx;
+
+typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
+
/* This structure represents a module inside the system. */
struct RedisModule {
void *handle; /* Module dlopen() handle. */
@@ -47,9 +57,26 @@ struct RedisModule {
int ver; /* Module version. We use just progressive integers. */
int apiver; /* Module API version as requested during initialization.*/
list *types; /* Module data types. */
+ list *usedby; /* List of modules using APIs from this one. */
+ list *using; /* List of modules we use some APIs of. */
+ list *filters; /* List of filters the module has registered. */
+ int in_call; /* RM_Call() nesting level */
+ int in_hook; /* Hooks callback nesting level for this module (0 or 1). */
+ int options; /* Module options and capabilities. */
+ RedisModuleInfoFunc info_cb; /* Callback for module to add INFO fields. */
};
typedef struct RedisModule RedisModule;
+/* This represents a shared API. Shared APIs will be used to populate
+ * the server.sharedapi dictionary, mapping names of APIs exported by
+ * modules for other modules to use, to their structure specifying the
+ * function pointer that can be called. */
+struct RedisModuleSharedAPI {
+ void *func;
+ RedisModule *module;
+};
+typedef struct RedisModuleSharedAPI RedisModuleSharedAPI;
+
static dict *modules; /* Hash table of modules. SDS -> RedisModule ptr.*/
/* Entries in the context->amqueue array, representing objects to free
@@ -64,6 +91,7 @@ struct AutoMemEntry {
#define REDISMODULE_AM_STRING 1
#define REDISMODULE_AM_REPLY 2
#define REDISMODULE_AM_FREED 3 /* Explicitly freed by user already. */
+#define REDISMODULE_AM_DICT 4
/* The pool allocator block. Redis Modules can allocate memory via this special
* allocator that will automatically release it all once the callback returns.
@@ -117,16 +145,22 @@ struct RedisModuleCtx {
int keys_count;
struct RedisModulePoolAllocBlock *pa_head;
+ redisOpArray saved_oparray; /* When propagating commands in a callback
+ we reallocate the "also propagate" op
+ array. Here we save the old one to
+ restore it later. */
};
typedef struct RedisModuleCtx RedisModuleCtx;
-#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL}
+#define REDISMODULE_CTX_INIT {(void*)(unsigned long)&RM_GetApi, NULL, NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL, NULL, 0, NULL, {0}}
#define REDISMODULE_CTX_MULTI_EMITTED (1<<0)
#define REDISMODULE_CTX_AUTO_MEMORY (1<<1)
#define REDISMODULE_CTX_KEYS_POS_REQUEST (1<<2)
#define REDISMODULE_CTX_BLOCKED_REPLY (1<<3)
#define REDISMODULE_CTX_BLOCKED_TIMEOUT (1<<4)
#define REDISMODULE_CTX_THREAD_SAFE (1<<5)
+#define REDISMODULE_CTX_BLOCKED_DISCONNECTED (1<<6)
+#define REDISMODULE_CTX_MODULE_COMMAND_CALL (1<<7)
/* This represents a Redis key opened with RM_OpenKey(). */
struct RedisModuleKey {
@@ -157,7 +191,9 @@ typedef struct RedisModuleKey RedisModuleKey;
/* Function pointer type of a function representing a command inside
* a Redis module. */
+struct RedisModuleBlockedClient;
typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, void **argv, int argc);
+typedef void (*RedisModuleDisconnectFunc) (RedisModuleCtx *ctx, struct RedisModuleBlockedClient *bc);
/* This struct holds the information about a command registered by a module.*/
struct RedisModuleCommandProxy {
@@ -200,7 +236,8 @@ typedef struct RedisModuleBlockedClient {
RedisModule *module; /* Module blocking the client. */
RedisModuleCmdFunc reply_callback; /* Reply callback on normal completion.*/
RedisModuleCmdFunc timeout_callback; /* Reply callback on timeout. */
- void (*free_privdata)(void *); /* privdata cleanup callback. */
+ RedisModuleDisconnectFunc disconnect_callback; /* Called on disconnection.*/
+ void (*free_privdata)(RedisModuleCtx*,void*);/* privdata cleanup callback.*/
void *privdata; /* Module private data that may be used by the reply
or timeout callback. It is set via the
RedisModule_UnblockClient() API. */
@@ -237,9 +274,67 @@ typedef struct RedisModuleKeyspaceSubscriber {
/* The module keyspace notification subscribers list */
static list *moduleKeyspaceSubscribers;
-/* Static client recycled for all notification clients, to avoid allocating
- * per round. */
-static client *moduleKeyspaceSubscribersClient;
+/* Static client recycled for when we need to provide a context with a client
+ * in a situation where there is no client to provide. This avoidsallocating
+ * a new client per round. For instance this is used in the keyspace
+ * notifications, timers and cluster messages callbacks. */
+static client *moduleFreeContextReusedClient;
+
+/* Data structures related to the exported dictionary data structure. */
+typedef struct RedisModuleDict {
+ rax *rax; /* The radix tree. */
+} RedisModuleDict;
+
+typedef struct RedisModuleDictIter {
+ RedisModuleDict *dict;
+ raxIterator ri;
+} RedisModuleDictIter;
+
+typedef struct RedisModuleCommandFilterCtx {
+ RedisModuleString **argv;
+ int argc;
+} RedisModuleCommandFilterCtx;
+
+typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
+
+typedef struct RedisModuleCommandFilter {
+ /* The module that registered the filter */
+ RedisModule *module;
+ /* Filter callback function */
+ RedisModuleCommandFilterFunc callback;
+ /* REDISMODULE_CMDFILTER_* flags */
+ int flags;
+} RedisModuleCommandFilter;
+
+/* Registered filters */
+static list *moduleCommandFilters;
+
+typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
+
+static struct RedisModuleForkInfo {
+ RedisModuleForkDoneHandler done_handler;
+ void* done_handler_user_data;
+} moduleForkInfo = {0};
+
+/* Flags for moduleCreateArgvFromUserFormat(). */
+#define REDISMODULE_ARGV_REPLICATE (1<<0)
+#define REDISMODULE_ARGV_NO_AOF (1<<1)
+#define REDISMODULE_ARGV_NO_REPLICAS (1<<2)
+
+/* Server events hooks data structures and defines: this modules API
+ * allow modules to subscribe to certain events in Redis, such as
+ * the start and end of an RDB or AOF save, the change of role in replication,
+ * and similar other events. */
+
+typedef struct RedisModuleEventListener {
+ RedisModule *module;
+ RedisModuleEvent event;
+ RedisModuleEventCallback callback;
+} RedisModuleEventListener;
+
+list *RedisModule_EventListeners; /* Global list of all the active events. */
+unsigned long long ModulesInHooks = 0; /* Total number of modules in hooks
+ callbacks right now. */
/* --------------------------------------------------------------------------
* Prototypes
@@ -252,6 +347,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx);
void RM_ZsetRangeStop(RedisModuleKey *kp);
static void zsetKeyReset(RedisModuleKey *key);
+void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d);
/* --------------------------------------------------------------------------
* Heap allocation raw functions
@@ -445,8 +541,47 @@ int RM_GetApi(const char *funcname, void **targetPtrPtr) {
return REDISMODULE_OK;
}
+/* Helper function for when a command callback is called, in order to handle
+ * details needed to correctly replicate commands. */
+void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
+ client *c = ctx->client;
+
+ /* We don't need to do anything here if the context was never used
+ * in order to propagate commands. */
+ if (!(ctx->flags & REDISMODULE_CTX_MULTI_EMITTED)) return;
+
+ if (c->flags & CLIENT_LUA) return;
+
+ /* Handle the replication of the final EXEC, since whatever a command
+ * emits is always wrapped around MULTI/EXEC. */
+ robj *propargv[1];
+ propargv[0] = createStringObject("EXEC",4);
+ alsoPropagate(server.execCommand,c->db->id,propargv,1,
+ PROPAGATE_AOF|PROPAGATE_REPL);
+ decrRefCount(propargv[0]);
+
+ /* If this is not a module command context (but is instead a simple
+ * callback context), we have to handle directly the "also propagate"
+ * array and emit it. In a module command call this will be handled
+ * directly by call(). */
+ if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL) &&
+ server.also_propagate.numops)
+ {
+ for (int j = 0; j < server.also_propagate.numops; j++) {
+ redisOp *rop = &server.also_propagate.ops[j];
+ int target = rop->target;
+ if (target)
+ propagate(rop->cmd,rop->dbid,rop->argv,rop->argc,target);
+ }
+ redisOpArrayFree(&server.also_propagate);
+ /* Restore the previous oparray in case of nexted use of the API. */
+ server.also_propagate = ctx->saved_oparray;
+ }
+}
+
/* Free the context after the user function was called. */
void moduleFreeContext(RedisModuleCtx *ctx) {
+ moduleHandlePropagationAfterCommandCallback(ctx);
autoMemoryCollect(ctx);
poolAllocRelease(ctx);
if (ctx->postponed_arrays) {
@@ -462,35 +597,33 @@ void moduleFreeContext(RedisModuleCtx *ctx) {
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) freeClient(ctx->client);
}
-/* Helper function for when a command callback is called, in order to handle
- * details needed to correctly replicate commands. */
-void moduleHandlePropagationAfterCommandCallback(RedisModuleCtx *ctx) {
- client *c = ctx->client;
-
- if (c->flags & CLIENT_LUA) return;
-
- /* Handle the replication of the final EXEC, since whatever a command
- * emits is always wrappered around MULTI/EXEC. */
- if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) {
- robj *propargv[1];
- propargv[0] = createStringObject("EXEC",4);
- alsoPropagate(server.execCommand,c->db->id,propargv,1,
- PROPAGATE_AOF|PROPAGATE_REPL);
- decrRefCount(propargv[0]);
- }
-}
-
/* This Redis command binds the normal Redis command invocation with commands
* exported by modules. */
void RedisModuleCommandDispatcher(client *c) {
RedisModuleCommandProxy *cp = (void*)(unsigned long)c->cmd->getkeys_proc;
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ ctx.flags |= REDISMODULE_CTX_MODULE_COMMAND_CALL;
ctx.module = cp->module;
ctx.client = c;
cp->func(&ctx,(void**)c->argv,c->argc);
- moduleHandlePropagationAfterCommandCallback(&ctx);
moduleFreeContext(&ctx);
+
+ /* In some cases processMultibulkBuffer uses sdsMakeRoomFor to
+ * expand the query buffer, and in order to avoid a big object copy
+ * the query buffer SDS may be used directly as the SDS string backing
+ * the client argument vectors: sometimes this will result in the SDS
+ * string having unused space at the end. Later if a module takes ownership
+ * of the RedisString, such space will be wasted forever. Inside the
+ * Redis core this is not a problem because tryObjectEncoding() is called
+ * before storing strings in the key space. Here we need to do it
+ * for the module. */
+ for (int i = 0; i < c->argc; i++) {
+ /* Only do the work if the module took ownership of the object:
+ * in that case the refcount is no longer 1. */
+ if (c->argv[i]->refcount > 1)
+ trimStringObjectIfNeeded(c->argv[i]);
+ }
}
/* This function returns the list of keys, with the same interface as the
@@ -544,7 +677,7 @@ void RM_KeyAtPos(RedisModuleCtx *ctx, int pos) {
ctx->keys_pos[ctx->keys_count++] = pos;
}
-/* Helper for RM_CreateCommand(). Truns a string representing command
+/* Helper for RM_CreateCommand(). Turns a string representing command
* flags into the command flags used by the Redis core.
*
* It returns the set of flags, or -1 if unknown flags are found. */
@@ -591,7 +724,7 @@ int commandFlagsFromString(char *s) {
* And is supposed to always return REDISMODULE_OK.
*
* The set of flags 'strflags' specify the behavior of the command, and should
- * be passed as a C string compoesd of space separated words, like for
+ * be passed as a C string composed of space separated words, like for
* example "write deny-oom". The set of flags are:
*
* * **"write"**: The command may modify the data set (it may also read
@@ -612,7 +745,7 @@ int commandFlagsFromString(char *s) {
* * **"allow-stale"**: The command is allowed to run on slaves that don't
* serve stale data. Don't use if you don't know what
* this means.
- * * **"no-monitor"**: Don't propoagate the command on monitor. Use this if
+ * * **"no-monitor"**: Don't propagate the command on monitor. Use this if
* the command has sensible data among the arguments.
* * **"fast"**: The command time complexity is not greater
* than O(log(N)) where N is the size of the collection or
@@ -666,6 +799,7 @@ int RM_CreateCommand(RedisModuleCtx *ctx, const char *name, RedisModuleCmdFunc c
cp->rediscmd->calls = 0;
dictAdd(server.commands,sdsdup(cmdname),cp->rediscmd);
dictAdd(server.orig_commands,sdsdup(cmdname),cp->rediscmd);
+ cp->rediscmd->id = ACLGetCommandID(cmdname); /* ID used for ACL. */
return REDISMODULE_OK;
}
@@ -682,6 +816,11 @@ void RM_SetModuleAttribs(RedisModuleCtx *ctx, const char *name, int ver, int api
module->ver = ver;
module->apiver = apiver;
module->types = listCreate();
+ module->usedby = listCreate();
+ module->using = listCreate();
+ module->filters = listCreate();
+ module->in_call = 0;
+ module->in_hook = 0;
ctx->module = module;
}
@@ -699,6 +838,19 @@ long long RM_Milliseconds(void) {
return mstime();
}
+/* Set flags defining capabilities or behavior bit flags.
+ *
+ * REDISMODULE_OPTIONS_HANDLE_IO_ERRORS:
+ * Generally, modules don't need to bother with this, as the process will just
+ * terminate if a read error happens, however, setting this flag would allow
+ * repl-diskless-load to work if enabled.
+ * The module should use RedisModule_IsIOError after reads, before using the
+ * data that was read, and in case of error, propagate it upwards, and also be
+ * able to release the partially populated value and all it's allocations. */
+void RM_SetModuleOptions(RedisModuleCtx *ctx, int options) {
+ ctx->module->options = options;
+}
+
/* --------------------------------------------------------------------------
* Automatic memory management for modules
* -------------------------------------------------------------------------- */
@@ -773,6 +925,7 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
case REDISMODULE_AM_STRING: decrRefCount(ptr); break;
case REDISMODULE_AM_REPLY: RM_FreeCallReply(ptr); break;
case REDISMODULE_AM_KEY: RM_CloseKey(ptr); break;
+ case REDISMODULE_AM_DICT: RM_FreeDict(NULL,ptr); break;
}
}
ctx->flags |= REDISMODULE_CTX_AUTO_MEMORY;
@@ -790,19 +943,26 @@ void autoMemoryCollect(RedisModuleCtx *ctx) {
* with RedisModule_FreeString(), unless automatic memory is enabled.
*
* The string is created by copying the `len` bytes starting
- * at `ptr`. No reference is retained to the passed buffer. */
+ * at `ptr`. No reference is retained to the passed buffer.
+ *
+ * The module context 'ctx' is optional and may be NULL if you want to create
+ * a string out of the context scope. However in that case, the automatic
+ * memory management will not be available, and the string memory must be
+ * managed manually. */
RedisModuleString *RM_CreateString(RedisModuleCtx *ctx, const char *ptr, size_t len) {
RedisModuleString *o = createStringObject(ptr,len);
- autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
+ if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
return o;
}
-
/* Create a new module string object from a printf format and arguments.
* The returned string must be freed with RedisModule_FreeString(), unless
* automatic memory is enabled.
*
- * The string is created using the sds formatter function sdscatvprintf(). */
+ * The string is created using the sds formatter function sdscatvprintf().
+ *
+ * The passed context 'ctx' may be NULL if necessary, see the
+ * RedisModule_CreateString() documentation for more info. */
RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, ...) {
sds s = sdsempty();
@@ -812,7 +972,7 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
va_end(ap);
RedisModuleString *o = createObject(OBJ_STRING, s);
- autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
+ if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
return o;
}
@@ -822,7 +982,10 @@ RedisModuleString *RM_CreateStringPrintf(RedisModuleCtx *ctx, const char *fmt, .
* integer instead of taking a buffer and its length.
*
* The returned string must be released with RedisModule_FreeString() or by
- * enabling automatic memory management. */
+ * enabling automatic memory management.
+ *
+ * The passed context 'ctx' may be NULL if necessary, see the
+ * RedisModule_CreateString() documentation for more info. */
RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll) {
char buf[LONG_STR_SIZE];
size_t len = ll2string(buf,sizeof(buf),ll);
@@ -833,10 +996,13 @@ RedisModuleString *RM_CreateStringFromLongLong(RedisModuleCtx *ctx, long long ll
* RedisModuleString.
*
* The returned string must be released with RedisModule_FreeString() or by
- * enabling automatic memory management. */
+ * enabling automatic memory management.
+ *
+ * The passed context 'ctx' may be NULL if necessary, see the
+ * RedisModule_CreateString() documentation for more info. */
RedisModuleString *RM_CreateStringFromString(RedisModuleCtx *ctx, const RedisModuleString *str) {
RedisModuleString *o = dupStringObject(str);
- autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
+ if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_STRING,o);
return o;
}
@@ -845,10 +1011,16 @@ RedisModuleString *RM_CreateStringFromString(RedisModuleCtx *ctx, const RedisMod
*
* It is possible to call this function even when automatic memory management
* is enabled. In that case the string will be released ASAP and removed
- * from the pool of string to release at the end. */
+ * from the pool of string to release at the end.
+ *
+ * If the string was created with a NULL context 'ctx', it is also possible to
+ * pass ctx as NULL when releasing the string (but passing a context will not
+ * create any issue). Strings created with a context should be freed also passing
+ * the context, so if you want to free a string out of context later, make sure
+ * to create it using a NULL context. */
void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) {
decrRefCount(str);
- autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str);
+ if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str);
}
/* Every call to this function, will make the string 'str' requiring
@@ -872,9 +1044,11 @@ void RM_FreeString(RedisModuleCtx *ctx, RedisModuleString *str) {
* Note that when memory management is turned off, you don't need
* any call to RetainString() since creating a string will always result
* into a string that lives after the callback function returns, if
- * no FreeString() call is performed. */
+ * no FreeString() call is performed.
+ *
+ * It is possible to call this function with a NULL context. */
void RM_RetainString(RedisModuleCtx *ctx, RedisModuleString *str) {
- if (!autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str)) {
+ if (ctx == NULL || !autoMemoryFreed(ctx,REDISMODULE_AM_STRING,str)) {
/* Increment the string reference counting only if we can't
* just remove the object from the list of objects that should
* be reclaimed. Why we do that, instead of just incrementing
@@ -952,9 +1126,9 @@ RedisModuleString *moduleAssertUnsharedString(RedisModuleString *str) {
return str;
}
-/* Append the specified buffere to the string 'str'. The string must be a
+/* Append the specified buffer to the string 'str'. The string must be a
* string created by the user that is referenced only a single time, otherwise
- * REDISMODULE_ERR is returend and the operation is not performed. */
+ * REDISMODULE_ERR is returned and the operation is not performed. */
int RM_StringAppendBuffer(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len) {
UNUSED(ctx);
str = moduleAssertUnsharedString(str);
@@ -999,13 +1173,21 @@ int RM_WrongArity(RedisModuleCtx *ctx) {
* The function returns the client pointer depending on the context, or
* NULL if there is no potential client. This happens when we are in the
* context of a thread safe context that was not initialized with a blocked
- * client object. */
+ * client object. Other contexts without associated clients are the ones
+ * initialized to run the timers callbacks. */
client *moduleGetReplyClient(RedisModuleCtx *ctx) {
- if (!(ctx->flags & REDISMODULE_CTX_THREAD_SAFE) && ctx->client)
+ if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
+ if (ctx->blocked_client)
+ return ctx->blocked_client->reply_client;
+ else
+ return NULL;
+ } else {
+ /* If this is a non thread safe context, just return the client
+ * that is running the command if any. This may be NULL as well
+ * in the case of contexts that are not executed with associated
+ * clients, like timer contexts. */
return ctx->client;
- if (ctx->blocked_client)
- return ctx->blocked_client->reply_client;
- return NULL;
+ }
}
/* Send an integer reply to the client, with the specified long long value.
@@ -1023,10 +1205,9 @@ int RM_ReplyWithLongLong(RedisModuleCtx *ctx, long long ll) {
int replyWithStatus(RedisModuleCtx *ctx, const char *msg, char *prefix) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK;
- sds strmsg = sdsnewlen(prefix,1);
- strmsg = sdscat(strmsg,msg);
- strmsg = sdscatlen(strmsg,"\r\n",2);
- addReplySds(c,strmsg);
+ addReplyProto(c,prefix,strlen(prefix));
+ addReplyProto(c,msg,strlen(msg));
+ addReplyProto(c,"\r\n",2);
return REDISMODULE_OK;
}
@@ -1075,10 +1256,10 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
ctx->postponed_arrays = zrealloc(ctx->postponed_arrays,sizeof(void*)*
(ctx->postponed_arrays_count+1));
ctx->postponed_arrays[ctx->postponed_arrays_count] =
- addDeferredMultiBulkLength(c);
+ addReplyDeferredLen(c);
ctx->postponed_arrays_count++;
} else {
- addReplyMultiBulkLen(c,len);
+ addReplyArrayLen(c,len);
}
return REDISMODULE_OK;
}
@@ -1106,7 +1287,7 @@ int RM_ReplyWithArray(RedisModuleCtx *ctx, long len) {
*
* Note that in the above example there is no reason to postpone the array
* length, since we produce a fixed number of elements, but in the practice
- * the code may use an interator or other ways of creating the output so
+ * the code may use an iterator or other ways of creating the output so
* that is not easy to calculate in advance the number of elements.
*/
void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
@@ -1121,7 +1302,7 @@ void RM_ReplySetArrayLength(RedisModuleCtx *ctx, long len) {
return;
}
ctx->postponed_arrays_count--;
- setDeferredMultiBulkLength(c,
+ setDeferredArrayLen(c,
ctx->postponed_arrays[ctx->postponed_arrays_count],
len);
if (ctx->postponed_arrays_count == 0) {
@@ -1140,6 +1321,17 @@ int RM_ReplyWithStringBuffer(RedisModuleCtx *ctx, const char *buf, size_t len) {
return REDISMODULE_OK;
}
+/* Reply with a bulk string, taking in input a C buffer pointer that is
+ * assumed to be null-terminated.
+ *
+ * The function always returns REDISMODULE_OK. */
+int RM_ReplyWithCString(RedisModuleCtx *ctx, const char *buf) {
+ client *c = moduleGetReplyClient(ctx);
+ if (c == NULL) return REDISMODULE_OK;
+ addReplyBulkCString(c,(char*)buf);
+ return REDISMODULE_OK;
+}
+
/* Reply with a bulk string, taking in input a RedisModuleString object.
*
* The function always returns REDISMODULE_OK. */
@@ -1157,7 +1349,7 @@ int RM_ReplyWithString(RedisModuleCtx *ctx, RedisModuleString *str) {
int RM_ReplyWithNull(RedisModuleCtx *ctx) {
client *c = moduleGetReplyClient(ctx);
if (c == NULL) return REDISMODULE_OK;
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
return REDISMODULE_OK;
}
@@ -1202,9 +1394,16 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
/* If we already emitted MULTI return ASAP. */
if (ctx->flags & REDISMODULE_CTX_MULTI_EMITTED) return;
/* If this is a thread safe context, we do not want to wrap commands
- * executed into MUTLI/EXEC, they are executed as single commands
+ * executed into MULTI/EXEC, they are executed as single commands
* from an external client in essence. */
if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) return;
+ /* If this is a callback context, and not a module command execution
+ * context, we have to setup the op array for the "also propagate" API
+ * so that RM_Replicate() will work. */
+ if (!(ctx->flags & REDISMODULE_CTX_MODULE_COMMAND_CALL)) {
+ ctx->saved_oparray = server.also_propagate;
+ redisOpArrayInit(&server.also_propagate);
+ }
execCommandPropagateMulti(ctx->client);
ctx->flags |= REDISMODULE_CTX_MULTI_EMITTED;
}
@@ -1226,6 +1425,24 @@ void moduleReplicateMultiIfNeeded(RedisModuleCtx *ctx) {
*
* Please refer to RedisModule_Call() for more information.
*
+ * Using the special "A" and "R" modifiers, the caller can exclude either
+ * the AOF or the replicas from the propagation of the specified command.
+ * Otherwise, by default, the command will be propagated in both channels.
+ *
+ * ## Note about calling this function from a thread safe context:
+ *
+ * Normally when you call this function from the callback implementing a
+ * module command, or any other callback provided by the Redis Module API,
+ * Redis will accumulate all the calls to this function in the context of
+ * the callback, and will propagate all the commands wrapped in a MULTI/EXEC
+ * transaction. However when calling this function from a threaded safe context
+ * that can live an undefined amount of time, and can be locked/unlocked in
+ * at will, the behavior is different: MULTI/EXEC wrapper is not emitted
+ * and the command specified is inserted in the AOF and replication stream
+ * immediately.
+ *
+ * ## Return value
+ *
* The command returns REDISMODULE_ERR if the format specifiers are invalid
* or the command name does not belong to a known command. */
int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
@@ -1243,10 +1460,23 @@ int RM_Replicate(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...)
va_end(ap);
if (argv == NULL) return REDISMODULE_ERR;
- /* Replicate! */
- moduleReplicateMultiIfNeeded(ctx);
- alsoPropagate(cmd,ctx->client->db->id,argv,argc,
- PROPAGATE_AOF|PROPAGATE_REPL);
+ /* Select the propagation target. Usually is AOF + replicas, however
+ * the caller can exclude one or the other using the "A" or "R"
+ * modifiers. */
+ int target = 0;
+ if (!(flags & REDISMODULE_ARGV_NO_AOF)) target |= PROPAGATE_AOF;
+ if (!(flags & REDISMODULE_ARGV_NO_REPLICAS)) target |= PROPAGATE_REPL;
+
+ /* Replicate! When we are in a threaded context, we want to just insert
+ * the replicated command ASAP, since it is not clear when the context
+ * will stop being used, so accumulating stuff does not make much sense,
+ * nor we could easily use the alsoPropagate() API from threads. */
+ if (ctx->flags & REDISMODULE_CTX_THREAD_SAFE) {
+ propagate(cmd,ctx->client->db->id,argv,argc,target);
+ } else {
+ moduleReplicateMultiIfNeeded(ctx);
+ alsoPropagate(cmd,ctx->client->db->id,argv,argc,target);
+ }
/* Release the argv. */
for (j = 0; j < argc; j++) decrRefCount(argv[j]);
@@ -1288,63 +1518,184 @@ int RM_ReplicateVerbatim(RedisModuleCtx *ctx) {
* are guaranteed to get IDs greater than any past ID previously seen.
*
* Valid IDs are from 1 to 2^64-1. If 0 is returned it means there is no way
- * to fetch the ID in the context the function was currently called. */
+ * to fetch the ID in the context the function was currently called.
+ *
+ * After obtaining the ID, it is possible to check if the command execution
+ * is actually happening in the context of AOF loading, using this macro:
+ *
+ * if (RedisModule_IsAOFClient(RedisModule_GetClientId(ctx)) {
+ * // Handle it differently.
+ * }
+ */
unsigned long long RM_GetClientId(RedisModuleCtx *ctx) {
if (ctx->client == NULL) return 0;
return ctx->client->id;
}
+/* This is an helper for RM_GetClientInfoById() and other functions: given
+ * a client, it populates the client info structure with the appropriate
+ * fields depending on the version provided. If the version is not valid
+ * then REDISMODULE_ERR is returned. Otherwise the function returns
+ * REDISMODULE_OK and the structure pointed by 'ci' gets populated. */
+
+int modulePopulateClientInfoStructure(void *ci, client *client, int structver) {
+ if (structver != 1) return REDISMODULE_ERR;
+
+ RedisModuleClientInfoV1 *ci1 = ci;
+ memset(ci1,0,sizeof(*ci1));
+ ci1->version = structver;
+ if (client->flags & CLIENT_MULTI)
+ ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_MULTI;
+ if (client->flags & CLIENT_PUBSUB)
+ ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_PUBSUB;
+ if (client->flags & CLIENT_UNIX_SOCKET)
+ ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET;
+ if (client->flags & CLIENT_TRACKING)
+ ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_TRACKING;
+ if (client->flags & CLIENT_BLOCKED)
+ ci1->flags |= REDISMODULE_CLIENTINFO_FLAG_BLOCKED;
+
+ int port;
+ connPeerToString(client->conn,ci1->addr,sizeof(ci1->addr),&port);
+ ci1->port = port;
+ ci1->db = client->db->id;
+ ci1->id = client->id;
+ return REDISMODULE_OK;
+}
+
+/* Return information about the client with the specified ID (that was
+ * previously obtained via the RedisModule_GetClientId() API). If the
+ * client exists, REDISMODULE_OK is returned, otherwise REDISMODULE_ERR
+ * is returned.
+ *
+ * When the client exist and the `ci` pointer is not NULL, but points to
+ * a structure of type RedisModuleClientInfo, previously initialized with
+ * the correct REDISMODULE_CLIENTINFO_INITIALIZER, the structure is populated
+ * with the following fields:
+ *
+ * uint64_t flags; // REDISMODULE_CLIENTINFO_FLAG_*
+ * uint64_t id; // Client ID
+ * char addr[46]; // IPv4 or IPv6 address.
+ * uint16_t port; // TCP port.
+ * uint16_t db; // Selected DB.
+ *
+ * Note: the client ID is useless in the context of this call, since we
+ * already know, however the same structure could be used in other
+ * contexts where we don't know the client ID, yet the same structure
+ * is returned.
+ *
+ * With flags having the following meaning:
+ *
+ * REDISMODULE_CLIENTINFO_FLAG_SSL Client using SSL connection.
+ * REDISMODULE_CLIENTINFO_FLAG_PUBSUB Client in Pub/Sub mode.
+ * REDISMODULE_CLIENTINFO_FLAG_BLOCKED Client blocked in command.
+ * REDISMODULE_CLIENTINFO_FLAG_TRACKING Client with keys tracking on.
+ * REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET Client using unix domain socket.
+ * REDISMODULE_CLIENTINFO_FLAG_MULTI Client in MULTI state.
+ *
+ * However passing NULL is a way to just check if the client exists in case
+ * we are not interested in any additional information.
+ *
+ * This is the correct usage when we want the client info structure
+ * returned:
+ *
+ * RedisModuleClientInfo ci = REDISMODULE_CLIENTINFO_INITIALIZER;
+ * int retval = RedisModule_GetClientInfoById(&ci,client_id);
+ * if (retval == REDISMODULE_OK) {
+ * printf("Address: %s\n", ci.addr);
+ * }
+ */
+int RM_GetClientInfoById(void *ci, uint64_t id) {
+ client *client = lookupClientByID(id);
+ if (client == NULL) return REDISMODULE_ERR;
+ if (ci == NULL) return REDISMODULE_OK;
+
+ /* Fill the info structure if passed. */
+ uint64_t structver = ((uint64_t*)ci)[0];
+ return modulePopulateClientInfoStructure(ci,client,structver);
+}
+
/* Return the currently selected DB. */
int RM_GetSelectedDb(RedisModuleCtx *ctx) {
return ctx->client->db->id;
}
-/* Return the current context's flags. The flags provide information on the
+/* Return the current context's flags. The flags provide information on the
* current request context (whether the client is a Lua script or in a MULTI),
- * and about the Redis instance in general, i.e replication and persistence.
- *
+ * and about the Redis instance in general, i.e replication and persistence.
+ *
* The available flags are:
- *
+ *
* * REDISMODULE_CTX_FLAGS_LUA: The command is running in a Lua script
- *
+ *
* * REDISMODULE_CTX_FLAGS_MULTI: The command is running inside a transaction
- *
+ *
+ * * REDISMODULE_CTX_FLAGS_REPLICATED: The command was sent over the replication
+ * link by the MASTER
+ *
* * REDISMODULE_CTX_FLAGS_MASTER: The Redis instance is a master
- *
+ *
* * REDISMODULE_CTX_FLAGS_SLAVE: The Redis instance is a slave
- *
+ *
* * REDISMODULE_CTX_FLAGS_READONLY: The Redis instance is read-only
- *
+ *
* * REDISMODULE_CTX_FLAGS_CLUSTER: The Redis instance is in cluster mode
- *
+ *
* * REDISMODULE_CTX_FLAGS_AOF: The Redis instance has AOF enabled
- *
+ *
* * REDISMODULE_CTX_FLAGS_RDB: The instance has RDB enabled
- *
+ *
* * REDISMODULE_CTX_FLAGS_MAXMEMORY: The instance has Maxmemory set
- *
+ *
* * REDISMODULE_CTX_FLAGS_EVICT: Maxmemory is set and has an eviction
* policy that may delete keys
+ *
+ * * REDISMODULE_CTX_FLAGS_OOM: Redis is out of memory according to the
+ * maxmemory setting.
+ *
+ * * REDISMODULE_CTX_FLAGS_OOM_WARNING: Less than 25% of memory remains before
+ * reaching the maxmemory level.
+ *
+ * * REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE: No active link with the master.
+ *
+ * * REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING: The replica is trying to
+ * connect with the master.
+ *
+ * * REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING: Master -> Replica RDB
+ * transfer is in progress.
+ *
+ * * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE: The replica has an active link
+ * with its master. This is the
+ * contrary of STALE state.
+ *
+ * * REDISMODULE_CTX_FLAGS_ACTIVE_CHILD: There is currently some background
+ * process active (RDB, AUX or module).
*/
int RM_GetContextFlags(RedisModuleCtx *ctx) {
-
+
int flags = 0;
/* Client specific flags */
if (ctx->client) {
- if (ctx->client->flags & CLIENT_LUA)
+ if (ctx->client->flags & CLIENT_LUA)
flags |= REDISMODULE_CTX_FLAGS_LUA;
- if (ctx->client->flags & CLIENT_MULTI)
+ if (ctx->client->flags & CLIENT_MULTI)
flags |= REDISMODULE_CTX_FLAGS_MULTI;
+ /* Module command recieved from MASTER, is replicated. */
+ if (ctx->client->flags & CLIENT_MASTER)
+ flags |= REDISMODULE_CTX_FLAGS_REPLICATED;
}
if (server.cluster_enabled)
flags |= REDISMODULE_CTX_FLAGS_CLUSTER;
-
+
+ if (server.loading)
+ flags |= REDISMODULE_CTX_FLAGS_LOADING;
+
/* Maxmemory and eviction policy */
if (server.maxmemory > 0) {
flags |= REDISMODULE_CTX_FLAGS_MAXMEMORY;
-
+
if (server.maxmemory_policy != MAXMEMORY_NO_EVICTION)
flags |= REDISMODULE_CTX_FLAGS_EVICT;
}
@@ -1362,8 +1713,31 @@ int RM_GetContextFlags(RedisModuleCtx *ctx) {
flags |= REDISMODULE_CTX_FLAGS_SLAVE;
if (server.repl_slave_ro)
flags |= REDISMODULE_CTX_FLAGS_READONLY;
+
+ /* Replica state flags. */
+ if (server.repl_state == REPL_STATE_CONNECT ||
+ server.repl_state == REPL_STATE_CONNECTING)
+ {
+ flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING;
+ } else if (server.repl_state == REPL_STATE_TRANSFER) {
+ flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING;
+ } else if (server.repl_state == REPL_STATE_CONNECTED) {
+ flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE;
+ }
+
+ if (server.repl_state != REPL_STATE_CONNECTED)
+ flags |= REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE;
}
-
+
+ /* OOM flag. */
+ float level;
+ int retval = getMaxmemoryState(NULL,NULL,NULL,&level);
+ if (retval == C_ERR) flags |= REDISMODULE_CTX_FLAGS_OOM;
+ if (level > 0.75) flags |= REDISMODULE_CTX_FLAGS_OOM_WARNING;
+
+ /* Presence of children processes. */
+ if (hasActiveChildProcess()) flags |= REDISMODULE_CTX_FLAGS_ACTIVE_CHILD;
+
return flags;
}
@@ -1386,7 +1760,7 @@ int RM_SelectDb(RedisModuleCtx *ctx, int newid) {
* to call other APIs with the key handle as argument to perform
* operations on the key.
*
- * The return value is the handle repesenting the key, that must be
+ * The return value is the handle representing the key, that must be
* closed with RM_CloseKey().
*
* If the key does not exist and WRITE mode is requested, the handle
@@ -1640,7 +2014,7 @@ int RM_StringTruncate(RedisModuleKey *key, size_t newlen) {
* Key API for List type
* -------------------------------------------------------------------------- */
-/* Push an element into a list, on head or tail depending on 'where' argumnet.
+/* Push an element into a list, on head or tail depending on 'where' argument.
* If the key pointer is about an empty key opened for writing, the key
* is created. On error (key opened for read-only operations or of the wrong
* type) REDISMODULE_ERR is returned, otherwise REDISMODULE_OK is returned. */
@@ -1745,7 +2119,7 @@ int RM_ZsetAdd(RedisModuleKey *key, double score, RedisModuleString *ele, int *f
* The input and output flags, and the return value, have the same exact
* meaning, with the only difference that this function will return
* REDISMODULE_ERR even when 'score' is a valid double number, but adding it
- * to the existing score resuts into a NaN (not a number) condition.
+ * to the existing score results into a NaN (not a number) condition.
*
* This function has an additional field 'newscore', if not NULL is filled
* with the new score of the element after the increment, if no error
@@ -2126,7 +2500,9 @@ int RM_ZsetRangePrev(RedisModuleKey *key) {
*
* The function is variadic and the user must specify pairs of field
* names and values, both as RedisModuleString pointers (unless the
- * CFIELD option is set, see later).
+ * CFIELD option is set, see later). At the end of the field/value-ptr pairs,
+ * NULL must be specified as last argument to signal the end of the arguments
+ * in the variadic function.
*
* Example to set the hash argv[1] to the value argv[2]:
*
@@ -2215,6 +2591,9 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
* to avoid a useless copy. */
if (flags & REDISMODULE_HASH_CFIELDS)
low_flags |= HASH_SET_TAKE_FIELD;
+
+ robj *argv[2] = {field,value};
+ hashTypeTryConversion(key->value,argv,0,1);
updated += hashTypeSet(key->value, field->ptr, value->ptr, low_flags);
/* If CFIELDS is active, SDS string ownership is now of hashTypeSet(),
@@ -2249,7 +2628,7 @@ int RM_HashSet(RedisModuleKey *key, int flags, ...) {
*
* REDISMODULE_HASH_EXISTS: instead of setting the value of the field
* expecting a RedisModuleString pointer to pointer, the function just
- * reports if the field esists or not and expects an integer pointer
+ * reports if the field exists or not and expects an integer pointer
* as the second element of each pair.
*
* Example of REDISMODULE_HASH_CFIELD:
@@ -2538,12 +2917,11 @@ RedisModuleString *RM_CreateStringFromCallReply(RedisModuleCallReply *reply) {
* to special modifiers in "fmt". For now only one exists:
*
* "!" -> REDISMODULE_ARGV_REPLICATE
+ * "A" -> REDISMODULE_ARGV_NO_AOF
+ * "R" -> REDISMODULE_ARGV_NO_REPLICAS
*
* On error (format specifier error) NULL is returned and nothing is
* allocated. On success the argument vector is returned. */
-
-#define REDISMODULE_ARGV_REPLICATE (1<<0)
-
robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int *argcp, int *flags, va_list ap) {
int argc = 0, argv_size, j;
robj **argv = NULL;
@@ -2572,7 +2950,7 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
size_t len = va_arg(ap,size_t);
argv[argc++] = createStringObject(buf,len);
} else if (*p == 'l') {
- long ll = va_arg(ap,long long);
+ long long ll = va_arg(ap,long long);
argv[argc++] = createObject(OBJ_STRING,sdsfromlonglong(ll));
} else if (*p == 'v') {
/* A vector of strings */
@@ -2592,6 +2970,10 @@ robj **moduleCreateArgvFromUserFormat(const char *cmdname, const char *fmt, int
}
} else if (*p == '!') {
if (flags) (*flags) |= REDISMODULE_ARGV_REPLICATE;
+ } else if (*p == 'A') {
+ if (flags) (*flags) |= REDISMODULE_ARGV_NO_AOF;
+ } else if (*p == 'R') {
+ if (flags) (*flags) |= REDISMODULE_ARGV_NO_REPLICAS;
} else {
goto fmterr;
}
@@ -2612,7 +2994,10 @@ fmterr:
* NULL is returned and errno is set to the following values:
*
* EINVAL: command non existing, wrong arity, wrong format specifier.
- * EPERM: operation in Cluster instance with key in non local slot. */
+ * EPERM: operation in Cluster instance with key in non local slot.
+ *
+ * This API is documented here: https://redis.io/topics/modules-intro
+ */
RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const char *fmt, ...) {
struct redisCommand *cmd;
client *c = NULL;
@@ -2622,15 +3007,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
RedisModuleCallReply *reply = NULL;
int replicate = 0; /* Replicate this command? */
- cmd = lookupCommandByCString((char*)cmdname);
- if (!cmd) {
- errno = EINVAL;
- return NULL;
- }
-
/* Create the client and dispatch the command. */
va_start(ap, fmt);
- c = createClient(-1);
+ c = createClient(NULL);
+ c->user = NULL; /* Root user. */
argv = moduleCreateArgvFromUserFormat(cmdname,fmt,&argc,&flags,ap);
replicate = flags & REDISMODULE_ARGV_REPLICATE;
va_end(ap);
@@ -2640,11 +3020,25 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
c->db = ctx->client->db;
c->argv = argv;
c->argc = argc;
- c->cmd = c->lastcmd = cmd;
+ if (ctx->module) ctx->module->in_call++;
+
/* We handle the above format error only when the client is setup so that
* we can free it normally. */
if (argv == NULL) goto cleanup;
+ /* Call command filters */
+ moduleCallCommandFilters(c);
+
+ /* Lookup command now, after filters had a chance to make modifications
+ * if necessary.
+ */
+ cmd = lookupCommand(c->argv[0]->ptr);
+ if (!cmd) {
+ errno = EINVAL;
+ goto cleanup;
+ }
+ c->cmd = c->lastcmd = cmd;
+
/* Basic arity checks. */
if ((cmd->arity > 0 && cmd->arity != argc) || (argc < -cmd->arity)) {
errno = EINVAL;
@@ -2674,8 +3068,10 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
/* Run the command */
int call_flags = CMD_CALL_SLOWLOG | CMD_CALL_STATS;
if (replicate) {
- call_flags |= CMD_CALL_PROPAGATE_AOF;
- call_flags |= CMD_CALL_PROPAGATE_REPL;
+ if (!(flags & REDISMODULE_ARGV_NO_AOF))
+ call_flags |= CMD_CALL_PROPAGATE_AOF;
+ if (!(flags & REDISMODULE_ARGV_NO_REPLICAS))
+ call_flags |= CMD_CALL_PROPAGATE_REPL;
}
call(c,call_flags);
@@ -2685,15 +3081,16 @@ RedisModuleCallReply *RM_Call(RedisModuleCtx *ctx, const char *cmdname, const ch
sds proto = sdsnewlen(c->buf,c->bufpos);
c->bufpos = 0;
while(listLength(c->reply)) {
- sds o = listNodeValue(listFirst(c->reply));
+ clientReplyBlock *o = listNodeValue(listFirst(c->reply));
- proto = sdscatsds(proto,o);
+ proto = sdscatlen(proto,o->buf,o->used);
listDelNode(c->reply,listFirst(c->reply));
}
reply = moduleCreateCallReplyFromProto(ctx,proto);
autoMemoryAdd(ctx,REDISMODULE_AM_REPLY,reply);
cleanup:
+ if (ctx->module) ctx->module->in_call--;
freeClient(c);
return reply;
}
@@ -2929,6 +3326,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest;
moduleTypeFreeFunc free;
+ struct {
+ moduleTypeAuxLoadFunc aux_load;
+ moduleTypeAuxSaveFunc aux_save;
+ int aux_save_triggers;
+ } v2;
} *tms = (struct typemethods*) typemethods_ptr;
moduleType *mt = zcalloc(sizeof(*mt));
@@ -2940,6 +3342,11 @@ moduleType *RM_CreateDataType(RedisModuleCtx *ctx, const char *name, int encver,
mt->mem_usage = tms->mem_usage;
mt->digest = tms->digest;
mt->free = tms->free;
+ if (tms->version >= 2) {
+ mt->aux_load = tms->v2.aux_load;
+ mt->aux_save = tms->v2.aux_save;
+ mt->aux_save_triggers = tms->v2.aux_save_triggers;
+ }
memcpy(mt->name,name,sizeof(mt->name));
listAddNodeTail(ctx->module->types,mt);
return mt;
@@ -2960,7 +3367,7 @@ int RM_ModuleTypeSetValue(RedisModuleKey *key, moduleType *mt, void *value) {
}
/* Assuming RedisModule_KeyType() returned REDISMODULE_KEYTYPE_MODULE on
- * the key, returns the moduel type pointer of the value stored at key.
+ * the key, returns the module type pointer of the value stored at key.
*
* If the key is NULL, is not associated with a module type, or is empty,
* then NULL is returned instead. */
@@ -2990,9 +3397,14 @@ void *RM_ModuleTypeGetValue(RedisModuleKey *key) {
* RDB loading and saving functions
* -------------------------------------------------------------------------- */
-/* Called when there is a load error in the context of a module. This cannot
- * be recovered like for the built-in types. */
+/* Called when there is a load error in the context of a module. On some
+ * modules this cannot be recovered, but if the module declared capability
+ * to handle errors, we'll raise a flag rather than exiting. */
void moduleRDBLoadError(RedisModuleIO *io) {
+ if (io->type->module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS) {
+ io->error = 1;
+ return;
+ }
serverLog(LL_WARNING,
"Error loading data from RDB (short read or EOF). "
"Read performed by module '%s' about type '%s' "
@@ -3003,6 +3415,33 @@ void moduleRDBLoadError(RedisModuleIO *io) {
exit(1);
}
+/* Returns 0 if there's at least one registered data type that did not declare
+ * REDISMODULE_OPTIONS_HANDLE_IO_ERRORS, in which case diskless loading should
+ * be avoided since it could cause data loss. */
+int moduleAllDatatypesHandleErrors() {
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ struct RedisModule *module = dictGetVal(de);
+ if (listLength(module->types) &&
+ !(module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS))
+ {
+ dictReleaseIterator(di);
+ return 0;
+ }
+ }
+ dictReleaseIterator(di);
+ return 1;
+}
+
+/* Returns true if any previous IO API failed.
+ * for Load* APIs the REDISMODULE_OPTIONS_HANDLE_IO_ERRORS flag must be set with
+ * RediModule_SetModuleOptions first. */
+int RM_IsIOError(RedisModuleIO *io) {
+ return io->error;
+}
+
/* Save an unsigned 64 bit value into the RDB file. This function should only
* be called in the context of the rdb_save method of modules implementing new
* data types. */
@@ -3026,6 +3465,7 @@ saveerr:
* be called in the context of the rdb_load method of modules implementing
* new data types. */
uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
+ if (io->error) return 0;
if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_UINT) goto loaderr;
@@ -3037,7 +3477,7 @@ uint64_t RM_LoadUnsigned(RedisModuleIO *io) {
loaderr:
moduleRDBLoadError(io);
- return 0; /* Never reached. */
+ return 0;
}
/* Like RedisModule_SaveUnsigned() but for signed 64 bit values. */
@@ -3096,6 +3536,7 @@ saveerr:
/* Implements RM_LoadString() and RM_LoadStringBuffer() */
void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
+ if (io->error) return NULL;
if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_STRING) goto loaderr;
@@ -3107,7 +3548,7 @@ void *moduleLoadString(RedisModuleIO *io, int plain, size_t *lenptr) {
loaderr:
moduleRDBLoadError(io);
- return NULL; /* Never reached. */
+ return NULL;
}
/* In the context of the rdb_load method of a module data type, loads a string
@@ -3128,7 +3569,7 @@ RedisModuleString *RM_LoadString(RedisModuleIO *io) {
* RedisModule_Realloc() or RedisModule_Free().
*
* The size of the string is stored at '*lenptr' if not NULL.
- * The returned string is not automatically NULL termianted, it is loaded
+ * The returned string is not automatically NULL terminated, it is loaded
* exactly as it was stored inisde the RDB file. */
char *RM_LoadStringBuffer(RedisModuleIO *io, size_t *lenptr) {
return moduleLoadString(io,1,lenptr);
@@ -3156,6 +3597,7 @@ saveerr:
/* In the context of the rdb_save method of a module data type, loads back the
* double value saved by RedisModule_SaveDouble(). */
double RM_LoadDouble(RedisModuleIO *io) {
+ if (io->error) return 0;
if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_DOUBLE) goto loaderr;
@@ -3167,7 +3609,7 @@ double RM_LoadDouble(RedisModuleIO *io) {
loaderr:
moduleRDBLoadError(io);
- return 0; /* Never reached. */
+ return 0;
}
/* In the context of the rdb_save method of a module data type, saves a float
@@ -3192,6 +3634,7 @@ saveerr:
/* In the context of the rdb_save method of a module data type, loads back the
* float value saved by RedisModule_SaveFloat(). */
float RM_LoadFloat(RedisModuleIO *io) {
+ if (io->error) return 0;
if (io->ver == 2) {
uint64_t opcode = rdbLoadLen(io->rio,NULL);
if (opcode != RDB_MODULE_OPCODE_FLOAT) goto loaderr;
@@ -3203,7 +3646,37 @@ float RM_LoadFloat(RedisModuleIO *io) {
loaderr:
moduleRDBLoadError(io);
- return 0; /* Never reached. */
+ return 0;
+}
+
+/* Iterate over modules, and trigger rdb aux saving for the ones modules types
+ * who asked for it. */
+ssize_t rdbSaveModulesAux(rio *rdb, int when) {
+ size_t total_written = 0;
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ struct RedisModule *module = dictGetVal(de);
+ listIter li;
+ listNode *ln;
+
+ listRewind(module->types,&li);
+ while((ln = listNext(&li))) {
+ moduleType *mt = ln->value;
+ if (!mt->aux_save || !(mt->aux_save_triggers & when))
+ continue;
+ ssize_t ret = rdbSaveSingleModuleAux(rdb, when, mt);
+ if (ret==-1) {
+ dictReleaseIterator(di);
+ return -1;
+ }
+ total_written += ret;
+ }
+ }
+
+ dictReleaseIterator(di);
+ return total_written;
}
/* --------------------------------------------------------------------------
@@ -3260,7 +3733,7 @@ void RM_DigestAddLongLong(RedisModuleDigest *md, long long ll) {
mixDigest(md->o,buf,len);
}
-/* See the doucmnetation for `RedisModule_DigestAddElement()`. */
+/* See the documentation for `RedisModule_DigestAddElement()`. */
void RM_DigestEndSequence(RedisModuleDigest *md) {
xorDigest(md->x,md->o,sizeof(md->o));
memset(md->o,0,sizeof(md->o));
@@ -3335,6 +3808,14 @@ RedisModuleCtx *RM_GetContextFromIO(RedisModuleIO *io) {
return io->ctx;
}
+/* Returns a RedisModuleString with the name of the key currently saving or
+ * loading, when an IO data type callback is called. There is no guarantee
+ * that the key name is always available, so this may return NULL.
+ */
+const RedisModuleString *RM_GetKeyNameFromIO(RedisModuleIO *io) {
+ return io->key;
+}
+
/* --------------------------------------------------------------------------
* Logging
* -------------------------------------------------------------------------- */
@@ -3356,7 +3837,9 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
else if (!strcasecmp(levelstr,"warning")) level = LL_WARNING;
else level = LL_VERBOSE; /* Default. */
- name_len = snprintf(msg, sizeof(msg),"<%s> ", module->name);
+ if (level < server.verbosity) return;
+
+ name_len = snprintf(msg, sizeof(msg),"<%s> ", module? module->name: "module");
vsnprintf(msg + name_len, sizeof(msg) - name_len, fmt, ap);
serverLogRaw(level,msg);
}
@@ -3372,15 +3855,17 @@ void RM_LogRaw(RedisModule *module, const char *levelstr, const char *fmt, va_li
*
* If the specified log level is invalid, verbose is used by default.
* There is a fixed limit to the length of the log line this function is able
- * to emit, this limti is not specified but is guaranteed to be more than
+ * to emit, this limit is not specified but is guaranteed to be more than
* a few lines of text.
+ *
+ * The ctx argument may be NULL if cannot be provided in the context of the
+ * caller for instance threads or callbacks, in which case a generic "module"
+ * will be used instead of the module name.
*/
void RM_Log(RedisModuleCtx *ctx, const char *levelstr, const char *fmt, ...) {
- if (!ctx->module) return; /* Can only log if module is initialized */
-
va_list ap;
va_start(ap, fmt);
- RM_LogRaw(ctx->module,levelstr,fmt,ap);
+ RM_LogRaw(ctx? ctx->module: NULL,levelstr,fmt,ap);
va_end(ap);
}
@@ -3396,6 +3881,15 @@ void RM_LogIOError(RedisModuleIO *io, const char *levelstr, const char *fmt, ...
va_end(ap);
}
+/* Redis-like assert function.
+ *
+ * A failed assertion will shut down the server and produce logging information
+ * that looks identical to information generated by Redis itself.
+ */
+void RM__Assert(const char *estr, const char *file, int line) {
+ _serverAssert(estr, file, line);
+}
+
/* --------------------------------------------------------------------------
* Blocking clients from modules
* -------------------------------------------------------------------------- */
@@ -3425,6 +3919,17 @@ void moduleBlockedClientPipeReadable(aeEventLoop *el, int fd, void *privdata, in
* running the list of clients blocked by a module that need to be unblocked. */
void unblockClientFromModule(client *c) {
RedisModuleBlockedClient *bc = c->bpop.module_blocked_handle;
+
+ /* Call the disconnection callback if any. */
+ if (bc->disconnect_callback) {
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ ctx.blocked_privdata = bc->privdata;
+ ctx.module = bc->module;
+ ctx.client = bc->client;
+ bc->disconnect_callback(&ctx,bc);
+ moduleFreeContext(&ctx);
+ }
+
bc->client = NULL;
/* Reset the client for a new query since, for blocking commands implemented
* into modules, we do not it immediately after the command returns (and
@@ -3446,10 +3951,10 @@ void unblockClientFromModule(client *c) {
* reply_timeout: called when the timeout is reached in order to send an
* error to the client.
*
- * free_privdata: called in order to free the privata data that is passed
+ * free_privdata: called in order to free the private data that is passed
* by RedisModule_UnblockClient() call.
*/
-RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms) {
+RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms) {
client *c = ctx->client;
int islua = c->flags & CLIENT_LUA;
int ismulti = c->flags & CLIENT_MULTI;
@@ -3465,9 +3970,10 @@ RedisModuleBlockedClient *RM_BlockClient(RedisModuleCtx *ctx, RedisModuleCmdFunc
bc->module = ctx->module;
bc->reply_callback = reply_callback;
bc->timeout_callback = timeout_callback;
+ bc->disconnect_callback = NULL; /* Set by RM_SetDisconnectCallback() */
bc->free_privdata = free_privdata;
bc->privdata = NULL;
- bc->reply_client = createClient(-1);
+ bc->reply_client = createClient(NULL);
bc->reply_client->flags |= CLIENT_MODULE;
bc->dbid = c->db->id;
c->bpop.timeout = timeout_ms ? (mstime()+timeout_ms) : 0;
@@ -3506,12 +4012,33 @@ int RM_UnblockClient(RedisModuleBlockedClient *bc, void *privdata) {
}
/* Abort a blocked client blocking operation: the client will be unblocked
- * without firing the reply callback. */
+ * without firing any callback. */
int RM_AbortBlock(RedisModuleBlockedClient *bc) {
bc->reply_callback = NULL;
+ bc->disconnect_callback = NULL;
return RM_UnblockClient(bc,NULL);
}
+/* Set a callback that will be called if a blocked client disconnects
+ * before the module has a chance to call RedisModule_UnblockClient()
+ *
+ * Usually what you want to do there, is to cleanup your module state
+ * so that you can call RedisModule_UnblockClient() safely, otherwise
+ * the client will remain blocked forever if the timeout is large.
+ *
+ * Notes:
+ *
+ * 1. It is not safe to call Reply* family functions here, it is also
+ * useless since the client is gone.
+ *
+ * 2. This callback is not called if the client disconnects because of
+ * a timeout. In such a case, the client is unblocked automatically
+ * and the timeout callback is called.
+ */
+void RM_SetDisconnectCallback(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback) {
+ bc->disconnect_callback = callback;
+}
+
/* This function will check the moduleUnblockedClients queue in order to
* call the reply callback and really unblock the client.
*
@@ -3547,30 +4074,36 @@ void moduleHandleBlockedClients(void) {
ctx.blocked_privdata = bc->privdata;
ctx.module = bc->module;
ctx.client = bc->client;
+ ctx.blocked_client = bc;
bc->reply_callback(&ctx,(void**)c->argv,c->argc);
moduleHandlePropagationAfterCommandCallback(&ctx);
moduleFreeContext(&ctx);
}
/* Free privdata if any. */
- if (bc->privdata && bc->free_privdata)
- bc->free_privdata(bc->privdata);
+ if (bc->privdata && bc->free_privdata) {
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ if (c == NULL)
+ ctx.flags |= REDISMODULE_CTX_BLOCKED_DISCONNECTED;
+ ctx.blocked_privdata = bc->privdata;
+ ctx.module = bc->module;
+ ctx.client = bc->client;
+ bc->free_privdata(&ctx,bc->privdata);
+ moduleFreeContext(&ctx);
+ }
/* It is possible that this blocked client object accumulated
* replies to send to the client in a thread safe context.
* We need to glue such replies to the client output buffer and
* free the temporary client we just used for the replies. */
- if (c) {
- if (bc->reply_client->bufpos)
- addReplyString(c,bc->reply_client->buf,
- bc->reply_client->bufpos);
- if (listLength(bc->reply_client->reply))
- listJoin(c->reply,bc->reply_client->reply);
- c->reply_bytes += bc->reply_client->reply_bytes;
- }
+ if (c) AddReplyFromClient(c, bc->reply_client);
freeClient(bc->reply_client);
if (c != NULL) {
+ /* Before unblocking the client, set the disconnect callback
+ * to NULL, because if we reached this point, the client was
+ * properly unblocked by the module. */
+ bc->disconnect_callback = NULL;
unblockClient(c);
/* Put the client in the list of clients that need to write
* if there are pending replies here. This is needed since
@@ -3604,8 +4137,13 @@ void moduleBlockedClientTimedOut(client *c) {
ctx.flags |= REDISMODULE_CTX_BLOCKED_TIMEOUT;
ctx.module = bc->module;
ctx.client = bc->client;
+ ctx.blocked_client = bc;
bc->timeout_callback(&ctx,(void**)c->argv,c->argc);
moduleFreeContext(&ctx);
+ /* For timeout events, we do not want to call the disconnect callback,
+ * because the blocked client will be automatically disconnected in
+ * this case, and the user can still hook using the timeout callback. */
+ bc->disconnect_callback = NULL;
}
/* Return non-zero if a module command was called in order to fill the
@@ -3620,11 +4158,26 @@ int RM_IsBlockedTimeoutRequest(RedisModuleCtx *ctx) {
return (ctx->flags & REDISMODULE_CTX_BLOCKED_TIMEOUT) != 0;
}
-/* Get the privata data set by RedisModule_UnblockClient() */
+/* Get the private data set by RedisModule_UnblockClient() */
void *RM_GetBlockedClientPrivateData(RedisModuleCtx *ctx) {
return ctx->blocked_privdata;
}
+/* Get the blocked client associated with a given context.
+ * This is useful in the reply and timeout callbacks of blocked clients,
+ * before sometimes the module has the blocked client handle references
+ * around, and wants to cleanup it. */
+RedisModuleBlockedClient *RM_GetBlockedClientHandle(RedisModuleCtx *ctx) {
+ return ctx->blocked_client;
+}
+
+/* Return true if when the free callback of a blocked client is called,
+ * the reason for the client to be unblocked is that it disconnected
+ * while it was blocked. */
+int RM_BlockedClientDisconnected(RedisModuleCtx *ctx) {
+ return (ctx->flags & REDISMODULE_CTX_BLOCKED_DISCONNECTED) != 0;
+}
+
/* --------------------------------------------------------------------------
* Thread Safe Contexts
* -------------------------------------------------------------------------- */
@@ -3661,8 +4214,11 @@ RedisModuleCtx *RM_GetThreadSafeContext(RedisModuleBlockedClient *bc) {
* access it safely from another thread, so we create a fake client here
* in order to keep things like the currently selected database and similar
* things. */
- ctx->client = createClient(-1);
- if (bc) selectDb(ctx->client,bc->dbid);
+ ctx->client = createClient(NULL);
+ if (bc) {
+ selectDb(ctx->client,bc->dbid);
+ ctx->client->id = bc->client->id;
+ }
return ctx;
}
@@ -3676,13 +4232,13 @@ void RM_FreeThreadSafeContext(RedisModuleCtx *ctx) {
* This is not needed for `RedisModule_Reply*` calls when there is
* a blocked client connected to the thread safe context. */
void RM_ThreadSafeContextLock(RedisModuleCtx *ctx) {
- DICT_NOTUSED(ctx);
+ UNUSED(ctx);
moduleAcquireGIL();
}
/* Release the server lock after a thread safe API call was executed. */
void RM_ThreadSafeContextUnlock(RedisModuleCtx *ctx) {
- DICT_NOTUSED(ctx);
+ UNUSED(ctx);
moduleReleaseGIL();
}
@@ -3700,11 +4256,11 @@ void moduleReleaseGIL(void) {
* -------------------------------------------------------------------------- */
/* Subscribe to keyspace notifications. This is a low-level version of the
- * keyspace-notifications API. A module cand register callbacks to be notified
+ * keyspace-notifications API. A module can register callbacks to be notified
* when keyspce events occur.
*
* Notification events are filtered by their type (string events, set events,
- * etc), and the subsriber callback receives only events that match a specific
+ * etc), and the subscriber callback receives only events that match a specific
* mask of event types.
*
* When subscribing to notifications with RedisModule_SubscribeToKeyspaceEvents
@@ -3737,9 +4293,9 @@ void moduleReleaseGIL(void) {
*
* Notification callback gets executed with a redis context that can not be
* used to send anything to the client, and has the db number where the event
- * occured as its selected db number.
+ * occurred as its selected db number.
*
- * Notice that it is not necessary to enable norifications in redis.conf for
+ * Notice that it is not necessary to enable notifications in redis.conf for
* module notifications to work.
*
* Warning: the notification callbacks are performed in a synchronous manner,
@@ -3780,10 +4336,10 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
if ((sub->event_mask & type) && sub->active == 0) {
RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
ctx.module = sub->module;
- ctx.client = moduleKeyspaceSubscribersClient;
+ ctx.client = moduleFreeContextReusedClient;
selectDb(ctx.client, dbid);
- /* mark the handler as activer to avoid reentrant loops.
+ /* mark the handler as active to avoid reentrant loops.
* If the subscriber performs an action triggering itself,
* it will not be notified about it. */
sub->active = 1;
@@ -3794,7 +4350,7 @@ void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid)
}
}
-/* Unsubscribe any notification subscirbers this module has upon unloading */
+/* Unsubscribe any notification subscribers this module has upon unloading */
void moduleUnsubscribeNotifications(RedisModule *module) {
listIter li;
listNode *ln;
@@ -3809,6 +4365,1566 @@ void moduleUnsubscribeNotifications(RedisModule *module) {
}
/* --------------------------------------------------------------------------
+ * Modules Cluster API
+ * -------------------------------------------------------------------------- */
+
+/* The Cluster message callback function pointer type. */
+typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
+
+/* This structure identifies a registered caller: it must match a given module
+ * ID, for a given message type. The callback function is just the function
+ * that was registered as receiver. */
+typedef struct moduleClusterReceiver {
+ uint64_t module_id;
+ RedisModuleClusterMessageReceiver callback;
+ struct RedisModule *module;
+ struct moduleClusterReceiver *next;
+} moduleClusterReceiver;
+
+typedef struct moduleClusterNodeInfo {
+ int flags;
+ char ip[NET_IP_STR_LEN];
+ int port;
+ char master_id[40]; /* Only if flags & REDISMODULE_NODE_MASTER is true. */
+} mdouleClusterNodeInfo;
+
+/* We have an array of message types: each bucket is a linked list of
+ * configured receivers. */
+static moduleClusterReceiver *clusterReceivers[UINT8_MAX];
+
+/* Dispatch the message to the right module receiver. */
+void moduleCallClusterReceivers(const char *sender_id, uint64_t module_id, uint8_t type, const unsigned char *payload, uint32_t len) {
+ moduleClusterReceiver *r = clusterReceivers[type];
+ while(r) {
+ if (r->module_id == module_id) {
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ ctx.module = r->module;
+ ctx.client = moduleFreeContextReusedClient;
+ selectDb(ctx.client, 0);
+ r->callback(&ctx,sender_id,type,payload,len);
+ moduleFreeContext(&ctx);
+ return;
+ }
+ r = r->next;
+ }
+}
+
+/* Register a callback receiver for cluster messages of type 'type'. If there
+ * was already a registered callback, this will replace the callback function
+ * with the one provided, otherwise if the callback is set to NULL and there
+ * is already a callback for this function, the callback is unregistered
+ * (so this API call is also used in order to delete the receiver). */
+void RM_RegisterClusterMessageReceiver(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback) {
+ if (!server.cluster_enabled) return;
+
+ uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0);
+ moduleClusterReceiver *r = clusterReceivers[type], *prev = NULL;
+ while(r) {
+ if (r->module_id == module_id) {
+ /* Found! Set or delete. */
+ if (callback) {
+ r->callback = callback;
+ } else {
+ /* Delete the receiver entry if the user is setting
+ * it to NULL. Just unlink the receiver node from the
+ * linked list. */
+ if (prev)
+ prev->next = r->next;
+ else
+ clusterReceivers[type]->next = r->next;
+ zfree(r);
+ }
+ return;
+ }
+ prev = r;
+ r = r->next;
+ }
+
+ /* Not found, let's add it. */
+ if (callback) {
+ r = zmalloc(sizeof(*r));
+ r->module_id = module_id;
+ r->module = ctx->module;
+ r->callback = callback;
+ r->next = clusterReceivers[type];
+ clusterReceivers[type] = r;
+ }
+}
+
+/* Send a message to all the nodes in the cluster if `target` is NULL, otherwise
+ * at the specified target, which is a REDISMODULE_NODE_ID_LEN bytes node ID, as
+ * returned by the receiver callback or by the nodes iteration functions.
+ *
+ * The function returns REDISMODULE_OK if the message was successfully sent,
+ * otherwise if the node is not connected or such node ID does not map to any
+ * known cluster node, REDISMODULE_ERR is returned. */
+int RM_SendClusterMessage(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len) {
+ if (!server.cluster_enabled) return REDISMODULE_ERR;
+ uint64_t module_id = moduleTypeEncodeId(ctx->module->name,0);
+ if (clusterSendModuleMessageToTarget(target_id,module_id,type,msg,len) == C_OK)
+ return REDISMODULE_OK;
+ else
+ return REDISMODULE_ERR;
+}
+
+/* Return an array of string pointers, each string pointer points to a cluster
+ * node ID of exactly REDISMODULE_NODE_ID_SIZE bytes (without any null term).
+ * The number of returned node IDs is stored into `*numnodes`.
+ * However if this function is called by a module not running an a Redis
+ * instance with Redis Cluster enabled, NULL is returned instead.
+ *
+ * The IDs returned can be used with RedisModule_GetClusterNodeInfo() in order
+ * to get more information about single nodes.
+ *
+ * The array returned by this function must be freed using the function
+ * RedisModule_FreeClusterNodesList().
+ *
+ * Example:
+ *
+ * size_t count, j;
+ * char **ids = RedisModule_GetClusterNodesList(ctx,&count);
+ * for (j = 0; j < count; j++) {
+ * RedisModule_Log("notice","Node %.*s",
+ * REDISMODULE_NODE_ID_LEN,ids[j]);
+ * }
+ * RedisModule_FreeClusterNodesList(ids);
+ */
+char **RM_GetClusterNodesList(RedisModuleCtx *ctx, size_t *numnodes) {
+ UNUSED(ctx);
+
+ if (!server.cluster_enabled) return NULL;
+ size_t count = dictSize(server.cluster->nodes);
+ char **ids = zmalloc((count+1)*REDISMODULE_NODE_ID_LEN);
+ dictIterator *di = dictGetIterator(server.cluster->nodes);
+ dictEntry *de;
+ int j = 0;
+ while((de = dictNext(di)) != NULL) {
+ clusterNode *node = dictGetVal(de);
+ if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE)) continue;
+ ids[j] = zmalloc(REDISMODULE_NODE_ID_LEN);
+ memcpy(ids[j],node->name,REDISMODULE_NODE_ID_LEN);
+ j++;
+ }
+ *numnodes = j;
+ ids[j] = NULL; /* Null term so that FreeClusterNodesList does not need
+ * to also get the count argument. */
+ dictReleaseIterator(di);
+ return ids;
+}
+
+/* Free the node list obtained with RedisModule_GetClusterNodesList. */
+void RM_FreeClusterNodesList(char **ids) {
+ if (ids == NULL) return;
+ for (int j = 0; ids[j]; j++) zfree(ids[j]);
+ zfree(ids);
+}
+
+/* Return this node ID (REDISMODULE_CLUSTER_ID_LEN bytes) or NULL if the cluster
+ * is disabled. */
+const char *RM_GetMyClusterID(void) {
+ if (!server.cluster_enabled) return NULL;
+ return server.cluster->myself->name;
+}
+
+/* Return the number of nodes in the cluster, regardless of their state
+ * (handshake, noaddress, ...) so that the number of active nodes may actually
+ * be smaller, but not greater than this number. If the instance is not in
+ * cluster mode, zero is returned. */
+size_t RM_GetClusterSize(void) {
+ if (!server.cluster_enabled) return 0;
+ return dictSize(server.cluster->nodes);
+}
+
+/* Populate the specified info for the node having as ID the specified 'id',
+ * then returns REDISMODULE_OK. Otherwise if the node ID does not exist from
+ * the POV of this local node, REDISMODULE_ERR is returned.
+ *
+ * The arguments ip, master_id, port and flags can be NULL in case we don't
+ * need to populate back certain info. If an ip and master_id (only populated
+ * if the instance is a slave) are specified, they point to buffers holding
+ * at least REDISMODULE_NODE_ID_LEN bytes. The strings written back as ip
+ * and master_id are not null terminated.
+ *
+ * The list of flags reported is the following:
+ *
+ * * REDISMODULE_NODE_MYSELF This node
+ * * REDISMODULE_NODE_MASTER The node is a master
+ * * REDISMODULE_NODE_SLAVE The node is a replica
+ * * REDISMODULE_NODE_PFAIL We see the node as failing
+ * * REDISMODULE_NODE_FAIL The cluster agrees the node is failing
+ * * REDISMODULE_NODE_NOFAILOVER The slave is configured to never failover
+ */
+
+clusterNode *clusterLookupNode(const char *name); /* We need access to internals */
+
+int RM_GetClusterNodeInfo(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags) {
+ UNUSED(ctx);
+
+ clusterNode *node = clusterLookupNode(id);
+ if (node->flags & (CLUSTER_NODE_NOADDR|CLUSTER_NODE_HANDSHAKE))
+ return REDISMODULE_ERR;
+
+ if (ip) memcpy(ip,node->name,REDISMODULE_NODE_ID_LEN);
+
+ if (master_id) {
+ /* If the information is not available, the function will set the
+ * field to zero bytes, so that when the field can't be populated the
+ * function kinda remains predictable. */
+ if (node->flags & CLUSTER_NODE_MASTER && node->slaveof)
+ memcpy(master_id,node->slaveof->name,REDISMODULE_NODE_ID_LEN);
+ else
+ memset(master_id,0,REDISMODULE_NODE_ID_LEN);
+ }
+ if (port) *port = node->port;
+
+ /* As usually we have to remap flags for modules, in order to ensure
+ * we can provide binary compatibility. */
+ if (flags) {
+ *flags = 0;
+ if (node->flags & CLUSTER_NODE_MYSELF) *flags |= REDISMODULE_NODE_MYSELF;
+ if (node->flags & CLUSTER_NODE_MASTER) *flags |= REDISMODULE_NODE_MASTER;
+ if (node->flags & CLUSTER_NODE_SLAVE) *flags |= REDISMODULE_NODE_SLAVE;
+ if (node->flags & CLUSTER_NODE_PFAIL) *flags |= REDISMODULE_NODE_PFAIL;
+ if (node->flags & CLUSTER_NODE_FAIL) *flags |= REDISMODULE_NODE_FAIL;
+ if (node->flags & CLUSTER_NODE_NOFAILOVER) *flags |= REDISMODULE_NODE_NOFAILOVER;
+ }
+ return REDISMODULE_OK;
+}
+
+/* Set Redis Cluster flags in order to change the normal behavior of
+ * Redis Cluster, especially with the goal of disabling certain functions.
+ * This is useful for modules that use the Cluster API in order to create
+ * a different distributed system, but still want to use the Redis Cluster
+ * message bus. Flags that can be set:
+ *
+ * CLUSTER_MODULE_FLAG_NO_FAILOVER
+ * CLUSTER_MODULE_FLAG_NO_REDIRECTION
+ *
+ * With the following effects:
+ *
+ * NO_FAILOVER: prevent Redis Cluster slaves to failover a failing master.
+ * Also disables the replica migration feature.
+ *
+ * NO_REDIRECTION: Every node will accept any key, without trying to perform
+ * partitioning according to the user Redis Cluster algorithm.
+ * Slots informations will still be propagated across the
+ * cluster, but without effects. */
+void RM_SetClusterFlags(RedisModuleCtx *ctx, uint64_t flags) {
+ UNUSED(ctx);
+ if (flags & REDISMODULE_CLUSTER_FLAG_NO_FAILOVER)
+ server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_FAILOVER;
+ if (flags & REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION)
+ server.cluster_module_flags |= CLUSTER_MODULE_FLAG_NO_REDIRECTION;
+}
+
+/* --------------------------------------------------------------------------
+ * Modules Timers API
+ *
+ * Module timers are an high precision "green timers" abstraction where
+ * every module can register even millions of timers without problems, even if
+ * the actual event loop will just have a single timer that is used to awake the
+ * module timers subsystem in order to process the next event.
+ *
+ * All the timers are stored into a radix tree, ordered by expire time, when
+ * the main Redis event loop timer callback is called, we try to process all
+ * the timers already expired one after the other. Then we re-enter the event
+ * loop registering a timer that will expire when the next to process module
+ * timer will expire.
+ *
+ * Every time the list of active timers drops to zero, we unregister the
+ * main event loop timer, so that there is no overhead when such feature is
+ * not used.
+ * -------------------------------------------------------------------------- */
+
+static rax *Timers; /* The radix tree of all the timers sorted by expire. */
+long long aeTimer = -1; /* Main event loop (ae.c) timer identifier. */
+
+typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
+
+/* The timer descriptor, stored as value in the radix tree. */
+typedef struct RedisModuleTimer {
+ RedisModule *module; /* Module reference. */
+ RedisModuleTimerProc callback; /* The callback to invoke on expire. */
+ void *data; /* Private data for the callback. */
+ int dbid; /* Database number selected by the original client. */
+} RedisModuleTimer;
+
+/* This is the timer handler that is called by the main event loop. We schedule
+ * this timer to be called when the nearest of our module timers will expire. */
+int moduleTimerHandler(struct aeEventLoop *eventLoop, long long id, void *clientData) {
+ UNUSED(eventLoop);
+ UNUSED(id);
+ UNUSED(clientData);
+
+ /* To start let's try to fire all the timers already expired. */
+ raxIterator ri;
+ raxStart(&ri,Timers);
+ uint64_t now = ustime();
+ long long next_period = 0;
+ while(1) {
+ raxSeek(&ri,"^",NULL,0);
+ if (!raxNext(&ri)) break;
+ uint64_t expiretime;
+ memcpy(&expiretime,ri.key,sizeof(expiretime));
+ expiretime = ntohu64(expiretime);
+ if (now >= expiretime) {
+ RedisModuleTimer *timer = ri.data;
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+
+ ctx.module = timer->module;
+ ctx.client = moduleFreeContextReusedClient;
+ selectDb(ctx.client, timer->dbid);
+ timer->callback(&ctx,timer->data);
+ moduleFreeContext(&ctx);
+ raxRemove(Timers,(unsigned char*)ri.key,ri.key_len,NULL);
+ zfree(timer);
+ } else {
+ next_period = (expiretime-now)/1000; /* Scale to milliseconds. */
+ break;
+ }
+ }
+ raxStop(&ri);
+
+ /* Reschedule the next timer or cancel it. */
+ if (next_period <= 0) next_period = 1;
+ return (raxSize(Timers) > 0) ? next_period : AE_NOMORE;
+}
+
+/* Create a new timer that will fire after `period` milliseconds, and will call
+ * the specified function using `data` as argument. The returned timer ID can be
+ * used to get information from the timer or to stop it before it fires. */
+RedisModuleTimerID RM_CreateTimer(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data) {
+ RedisModuleTimer *timer = zmalloc(sizeof(*timer));
+ timer->module = ctx->module;
+ timer->callback = callback;
+ timer->data = data;
+ timer->dbid = ctx->client->db->id;
+ uint64_t expiretime = ustime()+period*1000;
+ uint64_t key;
+
+ while(1) {
+ key = htonu64(expiretime);
+ if (raxFind(Timers, (unsigned char*)&key,sizeof(key)) == raxNotFound) {
+ raxInsert(Timers,(unsigned char*)&key,sizeof(key),timer,NULL);
+ break;
+ } else {
+ expiretime++;
+ }
+ }
+
+ /* We need to install the main event loop timer if it's not already
+ * installed, or we may need to refresh its period if we just installed
+ * a timer that will expire sooner than any other else. */
+ if (aeTimer != -1) {
+ raxIterator ri;
+ raxStart(&ri,Timers);
+ raxSeek(&ri,"^",NULL,0);
+ raxNext(&ri);
+ if (memcmp(ri.key,&key,sizeof(key)) == 0) {
+ /* This is the first key, we need to re-install the timer according
+ * to the just added event. */
+ aeDeleteTimeEvent(server.el,aeTimer);
+ aeTimer = -1;
+ }
+ raxStop(&ri);
+ }
+
+ /* If we have no main timer (the old one was invalidated, or this is the
+ * first module timer we have), install one. */
+ if (aeTimer == -1)
+ aeTimer = aeCreateTimeEvent(server.el,period,moduleTimerHandler,NULL,NULL);
+
+ return key;
+}
+
+/* Stop a timer, returns REDISMODULE_OK if the timer was found, belonged to the
+ * calling module, and was stopped, otherwise REDISMODULE_ERR is returned.
+ * If not NULL, the data pointer is set to the value of the data argument when
+ * the timer was created. */
+int RM_StopTimer(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data) {
+ RedisModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id));
+ if (timer == raxNotFound || timer->module != ctx->module)
+ return REDISMODULE_ERR;
+ if (data) *data = timer->data;
+ raxRemove(Timers,(unsigned char*)&id,sizeof(id),NULL);
+ zfree(timer);
+ return REDISMODULE_OK;
+}
+
+/* Obtain information about a timer: its remaining time before firing
+ * (in milliseconds), and the private data pointer associated with the timer.
+ * If the timer specified does not exist or belongs to a different module
+ * no information is returned and the function returns REDISMODULE_ERR, otherwise
+ * REDISMODULE_OK is returned. The arguments remaining or data can be NULL if
+ * the caller does not need certain information. */
+int RM_GetTimerInfo(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data) {
+ RedisModuleTimer *timer = raxFind(Timers,(unsigned char*)&id,sizeof(id));
+ if (timer == raxNotFound || timer->module != ctx->module)
+ return REDISMODULE_ERR;
+ if (remaining) {
+ int64_t rem = ntohu64(id)-ustime();
+ if (rem < 0) rem = 0;
+ *remaining = rem/1000; /* Scale to milliseconds. */
+ }
+ if (data) *data = timer->data;
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * Modules Dictionary API
+ *
+ * Implements a sorted dictionary (actually backed by a radix tree) with
+ * the usual get / set / del / num-items API, together with an iterator
+ * capable of going back and forth.
+ * -------------------------------------------------------------------------- */
+
+/* Create a new dictionary. The 'ctx' pointer can be the current module context
+ * or NULL, depending on what you want. Please follow the following rules:
+ *
+ * 1. Use a NULL context if you plan to retain a reference to this dictionary
+ * that will survive the time of the module callback where you created it.
+ * 2. Use a NULL context if no context is available at the time you are creating
+ * the dictionary (of course...).
+ * 3. However use the current callback context as 'ctx' argument if the
+ * dictionary time to live is just limited to the callback scope. In this
+ * case, if enabled, you can enjoy the automatic memory management that will
+ * reclaim the dictionary memory, as well as the strings returned by the
+ * Next / Prev dictionary iterator calls.
+ */
+RedisModuleDict *RM_CreateDict(RedisModuleCtx *ctx) {
+ struct RedisModuleDict *d = zmalloc(sizeof(*d));
+ d->rax = raxNew();
+ if (ctx != NULL) autoMemoryAdd(ctx,REDISMODULE_AM_DICT,d);
+ return d;
+}
+
+/* Free a dictionary created with RM_CreateDict(). You need to pass the
+ * context pointer 'ctx' only if the dictionary was created using the
+ * context instead of passing NULL. */
+void RM_FreeDict(RedisModuleCtx *ctx, RedisModuleDict *d) {
+ if (ctx != NULL) autoMemoryFreed(ctx,REDISMODULE_AM_DICT,d);
+ raxFree(d->rax);
+ zfree(d);
+}
+
+/* Return the size of the dictionary (number of keys). */
+uint64_t RM_DictSize(RedisModuleDict *d) {
+ return raxSize(d->rax);
+}
+
+/* Store the specified key into the dictionary, setting its value to the
+ * pointer 'ptr'. If the key was added with success, since it did not
+ * already exist, REDISMODULE_OK is returned. Otherwise if the key already
+ * exists the function returns REDISMODULE_ERR. */
+int RM_DictSetC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) {
+ int retval = raxTryInsert(d->rax,key,keylen,ptr,NULL);
+ return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+/* Like RedisModule_DictSetC() but will replace the key with the new
+ * value if the key already exists. */
+int RM_DictReplaceC(RedisModuleDict *d, void *key, size_t keylen, void *ptr) {
+ int retval = raxInsert(d->rax,key,keylen,ptr,NULL);
+ return (retval == 1) ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+/* Like RedisModule_DictSetC() but takes the key as a RedisModuleString. */
+int RM_DictSet(RedisModuleDict *d, RedisModuleString *key, void *ptr) {
+ return RM_DictSetC(d,key->ptr,sdslen(key->ptr),ptr);
+}
+
+/* Like RedisModule_DictReplaceC() but takes the key as a RedisModuleString. */
+int RM_DictReplace(RedisModuleDict *d, RedisModuleString *key, void *ptr) {
+ return RM_DictReplaceC(d,key->ptr,sdslen(key->ptr),ptr);
+}
+
+/* Return the value stored at the specified key. The function returns NULL
+ * both in the case the key does not exist, or if you actually stored
+ * NULL at key. So, optionally, if the 'nokey' pointer is not NULL, it will
+ * be set by reference to 1 if the key does not exist, or to 0 if the key
+ * exists. */
+void *RM_DictGetC(RedisModuleDict *d, void *key, size_t keylen, int *nokey) {
+ void *res = raxFind(d->rax,key,keylen);
+ if (nokey) *nokey = (res == raxNotFound);
+ return (res == raxNotFound) ? NULL : res;
+}
+
+/* Like RedisModule_DictGetC() but takes the key as a RedisModuleString. */
+void *RM_DictGet(RedisModuleDict *d, RedisModuleString *key, int *nokey) {
+ return RM_DictGetC(d,key->ptr,sdslen(key->ptr),nokey);
+}
+
+/* Remove the specified key from the dictionary, returning REDISMODULE_OK if
+ * the key was found and delted, or REDISMODULE_ERR if instead there was
+ * no such key in the dictionary. When the operation is successful, if
+ * 'oldval' is not NULL, then '*oldval' is set to the value stored at the
+ * key before it was deleted. Using this feature it is possible to get
+ * a pointer to the value (for instance in order to release it), without
+ * having to call RedisModule_DictGet() before deleting the key. */
+int RM_DictDelC(RedisModuleDict *d, void *key, size_t keylen, void *oldval) {
+ int retval = raxRemove(d->rax,key,keylen,oldval);
+ return retval ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+/* Like RedisModule_DictDelC() but gets the key as a RedisModuleString. */
+int RM_DictDel(RedisModuleDict *d, RedisModuleString *key, void *oldval) {
+ return RM_DictDelC(d,key->ptr,sdslen(key->ptr),oldval);
+}
+
+/* Return an interator, setup in order to start iterating from the specified
+ * key by applying the operator 'op', which is just a string specifying the
+ * comparison operator to use in order to seek the first element. The
+ * operators avalable are:
+ *
+ * "^" -- Seek the first (lexicographically smaller) key.
+ * "$" -- Seek the last (lexicographically biffer) key.
+ * ">" -- Seek the first element greter than the specified key.
+ * ">=" -- Seek the first element greater or equal than the specified key.
+ * "<" -- Seek the first element smaller than the specified key.
+ * "<=" -- Seek the first element smaller or equal than the specified key.
+ * "==" -- Seek the first element matching exactly the specified key.
+ *
+ * Note that for "^" and "$" the passed key is not used, and the user may
+ * just pass NULL with a length of 0.
+ *
+ * If the element to start the iteration cannot be seeked based on the
+ * key and operator passed, RedisModule_DictNext() / Prev() will just return
+ * REDISMODULE_ERR at the first call, otherwise they'll produce elements.
+ */
+RedisModuleDictIter *RM_DictIteratorStartC(RedisModuleDict *d, const char *op, void *key, size_t keylen) {
+ RedisModuleDictIter *di = zmalloc(sizeof(*di));
+ di->dict = d;
+ raxStart(&di->ri,d->rax);
+ raxSeek(&di->ri,op,key,keylen);
+ return di;
+}
+
+/* Exactly like RedisModule_DictIteratorStartC, but the key is passed as a
+ * RedisModuleString. */
+RedisModuleDictIter *RM_DictIteratorStart(RedisModuleDict *d, const char *op, RedisModuleString *key) {
+ return RM_DictIteratorStartC(d,op,key->ptr,sdslen(key->ptr));
+}
+
+/* Release the iterator created with RedisModule_DictIteratorStart(). This call
+ * is mandatory otherwise a memory leak is introduced in the module. */
+void RM_DictIteratorStop(RedisModuleDictIter *di) {
+ raxStop(&di->ri);
+ zfree(di);
+}
+
+/* After its creation with RedisModule_DictIteratorStart(), it is possible to
+ * change the currently selected element of the iterator by using this
+ * API call. The result based on the operator and key is exactly like
+ * the function RedisModule_DictIteratorStart(), however in this case the
+ * return value is just REDISMODULE_OK in case the seeked element was found,
+ * or REDISMODULE_ERR in case it was not possible to seek the specified
+ * element. It is possible to reseek an iterator as many times as you want. */
+int RM_DictIteratorReseekC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) {
+ return raxSeek(&di->ri,op,key,keylen);
+}
+
+/* Like RedisModule_DictIteratorReseekC() but takes the key as as a
+ * RedisModuleString. */
+int RM_DictIteratorReseek(RedisModuleDictIter *di, const char *op, RedisModuleString *key) {
+ return RM_DictIteratorReseekC(di,op,key->ptr,sdslen(key->ptr));
+}
+
+/* Return the current item of the dictionary iterator 'di' and steps to the
+ * next element. If the iterator already yield the last element and there
+ * are no other elements to return, NULL is returned, otherwise a pointer
+ * to a string representing the key is provided, and the '*keylen' length
+ * is set by reference (if keylen is not NULL). The '*dataptr', if not NULL
+ * is set to the value of the pointer stored at the returned key as auxiliary
+ * data (as set by the RedisModule_DictSet API).
+ *
+ * Usage example:
+ *
+ * ... create the iterator here ...
+ * char *key;
+ * void *data;
+ * while((key = RedisModule_DictNextC(iter,&keylen,&data)) != NULL) {
+ * printf("%.*s %p\n", (int)keylen, key, data);
+ * }
+ *
+ * The returned pointer is of type void because sometimes it makes sense
+ * to cast it to a char* sometimes to an unsigned char* depending on the
+ * fact it contains or not binary data, so this API ends being more
+ * comfortable to use.
+ *
+ * The validity of the returned pointer is until the next call to the
+ * next/prev iterator step. Also the pointer is no longer valid once the
+ * iterator is released. */
+void *RM_DictNextC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) {
+ if (!raxNext(&di->ri)) return NULL;
+ if (keylen) *keylen = di->ri.key_len;
+ if (dataptr) *dataptr = di->ri.data;
+ return di->ri.key;
+}
+
+/* This function is exactly like RedisModule_DictNext() but after returning
+ * the currently selected element in the iterator, it selects the previous
+ * element (laxicographically smaller) instead of the next one. */
+void *RM_DictPrevC(RedisModuleDictIter *di, size_t *keylen, void **dataptr) {
+ if (!raxPrev(&di->ri)) return NULL;
+ if (keylen) *keylen = di->ri.key_len;
+ if (dataptr) *dataptr = di->ri.data;
+ return di->ri.key;
+}
+
+/* Like RedisModuleNextC(), but instead of returning an internally allocated
+ * buffer and key length, it returns directly a module string object allocated
+ * in the specified context 'ctx' (that may be NULL exactly like for the main
+ * API RedisModule_CreateString).
+ *
+ * The returned string object should be deallocated after use, either manually
+ * or by using a context that has automatic memory management active. */
+RedisModuleString *RM_DictNext(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) {
+ size_t keylen;
+ void *key = RM_DictNextC(di,&keylen,dataptr);
+ if (key == NULL) return NULL;
+ return RM_CreateString(ctx,key,keylen);
+}
+
+/* Like RedisModule_DictNext() but after returning the currently selected
+ * element in the iterator, it selects the previous element (laxicographically
+ * smaller) instead of the next one. */
+RedisModuleString *RM_DictPrev(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr) {
+ size_t keylen;
+ void *key = RM_DictPrevC(di,&keylen,dataptr);
+ if (key == NULL) return NULL;
+ return RM_CreateString(ctx,key,keylen);
+}
+
+/* Compare the element currently pointed by the iterator to the specified
+ * element given by key/keylen, according to the operator 'op' (the set of
+ * valid operators are the same valid for RedisModule_DictIteratorStart).
+ * If the comparision is successful the command returns REDISMODULE_OK
+ * otherwise REDISMODULE_ERR is returned.
+ *
+ * This is useful when we want to just emit a lexicographical range, so
+ * in the loop, as we iterate elements, we can also check if we are still
+ * on range.
+ *
+ * The function returne REDISMODULE_ERR if the iterator reached the
+ * end of elements condition as well. */
+int RM_DictCompareC(RedisModuleDictIter *di, const char *op, void *key, size_t keylen) {
+ if (raxEOF(&di->ri)) return REDISMODULE_ERR;
+ int res = raxCompare(&di->ri,op,key,keylen);
+ return res ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+/* Like RedisModule_DictCompareC but gets the key to compare with the current
+ * iterator key as a RedisModuleString. */
+int RM_DictCompare(RedisModuleDictIter *di, const char *op, RedisModuleString *key) {
+ if (raxEOF(&di->ri)) return REDISMODULE_ERR;
+ int res = raxCompare(&di->ri,op,key->ptr,sdslen(key->ptr));
+ return res ? REDISMODULE_OK : REDISMODULE_ERR;
+}
+
+
+
+
+/* --------------------------------------------------------------------------
+ * Modules Info fields
+ * -------------------------------------------------------------------------- */
+
+int RM_InfoEndDictField(RedisModuleInfoCtx *ctx);
+
+/* Used to start a new section, before adding any fields. the section name will
+ * be prefixed by "<modulename>_" and must only include A-Z,a-z,0-9.
+ * NULL or empty string indicates the default section (only <modulename>) is used.
+ * When return value is REDISMODULE_ERR, the section should and will be skipped. */
+int RM_InfoAddSection(RedisModuleInfoCtx *ctx, char *name) {
+ sds full_name = sdsdup(ctx->module->name);
+ if (name != NULL && strlen(name) > 0)
+ full_name = sdscatfmt(full_name, "_%s", name);
+
+ /* Implicitly end dicts, instead of returning an error which is likely un checked. */
+ if (ctx->in_dict_field)
+ RM_InfoEndDictField(ctx);
+
+ /* proceed only if:
+ * 1) no section was requested (emit all)
+ * 2) the module name was requested (emit all)
+ * 3) this specific section was requested. */
+ if (ctx->requested_section) {
+ if (strcasecmp(ctx->requested_section, full_name) &&
+ strcasecmp(ctx->requested_section, ctx->module->name)) {
+ sdsfree(full_name);
+ ctx->in_section = 0;
+ return REDISMODULE_ERR;
+ }
+ }
+ if (ctx->sections++) ctx->info = sdscat(ctx->info,"\r\n");
+ ctx->info = sdscatfmt(ctx->info, "# %S\r\n", full_name);
+ ctx->in_section = 1;
+ sdsfree(full_name);
+ return REDISMODULE_OK;
+}
+
+/* Starts a dict field, similar to the ones in INFO KEYSPACE. Use normal
+ * RedisModule_InfoAddField* functions to add the items to this field, and
+ * terminate with RedisModule_InfoEndDictField. */
+int RM_InfoBeginDictField(RedisModuleInfoCtx *ctx, char *name) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ /* Implicitly end dicts, instead of returning an error which is likely un checked. */
+ if (ctx->in_dict_field)
+ RM_InfoEndDictField(ctx);
+ ctx->info = sdscatfmt(ctx->info,
+ "%s_%s:",
+ ctx->module->name,
+ name);
+ ctx->in_dict_field = 1;
+ return REDISMODULE_OK;
+}
+
+/* Ends a dict field, see RedisModule_InfoBeginDictField */
+int RM_InfoEndDictField(RedisModuleInfoCtx *ctx) {
+ if (!ctx->in_dict_field)
+ return REDISMODULE_ERR;
+ /* trim the last ',' if found. */
+ if (ctx->info[sdslen(ctx->info)-1]==',')
+ sdsIncrLen(ctx->info, -1);
+ ctx->info = sdscat(ctx->info, "\r\n");
+ ctx->in_dict_field = 0;
+ return REDISMODULE_OK;
+}
+
+/* Used by RedisModuleInfoFunc to add info fields.
+ * Each field will be automatically prefixed by "<modulename>_".
+ * Field names or values must not include \r\n of ":" */
+int RM_InfoAddFieldString(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ if (ctx->in_dict_field) {
+ ctx->info = sdscatfmt(ctx->info,
+ "%s=%S,",
+ field,
+ (sds)value->ptr);
+ return REDISMODULE_OK;
+ }
+ ctx->info = sdscatfmt(ctx->info,
+ "%s_%s:%S\r\n",
+ ctx->module->name,
+ field,
+ (sds)value->ptr);
+ return REDISMODULE_OK;
+}
+
+int RM_InfoAddFieldCString(RedisModuleInfoCtx *ctx, char *field, char *value) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ if (ctx->in_dict_field) {
+ ctx->info = sdscatfmt(ctx->info,
+ "%s=%s,",
+ field,
+ value);
+ return REDISMODULE_OK;
+ }
+ ctx->info = sdscatfmt(ctx->info,
+ "%s_%s:%s\r\n",
+ ctx->module->name,
+ field,
+ value);
+ return REDISMODULE_OK;
+}
+
+int RM_InfoAddFieldDouble(RedisModuleInfoCtx *ctx, char *field, double value) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ if (ctx->in_dict_field) {
+ ctx->info = sdscatprintf(ctx->info,
+ "%s=%.17g,",
+ field,
+ value);
+ return REDISMODULE_OK;
+ }
+ ctx->info = sdscatprintf(ctx->info,
+ "%s_%s:%.17g\r\n",
+ ctx->module->name,
+ field,
+ value);
+ return REDISMODULE_OK;
+}
+
+int RM_InfoAddFieldLongLong(RedisModuleInfoCtx *ctx, char *field, long long value) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ if (ctx->in_dict_field) {
+ ctx->info = sdscatfmt(ctx->info,
+ "%s=%I,",
+ field,
+ value);
+ return REDISMODULE_OK;
+ }
+ ctx->info = sdscatfmt(ctx->info,
+ "%s_%s:%I\r\n",
+ ctx->module->name,
+ field,
+ value);
+ return REDISMODULE_OK;
+}
+
+int RM_InfoAddFieldULongLong(RedisModuleInfoCtx *ctx, char *field, unsigned long long value) {
+ if (!ctx->in_section)
+ return REDISMODULE_ERR;
+ if (ctx->in_dict_field) {
+ ctx->info = sdscatfmt(ctx->info,
+ "%s=%U,",
+ field,
+ value);
+ return REDISMODULE_OK;
+ }
+ ctx->info = sdscatfmt(ctx->info,
+ "%s_%s:%U\r\n",
+ ctx->module->name,
+ field,
+ value);
+ return REDISMODULE_OK;
+}
+
+int RM_RegisterInfoFunc(RedisModuleCtx *ctx, RedisModuleInfoFunc cb) {
+ ctx->module->info_cb = cb;
+ return REDISMODULE_OK;
+}
+
+sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections) {
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ struct RedisModule *module = dictGetVal(de);
+ if (!module->info_cb)
+ continue;
+ RedisModuleInfoCtx info_ctx = {module, section, info, sections, 0};
+ module->info_cb(&info_ctx, for_crash_report);
+ /* Implicitly end dicts (no way to handle errors, and we must add the newline). */
+ if (info_ctx.in_dict_field)
+ RM_InfoEndDictField(&info_ctx);
+ info = info_ctx.info;
+ sections = info_ctx.sections;
+ }
+ dictReleaseIterator(di);
+ return info;
+}
+
+/* --------------------------------------------------------------------------
+ * Modules utility APIs
+ * -------------------------------------------------------------------------- */
+
+/* Return random bytes using SHA1 in counter mode with a /dev/urandom
+ * initialized seed. This function is fast so can be used to generate
+ * many bytes without any effect on the operating system entropy pool.
+ * Currently this function is not thread safe. */
+void RM_GetRandomBytes(unsigned char *dst, size_t len) {
+ getRandomBytes(dst,len);
+}
+
+/* Like RedisModule_GetRandomBytes() but instead of setting the string to
+ * random bytes the string is set to random characters in the in the
+ * hex charset [0-9a-f]. */
+void RM_GetRandomHexChars(char *dst, size_t len) {
+ getRandomHexChars(dst,len);
+}
+
+/* --------------------------------------------------------------------------
+ * Modules API exporting / importing
+ * -------------------------------------------------------------------------- */
+
+/* This function is called by a module in order to export some API with a
+ * given name. Other modules will be able to use this API by calling the
+ * symmetrical function RM_GetSharedAPI() and casting the return value to
+ * the right function pointer.
+ *
+ * The function will return REDISMODULE_OK if the name is not already taken,
+ * otherwise REDISMODULE_ERR will be returned and no operation will be
+ * performed.
+ *
+ * IMPORTANT: the apiname argument should be a string literal with static
+ * lifetime. The API relies on the fact that it will always be valid in
+ * the future. */
+int RM_ExportSharedAPI(RedisModuleCtx *ctx, const char *apiname, void *func) {
+ RedisModuleSharedAPI *sapi = zmalloc(sizeof(*sapi));
+ sapi->module = ctx->module;
+ sapi->func = func;
+ if (dictAdd(server.sharedapi, (char*)apiname, sapi) != DICT_OK) {
+ zfree(sapi);
+ return REDISMODULE_ERR;
+ }
+ return REDISMODULE_OK;
+}
+
+/* Request an exported API pointer. The return value is just a void pointer
+ * that the caller of this function will be required to cast to the right
+ * function pointer, so this is a private contract between modules.
+ *
+ * If the requested API is not available then NULL is returned. Because
+ * modules can be loaded at different times with different order, this
+ * function calls should be put inside some module generic API registering
+ * step, that is called every time a module attempts to execute a
+ * command that requires external APIs: if some API cannot be resolved, the
+ * command should return an error.
+ *
+ * Here is an exmaple:
+ *
+ * int ... myCommandImplementation() {
+ * if (getExternalAPIs() == 0) {
+ * reply with an error here if we cannot have the APIs
+ * }
+ * // Use the API:
+ * myFunctionPointer(foo);
+ * }
+ *
+ * And the function registerAPI() is:
+ *
+ * int getExternalAPIs(void) {
+ * static int api_loaded = 0;
+ * if (api_loaded != 0) return 1; // APIs already resolved.
+ *
+ * myFunctionPointer = RedisModule_GetOtherModuleAPI("...");
+ * if (myFunctionPointer == NULL) return 0;
+ *
+ * return 1;
+ * }
+ */
+void *RM_GetSharedAPI(RedisModuleCtx *ctx, const char *apiname) {
+ dictEntry *de = dictFind(server.sharedapi, apiname);
+ if (de == NULL) return NULL;
+ RedisModuleSharedAPI *sapi = dictGetVal(de);
+ if (listSearchKey(sapi->module->usedby,ctx->module) == NULL) {
+ listAddNodeTail(sapi->module->usedby,ctx->module);
+ listAddNodeTail(ctx->module->using,sapi->module);
+ }
+ return sapi->func;
+}
+
+/* Remove all the APIs registered by the specified module. Usually you
+ * want this when the module is going to be unloaded. This function
+ * assumes that's caller responsibility to make sure the APIs are not
+ * used by other modules.
+ *
+ * The number of unregistered APIs is returned. */
+int moduleUnregisterSharedAPI(RedisModule *module) {
+ int count = 0;
+ dictIterator *di = dictGetSafeIterator(server.sharedapi);
+ dictEntry *de;
+ while ((de = dictNext(di)) != NULL) {
+ const char *apiname = dictGetKey(de);
+ RedisModuleSharedAPI *sapi = dictGetVal(de);
+ if (sapi->module == module) {
+ dictDelete(server.sharedapi,apiname);
+ zfree(sapi);
+ count++;
+ }
+ }
+ dictReleaseIterator(di);
+ return count;
+}
+
+/* Remove the specified module as an user of APIs of ever other module.
+ * This is usually called when a module is unloaded.
+ *
+ * Returns the number of modules this module was using APIs from. */
+int moduleUnregisterUsedAPI(RedisModule *module) {
+ listIter li;
+ listNode *ln;
+ int count = 0;
+
+ listRewind(module->using,&li);
+ while((ln = listNext(&li))) {
+ RedisModule *used = ln->value;
+ listNode *ln = listSearchKey(used->usedby,module);
+ if (ln) {
+ listDelNode(module->using,ln);
+ count++;
+ }
+ }
+ return count;
+}
+
+/* Unregister all filters registered by a module.
+ * This is called when a module is being unloaded.
+ *
+ * Returns the number of filters unregistered. */
+int moduleUnregisterFilters(RedisModule *module) {
+ listIter li;
+ listNode *ln;
+ int count = 0;
+
+ listRewind(module->filters,&li);
+ while((ln = listNext(&li))) {
+ RedisModuleCommandFilter *filter = ln->value;
+ listNode *ln = listSearchKey(moduleCommandFilters,filter);
+ if (ln) {
+ listDelNode(moduleCommandFilters,ln);
+ count++;
+ }
+ zfree(filter);
+ }
+ return count;
+}
+
+/* --------------------------------------------------------------------------
+ * Module Command Filter API
+ * -------------------------------------------------------------------------- */
+
+/* Register a new command filter function.
+ *
+ * Command filtering makes it possible for modules to extend Redis by plugging
+ * into the execution flow of all commands.
+ *
+ * A registered filter gets called before Redis executes *any* command. This
+ * includes both core Redis commands and commands registered by any module. The
+ * filter applies in all execution paths including:
+ *
+ * 1. Invocation by a client.
+ * 2. Invocation through `RedisModule_Call()` by any module.
+ * 3. Invocation through Lua 'redis.call()`.
+ * 4. Replication of a command from a master.
+ *
+ * The filter executes in a special filter context, which is different and more
+ * limited than a RedisModuleCtx. Because the filter affects any command, it
+ * must be implemented in a very efficient way to reduce the performance impact
+ * on Redis. All Redis Module API calls that require a valid context (such as
+ * `RedisModule_Call()`, `RedisModule_OpenKey()`, etc.) are not supported in a
+ * filter context.
+ *
+ * The `RedisModuleCommandFilterCtx` can be used to inspect or modify the
+ * executed command and its arguments. As the filter executes before Redis
+ * begins processing the command, any change will affect the way the command is
+ * processed. For example, a module can override Redis commands this way:
+ *
+ * 1. Register a `MODULE.SET` command which implements an extended version of
+ * the Redis `SET` command.
+ * 2. Register a command filter which detects invocation of `SET` on a specific
+ * pattern of keys. Once detected, the filter will replace the first
+ * argument from `SET` to `MODULE.SET`.
+ * 3. When filter execution is complete, Redis considers the new command name
+ * and therefore executes the module's own command.
+ *
+ * Note that in the above use case, if `MODULE.SET` itself uses
+ * `RedisModule_Call()` the filter will be applied on that call as well. If
+ * that is not desired, the `REDISMODULE_CMDFILTER_NOSELF` flag can be set when
+ * registering the filter.
+ *
+ * The `REDISMODULE_CMDFILTER_NOSELF` flag prevents execution flows that
+ * originate from the module's own `RM_Call()` from reaching the filter. This
+ * flag is effective for all execution flows, including nested ones, as long as
+ * the execution begins from the module's command context or a thread-safe
+ * context that is associated with a blocking command.
+ *
+ * Detached thread-safe contexts are *not* associated with the module and cannot
+ * be protected by this flag.
+ *
+ * If multiple filters are registered (by the same or different modules), they
+ * are executed in the order of registration.
+ */
+
+RedisModuleCommandFilter *RM_RegisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc callback, int flags) {
+ RedisModuleCommandFilter *filter = zmalloc(sizeof(*filter));
+ filter->module = ctx->module;
+ filter->callback = callback;
+ filter->flags = flags;
+
+ listAddNodeTail(moduleCommandFilters, filter);
+ listAddNodeTail(ctx->module->filters, filter);
+ return filter;
+}
+
+/* Unregister a command filter.
+ */
+int RM_UnregisterCommandFilter(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter) {
+ listNode *ln;
+
+ /* A module can only remove its own filters */
+ if (filter->module != ctx->module) return REDISMODULE_ERR;
+
+ ln = listSearchKey(moduleCommandFilters,filter);
+ if (!ln) return REDISMODULE_ERR;
+ listDelNode(moduleCommandFilters,ln);
+
+ ln = listSearchKey(ctx->module->filters,filter);
+ if (!ln) return REDISMODULE_ERR; /* Shouldn't happen */
+ listDelNode(ctx->module->filters,ln);
+
+ zfree(filter);
+
+ return REDISMODULE_OK;
+}
+
+void moduleCallCommandFilters(client *c) {
+ if (listLength(moduleCommandFilters) == 0) return;
+
+ listIter li;
+ listNode *ln;
+ listRewind(moduleCommandFilters,&li);
+
+ RedisModuleCommandFilterCtx filter = {
+ .argv = c->argv,
+ .argc = c->argc
+ };
+
+ while((ln = listNext(&li))) {
+ RedisModuleCommandFilter *f = ln->value;
+
+ /* Skip filter if REDISMODULE_CMDFILTER_NOSELF is set and module is
+ * currently processing a command.
+ */
+ if ((f->flags & REDISMODULE_CMDFILTER_NOSELF) && f->module->in_call) continue;
+
+ /* Call filter */
+ f->callback(&filter);
+ }
+
+ c->argv = filter.argv;
+ c->argc = filter.argc;
+}
+
+/* Return the number of arguments a filtered command has. The number of
+ * arguments include the command itself.
+ */
+int RM_CommandFilterArgsCount(RedisModuleCommandFilterCtx *fctx)
+{
+ return fctx->argc;
+}
+
+/* Return the specified command argument. The first argument (position 0) is
+ * the command itself, and the rest are user-provided args.
+ */
+const RedisModuleString *RM_CommandFilterArgGet(RedisModuleCommandFilterCtx *fctx, int pos)
+{
+ if (pos < 0 || pos >= fctx->argc) return NULL;
+ return fctx->argv[pos];
+}
+
+/* Modify the filtered command by inserting a new argument at the specified
+ * position. The specified RedisModuleString argument may be used by Redis
+ * after the filter context is destroyed, so it must not be auto-memory
+ * allocated, freed or used elsewhere.
+ */
+
+int RM_CommandFilterArgInsert(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
+{
+ int i;
+
+ if (pos < 0 || pos > fctx->argc) return REDISMODULE_ERR;
+
+ fctx->argv = zrealloc(fctx->argv, (fctx->argc+1)*sizeof(RedisModuleString *));
+ for (i = fctx->argc; i > pos; i--) {
+ fctx->argv[i] = fctx->argv[i-1];
+ }
+ fctx->argv[pos] = arg;
+ fctx->argc++;
+
+ return REDISMODULE_OK;
+}
+
+/* Modify the filtered command by replacing an existing argument with a new one.
+ * The specified RedisModuleString argument may be used by Redis after the
+ * filter context is destroyed, so it must not be auto-memory allocated, freed
+ * or used elsewhere.
+ */
+
+int RM_CommandFilterArgReplace(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg)
+{
+ if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
+
+ decrRefCount(fctx->argv[pos]);
+ fctx->argv[pos] = arg;
+
+ return REDISMODULE_OK;
+}
+
+/* Modify the filtered command by deleting an argument at the specified
+ * position.
+ */
+int RM_CommandFilterArgDelete(RedisModuleCommandFilterCtx *fctx, int pos)
+{
+ int i;
+ if (pos < 0 || pos >= fctx->argc) return REDISMODULE_ERR;
+
+ decrRefCount(fctx->argv[pos]);
+ for (i = pos; i < fctx->argc-1; i++) {
+ fctx->argv[i] = fctx->argv[i+1];
+ }
+ fctx->argc--;
+
+ return REDISMODULE_OK;
+}
+
+/* --------------------------------------------------------------------------
+ * Module fork API
+ * -------------------------------------------------------------------------- */
+
+/* Create a background child process with the current frozen snaphost of the
+ * main process where you can do some processing in the background without
+ * affecting / freezing the traffic and no need for threads and GIL locking.
+ * Note that Redis allows for only one concurrent fork.
+ * When the child wants to exit, it should call RedisModule_ExitFromChild.
+ * If the parent wants to kill the child it should call RedisModule_KillForkChild
+ * The done handler callback will be executed on the parent process when the
+ * child existed (but not when killed)
+ * Return: -1 on failure, on success the parent process will get a positive PID
+ * of the child, and the child process will get 0.
+ */
+int RM_Fork(RedisModuleForkDoneHandler cb, void *user_data) {
+ pid_t childpid;
+ if (hasActiveChildProcess()) {
+ return -1;
+ }
+
+ openChildInfoPipe();
+ if ((childpid = redisFork()) == 0) {
+ /* Child */
+ redisSetProcTitle("redis-module-fork");
+ } else if (childpid == -1) {
+ closeChildInfoPipe();
+ serverLog(LL_WARNING,"Can't fork for module: %s", strerror(errno));
+ } else {
+ /* Parent */
+ server.module_child_pid = childpid;
+ moduleForkInfo.done_handler = cb;
+ moduleForkInfo.done_handler_user_data = user_data;
+ serverLog(LL_NOTICE, "Module fork started pid: %d ", childpid);
+ }
+ return childpid;
+}
+
+/* Call from the child process when you want to terminate it.
+ * retcode will be provided to the done handler executed on the parent process.
+ */
+int RM_ExitFromChild(int retcode) {
+ sendChildCOWInfo(CHILD_INFO_TYPE_MODULE, "Module fork");
+ exitFromChild(retcode);
+ return REDISMODULE_OK;
+}
+
+/* Kill the active module forked child, if there is one active and the
+ * pid matches, and returns C_OK. Otherwise if there is no active module
+ * child or the pid does not match, return C_ERR without doing anything. */
+int TerminateModuleForkChild(int child_pid, int wait) {
+ /* Module child should be active and pid should match. */
+ if (server.module_child_pid == -1 ||
+ server.module_child_pid != child_pid) return C_ERR;
+
+ int statloc;
+ serverLog(LL_NOTICE,"Killing running module fork child: %ld",
+ (long) server.module_child_pid);
+ if (kill(server.module_child_pid,SIGUSR1) != -1 && wait) {
+ while(wait4(server.module_child_pid,&statloc,0,NULL) !=
+ server.module_child_pid);
+ }
+ /* Reset the buffer accumulating changes while the child saves. */
+ server.module_child_pid = -1;
+ moduleForkInfo.done_handler = NULL;
+ moduleForkInfo.done_handler_user_data = NULL;
+ closeChildInfoPipe();
+ updateDictResizePolicy();
+ return C_OK;
+}
+
+/* Can be used to kill the forked child process from the parent process.
+ * child_pid whould be the return value of RedisModule_Fork. */
+int RM_KillForkChild(int child_pid) {
+ /* Kill module child, wait for child exit. */
+ if (TerminateModuleForkChild(child_pid,1) == C_OK)
+ return REDISMODULE_OK;
+ else
+ return REDISMODULE_ERR;
+}
+
+void ModuleForkDoneHandler(int exitcode, int bysignal) {
+ serverLog(LL_NOTICE,
+ "Module fork exited pid: %d, retcode: %d, bysignal: %d",
+ server.module_child_pid, exitcode, bysignal);
+ if (moduleForkInfo.done_handler) {
+ moduleForkInfo.done_handler(exitcode, bysignal,
+ moduleForkInfo.done_handler_user_data);
+ }
+ server.module_child_pid = -1;
+ moduleForkInfo.done_handler = NULL;
+ moduleForkInfo.done_handler_user_data = NULL;
+}
+
+/* --------------------------------------------------------------------------
+ * Server hooks implementation
+ * -------------------------------------------------------------------------- */
+
+/* Register to be notified, via a callback, when the specified server event
+ * happens. The callback is called with the event as argument, and an additional
+ * argument which is a void pointer and should be cased to a specific type
+ * that is event-specific (but many events will just use NULL since they do not
+ * have additional information to pass to the callback).
+ *
+ * If the callback is NULL and there was a previous subscription, the module
+ * will be unsubscribed. If there was a previous subscription and the callback
+ * is not null, the old callback will be replaced with the new one.
+ *
+ * The callback must be of this type:
+ *
+ * int (*RedisModuleEventCallback)(RedisModuleCtx *ctx,
+ * RedisModuleEvent eid,
+ * uint64_t subevent,
+ * void *data);
+ *
+ * The 'ctx' is a normal Redis module context that the callback can use in
+ * order to call other modules APIs. The 'eid' is the event itself, this
+ * is only useful in the case the module subscribed to multiple events: using
+ * the 'id' field of this structure it is possible to check if the event
+ * is one of the events we registered with this callback. The 'subevent' field
+ * depends on the event that fired.
+ *
+ * Finally the 'data' pointer may be populated, only for certain events, with
+ * more relevant data.
+ *
+ * Here is a list of events you can use as 'eid' and related sub events:
+ *
+ * RedisModuleEvent_ReplicationRoleChanged
+ *
+ * This event is called when the instance switches from master
+ * to replica or the other way around, however the event is
+ * also called when the replica remains a replica but starts to
+ * replicate with a different master.
+ *
+ * The following sub events are available:
+ *
+ * REDISMODULE_EVENT_REPLROLECHANGED_NOW_MASTER
+ * REDISMODULE_EVENT_REPLROLECHANGED_NOW_REPLICA
+ *
+ * The 'data' field can be casted by the callback to a
+ * RedisModuleReplicationInfo structure with the following fields:
+ *
+ * int master; // true if master, false if replica
+ * char *masterhost; // master instance hostname for NOW_REPLICA
+ * int masterport; // master instance port for NOW_REPLICA
+ * char *replid1; // Main replication ID
+ * char *replid2; // Secondary replication ID
+ * uint64_t repl2_offset; // Offset of replid2 validity
+ * uint64_t main_repl_offset; // Replication offset
+ *
+ * RedisModuleEvent_Persistence
+ *
+ * This event is called when RDB saving or AOF rewriting starts
+ * and ends. The following sub events are available:
+ *
+ * REDISMODULE_EVENT_LOADING_RDB_START // BGSAVE start
+ * REDISMODULE_EVENT_LOADING_RDB_END // BGSAVE end
+ * REDISMODULE_EVENT_LOADING_SYNC_RDB_START // SAVE start
+ * REDISMODULE_EVENT_LOADING_SYNC_RDB_START // SAVE end
+ * REDISMODULE_EVENT_LOADING_AOF_START // AOF rewrite start
+ * REDISMODULE_EVENT_LOADING_AOF_END // AOF rewrite end
+ *
+ * The above events are triggered not just when the user calls the
+ * relevant commands like BGSAVE, but also when a saving operation
+ * or AOF rewriting occurs because of internal server triggers.
+ *
+ * RedisModuleEvent_FlushDB
+ *
+ * The FLUSHALL, FLUSHDB or an internal flush (for instance
+ * because of replication, after the replica synchronization)
+ * happened. The following sub events are available:
+ *
+ * REDISMODULE_EVENT_FLUSHDB_START
+ * REDISMODULE_EVENT_FLUSHDB_END
+ *
+ * The data pointer can be casted to a RedisModuleFlushInfo
+ * structure with the following fields:
+ *
+ * int32_t async; // True if the flush is done in a thread.
+ * See for instance FLUSHALL ASYNC.
+ * In this case the END callback is invoked
+ * immediately after the database is put
+ * in the free list of the thread.
+ * int32_t dbnum; // Flushed database number, -1 for all the DBs
+ * in the case of the FLUSHALL operation.
+ *
+ * The start event is called *before* the operation is initated, thus
+ * allowing the callback to call DBSIZE or other operation on the
+ * yet-to-free keyspace.
+ *
+ * RedisModuleEvent_Loading
+ *
+ * Called on loading operations: at startup when the server is
+ * started, but also after a first synchronization when the
+ * replica is loading the RDB file from the master.
+ * The following sub events are available:
+ *
+ * REDISMODULE_EVENT_LOADING_RDB_START
+ * REDISMODULE_EVENT_LOADING_RDB_END
+ * REDISMODULE_EVENT_LOADING_MASTER_RDB_START
+ * REDISMODULE_EVENT_LOADING_MASTER_RDB_END
+ * REDISMODULE_EVENT_LOADING_AOF_START
+ * REDISMODULE_EVENT_LOADING_AOF_END
+ *
+ * RedisModuleEvent_ClientChange
+ *
+ * Called when a client connects or disconnects.
+ * The data pointer can be casted to a RedisModuleClientInfo
+ * structure, documented in RedisModule_GetClientInfoById().
+ * The following sub events are available:
+ *
+ * REDISMODULE_EVENT_CLIENT_CHANGE_CONNECTED
+ * REDISMODULE_EVENT_CLIENT_CHANGE_DISCONNECTED
+ *
+ * RedisModuleEvent_Shutdown
+ *
+ * The server is shutting down. No subevents are available.
+ *
+ * RedisModuleEvent_ReplicaChange
+ *
+ * This event is called when the instance (that can be both a
+ * master or a replica) get a new online replica, or lose a
+ * replica since it gets disconnected.
+ * The following sub events are availble:
+ *
+ * REDISMODULE_EVENT_REPLICA_CHANGE_ONLINE
+ * REDISMODULE_EVENT_REPLICA_CHANGE_OFFLINE
+ *
+ * No additional information is available so far: future versions
+ * of Redis will have an API in order to enumerate the replicas
+ * connected and their state.
+ *
+ * RedisModuleEvent_CronLoop
+ *
+ * This event is called every time Redis calls the serverCron()
+ * function in order to do certain bookkeeping. Modules that are
+ * required to do operations from time to time may use this callback.
+ * Normally Redis calls this function 10 times per second, but
+ * this changes depending on the "hz" configuration.
+ * No sub events are available.
+ *
+ * RedisModuleEvent_MasterLinkChange
+ *
+ * This is called for replicas in order to notify when the
+ * replication link becomes functional (up) with our master,
+ * or when it goes down. Note that the link is not considered
+ * up when we just connected to the master, but only if the
+ * replication is happening correctly.
+ * The following sub events are available:
+ *
+ * REDISMODULE_EVENT_MASTER_LINK_UP
+ * REDISMODULE_EVENT_MASTER_LINK_DOWN
+ *
+ * The function returns REDISMODULE_OK if the module was successfully subscrived
+ * for the specified event. If the API is called from a wrong context then
+ * REDISMODULE_ERR is returned. */
+int RM_SubscribeToServerEvent(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback) {
+ RedisModuleEventListener *el;
+
+ /* Protect in case of calls from contexts without a module reference. */
+ if (ctx->module == NULL) return REDISMODULE_ERR;
+
+ /* Search an event matching this module and event ID. */
+ listIter li;
+ listNode *ln;
+ listRewind(RedisModule_EventListeners,&li);
+ while((ln = listNext(&li))) {
+ el = ln->value;
+ if (el->module == ctx->module && el->event.id == event.id)
+ break; /* Matching event found. */
+ }
+
+ /* Modify or remove the event listener if we already had one. */
+ if (ln) {
+ if (callback == NULL) {
+ listDelNode(RedisModule_EventListeners,ln);
+ zfree(el);
+ } else {
+ el->callback = callback; /* Update the callback with the new one. */
+ }
+ return REDISMODULE_OK;
+ }
+
+ /* No event found, we need to add a new one. */
+ el = zmalloc(sizeof(*el));
+ el->module = ctx->module;
+ el->event = event;
+ el->callback = callback;
+ listAddNodeTail(RedisModule_EventListeners,el);
+ return REDISMODULE_OK;
+}
+
+/* This is called by the Redis internals every time we want to fire an
+ * event that can be interceppted by some module. The pointer 'data' is useful
+ * in order to populate the event-specific structure when needed, in order
+ * to return the structure with more information to the callback.
+ *
+ * 'eid' and 'subid' are just the main event ID and the sub event associated
+ * with the event, depending on what exactly happened. */
+void moduleFireServerEvent(uint64_t eid, int subid, void *data) {
+ /* Fast path to return ASAP if there is nothing to do, avoiding to
+ * setup the iterator and so forth: we want this call to be extremely
+ * cheap if there are no registered modules. */
+ if (listLength(RedisModule_EventListeners) == 0) return;
+
+ listIter li;
+ listNode *ln;
+ listRewind(RedisModule_EventListeners,&li);
+ while((ln = listNext(&li))) {
+ RedisModuleEventListener *el = ln->value;
+ if (el->event.id == eid && !el->module->in_hook) {
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ ctx.module = el->module;
+
+ if (ModulesInHooks == 0) {
+ ctx.client = moduleFreeContextReusedClient;
+ } else {
+ ctx.client = createClient(NULL);
+ ctx.client->flags |= CLIENT_MODULE;
+ ctx.client->user = NULL; /* Root user. */
+ }
+
+ void *moduledata = NULL;
+ RedisModuleClientInfoV1 civ1;
+ /* Start at DB zero by default when calling the handler. It's
+ * up to the specific event setup to change it when it makes
+ * sense. For instance for FLUSHDB events we select the correct
+ * DB automatically. */
+ selectDb(ctx.client, 0);
+
+ /* Event specific context and data pointer setup. */
+ if (eid == REDISMODULE_EVENT_CLIENT_CHANGE) {
+ modulePopulateClientInfoStructure(&civ1,data,
+ el->event.dataver);
+ moduledata = &civ1;
+ } else if (eid == REDISMODULE_EVENT_FLUSHDB) {
+ moduledata = data;
+ RedisModuleFlushInfoV1 *fi = data;
+ if (fi->dbnum != -1)
+ selectDb(ctx.client, fi->dbnum);
+ }
+
+ ModulesInHooks++;
+ el->module->in_hook++;
+ el->callback(&ctx,el->event,subid,moduledata);
+ el->module->in_hook--;
+ ModulesInHooks--;
+
+ if (ModulesInHooks != 0) freeClient(ctx.client);
+ moduleFreeContext(&ctx);
+ }
+ }
+}
+
+/* Remove all the listeners for this module: this is used before unloading
+ * a module. */
+void moduleUnsubscribeAllServerEvents(RedisModule *module) {
+ RedisModuleEventListener *el;
+ listIter li;
+ listNode *ln;
+ listRewind(RedisModule_EventListeners,&li);
+
+ while((ln = listNext(&li))) {
+ el = ln->value;
+ if (el->module == module) {
+ listDelNode(RedisModule_EventListeners,ln);
+ zfree(el);
+ }
+ }
+}
+
+/* --------------------------------------------------------------------------
* Modules API internals
* -------------------------------------------------------------------------- */
@@ -3820,7 +5936,7 @@ uint64_t dictCStringKeyHash(const void *key) {
}
int dictCStringKeyCompare(void *privdata, const void *key1, const void *key2) {
- DICT_NOTUSED(privdata);
+ UNUSED(privdata);
return strcmp(key1,key2) == 0;
}
@@ -3850,8 +5966,12 @@ void moduleInitModulesSystem(void) {
/* Set up the keyspace notification susbscriber list and static client */
moduleKeyspaceSubscribers = listCreate();
- moduleKeyspaceSubscribersClient = createClient(-1);
- moduleKeyspaceSubscribersClient->flags |= CLIENT_MODULE;
+ moduleFreeContextReusedClient = createClient(NULL);
+ moduleFreeContextReusedClient->flags |= CLIENT_MODULE;
+ moduleFreeContextReusedClient->user = NULL; /* root user. */
+
+ /* Set up filter list */
+ moduleCommandFilters = listCreate();
moduleRegisterCoreAPI();
if (pipe(server.module_blocked_pipe) == -1) {
@@ -3865,6 +5985,12 @@ void moduleInitModulesSystem(void) {
anetNonBlock(NULL,server.module_blocked_pipe[0]);
anetNonBlock(NULL,server.module_blocked_pipe[1]);
+ /* Create the timers radix tree. */
+ Timers = raxNew();
+
+ /* Setup the event listeners data structures. */
+ RedisModule_EventListeners = listCreate();
+
/* Our thread-safe contexts GIL must start with already locked:
* it is just unlocked when it's safe. */
pthread_mutex_lock(&moduleGIL);
@@ -3876,7 +6002,7 @@ void moduleInitModulesSystem(void) {
* because the server must be fully initialized before loading modules.
*
* The function aborts the server on errors, since to start with missing
- * modules is not considered sane: clients may rely on the existance of
+ * modules is not considered sane: clients may rely on the existence of
* given commands, loading AOF also may need some modules to exist, and
* if this instance is a slave, it must understand commands from master. */
void moduleLoadFromQueue(void) {
@@ -3899,6 +6025,9 @@ void moduleLoadFromQueue(void) {
void moduleFreeModuleStructure(struct RedisModule *module) {
listRelease(module->types);
+ listRelease(module->filters);
+ listRelease(module->usedby);
+ listRelease(module->using);
sdsfree(module->name);
zfree(module);
}
@@ -3939,6 +6068,7 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
}
onload = (int (*)(void *, void **, int))(unsigned long) dlsym(handle,"RedisModule_OnLoad");
if (onload == NULL) {
+ dlclose(handle);
serverLog(LL_WARNING,
"Module %s does not export RedisModule_OnLoad() "
"symbol. Module not loaded.",path);
@@ -3947,6 +6077,8 @@ int moduleLoad(const char *path, void **module_argv, int module_argc) {
if (onload((void*)&ctx,module_argv,module_argc) == REDISMODULE_ERR) {
if (ctx.module) {
moduleUnregisterCommands(ctx.module);
+ moduleUnregisterSharedAPI(ctx.module);
+ moduleUnregisterUsedAPI(ctx.module);
moduleFreeModuleStructure(ctx.module);
}
dlclose(handle);
@@ -3976,19 +6108,39 @@ int moduleUnload(sds name) {
if (module == NULL) {
errno = ENOENT;
return REDISMODULE_ERR;
- }
-
- if (listLength(module->types)) {
+ } else if (listLength(module->types)) {
errno = EBUSY;
return REDISMODULE_ERR;
+ } else if (listLength(module->usedby)) {
+ errno = EPERM;
+ return REDISMODULE_ERR;
+ }
+
+ /* Give module a chance to clean up. */
+ int (*onunload)(void *);
+ onunload = (int (*)(void *))(unsigned long) dlsym(module->handle, "RedisModule_OnUnload");
+ if (onunload) {
+ RedisModuleCtx ctx = REDISMODULE_CTX_INIT;
+ ctx.module = module;
+ ctx.client = moduleFreeContextReusedClient;
+ int unload_status = onunload((void*)&ctx);
+ moduleFreeContext(&ctx);
+
+ if (unload_status == REDISMODULE_ERR) {
+ serverLog(LL_WARNING, "Module %s OnUnload failed. Unload canceled.", name);
+ errno = ECANCELED;
+ return REDISMODULE_ERR;
+ }
}
moduleUnregisterCommands(module);
+ moduleUnregisterSharedAPI(module);
+ moduleUnregisterUsedAPI(module);
+ moduleUnregisterFilters(module);
- /* Remvoe any noification subscribers this module might have */
+ /* Remove any notification subscribers this module might have */
moduleUnsubscribeNotifications(module);
-
- /* Unregister all the hooks. TODO: Yet no hooks support here. */
+ moduleUnsubscribeAllServerEvents(module);
/* Unload the dynamic library. */
if (dlclose(module->handle) == -1) {
@@ -4007,12 +6159,95 @@ int moduleUnload(sds name) {
return REDISMODULE_OK;
}
+/* Helper function for the MODULE and HELLO command: send the list of the
+ * loaded modules to the client. */
+void addReplyLoadedModules(client *c) {
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ addReplyArrayLen(c,dictSize(modules));
+ while ((de = dictNext(di)) != NULL) {
+ sds name = dictGetKey(de);
+ struct RedisModule *module = dictGetVal(de);
+ addReplyMapLen(c,2);
+ addReplyBulkCString(c,"name");
+ addReplyBulkCBuffer(c,name,sdslen(name));
+ addReplyBulkCString(c,"ver");
+ addReplyLongLong(c,module->ver);
+ }
+ dictReleaseIterator(di);
+}
+
+/* Helper for genModulesInfoString(): given a list of modules, return
+ * am SDS string in the form "[modulename|modulename2|...]" */
+sds genModulesInfoStringRenderModulesList(list *l) {
+ listIter li;
+ listNode *ln;
+ listRewind(l,&li);
+ sds output = sdsnew("[");
+ while((ln = listNext(&li))) {
+ RedisModule *module = ln->value;
+ output = sdscat(output,module->name);
+ }
+ output = sdstrim(output,"|");
+ output = sdscat(output,"]");
+ return output;
+}
+
+/* Helper for genModulesInfoString(): render module options as an SDS string. */
+sds genModulesInfoStringRenderModuleOptions(struct RedisModule *module) {
+ sds output = sdsnew("[");
+ if (module->options & REDISMODULE_OPTIONS_HANDLE_IO_ERRORS)
+ output = sdscat(output,"handle-io-errors|");
+ output = sdstrim(output,"|");
+ output = sdscat(output,"]");
+ return output;
+}
+
+
+/* Helper function for the INFO command: adds loaded modules as to info's
+ * output.
+ *
+ * After the call, the passed sds info string is no longer valid and all the
+ * references must be substituted with the new pointer returned by the call. */
+sds genModulesInfoString(sds info) {
+ dictIterator *di = dictGetIterator(modules);
+ dictEntry *de;
+
+ while ((de = dictNext(di)) != NULL) {
+ sds name = dictGetKey(de);
+ struct RedisModule *module = dictGetVal(de);
+
+ sds usedby = genModulesInfoStringRenderModulesList(module->usedby);
+ sds using = genModulesInfoStringRenderModulesList(module->using);
+ sds options = genModulesInfoStringRenderModuleOptions(module);
+ info = sdscatfmt(info,
+ "module:name=%S,ver=%i,api=%i,filters=%i,"
+ "usedby=%S,using=%S,options=%S\r\n",
+ name, module->ver, module->apiver,
+ (int)listLength(module->filters), usedby, using, options);
+ sdsfree(usedby);
+ sdsfree(using);
+ sdsfree(options);
+ }
+ dictReleaseIterator(di);
+ return info;
+}
+
/* Redis MODULE command.
*
* MODULE LOAD <path> [args...] */
void moduleCommand(client *c) {
char *subcmd = c->argv[1]->ptr;
-
+ if (c->argc == 2 && !strcasecmp(subcmd,"help")) {
+ const char *help[] = {
+"LIST -- Return a list of loaded modules.",
+"LOAD <path> [arg ...] -- Load a module library from <path>.",
+"UNLOAD <name> -- Unload a module.",
+NULL
+ };
+ addReplyHelp(c, help);
+ } else
if (!strcasecmp(subcmd,"load") && c->argc >= 3) {
robj **argv = NULL;
int argc = 0;
@@ -4037,7 +6272,12 @@ void moduleCommand(client *c) {
errmsg = "no such module with that name";
break;
case EBUSY:
- errmsg = "the module exports one or more module-side data types, can't unload";
+ errmsg = "the module exports one or more module-side data "
+ "types, can't unload";
+ break;
+ case EPERM:
+ errmsg = "the module exports APIs used by other modules. "
+ "Please unload them first and try again";
break;
default:
errmsg = "operation not possible.";
@@ -4046,22 +6286,10 @@ void moduleCommand(client *c) {
addReplyErrorFormat(c,"Error unloading module: %s",errmsg);
}
} else if (!strcasecmp(subcmd,"list") && c->argc == 2) {
- dictIterator *di = dictGetIterator(modules);
- dictEntry *de;
-
- addReplyMultiBulkLen(c,dictSize(modules));
- while ((de = dictNext(di)) != NULL) {
- sds name = dictGetKey(de);
- struct RedisModule *module = dictGetVal(de);
- addReplyMultiBulkLen(c,4);
- addReplyBulkCString(c,"name");
- addReplyBulkCBuffer(c,name,sdslen(name));
- addReplyBulkCString(c,"ver");
- addReplyLongLong(c,module->ver);
- }
- dictReleaseIterator(di);
+ addReplyLoadedModules(c);
} else {
- addReply(c,shared.syntaxerr);
+ addReplySubcommandSyntaxError(c);
+ return;
}
}
@@ -4074,6 +6302,7 @@ size_t moduleCount(void) {
* file so that's easy to seek it to add new entries. */
void moduleRegisterCoreAPI(void) {
server.moduleapi = dictCreate(&moduleAPIDictType,NULL);
+ server.sharedapi = dictCreate(&moduleAPIDictType,NULL);
REGISTER_API(Alloc);
REGISTER_API(Calloc);
REGISTER_API(Realloc);
@@ -4090,6 +6319,7 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ReplySetArrayLength);
REGISTER_API(ReplyWithString);
REGISTER_API(ReplyWithStringBuffer);
+ REGISTER_API(ReplyWithCString);
REGISTER_API(ReplyWithNull);
REGISTER_API(ReplyWithCallReply);
REGISTER_API(ReplyWithDouble);
@@ -4152,6 +6382,8 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(ModuleTypeSetValue);
REGISTER_API(ModuleTypeGetType);
REGISTER_API(ModuleTypeGetValue);
+ REGISTER_API(IsIOError);
+ REGISTER_API(SetModuleOptions);
REGISTER_API(SaveUnsigned);
REGISTER_API(LoadUnsigned);
REGISTER_API(SaveSigned);
@@ -4167,10 +6399,12 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(EmitAOF);
REGISTER_API(Log);
REGISTER_API(LogIOError);
+ REGISTER_API(_Assert);
REGISTER_API(StringAppendBuffer);
REGISTER_API(RetainString);
REGISTER_API(StringCompare);
REGISTER_API(GetContextFromIO);
+ REGISTER_API(GetKeyNameFromIO);
REGISTER_API(BlockClient);
REGISTER_API(UnblockClient);
REGISTER_API(IsBlockedReplyRequest);
@@ -4186,4 +6420,65 @@ void moduleRegisterCoreAPI(void) {
REGISTER_API(DigestAddLongLong);
REGISTER_API(DigestEndSequence);
REGISTER_API(SubscribeToKeyspaceEvents);
+ REGISTER_API(RegisterClusterMessageReceiver);
+ REGISTER_API(SendClusterMessage);
+ REGISTER_API(GetClusterNodeInfo);
+ REGISTER_API(GetClusterNodesList);
+ REGISTER_API(FreeClusterNodesList);
+ REGISTER_API(CreateTimer);
+ REGISTER_API(StopTimer);
+ REGISTER_API(GetTimerInfo);
+ REGISTER_API(GetMyClusterID);
+ REGISTER_API(GetClusterSize);
+ REGISTER_API(GetRandomBytes);
+ REGISTER_API(GetRandomHexChars);
+ REGISTER_API(BlockedClientDisconnected);
+ REGISTER_API(SetDisconnectCallback);
+ REGISTER_API(GetBlockedClientHandle);
+ REGISTER_API(SetClusterFlags);
+ REGISTER_API(CreateDict);
+ REGISTER_API(FreeDict);
+ REGISTER_API(DictSize);
+ REGISTER_API(DictSetC);
+ REGISTER_API(DictReplaceC);
+ REGISTER_API(DictSet);
+ REGISTER_API(DictReplace);
+ REGISTER_API(DictGetC);
+ REGISTER_API(DictGet);
+ REGISTER_API(DictDelC);
+ REGISTER_API(DictDel);
+ REGISTER_API(DictIteratorStartC);
+ REGISTER_API(DictIteratorStart);
+ REGISTER_API(DictIteratorStop);
+ REGISTER_API(DictIteratorReseekC);
+ REGISTER_API(DictIteratorReseek);
+ REGISTER_API(DictNextC);
+ REGISTER_API(DictPrevC);
+ REGISTER_API(DictNext);
+ REGISTER_API(DictPrev);
+ REGISTER_API(DictCompareC);
+ REGISTER_API(DictCompare);
+ REGISTER_API(ExportSharedAPI);
+ REGISTER_API(GetSharedAPI);
+ REGISTER_API(RegisterCommandFilter);
+ REGISTER_API(UnregisterCommandFilter);
+ REGISTER_API(CommandFilterArgsCount);
+ REGISTER_API(CommandFilterArgGet);
+ REGISTER_API(CommandFilterArgInsert);
+ REGISTER_API(CommandFilterArgReplace);
+ REGISTER_API(CommandFilterArgDelete);
+ REGISTER_API(Fork);
+ REGISTER_API(ExitFromChild);
+ REGISTER_API(KillForkChild);
+ REGISTER_API(RegisterInfoFunc);
+ REGISTER_API(InfoAddSection);
+ REGISTER_API(InfoBeginDictField);
+ REGISTER_API(InfoEndDictField);
+ REGISTER_API(InfoAddFieldString);
+ REGISTER_API(InfoAddFieldCString);
+ REGISTER_API(InfoAddFieldDouble);
+ REGISTER_API(InfoAddFieldLongLong);
+ REGISTER_API(InfoAddFieldULongLong);
+ REGISTER_API(GetClientInfoById);
+ REGISTER_API(SubscribeToServerEvent);
}
diff --git a/src/modules/Makefile b/src/modules/Makefile
index 066e65e9b..9fb84cff8 100644
--- a/src/modules/Makefile
+++ b/src/modules/Makefile
@@ -13,7 +13,7 @@ endif
.SUFFIXES: .c .so .xo .o
-all: helloworld.so hellotype.so helloblock.so testmodule.so
+all: helloworld.so hellotype.so helloblock.so testmodule.so hellocluster.so hellotimer.so hellodict.so hellohook.so
.c.xo:
$(CC) -I. $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
@@ -33,6 +33,26 @@ helloblock.xo: ../redismodule.h
helloblock.so: helloblock.xo
$(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lpthread -lc
+hellocluster.xo: ../redismodule.h
+
+hellocluster.so: hellocluster.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+hellotimer.xo: ../redismodule.h
+
+hellotimer.so: hellotimer.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+hellodict.xo: ../redismodule.h
+
+hellodict.so: hellodict.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+hellohook.xo: ../redismodule.h
+
+hellohook.so: hellohook.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
testmodule.xo: ../redismodule.h
testmodule.so: testmodule.xo
diff --git a/src/modules/gendoc.rb b/src/modules/gendoc.rb
index 516f5d795..ee6572884 100644
--- a/src/modules/gendoc.rb
+++ b/src/modules/gendoc.rb
@@ -1,5 +1,5 @@
# gendoc.rb -- Converts the top-comments inside module.c to modules API
-# reference documentaiton in markdown format.
+# reference documentation in markdown format.
# Convert the C comment to markdown
def markdown(s)
diff --git a/src/modules/helloblock.c b/src/modules/helloblock.c
index c74fcd30f..b90ccaa50 100644
--- a/src/modules/helloblock.c
+++ b/src/modules/helloblock.c
@@ -54,7 +54,8 @@ int HelloBlock_Timeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
}
/* Private data freeing callback for HELLO.BLOCK command. */
-void HelloBlock_FreeData(void *privdata) {
+void HelloBlock_FreeData(RedisModuleCtx *ctx, void *privdata) {
+ REDISMODULE_NOT_USED(ctx);
RedisModule_Free(privdata);
}
@@ -73,6 +74,23 @@ void *HelloBlock_ThreadMain(void *arg) {
return NULL;
}
+/* An example blocked client disconnection callback.
+ *
+ * Note that in the case of the HELLO.BLOCK command, the blocked client is now
+ * owned by the thread calling sleep(). In this specific case, there is not
+ * much we can do, however normally we could instead implement a way to
+ * signal the thread that the client disconnected, and sleep the specified
+ * amount of seconds with a while loop calling sleep(1), so that once we
+ * detect the client disconnection, we can terminate the thread ASAP. */
+void HelloBlock_Disconnected(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc) {
+ RedisModule_Log(ctx,"warning","Blocked client %p disconnected!",
+ (void*)bc);
+
+ /* Here you should cleanup your state / threads, and if possible
+ * call RedisModule_UnblockClient(), or notify the thread that will
+ * call the function ASAP. */
+}
+
/* HELLO.BLOCK <delay> <timeout> -- Block for <count> seconds, then reply with
* a random number. Timeout is the command timeout, so that you can test
* what happens when the delay is greater than the timeout. */
@@ -92,6 +110,11 @@ int HelloBlock_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int a
pthread_t tid;
RedisModuleBlockedClient *bc = RedisModule_BlockClient(ctx,HelloBlock_Reply,HelloBlock_Timeout,HelloBlock_FreeData,timeout);
+ /* Here we set a disconnection handler, however since this module will
+ * block in sleep() in a thread, there is not much we can do in the
+ * callback, so this is just to show you the API. */
+ RedisModule_SetDisconnectCallback(bc,HelloBlock_Disconnected);
+
/* Now that we setup a blocking client, we need to pass the control
* to the thread. However we need to pass arguments to the thread:
* the delay and a reference to the blocked client handle. */
diff --git a/src/modules/hellocluster.c b/src/modules/hellocluster.c
new file mode 100644
index 000000000..cb78187f9
--- /dev/null
+++ b/src/modules/hellocluster.c
@@ -0,0 +1,118 @@
+/* Helloworld cluster -- A ping/pong cluster API example.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "../redismodule.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#define MSGTYPE_PING 1
+#define MSGTYPE_PONG 2
+
+/* HELLOCLUSTER.PINGALL */
+int PingallCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PING,(unsigned char*)"Hey",3);
+ return RedisModule_ReplyWithSimpleString(ctx, "OK");
+}
+
+/* HELLOCLUSTER.LIST */
+int ListCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ size_t numnodes;
+ char **ids = RedisModule_GetClusterNodesList(ctx,&numnodes);
+ if (ids == NULL) {
+ return RedisModule_ReplyWithError(ctx,"Cluster not enabled");
+ }
+
+ RedisModule_ReplyWithArray(ctx,numnodes);
+ for (size_t j = 0; j < numnodes; j++) {
+ int port;
+ RedisModule_GetClusterNodeInfo(ctx,ids[j],NULL,NULL,&port,NULL);
+ RedisModule_ReplyWithArray(ctx,2);
+ RedisModule_ReplyWithStringBuffer(ctx,ids[j],REDISMODULE_NODE_ID_LEN);
+ RedisModule_ReplyWithLongLong(ctx,port);
+ }
+ RedisModule_FreeClusterNodesList(ids);
+ return REDISMODULE_OK;
+}
+
+/* Callback for message MSGTYPE_PING */
+void PingReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
+ RedisModule_Log(ctx,"notice","PING (type %d) RECEIVED from %.*s: '%.*s'",
+ type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload);
+ RedisModule_SendClusterMessage(ctx,NULL,MSGTYPE_PONG,(unsigned char*)"Ohi!",4);
+ RedisModule_Call(ctx, "INCR", "c", "pings_received");
+}
+
+/* Callback for message MSGTYPE_PONG. */
+void PongReceiver(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len) {
+ RedisModule_Log(ctx,"notice","PONG (type %d) RECEIVED from %.*s: '%.*s'",
+ type,REDISMODULE_NODE_ID_LEN,sender_id,(int)len, payload);
+}
+
+/* This function must be present on each Redis module. It is used in order to
+ * register the commands into the Redis server. */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"hellocluster",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellocluster.pingall",
+ PingallCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellocluster.list",
+ ListCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ /* Disable Redis Cluster sharding and redirections. This way every node
+ * will be able to access every possible key, regardless of the hash slot.
+ * This way the PING message handler will be able to increment a specific
+ * variable. Normally you do that in order for the distributed system
+ * you create as a module to have total freedom in the keyspace
+ * manipulation. */
+ RedisModule_SetClusterFlags(ctx,REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION);
+
+ /* Register our handlers for different message types. */
+ RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PING,PingReceiver);
+ RedisModule_RegisterClusterMessageReceiver(ctx,MSGTYPE_PONG,PongReceiver);
+ return REDISMODULE_OK;
+}
diff --git a/src/modules/hellodict.c b/src/modules/hellodict.c
new file mode 100644
index 000000000..651615b03
--- /dev/null
+++ b/src/modules/hellodict.c
@@ -0,0 +1,132 @@
+/* Hellodict -- An example of modules dictionary API
+ *
+ * This module implements a volatile key-value store on top of the
+ * dictionary exported by the Redis modules API.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "../redismodule.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+static RedisModuleDict *Keyspace;
+
+/* HELLODICT.SET <key> <value>
+ *
+ * Set the specified key to the specified value. */
+int cmd_SET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (argc != 3) return RedisModule_WrongArity(ctx);
+ RedisModule_DictSet(Keyspace,argv[1],argv[2]);
+ /* We need to keep a reference to the value stored at the key, otherwise
+ * it would be freed when this callback returns. */
+ RedisModule_RetainString(NULL,argv[2]);
+ return RedisModule_ReplyWithSimpleString(ctx, "OK");
+}
+
+/* HELLODICT.GET <key>
+ *
+ * Return the value of the specified key, or a null reply if the key
+ * is not defined. */
+int cmd_GET(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (argc != 2) return RedisModule_WrongArity(ctx);
+ RedisModuleString *val = RedisModule_DictGet(Keyspace,argv[1],NULL);
+ if (val == NULL) {
+ return RedisModule_ReplyWithNull(ctx);
+ } else {
+ return RedisModule_ReplyWithString(ctx, val);
+ }
+}
+
+/* HELLODICT.KEYRANGE <startkey> <endkey> <count>
+ *
+ * Return a list of matching keys, lexicographically between startkey
+ * and endkey. No more than 'count' items are emitted. */
+int cmd_KEYRANGE(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (argc != 4) return RedisModule_WrongArity(ctx);
+
+ /* Parse the count argument. */
+ long long count;
+ if (RedisModule_StringToLongLong(argv[3],&count) != REDISMODULE_OK) {
+ return RedisModule_ReplyWithError(ctx,"ERR invalid count");
+ }
+
+ /* Seek the iterator. */
+ RedisModuleDictIter *iter = RedisModule_DictIteratorStart(
+ Keyspace, ">=", argv[1]);
+
+ /* Reply with the matching items. */
+ char *key;
+ size_t keylen;
+ long long replylen = 0; /* Keep track of the amitted array len. */
+ RedisModule_ReplyWithArray(ctx,REDISMODULE_POSTPONED_ARRAY_LEN);
+ while((key = RedisModule_DictNextC(iter,&keylen,NULL)) != NULL) {
+ if (replylen >= count) break;
+ if (RedisModule_DictCompare(iter,"<=",argv[2]) == REDISMODULE_ERR)
+ break;
+ RedisModule_ReplyWithStringBuffer(ctx,key,keylen);
+ replylen++;
+ }
+ RedisModule_ReplySetArrayLength(ctx,replylen);
+
+ /* Cleanup. */
+ RedisModule_DictIteratorStop(iter);
+ return REDISMODULE_OK;
+}
+
+/* This function must be present on each Redis module. It is used in order to
+ * register the commands into the Redis server. */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"hellodict",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellodict.set",
+ cmd_SET,"write deny-oom",1,1,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellodict.get",
+ cmd_GET,"readonly",1,1,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellodict.keyrange",
+ cmd_KEYRANGE,"readonly",1,1,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ /* Create our global dictionray. Here we'll set our keys and values. */
+ Keyspace = RedisModule_CreateDict(NULL);
+
+ return REDISMODULE_OK;
+}
diff --git a/src/modules/hellohook.c b/src/modules/hellohook.c
new file mode 100644
index 000000000..7ab78ed07
--- /dev/null
+++ b/src/modules/hellohook.c
@@ -0,0 +1,93 @@
+/* Server hooks API example
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "../redismodule.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Client state change callback. */
+void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
+{
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(e);
+
+ RedisModuleClientInfo *ci = data;
+ printf("Client %s event for client #%llu %s:%d\n",
+ (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
+ "connection" : "disconnection",
+ ci->id,ci->addr,ci->port);
+}
+
+void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
+{
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(e);
+
+ RedisModuleFlushInfo *fi = data;
+ if (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) {
+ if (fi->dbnum != -1) {
+ RedisModuleCallReply *reply;
+ reply = RedisModule_Call(ctx,"DBSIZE","");
+ long long numkeys = RedisModule_CallReplyInteger(reply);
+ printf("FLUSHDB event of database %d started (%lld keys in DB)\n",
+ fi->dbnum, numkeys);
+ RedisModule_FreeCallReply(reply);
+ } else {
+ printf("FLUSHALL event started\n");
+ }
+ } else {
+ if (fi->dbnum != -1) {
+ printf("FLUSHDB event of database %d ended\n",fi->dbnum);
+ } else {
+ printf("FLUSHALL event ended\n");
+ }
+ }
+}
+
+/* This function must be present on each Redis module. It is used in order to
+ * register the commands into the Redis server. */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"hellohook",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ RedisModule_SubscribeToServerEvent(ctx,
+ RedisModuleEvent_ClientChange, clientChangeCallback);
+ RedisModule_SubscribeToServerEvent(ctx,
+ RedisModuleEvent_FlushDB, flushdbCallback);
+ return REDISMODULE_OK;
+}
diff --git a/src/modules/hellotimer.c b/src/modules/hellotimer.c
new file mode 100644
index 000000000..57b111b7c
--- /dev/null
+++ b/src/modules/hellotimer.c
@@ -0,0 +1,76 @@
+/* Timer API example -- Register and handle timer events
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "../redismodule.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+/* Timer callback. */
+void timerHandler(RedisModuleCtx *ctx, void *data) {
+ REDISMODULE_NOT_USED(ctx);
+ printf("Fired %s!\n", data);
+ RedisModule_Free(data);
+}
+
+/* HELLOTIMER.TIMER*/
+int TimerCommand_RedisCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ for (int j = 0; j < 10; j++) {
+ int delay = rand() % 5000;
+ char *buf = RedisModule_Alloc(256);
+ snprintf(buf,256,"After %d", delay);
+ RedisModuleTimerID tid = RedisModule_CreateTimer(ctx,delay,timerHandler,buf);
+ REDISMODULE_NOT_USED(tid);
+ }
+ return RedisModule_ReplyWithSimpleString(ctx, "OK");
+}
+
+/* This function must be present on each Redis module. It is used in order to
+ * register the commands into the Redis server. */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"hellotimer",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"hellotimer.timer",
+ TimerCommand_RedisCommand,"readonly",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/src/modules/testmodule.c b/src/modules/testmodule.c
index 67a861704..5381380e5 100644
--- a/src/modules/testmodule.c
+++ b/src/modules/testmodule.c
@@ -109,9 +109,9 @@ int TestStringPrintf(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
if (argc < 3) {
return RedisModule_WrongArity(ctx);
}
- RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
- "Got %d args. argv[1]: %s, argv[2]: %s",
- argc,
+ RedisModuleString *s = RedisModule_CreateStringPrintf(ctx,
+ "Got %d args. argv[1]: %s, argv[2]: %s",
+ argc,
RedisModule_StringPtrLen(argv[1], NULL),
RedisModule_StringPtrLen(argv[2], NULL)
);
@@ -133,7 +133,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModuleKey *k = RedisModule_OpenKey(ctx, RedisModule_CreateStringPrintf(ctx, "unlinked"), REDISMODULE_WRITE | REDISMODULE_READ);
if (!k) return failTest(ctx, "Could not create key");
-
+
if (REDISMODULE_ERR == RedisModule_StringSet(k, RedisModule_CreateStringPrintf(ctx, "Foobar"))) {
return failTest(ctx, "Could not set string value");
}
@@ -152,7 +152,7 @@ int TestUnlink(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
return failTest(ctx, "Could not verify key to be unlinked");
}
return RedisModule_ReplyWithSimpleString(ctx, "OK");
-
+
}
int NotifyCallback(RedisModuleCtx *ctx, int type, const char *event,
@@ -188,6 +188,10 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
RedisModule_Call(ctx, "LPUSH", "cc", "l", "y");
+ /* Miss some keys intentionally so we will get a "keymiss" notification. */
+ RedisModule_Call(ctx, "GET", "c", "nosuchkey");
+ RedisModule_Call(ctx, "SMEMBERS", "c", "nosuchkey");
+
size_t sz;
const char *rep;
RedisModuleCallReply *r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "foo");
@@ -225,6 +229,16 @@ int TestNotifications(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
FAIL("Wrong reply for l");
}
+ r = RedisModule_Call(ctx, "HGET", "cc", "notifications", "nosuchkey");
+ if (r == NULL || RedisModule_CallReplyType(r) != REDISMODULE_REPLY_STRING) {
+ FAIL("Wrong or no reply for nosuchkey");
+ } else {
+ rep = RedisModule_CallReplyStringPtr(r, &sz);
+ if (sz != 1 || *rep != '2') {
+ FAIL("Got reply '%.*s'. expected '2'", sz, rep);
+ }
+ }
+
RedisModule_Call(ctx, "FLUSHDB", "");
return RedisModule_ReplyWithSimpleString(ctx, "OK");
@@ -423,7 +437,7 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
if (RedisModule_CreateCommand(ctx,"test.ctxflags",
TestCtxFlags,"readonly",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
-
+
if (RedisModule_CreateCommand(ctx,"test.unlink",
TestUnlink,"write deny-oom",1,1,1) == REDISMODULE_ERR)
return REDISMODULE_ERR;
@@ -435,7 +449,8 @@ int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
RedisModule_SubscribeToKeyspaceEvents(ctx,
REDISMODULE_NOTIFY_HASH |
REDISMODULE_NOTIFY_SET |
- REDISMODULE_NOTIFY_STRING,
+ REDISMODULE_NOTIFY_STRING |
+ REDISMODULE_NOTIFY_KEY_MISS,
NotifyCallback);
if (RedisModule_CreateCommand(ctx,"test.notify",
TestNotifications,"write deny-oom",1,1,1) == REDISMODULE_ERR)
diff --git a/src/multi.c b/src/multi.c
index 112ce0605..f885fa19c 100644
--- a/src/multi.c
+++ b/src/multi.c
@@ -35,6 +35,7 @@
void initClientMultiState(client *c) {
c->mstate.commands = NULL;
c->mstate.count = 0;
+ c->mstate.cmd_flags = 0;
}
/* Release all the resources associated with MULTI/EXEC state */
@@ -67,6 +68,7 @@ void queueMultiCommand(client *c) {
for (j = 0; j < c->argc; j++)
incrRefCount(mc->argv[j]);
c->mstate.count++;
+ c->mstate.cmd_flags |= c->cmd->flags;
}
void discardTransaction(client *c) {
@@ -132,7 +134,22 @@ void execCommand(client *c) {
* in the second an EXECABORT error is returned. */
if (c->flags & (CLIENT_DIRTY_CAS|CLIENT_DIRTY_EXEC)) {
addReply(c, c->flags & CLIENT_DIRTY_EXEC ? shared.execaborterr :
- shared.nullmultibulk);
+ shared.nullarray[c->resp]);
+ discardTransaction(c);
+ goto handle_monitor;
+ }
+
+ /* If there are write commands inside the transaction, and this is a read
+ * only slave, we want to send an error. This happens when the transaction
+ * was initiated when the instance was a master or a writable replica and
+ * then the configuration changed (for example instance was turned into
+ * a replica). */
+ if (!server.loading && server.masterhost && server.repl_slave_ro &&
+ !(c->flags & CLIENT_MASTER) && c->mstate.cmd_flags & CMD_WRITE)
+ {
+ addReplyError(c,
+ "Transaction contains write commands but instance "
+ "is now a read-only replica. EXEC aborted.");
discardTransaction(c);
goto handle_monitor;
}
@@ -142,7 +159,7 @@ void execCommand(client *c) {
orig_argv = c->argv;
orig_argc = c->argc;
orig_cmd = c->cmd;
- addReplyMultiBulkLen(c,c->mstate.count);
+ addReplyArrayLen(c,c->mstate.count);
for (j = 0; j < c->mstate.count; j++) {
c->argc = c->mstate.commands[j].argc;
c->argv = c->mstate.commands[j].argv;
@@ -158,7 +175,19 @@ void execCommand(client *c) {
must_propagate = 1;
}
- call(c,CMD_CALL_FULL);
+ int acl_retval = ACLCheckCommandPerm(c);
+ if (acl_retval != ACL_OK) {
+ addReplyErrorFormat(c,
+ "-NOPERM ACLs rules changed between the moment the "
+ "transaction was accumulated and the EXEC call. "
+ "This command is no longer allowed for the "
+ "following reason: %s",
+ (acl_retval == ACL_DENIED_CMD) ?
+ "no permission to execute the command or subcommand" :
+ "no permission to touch the specified keys");
+ } else {
+ call(c,server.loading ? CMD_CALL_NONE : CMD_CALL_FULL);
+ }
/* Commands may alter argc/argv, restore mstate. */
c->mstate.commands[j].argc = c->argc;
diff --git a/src/networking.c b/src/networking.c
index 51c239fc8..e7cc561fa 100644
--- a/src/networking.c
+++ b/src/networking.c
@@ -29,11 +29,13 @@
#include "server.h"
#include "atomicvar.h"
+#include <sys/socket.h>
#include <sys/uio.h>
#include <math.h>
#include <ctype.h>
-static void setProtocolError(const char *errstr, client *c, long pos);
+static void setProtocolError(const char *errstr, client *c);
+int postponeClientRead(client *c);
/* Return the size consumed from the allocator, for the specified SDS string,
* including internal fragmentation. This function is used in order to compute
@@ -56,11 +58,14 @@ size_t getStringObjectSdsUsedMemory(robj *o) {
/* Client.reply list dup and free methods. */
void *dupClientReplyValue(void *o) {
- return sdsdup(o);
+ clientReplyBlock *old = o;
+ clientReplyBlock *buf = zmalloc(sizeof(clientReplyBlock) + old->size);
+ memcpy(buf, o, sizeof(clientReplyBlock) + old->size);
+ return buf;
}
void freeClientReplyValue(void *o) {
- sdsfree(o);
+ zfree(o);
}
int listMatchObjects(void *a, void *b) {
@@ -75,36 +80,34 @@ void linkClient(client *c) {
* this way removing the client in unlinkClient() will not require
* a linear scan, but just a constant time operation. */
c->client_list_node = listLast(server.clients);
+ uint64_t id = htonu64(c->id);
+ raxInsert(server.clients_index,(unsigned char*)&id,sizeof(id),c,NULL);
}
-client *createClient(int fd) {
+client *createClient(connection *conn) {
client *c = zmalloc(sizeof(client));
- /* passing -1 as fd it is possible to create a non connected client.
+ /* passing NULL as conn it is possible to create a non connected client.
* This is useful since all the commands needs to be executed
* in the context of a client. When commands are executed in other
* contexts (for instance a Lua script) we need a non connected client. */
- if (fd != -1) {
- anetNonBlock(NULL,fd);
- anetEnableTcpNoDelay(NULL,fd);
+ if (conn) {
+ connNonBlock(conn);
+ connEnableTcpNoDelay(conn);
if (server.tcpkeepalive)
- anetKeepAlive(NULL,fd,server.tcpkeepalive);
- if (aeCreateFileEvent(server.el,fd,AE_READABLE,
- readQueryFromClient, c) == AE_ERR)
- {
- close(fd);
- zfree(c);
- return NULL;
- }
+ connKeepAlive(conn,server.tcpkeepalive);
+ connSetReadHandler(conn, readQueryFromClient);
+ connSetPrivateData(conn, c);
}
selectDb(c,0);
- uint64_t client_id;
- atomicGetIncr(server.next_client_id,client_id,1);
+ uint64_t client_id = ++server.next_client_id;
c->id = client_id;
- c->fd = fd;
+ c->resp = 2;
+ c->conn = conn;
c->name = NULL;
c->bufpos = 0;
+ c->qb_pos = 0;
c->querybuf = sdsempty();
c->pending_querybuf = sdsempty();
c->querybuf_peak = 0;
@@ -112,12 +115,15 @@ client *createClient(int fd) {
c->argc = 0;
c->argv = NULL;
c->cmd = c->lastcmd = NULL;
+ c->user = DefaultUser;
c->multibulklen = 0;
c->bulklen = -1;
c->sentlen = 0;
c->flags = 0;
c->ctime = c->lastinteraction = server.unixtime;
- c->authenticated = 0;
+ /* If the default user does not require authentication, the user is
+ * directly authenticated. */
+ c->authenticated = (c->user->flags & USER_FLAG_NOPASS) != 0;
c->replstate = REPL_STATE_NONE;
c->repl_put_online_on_ack = 0;
c->reploff = 0;
@@ -137,6 +143,8 @@ client *createClient(int fd) {
c->bpop.keys = dictCreate(&objectKeyHeapPointerValueDictType,NULL);
c->bpop.target = NULL;
c->bpop.xread_group = NULL;
+ c->bpop.xread_consumer = NULL;
+ c->bpop.xread_group_noack = 0;
c->bpop.numreplicas = 0;
c->bpop.reploffset = 0;
c->woff = 0;
@@ -145,13 +153,40 @@ client *createClient(int fd) {
c->pubsub_patterns = listCreate();
c->peerid = NULL;
c->client_list_node = NULL;
+ c->client_tracking_redirection = 0;
listSetFreeMethod(c->pubsub_patterns,decrRefCountVoid);
listSetMatchMethod(c->pubsub_patterns,listMatchObjects);
- if (fd != -1) linkClient(c);
+ if (conn) linkClient(c);
initClientMultiState(c);
return c;
}
+/* This funciton puts the client in the queue of clients that should write
+ * their output buffers to the socket. Note that it does not *yet* install
+ * the write handler, to start clients are put in a queue of clients that need
+ * to write, so we try to do that before returning in the event loop (see the
+ * handleClientsWithPendingWrites() function).
+ * If we fail and there is more data to write, compared to what the socket
+ * buffers can hold, then we'll really install the handler. */
+void clientInstallWriteHandler(client *c) {
+ /* Schedule the client to write the output buffers to the socket only
+ * if not already done and, for slaves, if the slave can actually receive
+ * writes at this stage. */
+ if (!(c->flags & CLIENT_PENDING_WRITE) &&
+ (c->replstate == REPL_STATE_NONE ||
+ (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
+ {
+ /* Here instead of installing the write handler, we just flag the
+ * client and put it into a list of clients that have something
+ * to write to the socket. This way before re-entering the event
+ * loop, we can try to directly write to the client sockets avoiding
+ * a system call. We'll only really install the write handler if
+ * we'll not be able to write the whole reply at once. */
+ c->flags |= CLIENT_PENDING_WRITE;
+ listAddNodeHead(server.clients_pending_write,c);
+ }
+}
+
/* This function is called every time we are going to transmit new data
* to the client. The behavior is the following:
*
@@ -187,26 +222,11 @@ int prepareClientToWrite(client *c) {
if ((c->flags & CLIENT_MASTER) &&
!(c->flags & CLIENT_MASTER_FORCE_REPLY)) return C_ERR;
- if (c->fd <= 0) return C_ERR; /* Fake client for AOF loading. */
+ if (!c->conn) return C_ERR; /* Fake client for AOF loading. */
- /* Schedule the client to write the output buffers to the socket only
- * if not already done (there were no pending writes already and the client
- * was yet not flagged), and, for slaves, if the slave can actually
- * receive writes at this stage. */
- if (!clientHasPendingReplies(c) &&
- !(c->flags & CLIENT_PENDING_WRITE) &&
- (c->replstate == REPL_STATE_NONE ||
- (c->replstate == SLAVE_STATE_ONLINE && !c->repl_put_online_on_ack)))
- {
- /* Here instead of installing the write handler, we just flag the
- * client and put it into a list of clients that have something
- * to write to the socket. This way before re-entering the event
- * loop, we can try to directly write to the client sockets avoiding
- * a system call. We'll only really install the write handler if
- * we'll not be able to write the whole reply at once. */
- c->flags |= CLIENT_PENDING_WRITE;
- listAddNodeHead(server.clients_pending_write,c);
- }
+ /* Schedule the client to write the output buffers to the socket, unless
+ * it should already be setup to do so (it has already pending data). */
+ if (!clientHasPendingReplies(c)) clientInstallWriteHandler(c);
/* Authorize the caller to queue in the output buffer of this client. */
return C_OK;
@@ -233,84 +253,38 @@ int _addReplyToBuffer(client *c, const char *s, size_t len) {
return C_OK;
}
-void _addReplyObjectToList(client *c, robj *o) {
+void _addReplyProtoToList(client *c, const char *s, size_t len) {
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
- if (listLength(c->reply) == 0) {
- sds s = sdsdup(o->ptr);
- listAddNodeTail(c->reply,s);
- c->reply_bytes += sdslen(s);
- } else {
- listNode *ln = listLast(c->reply);
- sds tail = listNodeValue(ln);
-
- /* Append to this object when possible. If tail == NULL it was
- * set via addDeferredMultiBulkLength(). */
- if (tail && sdslen(tail)+sdslen(o->ptr) <= PROTO_REPLY_CHUNK_BYTES) {
- tail = sdscatsds(tail,o->ptr);
- listNodeValue(ln) = tail;
- c->reply_bytes += sdslen(o->ptr);
- } else {
- sds s = sdsdup(o->ptr);
- listAddNodeTail(c->reply,s);
- c->reply_bytes += sdslen(s);
- }
- }
- asyncCloseClientOnOutputBufferLimitReached(c);
-}
-
-/* This method takes responsibility over the sds. When it is no longer
- * needed it will be free'd, otherwise it ends up in a robj. */
-void _addReplySdsToList(client *c, sds s) {
- if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
- sdsfree(s);
- return;
- }
-
- if (listLength(c->reply) == 0) {
- listAddNodeTail(c->reply,s);
- c->reply_bytes += sdslen(s);
- } else {
- listNode *ln = listLast(c->reply);
- sds tail = listNodeValue(ln);
-
- /* Append to this object when possible. If tail == NULL it was
- * set via addDeferredMultiBulkLength(). */
- if (tail && sdslen(tail)+sdslen(s) <= PROTO_REPLY_CHUNK_BYTES) {
- tail = sdscatsds(tail,s);
- listNodeValue(ln) = tail;
- c->reply_bytes += sdslen(s);
- sdsfree(s);
- } else {
- listAddNodeTail(c->reply,s);
- c->reply_bytes += sdslen(s);
- }
- }
- asyncCloseClientOnOutputBufferLimitReached(c);
-}
-
-void _addReplyStringToList(client *c, const char *s, size_t len) {
- if (c->flags & CLIENT_CLOSE_AFTER_REPLY) return;
-
- if (listLength(c->reply) == 0) {
- sds node = sdsnewlen(s,len);
- listAddNodeTail(c->reply,node);
- c->reply_bytes += len;
- } else {
- listNode *ln = listLast(c->reply);
- sds tail = listNodeValue(ln);
-
- /* Append to this object when possible. If tail == NULL it was
- * set via addDeferredMultiBulkLength(). */
- if (tail && sdslen(tail)+len <= PROTO_REPLY_CHUNK_BYTES) {
- tail = sdscatlen(tail,s,len);
- listNodeValue(ln) = tail;
- c->reply_bytes += len;
- } else {
- sds node = sdsnewlen(s,len);
- listAddNodeTail(c->reply,node);
- c->reply_bytes += len;
- }
+ listNode *ln = listLast(c->reply);
+ clientReplyBlock *tail = ln? listNodeValue(ln): NULL;
+
+ /* Note that 'tail' may be NULL even if we have a tail node, becuase when
+ * addDeferredMultiBulkLength() is used, it sets a dummy node to NULL just
+ * fo fill it later, when the size of the bulk length is set. */
+
+ /* Append to tail string when possible. */
+ if (tail) {
+ /* Copy the part we can fit into the tail, and leave the rest for a
+ * new node */
+ size_t avail = tail->size - tail->used;
+ size_t copy = avail >= len? len: avail;
+ memcpy(tail->buf + tail->used, s, copy);
+ tail->used += copy;
+ s += copy;
+ len -= copy;
+ }
+ if (len) {
+ /* Create a new node, make sure it is allocated to at
+ * least PROTO_REPLY_CHUNK_BYTES */
+ size_t size = len < PROTO_REPLY_CHUNK_BYTES? PROTO_REPLY_CHUNK_BYTES: len;
+ tail = zmalloc(size + sizeof(clientReplyBlock));
+ /* take over the allocation's internal fragmentation */
+ tail->size = zmalloc_usable(tail) - sizeof(clientReplyBlock);
+ tail->used = len;
+ memcpy(tail->buf, s, len);
+ listAddNodeTail(c->reply, tail);
+ c->reply_bytes += tail->size;
}
asyncCloseClientOnOutputBufferLimitReached(c);
}
@@ -320,54 +294,37 @@ void _addReplyStringToList(client *c, const char *s, size_t len) {
* The following functions are the ones that commands implementations will call.
* -------------------------------------------------------------------------- */
+/* Add the object 'obj' string representation to the client output buffer. */
void addReply(client *c, robj *obj) {
if (prepareClientToWrite(c) != C_OK) return;
- /* This is an important place where we can avoid copy-on-write
- * when there is a saving child running, avoiding touching the
- * refcount field of the object if it's not needed.
- *
- * If the encoding is RAW and there is room in the static buffer
- * we'll be able to send the object to the client without
- * messing with its page. */
if (sdsEncodedObject(obj)) {
if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
- _addReplyObjectToList(c,obj);
+ _addReplyProtoToList(c,obj->ptr,sdslen(obj->ptr));
} else if (obj->encoding == OBJ_ENCODING_INT) {
- /* Optimization: if there is room in the static buffer for 32 bytes
- * (more than the max chars a 64 bit integer can take as string) we
- * avoid decoding the object and go for the lower level approach. */
- if (listLength(c->reply) == 0 && (sizeof(c->buf) - c->bufpos) >= 32) {
- char buf[32];
- int len;
-
- len = ll2string(buf,sizeof(buf),(long)obj->ptr);
- if (_addReplyToBuffer(c,buf,len) == C_OK)
- return;
- /* else... continue with the normal code path, but should never
- * happen actually since we verified there is room. */
- }
- obj = getDecodedObject(obj);
- if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != C_OK)
- _addReplyObjectToList(c,obj);
- decrRefCount(obj);
+ /* For integer encoded strings we just convert it into a string
+ * using our optimized function, and attach the resulting string
+ * to the output buffer. */
+ char buf[32];
+ size_t len = ll2string(buf,sizeof(buf),(long)obj->ptr);
+ if (_addReplyToBuffer(c,buf,len) != C_OK)
+ _addReplyProtoToList(c,buf,len);
} else {
serverPanic("Wrong obj->encoding in addReply()");
}
}
+/* Add the SDS 's' string to the client output buffer, as a side effect
+ * the SDS string is freed. */
void addReplySds(client *c, sds s) {
if (prepareClientToWrite(c) != C_OK) {
/* The caller expects the sds to be free'd. */
sdsfree(s);
return;
}
- if (_addReplyToBuffer(c,s,sdslen(s)) == C_OK) {
- sdsfree(s);
- } else {
- /* This method free's the sds when it is no longer needed. */
- _addReplySdsToList(c,s);
- }
+ if (_addReplyToBuffer(c,s,sdslen(s)) != C_OK)
+ _addReplyProtoToList(c,s,sdslen(s));
+ sdsfree(s);
}
/* This low level function just adds whatever protocol you send it to the
@@ -376,23 +333,46 @@ void addReplySds(client *c, sds s) {
*
* It is efficient because does not create an SDS object nor an Redis object
* if not needed. The object will only be created by calling
- * _addReplyStringToList() if we fail to extend the existing tail object
+ * _addReplyProtoToList() if we fail to extend the existing tail object
* in the list of objects. */
-void addReplyString(client *c, const char *s, size_t len) {
+void addReplyProto(client *c, const char *s, size_t len) {
if (prepareClientToWrite(c) != C_OK) return;
if (_addReplyToBuffer(c,s,len) != C_OK)
- _addReplyStringToList(c,s,len);
+ _addReplyProtoToList(c,s,len);
}
+/* Low level function called by the addReplyError...() functions.
+ * It emits the protocol for a Redis error, in the form:
+ *
+ * -ERRORCODE Error Message<CR><LF>
+ *
+ * If the error code is already passed in the string 's', the error
+ * code provided is used, otherwise the string "-ERR " for the generic
+ * error code is automatically added. */
void addReplyErrorLength(client *c, const char *s, size_t len) {
- addReplyString(c,"-ERR ",5);
- addReplyString(c,s,len);
- addReplyString(c,"\r\n",2);
- if (c->flags & CLIENT_MASTER) {
+ /* If the string already starts with "-..." then the error code
+ * is provided by the caller. Otherwise we use "-ERR". */
+ if (!len || s[0] != '-') addReplyProto(c,"-ERR ",5);
+ addReplyProto(c,s,len);
+ addReplyProto(c,"\r\n",2);
+
+ /* Sometimes it could be normal that a slave replies to a master with
+ * an error and this function gets called. Actually the error will never
+ * be sent because addReply*() against master clients has no effect...
+ * A notable example is:
+ *
+ * EVAL 'redis.call("incr",KEYS[1]); redis.call("nonexisting")' 1 x
+ *
+ * Where the master must propagate the first change even if the second
+ * will produce an error. However it is useful to log such events since
+ * they are rare and may hint at errors in a script or a bug in Redis. */
+ if (c->flags & (CLIENT_MASTER|CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
+ char* to = c->flags & CLIENT_MASTER? "master": "replica";
+ char* from = c->flags & CLIENT_MASTER? "replica": "master";
char *cmdname = c->lastcmd ? c->lastcmd->name : "<unknown>";
- serverLog(LL_WARNING,"== CRITICAL == This slave is sending an error "
- "to its master: '%s' after processing the command "
- "'%s'", s, cmdname);
+ serverLog(LL_WARNING,"== CRITICAL == This %s is sending an error "
+ "to its %s: '%s' after processing the command "
+ "'%s'", from, to, s, cmdname);
}
}
@@ -417,9 +397,9 @@ void addReplyErrorFormat(client *c, const char *fmt, ...) {
}
void addReplyStatusLength(client *c, const char *s, size_t len) {
- addReplyString(c,"+",1);
- addReplyString(c,s,len);
- addReplyString(c,"\r\n",2);
+ addReplyProto(c,"+",1);
+ addReplyProto(c,s,len);
+ addReplyProto(c,"\r\n",2);
}
void addReplyStatus(client *c, const char *status) {
@@ -437,54 +417,106 @@ void addReplyStatusFormat(client *c, const char *fmt, ...) {
/* Adds an empty object to the reply list that will contain the multi bulk
* length, which is not known when this function is called. */
-void *addDeferredMultiBulkLength(client *c) {
+void *addReplyDeferredLen(client *c) {
/* Note that we install the write event here even if the object is not
* ready to be sent, since we are sure that before returning to the
- * event loop setDeferredMultiBulkLength() will be called. */
+ * event loop setDeferredAggregateLen() will be called. */
if (prepareClientToWrite(c) != C_OK) return NULL;
listAddNodeTail(c->reply,NULL); /* NULL is our placeholder. */
return listLast(c->reply);
}
/* Populate the length object and try gluing it to the next chunk. */
-void setDeferredMultiBulkLength(client *c, void *node, long length) {
+void setDeferredAggregateLen(client *c, void *node, long length, char prefix) {
listNode *ln = (listNode*)node;
- sds len, next;
+ clientReplyBlock *next;
+ char lenstr[128];
+ size_t lenstr_len = sprintf(lenstr, "%c%ld\r\n", prefix, length);
/* Abort when *node is NULL: when the client should not accept writes
- * we return NULL in addDeferredMultiBulkLength() */
+ * we return NULL in addReplyDeferredLen() */
if (node == NULL) return;
-
- len = sdscatprintf(sdsnewlen("*",1),"%ld\r\n",length);
- listNodeValue(ln) = len;
- c->reply_bytes += sdslen(len);
- if (ln->next != NULL) {
- next = listNodeValue(ln->next);
-
- /* Only glue when the next node is non-NULL (an sds in this case) */
- if (next != NULL) {
- len = sdscatsds(len,next);
- listDelNode(c->reply,ln->next);
- listNodeValue(ln) = len;
- /* No need to update c->reply_bytes: we are just moving the same
- * amount of bytes from one node to another. */
- }
+ serverAssert(!listNodeValue(ln));
+
+ /* Normally we fill this dummy NULL node, added by addReplyDeferredLen(),
+ * with a new buffer structure containing the protocol needed to specify
+ * the length of the array following. However sometimes when there is
+ * little memory to move, we may instead remove this NULL node, and prefix
+ * our protocol in the node immediately after to it, in order to save a
+ * write(2) syscall later. Conditions needed to do it:
+ *
+ * - The next node is non-NULL,
+ * - It has enough room already allocated
+ * - And not too large (avoid large memmove) */
+ if (ln->next != NULL && (next = listNodeValue(ln->next)) &&
+ next->size - next->used >= lenstr_len &&
+ next->used < PROTO_REPLY_CHUNK_BYTES * 4) {
+ memmove(next->buf + lenstr_len, next->buf, next->used);
+ memcpy(next->buf, lenstr, lenstr_len);
+ next->used += lenstr_len;
+ listDelNode(c->reply,ln);
+ } else {
+ /* Create a new node */
+ clientReplyBlock *buf = zmalloc(lenstr_len + sizeof(clientReplyBlock));
+ /* Take over the allocation's internal fragmentation */
+ buf->size = zmalloc_usable(buf) - sizeof(clientReplyBlock);
+ buf->used = lenstr_len;
+ memcpy(buf->buf, lenstr, lenstr_len);
+ listNodeValue(ln) = buf;
+ c->reply_bytes += buf->size;
}
asyncCloseClientOnOutputBufferLimitReached(c);
}
+void setDeferredArrayLen(client *c, void *node, long length) {
+ setDeferredAggregateLen(c,node,length,'*');
+}
+
+void setDeferredMapLen(client *c, void *node, long length) {
+ int prefix = c->resp == 2 ? '*' : '%';
+ if (c->resp == 2) length *= 2;
+ setDeferredAggregateLen(c,node,length,prefix);
+}
+
+void setDeferredSetLen(client *c, void *node, long length) {
+ int prefix = c->resp == 2 ? '*' : '~';
+ setDeferredAggregateLen(c,node,length,prefix);
+}
+
+void setDeferredAttributeLen(client *c, void *node, long length) {
+ int prefix = c->resp == 2 ? '*' : '|';
+ if (c->resp == 2) length *= 2;
+ setDeferredAggregateLen(c,node,length,prefix);
+}
+
+void setDeferredPushLen(client *c, void *node, long length) {
+ int prefix = c->resp == 2 ? '*' : '>';
+ setDeferredAggregateLen(c,node,length,prefix);
+}
+
/* Add a double as a bulk reply */
void addReplyDouble(client *c, double d) {
- char dbuf[128], sbuf[128];
- int dlen, slen;
if (isinf(d)) {
/* Libc in odd systems (Hi Solaris!) will format infinite in a
* different way, so better to handle it in an explicit way. */
- addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
+ if (c->resp == 2) {
+ addReplyBulkCString(c, d > 0 ? "inf" : "-inf");
+ } else {
+ addReplyProto(c, d > 0 ? ",inf\r\n" : ",-inf\r\n",
+ d > 0 ? 6 : 7);
+ }
} else {
- dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
- slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
- addReplyString(c,sbuf,slen);
+ char dbuf[MAX_LONG_DOUBLE_CHARS+3],
+ sbuf[MAX_LONG_DOUBLE_CHARS+32];
+ int dlen, slen;
+ if (c->resp == 2) {
+ dlen = snprintf(dbuf,sizeof(dbuf),"%.17g",d);
+ slen = snprintf(sbuf,sizeof(sbuf),"$%d\r\n%s\r\n",dlen,dbuf);
+ addReplyProto(c,sbuf,slen);
+ } else {
+ dlen = snprintf(dbuf,sizeof(dbuf),",%.17g\r\n",d);
+ addReplyProto(c,dbuf,dlen);
+ }
}
}
@@ -492,9 +524,17 @@ void addReplyDouble(client *c, double d) {
* of the double instead of exposing the crude behavior of doubles to the
* dear user. */
void addReplyHumanLongDouble(client *c, long double d) {
- robj *o = createStringObjectFromLongDouble(d,1);
- addReplyBulk(c,o);
- decrRefCount(o);
+ if (c->resp == 2) {
+ robj *o = createStringObjectFromLongDouble(d,1);
+ addReplyBulk(c,o);
+ decrRefCount(o);
+ } else {
+ char buf[MAX_LONG_DOUBLE_CHARS];
+ int len = ld2string(buf,sizeof(buf),d,1);
+ addReplyProto(c,",",1);
+ addReplyProto(c,buf,len);
+ addReplyProto(c,"\r\n",2);
+ }
}
/* Add a long long as integer reply or bulk len / multi bulk count.
@@ -518,7 +558,7 @@ void addReplyLongLongWithPrefix(client *c, long long ll, char prefix) {
len = ll2string(buf+1,sizeof(buf)-1,ll);
buf[len+1] = '\r';
buf[len+2] = '\n';
- addReplyString(c,buf,len+3);
+ addReplyProto(c,buf,len+3);
}
void addReplyLongLong(client *c, long long ll) {
@@ -530,32 +570,70 @@ void addReplyLongLong(client *c, long long ll) {
addReplyLongLongWithPrefix(c,ll,':');
}
-void addReplyMultiBulkLen(client *c, long length) {
- if (length < OBJ_SHARED_BULKHDR_LEN)
+void addReplyAggregateLen(client *c, long length, int prefix) {
+ if (prefix == '*' && length < OBJ_SHARED_BULKHDR_LEN)
addReply(c,shared.mbulkhdr[length]);
else
- addReplyLongLongWithPrefix(c,length,'*');
+ addReplyLongLongWithPrefix(c,length,prefix);
}
-/* Create the length prefix of a bulk reply, example: $2234 */
-void addReplyBulkLen(client *c, robj *obj) {
- size_t len;
+void addReplyArrayLen(client *c, long length) {
+ addReplyAggregateLen(c,length,'*');
+}
- if (sdsEncodedObject(obj)) {
- len = sdslen(obj->ptr);
+void addReplyMapLen(client *c, long length) {
+ int prefix = c->resp == 2 ? '*' : '%';
+ if (c->resp == 2) length *= 2;
+ addReplyAggregateLen(c,length,prefix);
+}
+
+void addReplySetLen(client *c, long length) {
+ int prefix = c->resp == 2 ? '*' : '~';
+ addReplyAggregateLen(c,length,prefix);
+}
+
+void addReplyAttributeLen(client *c, long length) {
+ int prefix = c->resp == 2 ? '*' : '|';
+ if (c->resp == 2) length *= 2;
+ addReplyAggregateLen(c,length,prefix);
+}
+
+void addReplyPushLen(client *c, long length) {
+ int prefix = c->resp == 2 ? '*' : '>';
+ addReplyAggregateLen(c,length,prefix);
+}
+
+void addReplyNull(client *c) {
+ if (c->resp == 2) {
+ addReplyProto(c,"$-1\r\n",5);
} else {
- long n = (long)obj->ptr;
+ addReplyProto(c,"_\r\n",3);
+ }
+}
- /* Compute how many bytes will take this integer as a radix 10 string */
- len = 1;
- if (n < 0) {
- len++;
- n = -n;
- }
- while((n = n/10) != 0) {
- len++;
- }
+void addReplyBool(client *c, int b) {
+ if (c->resp == 2) {
+ addReply(c, b ? shared.cone : shared.czero);
+ } else {
+ addReplyProto(c, b ? "#t\r\n" : "#f\r\n",4);
}
+}
+
+/* A null array is a concept that no longer exists in RESP3. However
+ * RESP2 had it, so API-wise we have this call, that will emit the correct
+ * RESP2 protocol, however for RESP3 the reply will always be just the
+ * Null type "_\r\n". */
+void addReplyNullArray(client *c) {
+ if (c->resp == 2) {
+ addReplyProto(c,"*-1\r\n",5);
+ } else {
+ addReplyProto(c,"_\r\n",3);
+ }
+}
+
+/* Create the length prefix of a bulk reply, example: $2234 */
+void addReplyBulkLen(client *c, robj *obj) {
+ size_t len = stringObjectLen(obj);
if (len < OBJ_SHARED_BULKHDR_LEN)
addReply(c,shared.bulkhdr[len]);
@@ -573,7 +651,7 @@ void addReplyBulk(client *c, robj *obj) {
/* Add a C buffer as bulk reply */
void addReplyBulkCBuffer(client *c, const void *p, size_t len) {
addReplyLongLongWithPrefix(c,len,'$');
- addReplyString(c,p,len);
+ addReplyProto(c,p,len);
addReply(c,shared.crlf);
}
@@ -587,7 +665,7 @@ void addReplyBulkSds(client *c, sds s) {
/* Add a C null term string as bulk reply */
void addReplyBulkCString(client *c, const char *s) {
if (s == NULL) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
addReplyBulkCBuffer(c,s,strlen(s));
}
@@ -602,13 +680,42 @@ void addReplyBulkLongLong(client *c, long long ll) {
addReplyBulkCBuffer(c,buf,len);
}
+/* Reply with a verbatim type having the specified extension.
+ *
+ * The 'ext' is the "extension" of the file, actually just a three
+ * character type that describes the format of the verbatim string.
+ * For instance "txt" means it should be interpreted as a text only
+ * file by the receiver, "md " as markdown, and so forth. Only the
+ * three first characters of the extension are used, and if the
+ * provided one is shorter than that, the remaining is filled with
+ * spaces. */
+void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext) {
+ if (c->resp == 2) {
+ addReplyBulkCBuffer(c,s,len);
+ } else {
+ char buf[32];
+ size_t preflen = snprintf(buf,sizeof(buf),"=%zu\r\nxxx:",len+4);
+ char *p = buf+preflen-4;
+ for (int i = 0; i < 3; i++) {
+ if (*ext == '\0') {
+ p[i] = ' ';
+ } else {
+ p[i] = *ext++;
+ }
+ }
+ addReplyProto(c,buf,preflen);
+ addReplyProto(c,s,len);
+ addReplyProto(c,"\r\n",2);
+ }
+}
+
/* Add an array of C strings as status replies with a heading.
* This function is typically invoked by from commands that support
* subcommands in response to the 'help' subcommand. The help array
* is terminated by NULL sentinel. */
void addReplyHelp(client *c, const char **help) {
sds cmd = sdsnew((char*) c->argv[0]->ptr);
- void *blenp = addDeferredMultiBulkLength(c);
+ void *blenp = addReplyDeferredLen(c);
int blen = 0;
sdstoupper(cmd);
@@ -619,7 +726,32 @@ void addReplyHelp(client *c, const char **help) {
while (help[blen]) addReplyStatus(c,help[blen++]);
blen++; /* Account for the header line(s). */
- setDeferredMultiBulkLength(c,blenp,blen);
+ setDeferredArrayLen(c,blenp,blen);
+}
+
+/* Add a suggestive error reply.
+ * This function is typically invoked by from commands that support
+ * subcommands in response to an unknown subcommand or argument error. */
+void addReplySubcommandSyntaxError(client *c) {
+ sds cmd = sdsnew((char*) c->argv[0]->ptr);
+ sdstoupper(cmd);
+ addReplyErrorFormat(c,
+ "Unknown subcommand or wrong number of arguments for '%s'. Try %s HELP.",
+ (char*)c->argv[1]->ptr,cmd);
+ sdsfree(cmd);
+}
+
+/* Append 'src' client output buffers into 'dst' client output buffers.
+ * This function clears the output buffers of 'src' */
+void AddReplyFromClient(client *dst, client *src) {
+ if (prepareClientToWrite(dst) != C_OK)
+ return;
+ addReplyProto(dst,src->buf, src->bufpos);
+ if (listLength(src->reply))
+ listJoin(dst->reply,src->reply);
+ dst->reply_bytes += src->reply_bytes;
+ src->reply_bytes = 0;
+ src->bufpos = 0;
}
/* Copy 'src' client output buffers into 'dst' client output buffers.
@@ -627,6 +759,7 @@ void addReplyHelp(client *c, const char **help) {
* destination client. */
void copyClientOutputBuffer(client *dst, client *src) {
listRelease(dst->reply);
+ dst->sentlen = 0;
dst->reply = listDup(src->reply);
memcpy(dst->buf,src->buf,src->bufpos);
dst->bufpos = src->bufpos;
@@ -639,28 +772,13 @@ int clientHasPendingReplies(client *c) {
return c->bufpos || listLength(c->reply);
}
-#define MAX_ACCEPTS_PER_CALL 1000
-static void acceptCommonHandler(int fd, int flags, char *ip) {
- client *c;
- if ((c = createClient(fd)) == NULL) {
- serverLog(LL_WARNING,
- "Error registering fd event for the new client: %s (fd=%d)",
- strerror(errno),fd);
- close(fd); /* May be already closed, just ignore errors */
- return;
- }
- /* If maxclient directive is set and this is one client more... close the
- * connection. Note that we create the client instead to check before
- * for this condition, since now the socket is already set in non-blocking
- * mode and we can send an error for free using the Kernel I/O */
- if (listLength(server.clients) > server.maxclients) {
- char *err = "-ERR max number of clients reached\r\n";
+void clientAcceptHandler(connection *conn) {
+ client *c = connGetPrivateData(conn);
- /* That's a best effort error message, don't check write errors */
- if (write(c->fd,err,strlen(err)) == -1) {
- /* Nothing to do, Just to avoid the warning... */
- }
- server.stat_rejected_conn++;
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_WARNING,
+ "Error accepting a client connection: %s",
+ connGetLastError(conn));
freeClient(c);
return;
}
@@ -671,11 +789,13 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
* user what to do to fix it if needed. */
if (server.protected_mode &&
server.bindaddr_count == 0 &&
- server.requirepass == NULL &&
- !(flags & CLIENT_UNIX_SOCKET) &&
- ip != NULL)
+ DefaultUser->flags & USER_FLAG_NOPASS &&
+ !(c->flags & CLIENT_UNIX_SOCKET))
{
- if (strcmp(ip,"127.0.0.1") && strcmp(ip,"::1")) {
+ char cip[NET_IP_STR_LEN+1] = { 0 };
+ connPeerToString(conn, cip, sizeof(cip)-1, NULL);
+
+ if (strcmp(cip,"127.0.0.1") && strcmp(cip,"::1")) {
char *err =
"-DENIED Redis is running in protected mode because protected "
"mode is enabled, no bind address was specified, no "
@@ -697,7 +817,7 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
"4) Setup a bind address or an authentication password. "
"NOTE: You only need to do one of the above things in order for "
"the server to start accepting connections from the outside.\r\n";
- if (write(c->fd,err,strlen(err)) == -1) {
+ if (connWrite(c->conn,err,strlen(err)) == -1) {
/* Nothing to do, Just to avoid the warning... */
}
server.stat_rejected_conn++;
@@ -707,7 +827,65 @@ static void acceptCommonHandler(int fd, int flags, char *ip) {
}
server.stat_numconnections++;
+ moduleFireServerEvent(REDISMODULE_EVENT_CLIENT_CHANGE,
+ REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED,
+ c);
+}
+
+#define MAX_ACCEPTS_PER_CALL 1000
+static void acceptCommonHandler(connection *conn, int flags, char *ip) {
+ client *c;
+ UNUSED(ip);
+
+ /* Admission control will happen before a client is created and connAccept()
+ * called, because we don't want to even start transport-level negotiation
+ * if rejected.
+ */
+ if (listLength(server.clients) >= server.maxclients) {
+ char *err = "-ERR max number of clients reached\r\n";
+
+ /* That's a best effort error message, don't check write errors.
+ * Note that for TLS connections, no handshake was done yet so nothing is written
+ * and the connection will just drop.
+ */
+ if (connWrite(conn,err,strlen(err)) == -1) {
+ /* Nothing to do, Just to avoid the warning... */
+ }
+ server.stat_rejected_conn++;
+ connClose(conn);
+ return;
+ }
+
+ /* Create connection and client */
+ if ((c = createClient(conn)) == NULL) {
+ char conninfo[100];
+ serverLog(LL_WARNING,
+ "Error registering fd event for the new client: %s (conn: %s)",
+ connGetLastError(conn),
+ connGetInfo(conn, conninfo, sizeof(conninfo)));
+ connClose(conn); /* May be already closed, just ignore errors */
+ return;
+ }
+
+ /* Last chance to keep flags */
c->flags |= flags;
+
+ /* Initiate accept.
+ *
+ * Note that connAccept() is free to do two things here:
+ * 1. Call clientAcceptHandler() immediately;
+ * 2. Schedule a future call to clientAcceptHandler().
+ *
+ * Because of that, we must do nothing else afterwards.
+ */
+ if (connAccept(conn, clientAcceptHandler) == C_ERR) {
+ char conninfo[100];
+ serverLog(LL_WARNING,
+ "Error accepting a client connection: %s (conn: %s)",
+ connGetLastError(conn), connGetInfo(conn, conninfo, sizeof(conninfo)));
+ freeClient(connGetPrivateData(conn));
+ return;
+ }
}
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
@@ -726,7 +904,27 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
- acceptCommonHandler(cfd,0,cip);
+ acceptCommonHandler(connCreateAcceptedSocket(cfd),0,cip);
+ }
+}
+
+void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
+ int cport, cfd, max = MAX_ACCEPTS_PER_CALL;
+ char cip[NET_IP_STR_LEN];
+ UNUSED(el);
+ UNUSED(mask);
+ UNUSED(privdata);
+
+ while(max--) {
+ cfd = anetTcpAccept(server.neterr, fd, cip, sizeof(cip), &cport);
+ if (cfd == ANET_ERR) {
+ if (errno != EWOULDBLOCK)
+ serverLog(LL_WARNING,
+ "Accepting client connection: %s", server.neterr);
+ return;
+ }
+ serverLog(LL_VERBOSE,"Accepted %s:%d", cip, cport);
+ acceptCommonHandler(connCreateAcceptedTLS(cfd, server.tls_auth_clients),0,cip);
}
}
@@ -745,7 +943,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
return;
}
serverLog(LL_VERBOSE,"Accepted connection to %s", server.unixsocket);
- acceptCommonHandler(cfd,CLIENT_UNIX_SOCKET,NULL);
+ acceptCommonHandler(connCreateAcceptedSocket(cfd),CLIENT_UNIX_SOCKET,NULL);
}
}
@@ -776,21 +974,35 @@ void unlinkClient(client *c) {
/* If this is marked as current client unset it. */
if (server.current_client == c) server.current_client = NULL;
- /* Certain operations must be done only if the client has an active socket.
+ /* Certain operations must be done only if the client has an active connection.
* If the client was already unlinked or if it's a "fake client" the
- * fd is already set to -1. */
- if (c->fd != -1) {
+ * conn is already set to NULL. */
+ if (c->conn) {
/* Remove from the list of active clients. */
if (c->client_list_node) {
+ uint64_t id = htonu64(c->id);
+ raxRemove(server.clients_index,(unsigned char*)&id,sizeof(id),NULL);
listDelNode(server.clients,c->client_list_node);
c->client_list_node = NULL;
}
- /* Unregister async I/O handlers and close the socket. */
- aeDeleteFileEvent(server.el,c->fd,AE_READABLE);
- aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
- close(c->fd);
- c->fd = -1;
+ /* Check if this is a replica waiting for diskless replication (rdb pipe),
+ * in which case it needs to be cleaned from that list */
+ if (c->flags & CLIENT_SLAVE &&
+ c->replstate == SLAVE_STATE_WAIT_BGSAVE_END &&
+ server.rdb_pipe_conns)
+ {
+ int i;
+ for (i=0; i < server.rdb_pipe_numconns; i++) {
+ if (server.rdb_pipe_conns[i] == c->conn) {
+ rdbPipeWriteHandlerConnRemoved(c->conn);
+ server.rdb_pipe_conns[i] = NULL;
+ break;
+ }
+ }
+ }
+ connClose(c->conn);
+ c->conn = NULL;
}
/* Remove from the list of pending writes if needed. */
@@ -801,6 +1013,14 @@ void unlinkClient(client *c) {
c->flags &= ~CLIENT_PENDING_WRITE;
}
+ /* Remove from the list of pending reads if needed. */
+ if (c->flags & CLIENT_PENDING_READ) {
+ ln = listSearchKey(server.clients_pending_read,c);
+ serverAssert(ln != NULL);
+ listDelNode(server.clients_pending_read,ln);
+ c->flags &= ~CLIENT_PENDING_READ;
+ }
+
/* When client was just unblocked because of a blocking operation,
* remove it from the list of unblocked clients. */
if (c->flags & CLIENT_UNBLOCKED) {
@@ -809,11 +1029,28 @@ void unlinkClient(client *c) {
listDelNode(server.unblocked_clients,ln);
c->flags &= ~CLIENT_UNBLOCKED;
}
+
+ /* Clear the tracking status. */
+ if (c->flags & CLIENT_TRACKING) disableTracking(c);
}
void freeClient(client *c) {
listNode *ln;
+ /* If a client is protected, yet we need to free it right now, make sure
+ * to at least use asynchronous freeing. */
+ if (c->flags & CLIENT_PROTECTED) {
+ freeClientAsync(c);
+ return;
+ }
+
+ /* For connected clients, call the disconnection event of modules hooks. */
+ if (c->conn) {
+ moduleFireServerEvent(REDISMODULE_EVENT_CLIENT_CHANGE,
+ REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED,
+ c);
+ }
+
/* If it is our master that's beging disconnected we should make sure
* to cache the state to try a partial resynchronization later.
*
@@ -823,8 +1060,7 @@ void freeClient(client *c) {
serverLog(LL_WARNING,"Connection with master lost.");
if (!(c->flags & (CLIENT_CLOSE_AFTER_REPLY|
CLIENT_CLOSE_ASAP|
- CLIENT_BLOCKED|
- CLIENT_UNBLOCKED)))
+ CLIENT_BLOCKED)))
{
replicationCacheMaster(c);
return;
@@ -833,7 +1069,7 @@ void freeClient(client *c) {
/* Log link disconnection with slave */
if ((c->flags & CLIENT_SLAVE) && !(c->flags & CLIENT_MONITOR)) {
- serverLog(LL_WARNING,"Connection with slave %s lost.",
+ serverLog(LL_WARNING,"Connection with replica %s lost.",
replicationGetSlaveName(c));
}
@@ -910,9 +1146,17 @@ void freeClient(client *c) {
* a context where calling freeClient() is not possible, because the client
* should be valid for the continuation of the flow of the program. */
void freeClientAsync(client *c) {
+ /* We need to handle concurrent access to the server.clients_to_close list
+ * only in the freeClientAsync() function, since it's the only function that
+ * may access the list while Redis uses I/O threads. All the other accesses
+ * are in the context of the main thread while the other threads are
+ * idle. */
+ static pthread_mutex_t async_free_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
if (c->flags & CLIENT_CLOSE_ASAP || c->flags & CLIENT_LUA) return;
c->flags |= CLIENT_CLOSE_ASAP;
+ pthread_mutex_lock(&async_free_queue_mutex);
listAddNodeTail(server.clients_to_close,c);
+ pthread_mutex_unlock(&async_free_queue_mutex);
}
void freeClientsInAsyncFreeQueue(void) {
@@ -926,16 +1170,31 @@ void freeClientsInAsyncFreeQueue(void) {
}
}
+/* Return a client by ID, or NULL if the client ID is not in the set
+ * of registered clients. Note that "fake clients", created with -1 as FD,
+ * are not registered clients. */
+client *lookupClientByID(uint64_t id) {
+ id = htonu64(id);
+ client *c = raxFind(server.clients_index,(unsigned char*)&id,sizeof(id));
+ return (c == raxNotFound) ? NULL : c;
+}
+
/* Write data in output buffers to client. Return C_OK if the client
- * is still valid after the call, C_ERR if it was freed. */
-int writeToClient(int fd, client *c, int handler_installed) {
+ * is still valid after the call, C_ERR if it was freed because of some
+ * error. If handler_installed is set, it will attempt to clear the
+ * write event.
+ *
+ * This function is called by threads, but always with handler_installed
+ * set to 0. So when handler_installed is set to 0 the function must be
+ * thread safe. */
+int writeToClient(client *c, int handler_installed) {
ssize_t nwritten = 0, totwritten = 0;
size_t objlen;
- sds o;
+ clientReplyBlock *o;
while(clientHasPendingReplies(c)) {
if (c->bufpos > 0) {
- nwritten = write(fd,c->buf+c->sentlen,c->bufpos-c->sentlen);
+ nwritten = connWrite(c->conn,c->buf+c->sentlen,c->bufpos-c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
@@ -948,23 +1207,24 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
} else {
o = listNodeValue(listFirst(c->reply));
- objlen = sdslen(o);
+ objlen = o->used;
if (objlen == 0) {
+ c->reply_bytes -= o->size;
listDelNode(c->reply,listFirst(c->reply));
continue;
}
- nwritten = write(fd, o + c->sentlen, objlen - c->sentlen);
+ nwritten = connWrite(c->conn, o->buf + c->sentlen, objlen - c->sentlen);
if (nwritten <= 0) break;
c->sentlen += nwritten;
totwritten += nwritten;
/* If we fully sent the object on head go to the next one */
if (c->sentlen == objlen) {
+ c->reply_bytes -= o->size;
listDelNode(c->reply,listFirst(c->reply));
c->sentlen = 0;
- c->reply_bytes -= objlen;
/* If there are no longer objects in the list, we expect
* the count of reply bytes to be exactly zero. */
if (listLength(c->reply) == 0)
@@ -990,12 +1250,12 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
server.stat_net_output_bytes += totwritten;
if (nwritten == -1) {
- if (errno == EAGAIN) {
+ if (connGetState(c->conn) == CONN_STATE_CONNECTED) {
nwritten = 0;
} else {
serverLog(LL_VERBOSE,
- "Error writing to client: %s", strerror(errno));
- freeClient(c);
+ "Error writing to client: %s", connGetLastError(c->conn));
+ freeClientAsync(c);
return C_ERR;
}
}
@@ -1008,11 +1268,15 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
if (!clientHasPendingReplies(c)) {
c->sentlen = 0;
- if (handler_installed) aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
+ /* Note that writeToClient() is called in a threaded way, but
+ * adDeleteFileEvent() is not thread safe: however writeToClient()
+ * is always called with handler_installed set to 0 from threads
+ * so we are fine. */
+ if (handler_installed) connSetWriteHandler(c->conn, NULL);
/* Close connection after entire reply has been sent. */
if (c->flags & CLIENT_CLOSE_AFTER_REPLY) {
- freeClient(c);
+ freeClientAsync(c);
return C_ERR;
}
}
@@ -1020,10 +1284,9 @@ int writeToClient(int fd, client *c, int handler_installed) {
}
/* Write event handler. Just send data to the client. */
-void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
- UNUSED(el);
- UNUSED(mask);
- writeToClient(fd,privdata,1);
+void sendReplyToClient(connection *conn) {
+ client *c = connGetPrivateData(conn);
+ writeToClient(c,1);
}
/* This function is called just before entering the event loop, in the hope
@@ -1041,27 +1304,29 @@ int handleClientsWithPendingWrites(void) {
c->flags &= ~CLIENT_PENDING_WRITE;
listDelNode(server.clients_pending_write,ln);
+ /* If a client is protected, don't do anything,
+ * that may trigger write error or recreate handler. */
+ if (c->flags & CLIENT_PROTECTED) continue;
+
/* Try to write buffers to the client socket. */
- if (writeToClient(c->fd,c,0) == C_ERR) continue;
+ if (writeToClient(c,0) == C_ERR) continue;
/* If after the synchronous writes above we still have data to
* output to the client, we need to install the writable handler. */
if (clientHasPendingReplies(c)) {
- int ae_flags = AE_WRITABLE;
+ int ae_barrier = 0;
/* For the fsync=always policy, we want that a given FD is never
* served for reading and writing in the same event loop iteration,
* so that in the middle of receiving the query, and serving it
* to the client, we'll call beforeSleep() that will do the
- * actual fsync of AOF to disk. AE_BARRIER ensures that. */
+ * actual fsync of AOF to disk. the write barrier ensures that. */
if (server.aof_state == AOF_ON &&
server.aof_fsync == AOF_FSYNC_ALWAYS)
{
- ae_flags |= AE_BARRIER;
+ ae_barrier = 1;
}
- if (aeCreateFileEvent(server.el, c->fd, ae_flags,
- sendReplyToClient, c) == AE_ERR)
- {
- freeClientAsync(c);
+ if (connSetWriteHandlerWithBarrier(c->conn, sendReplyToClient, ae_barrier) == C_ERR) {
+ freeClientAsync(c);
}
}
}
@@ -1092,6 +1357,34 @@ void resetClient(client *c) {
}
}
+/* This funciton is used when we want to re-enter the event loop but there
+ * is the risk that the client we are dealing with will be freed in some
+ * way. This happens for instance in:
+ *
+ * * DEBUG RELOAD and similar.
+ * * When a Lua script is in -BUSY state.
+ *
+ * So the function will protect the client by doing two things:
+ *
+ * 1) It removes the file events. This way it is not possible that an
+ * error is signaled on the socket, freeing the client.
+ * 2) Moreover it makes sure that if the client is freed in a different code
+ * path, it is not really released, but only marked for later release. */
+void protectClient(client *c) {
+ c->flags |= CLIENT_PROTECTED;
+ connSetReadHandler(c->conn,NULL);
+ connSetWriteHandler(c->conn,NULL);
+}
+
+/* This will undo the client protection done by protectClient() */
+void unprotectClient(client *c) {
+ if (c->flags & CLIENT_PROTECTED) {
+ c->flags &= ~CLIENT_PROTECTED;
+ connSetReadHandler(c->conn,readQueryFromClient);
+ if (clientHasPendingReplies(c)) clientInstallWriteHandler(c);
+ }
+}
+
/* Like processMultibulkBuffer(), but for the inline protocol instead of RESP,
* this function consumes the client query buffer and creates a command ready
* to be executed inside the client structure. Returns C_OK if the command
@@ -1101,34 +1394,34 @@ void resetClient(client *c) {
* with the error and close the connection. */
int processInlineBuffer(client *c) {
char *newline;
- int argc, j;
+ int argc, j, linefeed_chars = 1;
sds *argv, aux;
size_t querylen;
/* Search for end of line */
- newline = strchr(c->querybuf,'\n');
+ newline = strchr(c->querybuf+c->qb_pos,'\n');
/* Nothing to do without a \r\n */
if (newline == NULL) {
- if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
+ if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big inline request");
- setProtocolError("too big inline request",c,0);
+ setProtocolError("too big inline request",c);
}
return C_ERR;
}
/* Handle the \r\n case. */
- if (newline && newline != c->querybuf && *(newline-1) == '\r')
- newline--;
+ if (newline && newline != c->querybuf+c->qb_pos && *(newline-1) == '\r')
+ newline--, linefeed_chars++;
/* Split the input buffer up to the \r\n */
- querylen = newline-(c->querybuf);
- aux = sdsnewlen(c->querybuf,querylen);
+ querylen = newline-(c->querybuf+c->qb_pos);
+ aux = sdsnewlen(c->querybuf+c->qb_pos,querylen);
argv = sdssplitargs(aux,&argc);
sdsfree(aux);
if (argv == NULL) {
addReplyError(c,"Protocol error: unbalanced quotes in request");
- setProtocolError("unbalanced quotes in inline request",c,0);
+ setProtocolError("unbalanced quotes in inline request",c);
return C_ERR;
}
@@ -1138,8 +1431,8 @@ int processInlineBuffer(client *c) {
if (querylen == 0 && c->flags & CLIENT_SLAVE)
c->repl_ack_time = server.unixtime;
- /* Leave data after the first line of the query in the buffer */
- sdsrange(c->querybuf,querylen+2,-1);
+ /* Move querybuffer position to the next query in the buffer. */
+ c->qb_pos += querylen+linefeed_chars;
/* Setup argv array on client structure */
if (argc) {
@@ -1160,19 +1453,19 @@ int processInlineBuffer(client *c) {
return C_OK;
}
-/* Helper function. Trims query buffer to make the function that processes
- * multi bulk requests idempotent. */
+/* Helper function. Record protocol erro details in server log,
+ * and set the client as CLIENT_CLOSE_AFTER_REPLY. */
#define PROTO_DUMP_LEN 128
-static void setProtocolError(const char *errstr, client *c, long pos) {
+static void setProtocolError(const char *errstr, client *c) {
if (server.verbosity <= LL_VERBOSE) {
sds client = catClientInfoString(sdsempty(),c);
/* Sample some protocol to given an idea about what was inside. */
char buf[256];
- if (sdslen(c->querybuf) < PROTO_DUMP_LEN) {
- snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf);
+ if (sdslen(c->querybuf)-c->qb_pos < PROTO_DUMP_LEN) {
+ snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%s'", c->querybuf+c->qb_pos);
} else {
- snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf, sdslen(c->querybuf)-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
+ snprintf(buf,sizeof(buf),"Query buffer during protocol error: '%.*s' (... more %zu bytes ...) '%.*s'", PROTO_DUMP_LEN/2, c->querybuf+c->qb_pos, sdslen(c->querybuf)-c->qb_pos-PROTO_DUMP_LEN, PROTO_DUMP_LEN/2, c->querybuf+sdslen(c->querybuf)-PROTO_DUMP_LEN/2);
}
/* Remove non printable chars. */
@@ -1188,7 +1481,6 @@ static void setProtocolError(const char *errstr, client *c, long pos) {
sdsfree(client);
}
c->flags |= CLIENT_CLOSE_AFTER_REPLY;
- sdsrange(c->querybuf,pos,-1);
}
/* Process the query buffer for client 'c', setting up the client argument
@@ -1204,7 +1496,6 @@ static void setProtocolError(const char *errstr, client *c, long pos) {
* to be '*'. Otherwise for inline commands processInlineBuffer() is called. */
int processMultibulkBuffer(client *c) {
char *newline = NULL;
- long pos = 0;
int ok;
long long ll;
@@ -1213,34 +1504,32 @@ int processMultibulkBuffer(client *c) {
serverAssertWithInfo(c,NULL,c->argc == 0);
/* Multi bulk length cannot be read without a \r\n */
- newline = strchr(c->querybuf,'\r');
+ newline = strchr(c->querybuf+c->qb_pos,'\r');
if (newline == NULL) {
- if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
+ if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
addReplyError(c,"Protocol error: too big mbulk count string");
- setProtocolError("too big mbulk count string",c,0);
+ setProtocolError("too big mbulk count string",c);
}
return C_ERR;
}
/* Buffer should also contain \n */
- if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
+ if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
return C_ERR;
/* We know for sure there is a whole line since newline != NULL,
* so go ahead and find out the multi bulk length. */
- serverAssertWithInfo(c,NULL,c->querybuf[0] == '*');
- ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll);
+ serverAssertWithInfo(c,NULL,c->querybuf[c->qb_pos] == '*');
+ ok = string2ll(c->querybuf+1+c->qb_pos,newline-(c->querybuf+1+c->qb_pos),&ll);
if (!ok || ll > 1024*1024) {
addReplyError(c,"Protocol error: invalid multibulk length");
- setProtocolError("invalid mbulk count",c,pos);
+ setProtocolError("invalid mbulk count",c);
return C_ERR;
}
- pos = (newline-c->querybuf)+2;
- if (ll <= 0) {
- sdsrange(c->querybuf,pos,-1);
- return C_OK;
- }
+ c->qb_pos = (newline-c->querybuf)+2;
+
+ if (ll <= 0) return C_OK;
c->multibulklen = ll;
@@ -1253,64 +1542,67 @@ int processMultibulkBuffer(client *c) {
while(c->multibulklen) {
/* Read bulk length if unknown */
if (c->bulklen == -1) {
- newline = strchr(c->querybuf+pos,'\r');
+ newline = strchr(c->querybuf+c->qb_pos,'\r');
if (newline == NULL) {
- if (sdslen(c->querybuf) > PROTO_INLINE_MAX_SIZE) {
+ if (sdslen(c->querybuf)-c->qb_pos > PROTO_INLINE_MAX_SIZE) {
addReplyError(c,
"Protocol error: too big bulk count string");
- setProtocolError("too big bulk count string",c,0);
+ setProtocolError("too big bulk count string",c);
return C_ERR;
}
break;
}
/* Buffer should also contain \n */
- if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2))
+ if (newline-(c->querybuf+c->qb_pos) > (ssize_t)(sdslen(c->querybuf)-c->qb_pos-2))
break;
- if (c->querybuf[pos] != '$') {
+ if (c->querybuf[c->qb_pos] != '$') {
addReplyErrorFormat(c,
"Protocol error: expected '$', got '%c'",
- c->querybuf[pos]);
- setProtocolError("expected $ but got something else",c,pos);
+ c->querybuf[c->qb_pos]);
+ setProtocolError("expected $ but got something else",c);
return C_ERR;
}
- ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll);
+ ok = string2ll(c->querybuf+c->qb_pos+1,newline-(c->querybuf+c->qb_pos+1),&ll);
if (!ok || ll < 0 || ll > server.proto_max_bulk_len) {
addReplyError(c,"Protocol error: invalid bulk length");
- setProtocolError("invalid bulk length",c,pos);
+ setProtocolError("invalid bulk length",c);
return C_ERR;
}
- pos += newline-(c->querybuf+pos)+2;
+ c->qb_pos = newline-c->querybuf+2;
if (ll >= PROTO_MBULK_BIG_ARG) {
- size_t qblen;
-
/* If we are going to read a large object from network
* try to make it likely that it will start at c->querybuf
* boundary so that we can optimize object creation
- * avoiding a large copy of data. */
- sdsrange(c->querybuf,pos,-1);
- pos = 0;
- qblen = sdslen(c->querybuf);
- /* Hint the sds library about the amount of bytes this string is
- * going to contain. */
- if (qblen < (size_t)ll+2)
- c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
+ * avoiding a large copy of data.
+ *
+ * But only when the data we have not parsed is less than
+ * or equal to ll+2. If the data length is greater than
+ * ll+2, trimming querybuf is just a waste of time, because
+ * at this time the querybuf contains not only our bulk. */
+ if (sdslen(c->querybuf)-c->qb_pos <= (size_t)ll+2) {
+ sdsrange(c->querybuf,c->qb_pos,-1);
+ c->qb_pos = 0;
+ /* Hint the sds library about the amount of bytes this string is
+ * going to contain. */
+ c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2);
+ }
}
c->bulklen = ll;
}
/* Read bulk argument */
- if (sdslen(c->querybuf)-pos < (size_t)(c->bulklen+2)) {
+ if (sdslen(c->querybuf)-c->qb_pos < (size_t)(c->bulklen+2)) {
/* Not enough data (+2 == trailing \r\n) */
break;
} else {
/* Optimization: if the buffer contains JUST our bulk element
* instead of creating a new object by *copying* the sds we
* just use the current sds string. */
- if (pos == 0 &&
+ if (c->qb_pos == 0 &&
c->bulklen >= PROTO_MBULK_BIG_ARG &&
sdslen(c->querybuf) == (size_t)(c->bulklen+2))
{
@@ -1320,20 +1612,16 @@ int processMultibulkBuffer(client *c) {
* likely... */
c->querybuf = sdsnewlen(SDS_NOINIT,c->bulklen+2);
sdsclear(c->querybuf);
- pos = 0;
} else {
c->argv[c->argc++] =
- createStringObject(c->querybuf+pos,c->bulklen);
- pos += c->bulklen+2;
+ createStringObject(c->querybuf+c->qb_pos,c->bulklen);
+ c->qb_pos += c->bulklen+2;
}
c->bulklen = -1;
c->multibulklen--;
}
}
- /* Trim to pos */
- if (pos) sdsrange(c->querybuf,pos,-1);
-
/* We're done when c->multibulk == 0 */
if (c->multibulklen == 0) return C_OK;
@@ -1341,20 +1629,65 @@ int processMultibulkBuffer(client *c) {
return C_ERR;
}
+/* This function calls processCommand(), but also performs a few sub tasks
+ * that are useful in that context:
+ *
+ * 1. It sets the current client to the client 'c'.
+ * 2. In the case of master clients, the replication offset is updated.
+ * 3. The client is reset unless there are reasons to avoid doing it.
+ *
+ * The function returns C_ERR in case the client was freed as a side effect
+ * of processing the command, otherwise C_OK is returned. */
+int processCommandAndResetClient(client *c) {
+ int deadclient = 0;
+ server.current_client = c;
+ if (processCommand(c) == C_OK) {
+ if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
+ /* Update the applied replication offset of our master. */
+ c->reploff = c->read_reploff - sdslen(c->querybuf) + c->qb_pos;
+ }
+
+ /* Don't reset the client structure for clients blocked in a
+ * module blocking command, so that the reply callback will
+ * still be able to access the client argv and argc field.
+ * The client will be reset in unblockClientFromModule(). */
+ if (!(c->flags & CLIENT_BLOCKED) ||
+ c->btype != BLOCKED_MODULE)
+ {
+ resetClient(c);
+ }
+ }
+ if (server.current_client == NULL) deadclient = 1;
+ server.current_client = NULL;
+ /* freeMemoryIfNeeded may flush slave output buffers. This may
+ * result into a slave, that may be the active client, to be
+ * freed. */
+ return deadclient ? C_ERR : C_OK;
+}
+
/* This function is called every time, in the client structure 'c', there is
* more query buffer to process, because we read more data from the socket
* or because a client was blocked and later reactivated, so there could be
* pending query buffer, already representing a full command, to process. */
void processInputBuffer(client *c) {
- server.current_client = c;
/* Keep processing while there is something in the input buffer */
- while(sdslen(c->querybuf)) {
+ while(c->qb_pos < sdslen(c->querybuf)) {
/* Return if clients are paused. */
if (!(c->flags & CLIENT_SLAVE) && clientsArePaused()) break;
/* Immediately abort if the client is in the middle of something. */
if (c->flags & CLIENT_BLOCKED) break;
+ /* Don't process more buffers from clients that have already pending
+ * commands to execute in c->argv. */
+ if (c->flags & CLIENT_PENDING_COMMAND) break;
+
+ /* Don't process input from the master while there is a busy script
+ * condition on the slave. We want just to accumulate the replication
+ * stream (instead of replying -BUSY like we do with other clients) and
+ * later resume the processing. */
+ if (server.lua_timedout && c->flags & CLIENT_MASTER) break;
+
/* CLIENT_CLOSE_AFTER_REPLY closes the connection once the reply is
* written to the client. Make sure to not let the reply grow after
* this flag has been set (i.e. don't process more commands).
@@ -1364,7 +1697,7 @@ void processInputBuffer(client *c) {
/* Determine request type when unknown. */
if (!c->reqtype) {
- if (c->querybuf[0] == '*') {
+ if (c->querybuf[c->qb_pos] == '*') {
c->reqtype = PROTO_REQ_MULTIBULK;
} else {
c->reqtype = PROTO_REQ_INLINE;
@@ -1373,6 +1706,17 @@ void processInputBuffer(client *c) {
if (c->reqtype == PROTO_REQ_INLINE) {
if (processInlineBuffer(c) != C_OK) break;
+ /* If the Gopher mode and we got zero or one argument, process
+ * the request in Gopher mode. */
+ if (server.gopher_enabled &&
+ ((c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '/') ||
+ c->argc == 0))
+ {
+ processGopherRequest(c);
+ resetClient(c);
+ c->flags |= CLIENT_CLOSE_AFTER_REPLY;
+ break;
+ }
} else if (c->reqtype == PROTO_REQ_MULTIBULK) {
if (processMultibulkBuffer(c) != C_OK) break;
} else {
@@ -1383,35 +1727,64 @@ void processInputBuffer(client *c) {
if (c->argc == 0) {
resetClient(c);
} else {
- /* Only reset the client when the command was executed. */
- if (processCommand(c) == C_OK) {
- if (c->flags & CLIENT_MASTER && !(c->flags & CLIENT_MULTI)) {
- /* Update the applied replication offset of our master. */
- c->reploff = c->read_reploff - sdslen(c->querybuf);
- }
+ /* If we are in the context of an I/O thread, we can't really
+ * execute the command here. All we can do is to flag the client
+ * as one that needs to process the command. */
+ if (c->flags & CLIENT_PENDING_READ) {
+ c->flags |= CLIENT_PENDING_COMMAND;
+ break;
+ }
- /* Don't reset the client structure for clients blocked in a
- * module blocking command, so that the reply callback will
- * still be able to access the client argv and argc field.
- * The client will be reset in unblockClientFromModule(). */
- if (!(c->flags & CLIENT_BLOCKED) || c->btype != BLOCKED_MODULE)
- resetClient(c);
+ /* We are finally ready to execute the command. */
+ if (processCommandAndResetClient(c) == C_ERR) {
+ /* If the client is no longer valid, we avoid exiting this
+ * loop and trimming the client buffer later. So we return
+ * ASAP in that case. */
+ return;
}
- /* freeMemoryIfNeeded may flush slave output buffers. This may
- * result into a slave, that may be the active client, to be
- * freed. */
- if (server.current_client == NULL) break;
}
}
- server.current_client = NULL;
+
+ /* Trim to pos */
+ if (c->qb_pos) {
+ sdsrange(c->querybuf,c->qb_pos,-1);
+ c->qb_pos = 0;
+ }
}
-void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
- client *c = (client*) privdata;
+/* This is a wrapper for processInputBuffer that also cares about handling
+ * the replication forwarding to the sub-replicas, in case the client 'c'
+ * is flagged as master. Usually you want to call this instead of the
+ * raw processInputBuffer(). */
+void processInputBufferAndReplicate(client *c) {
+ if (!(c->flags & CLIENT_MASTER)) {
+ processInputBuffer(c);
+ } else {
+ /* If the client is a master we need to compute the difference
+ * between the applied offset before and after processing the buffer,
+ * to understand how much of the replication stream was actually
+ * applied to the master state: this quantity, and its corresponding
+ * part of the replication stream, will be propagated to the
+ * sub-replicas and to the replication backlog. */
+ size_t prev_offset = c->reploff;
+ processInputBuffer(c);
+ size_t applied = c->reploff - prev_offset;
+ if (applied) {
+ replicationFeedSlavesFromMasterStream(server.slaves,
+ c->pending_querybuf, applied);
+ sdsrange(c->pending_querybuf,applied,-1);
+ }
+ }
+}
+
+void readQueryFromClient(connection *conn) {
+ client *c = connGetPrivateData(conn);
int nread, readlen;
size_t qblen;
- UNUSED(el);
- UNUSED(mask);
+
+ /* Check if we want to read from the client later when exiting from
+ * the event loop. This is the case if threaded I/O is enabled. */
+ if (postponeClientRead(c)) return;
readlen = PROTO_IOBUF_LEN;
/* If this is a multi bulk request, and we are processing a bulk reply
@@ -1425,24 +1798,26 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
{
ssize_t remaining = (size_t)(c->bulklen+2)-sdslen(c->querybuf);
- if (remaining < readlen) readlen = remaining;
+ /* Note that the 'remaining' variable may be zero in some edge case,
+ * for example once we resume a blocked client after CLIENT PAUSE. */
+ if (remaining > 0 && remaining < readlen) readlen = remaining;
}
qblen = sdslen(c->querybuf);
if (c->querybuf_peak < qblen) c->querybuf_peak = qblen;
c->querybuf = sdsMakeRoomFor(c->querybuf, readlen);
- nread = read(fd, c->querybuf+qblen, readlen);
+ nread = connRead(c->conn, c->querybuf+qblen, readlen);
if (nread == -1) {
- if (errno == EAGAIN) {
+ if (connGetState(conn) == CONN_STATE_CONNECTED) {
return;
} else {
- serverLog(LL_VERBOSE, "Reading from client: %s",strerror(errno));
- freeClient(c);
+ serverLog(LL_VERBOSE, "Reading from client: %s",connGetLastError(c->conn));
+ freeClientAsync(c);
return;
}
} else if (nread == 0) {
serverLog(LL_VERBOSE, "Client closed connection");
- freeClient(c);
+ freeClientAsync(c);
return;
} else if (c->flags & CLIENT_MASTER) {
/* Append the query buffer to the pending (not applied) buffer
@@ -1463,28 +1838,13 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog(LL_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes);
sdsfree(ci);
sdsfree(bytes);
- freeClient(c);
+ freeClientAsync(c);
return;
}
- /* Time to process the buffer. If the client is a master we need to
- * compute the difference between the applied offset before and after
- * processing the buffer, to understand how much of the replication stream
- * was actually applied to the master state: this quantity, and its
- * corresponding part of the replication stream, will be propagated to
- * the sub-slaves and to the replication backlog. */
- if (!(c->flags & CLIENT_MASTER)) {
- processInputBuffer(c);
- } else {
- size_t prev_offset = c->reploff;
- processInputBuffer(c);
- size_t applied = c->reploff - prev_offset;
- if (applied) {
- replicationFeedSlavesFromMasterStream(server.slaves,
- c->pending_querybuf, applied);
- sdsrange(c->pending_querybuf,applied,-1);
- }
- }
+ /* There is more data in the client input buffer, continue parsing it
+ * in case to check if there is a full command to execute. */
+ processInputBufferAndReplicate(c);
}
void getClientsMaxBuffers(unsigned long *longest_output_list,
@@ -1523,7 +1883,7 @@ void genClientPeerId(client *client, char *peerid,
snprintf(peerid,peerid_len,"%s:0",server.unixsocket);
} else {
/* TCP client. */
- anetFormatPeer(client->fd,peerid,peerid_len);
+ connFormatPeer(client->conn,peerid,peerid_len);
}
}
@@ -1544,8 +1904,7 @@ char *getClientPeerId(client *c) {
/* Concatenate a string representing the state of a client in an human
* readable format, into the sds string 's'. */
sds catClientInfoString(sds s, client *client) {
- char flags[16], events[3], *p;
- int emask;
+ char flags[16], events[3], conninfo[CONN_INFO_LEN], *p;
p = flags;
if (client->flags & CLIENT_SLAVE) {
@@ -1555,8 +1914,11 @@ sds catClientInfoString(sds s, client *client) {
*p++ = 'S';
}
if (client->flags & CLIENT_MASTER) *p++ = 'M';
+ if (client->flags & CLIENT_PUBSUB) *p++ = 'P';
if (client->flags & CLIENT_MULTI) *p++ = 'x';
if (client->flags & CLIENT_BLOCKED) *p++ = 'b';
+ if (client->flags & CLIENT_TRACKING) *p++ = 't';
+ if (client->flags & CLIENT_TRACKING_BROKEN_REDIR) *p++ = 'R';
if (client->flags & CLIENT_DIRTY_CAS) *p++ = 'd';
if (client->flags & CLIENT_CLOSE_AFTER_REPLY) *p++ = 'c';
if (client->flags & CLIENT_UNBLOCKED) *p++ = 'u';
@@ -1566,16 +1928,17 @@ sds catClientInfoString(sds s, client *client) {
if (p == flags) *p++ = 'N';
*p++ = '\0';
- emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd);
p = events;
- if (emask & AE_READABLE) *p++ = 'r';
- if (emask & AE_WRITABLE) *p++ = 'w';
+ if (client->conn) {
+ if (connHasReadHandler(client->conn)) *p++ = 'r';
+ if (connHasWriteHandler(client->conn)) *p++ = 'w';
+ }
*p = '\0';
return sdscatfmt(s,
- "id=%U addr=%s fd=%i name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s",
+ "id=%U addr=%s %s name=%s age=%I idle=%I flags=%s db=%i sub=%i psub=%i multi=%i qbuf=%U qbuf-free=%U obl=%U oll=%U omem=%U events=%s cmd=%s user=%s",
(unsigned long long) client->id,
getClientPeerId(client),
- client->fd,
+ connGetInfo(client->conn, conninfo, sizeof(conninfo)),
client->name ? (char*)client->name->ptr : "",
(long long)(server.unixtime - client->ctime),
(long long)(server.unixtime - client->lastinteraction),
@@ -1590,10 +1953,11 @@ sds catClientInfoString(sds s, client *client) {
(unsigned long long) listLength(client->reply),
(unsigned long long) getClientOutputBufferMemoryUsage(client),
events,
- client->lastcmd ? client->lastcmd->name : "NULL");
+ client->lastcmd ? client->lastcmd->name : "NULL",
+ client->user ? client->user->name : "(superuser)");
}
-sds getAllClientsInfoString(void) {
+sds getAllClientsInfoString(int type) {
listNode *ln;
listIter li;
client *client;
@@ -1602,12 +1966,52 @@ sds getAllClientsInfoString(void) {
listRewind(server.clients,&li);
while ((ln = listNext(&li)) != NULL) {
client = listNodeValue(ln);
+ if (type != -1 && getClientType(client) != type) continue;
o = catClientInfoString(o,client);
o = sdscatlen(o,"\n",1);
}
return o;
}
+/* This function implements CLIENT SETNAME, including replying to the
+ * user with an error if the charset is wrong (in that case C_ERR is
+ * returned). If the function succeeeded C_OK is returned, and it's up
+ * to the caller to send a reply if needed.
+ *
+ * Setting an empty string as name has the effect of unsetting the
+ * currently set name: the client will remain unnamed.
+ *
+ * This function is also used to implement the HELLO SETNAME option. */
+int clientSetNameOrReply(client *c, robj *name) {
+ int len = sdslen(name->ptr);
+ char *p = name->ptr;
+
+ /* Setting the client name to an empty string actually removes
+ * the current name. */
+ if (len == 0) {
+ if (c->name) decrRefCount(c->name);
+ c->name = NULL;
+ addReply(c,shared.ok);
+ return C_OK;
+ }
+
+ /* Otherwise check if the charset is ok. We need to do this otherwise
+ * CLIENT LIST format will break. You should always be able to
+ * split by space to get the different fields. */
+ for (int j = 0; j < len; j++) {
+ if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
+ addReplyError(c,
+ "Client names cannot contain spaces, "
+ "newlines or special characters.");
+ return C_ERR;
+ }
+ }
+ if (c->name) decrRefCount(c->name);
+ c->name = name;
+ incrRefCount(name);
+ return C_OK;
+}
+
void clientCommand(client *c) {
listNode *ln;
listIter li;
@@ -1615,23 +2019,43 @@ void clientCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"getname -- Return the name of the current connection.",
-"kill <ip:port> -- Kill connection made from <ip:port>.",
-"kill <option> <value> [option value ...] -- Kill connections. Options are:",
-" addr <ip:port> -- Kill connection made from <ip:port>.",
-" type (normal|master|slave|pubsub) -- Kill connections by type.",
-" skipme (yes|no) -- Skip killing current connection (default: yes).",
-"list -- Return information about client connections.",
-"pause <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
-"reply (on|off|skip) -- Control the replies sent to the current connection.",
-"setname <name> -- Assign the name <name> to the current connection.",
+"ID -- Return the ID of the current connection.",
+"GETNAME -- Return the name of the current connection.",
+"KILL <ip:port> -- Kill connection made from <ip:port>.",
+"KILL <option> <value> [option value ...] -- Kill connections. Options are:",
+" ADDR <ip:port> -- Kill connection made from <ip:port>",
+" TYPE (normal|master|replica|pubsub) -- Kill connections by type.",
+" SKIPME (yes|no) -- Skip killing current connection (default: yes).",
+"LIST [options ...] -- Return information about client connections. Options:",
+" TYPE (normal|master|replica|pubsub) -- Return clients of specified type.",
+"PAUSE <timeout> -- Suspend all Redis clients for <timout> milliseconds.",
+"REPLY (on|off|skip) -- Control the replies sent to the current connection.",
+"SETNAME <name> -- Assign the name <name> to the current connection.",
+"UNBLOCK <clientid> [TIMEOUT|ERROR] -- Unblock the specified blocked client.",
+"TRACKING (on|off) [REDIRECT <id>] -- Enable client keys tracking for client side caching.",
+"GETREDIR -- Return the client ID we are redirecting to when tracking is enabled.",
NULL
};
addReplyHelp(c, help);
- } else if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) {
+ } else if (!strcasecmp(c->argv[1]->ptr,"id") && c->argc == 2) {
+ /* CLIENT ID */
+ addReplyLongLong(c,c->id);
+ } else if (!strcasecmp(c->argv[1]->ptr,"list")) {
/* CLIENT LIST */
- sds o = getAllClientsInfoString();
- addReplyBulkCBuffer(c,o,sdslen(o));
+ int type = -1;
+ if (c->argc == 4 && !strcasecmp(c->argv[2]->ptr,"type")) {
+ type = getClientTypeByName(c->argv[3]->ptr);
+ if (type == -1) {
+ addReplyErrorFormat(c,"Unknown client type '%s'",
+ (char*) c->argv[3]->ptr);
+ return;
+ }
+ } else if (c->argc != 2) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ sds o = getAllClientsInfoString(type);
+ addReplyVerbatim(c,o,sdslen(o),"txt");
sdsfree(o);
} else if (!strcasecmp(c->argv[1]->ptr,"reply") && c->argc == 3) {
/* CLIENT REPLY ON|OFF|SKIP */
@@ -1733,51 +2157,169 @@ NULL
/* If this client has to be closed, flag it as CLOSE_AFTER_REPLY
* only after we queued the reply to its output buffers. */
if (close_this_client) c->flags |= CLIENT_CLOSE_AFTER_REPLY;
- } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
- int j, len = sdslen(c->argv[2]->ptr);
- char *p = c->argv[2]->ptr;
-
- /* Setting the client name to an empty string actually removes
- * the current name. */
- if (len == 0) {
- if (c->name) decrRefCount(c->name);
- c->name = NULL;
- addReply(c,shared.ok);
- return;
- }
-
- /* Otherwise check if the charset is ok. We need to do this otherwise
- * CLIENT LIST format will break. You should always be able to
- * split by space to get the different fields. */
- for (j = 0; j < len; j++) {
- if (p[j] < '!' || p[j] > '~') { /* ASCII is assumed. */
+ } else if (!strcasecmp(c->argv[1]->ptr,"unblock") && (c->argc == 3 ||
+ c->argc == 4))
+ {
+ /* CLIENT UNBLOCK <id> [timeout|error] */
+ long long id;
+ int unblock_error = 0;
+
+ if (c->argc == 4) {
+ if (!strcasecmp(c->argv[3]->ptr,"timeout")) {
+ unblock_error = 0;
+ } else if (!strcasecmp(c->argv[3]->ptr,"error")) {
+ unblock_error = 1;
+ } else {
addReplyError(c,
- "Client names cannot contain spaces, "
- "newlines or special characters.");
+ "CLIENT UNBLOCK reason should be TIMEOUT or ERROR");
return;
}
}
- if (c->name) decrRefCount(c->name);
- c->name = c->argv[2];
- incrRefCount(c->name);
- addReply(c,shared.ok);
+ if (getLongLongFromObjectOrReply(c,c->argv[2],&id,NULL)
+ != C_OK) return;
+ struct client *target = lookupClientByID(id);
+ if (target && target->flags & CLIENT_BLOCKED) {
+ if (unblock_error)
+ addReplyError(target,
+ "-UNBLOCKED client unblocked via CLIENT UNBLOCK");
+ else
+ replyToBlockedClientTimedOut(target);
+ unblockClient(target);
+ addReply(c,shared.cone);
+ } else {
+ addReply(c,shared.czero);
+ }
+ } else if (!strcasecmp(c->argv[1]->ptr,"setname") && c->argc == 3) {
+ /* CLIENT SETNAME */
+ if (clientSetNameOrReply(c,c->argv[2]) == C_OK)
+ addReply(c,shared.ok);
} else if (!strcasecmp(c->argv[1]->ptr,"getname") && c->argc == 2) {
+ /* CLIENT GETNAME */
if (c->name)
addReplyBulk(c,c->name);
else
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else if (!strcasecmp(c->argv[1]->ptr,"pause") && c->argc == 3) {
+ /* CLIENT PAUSE */
long long duration;
- if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,UNIT_MILLISECONDS)
- != C_OK) return;
+ if (getTimeoutFromObjectOrReply(c,c->argv[2],&duration,
+ UNIT_MILLISECONDS) != C_OK) return;
pauseClients(duration);
addReply(c,shared.ok);
+ } else if (!strcasecmp(c->argv[1]->ptr,"tracking") &&
+ (c->argc == 3 || c->argc == 5))
+ {
+ /* CLIENT TRACKING (on|off) [REDIRECT <id>] */
+ long long redir = 0;
+
+ /* Parse the redirection option: we'll require the client with
+ * the specified ID to exist right now, even if it is possible
+ * it will get disconnected later. */
+ if (c->argc == 5) {
+ if (strcasecmp(c->argv[3]->ptr,"redirect") != 0) {
+ addReply(c,shared.syntaxerr);
+ return;
+ } else {
+ if (getLongLongFromObjectOrReply(c,c->argv[4],&redir,NULL) !=
+ C_OK) return;
+ if (lookupClientByID(redir) == NULL) {
+ addReplyError(c,"The client ID you want redirect to "
+ "does not exist");
+ return;
+ }
+ }
+ }
+
+ if (!strcasecmp(c->argv[2]->ptr,"on")) {
+ enableTracking(c,redir);
+ } else if (!strcasecmp(c->argv[2]->ptr,"off")) {
+ disableTracking(c);
+ } else {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ addReply(c,shared.ok);
+ } else if (!strcasecmp(c->argv[1]->ptr,"getredir") && c->argc == 2) {
+ /* CLIENT GETREDIR */
+ if (c->flags & CLIENT_TRACKING) {
+ addReplyLongLong(c,c->client_tracking_redirection);
+ } else {
+ addReplyLongLong(c,-1);
+ }
} else {
addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try CLIENT HELP", (char*)c->argv[1]->ptr);
}
}
+/* HELLO <protocol-version> [AUTH <user> <password>] [SETNAME <name>] */
+void helloCommand(client *c) {
+ long long ver;
+
+ if (getLongLongFromObject(c->argv[1],&ver) != C_OK ||
+ ver < 2 || ver > 3)
+ {
+ addReplyError(c,"-NOPROTO unsupported protocol version");
+ return;
+ }
+
+ for (int j = 2; j < c->argc; j++) {
+ int moreargs = (c->argc-1) - j;
+ const char *opt = c->argv[j]->ptr;
+ if (!strcasecmp(opt,"AUTH") && moreargs >= 2) {
+ if (ACLAuthenticateUser(c, c->argv[j+1], c->argv[j+2]) == C_ERR) {
+ addReplyError(c,"-WRONGPASS invalid username-password pair");
+ return;
+ }
+ j += 2;
+ } else if (!strcasecmp(opt,"SETNAME") && moreargs) {
+ if (clientSetNameOrReply(c, c->argv[j+1]) == C_ERR) return;
+ j++;
+ } else {
+ addReplyErrorFormat(c,"Syntax error in HELLO option '%s'",opt);
+ return;
+ }
+ }
+
+ /* At this point we need to be authenticated to continue. */
+ if (!c->authenticated) {
+ addReplyError(c,"-NOAUTH HELLO must be called with the client already "
+ "authenticated, otherwise the HELLO AUTH <user> <pass> "
+ "option can be used to authenticate the client and "
+ "select the RESP protocol version at the same time");
+ return;
+ }
+
+ /* Let's switch to the specified RESP mode. */
+ c->resp = ver;
+ addReplyMapLen(c,7);
+
+ addReplyBulkCString(c,"server");
+ addReplyBulkCString(c,"redis");
+
+ addReplyBulkCString(c,"version");
+ addReplyBulkCString(c,REDIS_VERSION);
+
+ addReplyBulkCString(c,"proto");
+ addReplyLongLong(c,3);
+
+ addReplyBulkCString(c,"id");
+ addReplyLongLong(c,c->id);
+
+ addReplyBulkCString(c,"mode");
+ if (server.sentinel_mode) addReplyBulkCString(c,"sentinel");
+ if (server.cluster_enabled) addReplyBulkCString(c,"cluster");
+ else addReplyBulkCString(c,"standalone");
+
+ if (!server.sentinel_mode) {
+ addReplyBulkCString(c,"role");
+ addReplyBulkCString(c,server.masterhost ? "replica" : "master");
+ }
+
+ addReplyBulkCString(c,"modules");
+ addReplyLoadedModules(c);
+}
+
/* This callback is bound to POST and "Host:" command names. Those are not
* really commands, but are used in security attacks in order to talk to
* Redis instances via HTTP, with a technique called "cross protocol scripting"
@@ -1869,24 +2411,14 @@ void rewriteClientCommandArgument(client *c, int i, robj *newval) {
}
}
-/* This function returns the number of bytes that Redis is virtually
+/* This function returns the number of bytes that Redis is
* using to store the reply still not read by the client.
- * It is "virtual" since the reply output list may contain objects that
- * are shared and are not really using additional memory.
- *
- * The function returns the total sum of the length of all the objects
- * stored in the output list, plus the memory used to allocate every
- * list node. The static reply buffer is not taken into account since it
- * is allocated anyway.
*
* Note: this function is very fast so can be called as many time as
* the caller wishes. The main usage of this function currently is
* enforcing the client output length limits. */
unsigned long getClientOutputBufferMemoryUsage(client *c) {
- unsigned long list_item_size = sizeof(listNode)+5;
- /* The +5 above means we assume an sds16 hdr, may not be true
- * but is not going to be a problem. */
-
+ unsigned long list_item_size = sizeof(listNode) + sizeof(clientReplyBlock);
return c->reply_bytes + (list_item_size*listLength(c->reply));
}
@@ -1910,6 +2442,7 @@ int getClientType(client *c) {
int getClientTypeByName(char *name) {
if (!strcasecmp(name,"normal")) return CLIENT_TYPE_NORMAL;
else if (!strcasecmp(name,"slave")) return CLIENT_TYPE_SLAVE;
+ else if (!strcasecmp(name,"replica")) return CLIENT_TYPE_SLAVE;
else if (!strcasecmp(name,"pubsub")) return CLIENT_TYPE_PUBSUB;
else if (!strcasecmp(name,"master")) return CLIENT_TYPE_MASTER;
else return -1;
@@ -1977,6 +2510,7 @@ int checkClientOutputBufferLimits(client *c) {
* called from contexts where the client can't be freed safely, i.e. from the
* lower level functions pushing data inside the client output buffers. */
void asyncCloseClientOnOutputBufferLimitReached(client *c) {
+ if (!c->conn) return; /* It is unsafe to free fake clients. */
serverAssert(c->reply_bytes < SIZE_MAX-(1024*64));
if (c->reply_bytes == 0 || c->flags & CLIENT_CLOSE_ASAP) return;
if (checkClientOutputBufferLimits(c)) {
@@ -1999,20 +2533,29 @@ void flushSlavesOutputBuffers(void) {
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = listNodeValue(ln);
- int events;
-
- /* Note that the following will not flush output buffers of slaves
- * in STATE_ONLINE but having put_online_on_ack set to true: in this
- * case the writable event is never installed, since the purpose
- * of put_online_on_ack is to postpone the moment it is installed.
- * This is what we want since slaves in this state should not receive
- * writes before the first ACK. */
- events = aeGetFileEvents(server.el,slave->fd);
- if (events & AE_WRITABLE &&
- slave->replstate == SLAVE_STATE_ONLINE &&
+ int can_receive_writes = connHasWriteHandler(slave->conn) ||
+ (slave->flags & CLIENT_PENDING_WRITE);
+
+ /* We don't want to send the pending data to the replica in a few
+ * cases:
+ *
+ * 1. For some reason there is neither the write handler installed
+ * nor the client is flagged as to have pending writes: for some
+ * reason this replica may not be set to receive data. This is
+ * just for the sake of defensive programming.
+ *
+ * 2. The put_online_on_ack flag is true. To know why we don't want
+ * to send data to the replica in this case, please grep for the
+ * flag for this flag.
+ *
+ * 3. Obviously if the slave is not ONLINE.
+ */
+ if (slave->replstate == SLAVE_STATE_ONLINE &&
+ can_receive_writes &&
+ !slave->repl_put_online_on_ack &&
clientHasPendingReplies(slave))
{
- writeToClient(slave->fd,slave,0);
+ writeToClient(slave,0);
}
}
}
@@ -2058,11 +2601,10 @@ int clientsArePaused(void) {
while ((ln = listNext(&li)) != NULL) {
c = listNodeValue(ln);
- /* Don't touch slaves and blocked clients. The latter pending
- * requests be processed when unblocked. */
+ /* Don't touch slaves and blocked clients.
+ * The latter pending requests will be processed when unblocked. */
if (c->flags & (CLIENT_SLAVE|CLIENT_BLOCKED)) continue;
- c->flags |= CLIENT_UNBLOCKED;
- listAddNodeTail(server.unblocked_clients,c);
+ queueClientForReprocessing(c);
}
}
return server.clients_paused;
@@ -2092,3 +2634,276 @@ int processEventsWhileBlocked(void) {
}
return count;
}
+
+/* ==========================================================================
+ * Threaded I/O
+ * ========================================================================== */
+
+int tio_debug = 0;
+
+#define IO_THREADS_MAX_NUM 128
+#define IO_THREADS_OP_READ 0
+#define IO_THREADS_OP_WRITE 1
+
+pthread_t io_threads[IO_THREADS_MAX_NUM];
+pthread_mutex_t io_threads_mutex[IO_THREADS_MAX_NUM];
+_Atomic unsigned long io_threads_pending[IO_THREADS_MAX_NUM];
+int io_threads_active; /* Are the threads currently spinning waiting I/O? */
+int io_threads_op; /* IO_THREADS_OP_WRITE or IO_THREADS_OP_READ. */
+list *io_threads_list[IO_THREADS_MAX_NUM];
+
+void *IOThreadMain(void *myid) {
+ /* The ID is the thread number (from 0 to server.iothreads_num-1), and is
+ * used by the thread to just manipulate a single sub-array of clients. */
+ long id = (unsigned long)myid;
+
+ while(1) {
+ /* Wait for start */
+ for (int j = 0; j < 1000000; j++) {
+ if (io_threads_pending[id] != 0) break;
+ }
+
+ /* Give the main thread a chance to stop this thread. */
+ if (io_threads_pending[id] == 0) {
+ pthread_mutex_lock(&io_threads_mutex[id]);
+ pthread_mutex_unlock(&io_threads_mutex[id]);
+ continue;
+ }
+
+ serverAssert(io_threads_pending[id] != 0);
+
+ if (tio_debug) printf("[%ld] %d to handle\n", id, (int)listLength(io_threads_list[id]));
+
+ /* Process: note that the main thread will never touch our list
+ * before we drop the pending count to 0. */
+ listIter li;
+ listNode *ln;
+ listRewind(io_threads_list[id],&li);
+ while((ln = listNext(&li))) {
+ client *c = listNodeValue(ln);
+ if (io_threads_op == IO_THREADS_OP_WRITE) {
+ writeToClient(c,0);
+ } else if (io_threads_op == IO_THREADS_OP_READ) {
+ readQueryFromClient(c->conn);
+ } else {
+ serverPanic("io_threads_op value is unknown");
+ }
+ }
+ listEmpty(io_threads_list[id]);
+ io_threads_pending[id] = 0;
+
+ if (tio_debug) printf("[%ld] Done\n", id);
+ }
+}
+
+/* Initialize the data structures needed for threaded I/O. */
+void initThreadedIO(void) {
+ io_threads_active = 0; /* We start with threads not active. */
+
+ /* Don't spawn any thread if the user selected a single thread:
+ * we'll handle I/O directly from the main thread. */
+ if (server.io_threads_num == 1) return;
+
+ if (server.io_threads_num > IO_THREADS_MAX_NUM) {
+ serverLog(LL_WARNING,"Fatal: too many I/O threads configured. "
+ "The maximum number is %d.", IO_THREADS_MAX_NUM);
+ exit(1);
+ }
+
+ /* Spawn the I/O threads. */
+ for (int i = 0; i < server.io_threads_num; i++) {
+ pthread_t tid;
+ pthread_mutex_init(&io_threads_mutex[i],NULL);
+ io_threads_pending[i] = 0;
+ io_threads_list[i] = listCreate();
+ pthread_mutex_lock(&io_threads_mutex[i]); /* Thread will be stopped. */
+ if (pthread_create(&tid,NULL,IOThreadMain,(void*)(long)i) != 0) {
+ serverLog(LL_WARNING,"Fatal: Can't initialize IO thread.");
+ exit(1);
+ }
+ io_threads[i] = tid;
+ }
+}
+
+void startThreadedIO(void) {
+ if (tio_debug) { printf("S"); fflush(stdout); }
+ if (tio_debug) printf("--- STARTING THREADED IO ---\n");
+ serverAssert(io_threads_active == 0);
+ for (int j = 0; j < server.io_threads_num; j++)
+ pthread_mutex_unlock(&io_threads_mutex[j]);
+ io_threads_active = 1;
+}
+
+void stopThreadedIO(void) {
+ /* We may have still clients with pending reads when this function
+ * is called: handle them before stopping the threads. */
+ handleClientsWithPendingReadsUsingThreads();
+ if (tio_debug) { printf("E"); fflush(stdout); }
+ if (tio_debug) printf("--- STOPPING THREADED IO [R%d] [W%d] ---\n",
+ (int) listLength(server.clients_pending_read),
+ (int) listLength(server.clients_pending_write));
+ serverAssert(io_threads_active == 1);
+ for (int j = 0; j < server.io_threads_num; j++)
+ pthread_mutex_lock(&io_threads_mutex[j]);
+ io_threads_active = 0;
+}
+
+/* This function checks if there are not enough pending clients to justify
+ * taking the I/O threads active: in that case I/O threads are stopped if
+ * currently active. We track the pending writes as a measure of clients
+ * we need to handle in parallel, however the I/O threading is disabled
+ * globally for reads as well if we have too little pending clients.
+ *
+ * The function returns 0 if the I/O threading should be used becuase there
+ * are enough active threads, otherwise 1 is returned and the I/O threads
+ * could be possibly stopped (if already active) as a side effect. */
+int stopThreadedIOIfNeeded(void) {
+ int pending = listLength(server.clients_pending_write);
+
+ /* Return ASAP if IO threads are disabled (single threaded mode). */
+ if (server.io_threads_num == 1) return 1;
+
+ if (pending < (server.io_threads_num*2)) {
+ if (io_threads_active) stopThreadedIO();
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+int handleClientsWithPendingWritesUsingThreads(void) {
+ int processed = listLength(server.clients_pending_write);
+ if (processed == 0) return 0; /* Return ASAP if there are no clients. */
+
+ /* If we have just a few clients to serve, don't use I/O threads, but the
+ * boring synchronous code. */
+ if (stopThreadedIOIfNeeded()) {
+ return handleClientsWithPendingWrites();
+ }
+
+ /* Start threads if needed. */
+ if (!io_threads_active) startThreadedIO();
+
+ if (tio_debug) printf("%d TOTAL WRITE pending clients\n", processed);
+
+ /* Distribute the clients across N different lists. */
+ listIter li;
+ listNode *ln;
+ listRewind(server.clients_pending_write,&li);
+ int item_id = 0;
+ while((ln = listNext(&li))) {
+ client *c = listNodeValue(ln);
+ c->flags &= ~CLIENT_PENDING_WRITE;
+ int target_id = item_id % server.io_threads_num;
+ listAddNodeTail(io_threads_list[target_id],c);
+ item_id++;
+ }
+
+ /* Give the start condition to the waiting threads, by setting the
+ * start condition atomic var. */
+ io_threads_op = IO_THREADS_OP_WRITE;
+ for (int j = 0; j < server.io_threads_num; j++) {
+ int count = listLength(io_threads_list[j]);
+ io_threads_pending[j] = count;
+ }
+
+ /* Wait for all threads to end their work. */
+ while(1) {
+ unsigned long pending = 0;
+ for (int j = 0; j < server.io_threads_num; j++)
+ pending += io_threads_pending[j];
+ if (pending == 0) break;
+ }
+ if (tio_debug) printf("I/O WRITE All threads finshed\n");
+
+ /* Run the list of clients again to install the write handler where
+ * needed. */
+ listRewind(server.clients_pending_write,&li);
+ while((ln = listNext(&li))) {
+ client *c = listNodeValue(ln);
+
+ /* Install the write handler if there are pending writes in some
+ * of the clients. */
+ if (clientHasPendingReplies(c) &&
+ connSetWriteHandler(c->conn, sendReplyToClient) == AE_ERR)
+ {
+ freeClientAsync(c);
+ }
+ }
+ listEmpty(server.clients_pending_write);
+ return processed;
+}
+
+/* Return 1 if we want to handle the client read later using threaded I/O.
+ * This is called by the readable handler of the event loop.
+ * As a side effect of calling this function the client is put in the
+ * pending read clients and flagged as such. */
+int postponeClientRead(client *c) {
+ if (io_threads_active &&
+ server.io_threads_do_reads &&
+ !(c->flags & (CLIENT_MASTER|CLIENT_SLAVE|CLIENT_PENDING_READ)))
+ {
+ c->flags |= CLIENT_PENDING_READ;
+ listAddNodeHead(server.clients_pending_read,c);
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/* When threaded I/O is also enabled for the reading + parsing side, the
+ * readable handler will just put normal clients into a queue of clients to
+ * process (instead of serving them synchronously). This function runs
+ * the queue using the I/O threads, and process them in order to accumulate
+ * the reads in the buffers, and also parse the first command available
+ * rendering it in the client structures. */
+int handleClientsWithPendingReadsUsingThreads(void) {
+ if (!io_threads_active || !server.io_threads_do_reads) return 0;
+ int processed = listLength(server.clients_pending_read);
+ if (processed == 0) return 0;
+
+ if (tio_debug) printf("%d TOTAL READ pending clients\n", processed);
+
+ /* Distribute the clients across N different lists. */
+ listIter li;
+ listNode *ln;
+ listRewind(server.clients_pending_read,&li);
+ int item_id = 0;
+ while((ln = listNext(&li))) {
+ client *c = listNodeValue(ln);
+ int target_id = item_id % server.io_threads_num;
+ listAddNodeTail(io_threads_list[target_id],c);
+ item_id++;
+ }
+
+ /* Give the start condition to the waiting threads, by setting the
+ * start condition atomic var. */
+ io_threads_op = IO_THREADS_OP_READ;
+ for (int j = 0; j < server.io_threads_num; j++) {
+ int count = listLength(io_threads_list[j]);
+ io_threads_pending[j] = count;
+ }
+
+ /* Wait for all threads to end their work. */
+ while(1) {
+ unsigned long pending = 0;
+ for (int j = 0; j < server.io_threads_num; j++)
+ pending += io_threads_pending[j];
+ if (pending == 0) break;
+ }
+ if (tio_debug) printf("I/O READ All threads finshed\n");
+
+ /* Run the list of clients again to process the new buffers. */
+ listRewind(server.clients_pending_read,&li);
+ while((ln = listNext(&li))) {
+ client *c = listNodeValue(ln);
+ c->flags &= ~CLIENT_PENDING_READ;
+ if (c->flags & CLIENT_PENDING_COMMAND) {
+ c->flags &= ~ CLIENT_PENDING_COMMAND;
+ processCommandAndResetClient(c);
+ }
+ processInputBufferAndReplicate(c);
+ }
+ listEmpty(server.clients_pending_read);
+ return processed;
+}
diff --git a/src/notify.c b/src/notify.c
index 6dd72f0a6..d6c3ad403 100644
--- a/src/notify.c
+++ b/src/notify.c
@@ -29,8 +29,8 @@
#include "server.h"
-/* This file implements keyspace events notification via Pub/Sub ad
- * described at http://redis.io/topics/keyspace-events. */
+/* This file implements keyspace events notification via Pub/Sub and
+ * described at https://redis.io/topics/notifications. */
/* Turn a string representing notification classes into an integer
* representing notification classes flags xored.
@@ -55,6 +55,7 @@ int keyspaceEventsStringToFlags(char *classes) {
case 'K': flags |= NOTIFY_KEYSPACE; break;
case 'E': flags |= NOTIFY_KEYEVENT; break;
case 't': flags |= NOTIFY_STREAM; break;
+ case 'm': flags |= NOTIFY_KEY_MISS; break;
default: return -1;
}
}
@@ -81,6 +82,7 @@ sds keyspaceEventsFlagsToString(int flags) {
if (flags & NOTIFY_EXPIRED) res = sdscatlen(res,"x",1);
if (flags & NOTIFY_EVICTED) res = sdscatlen(res,"e",1);
if (flags & NOTIFY_STREAM) res = sdscatlen(res,"t",1);
+ if (flags & NOTIFY_KEY_MISS) res = sdscatlen(res,"m",1);
}
if (flags & NOTIFY_KEYSPACE) res = sdscatlen(res,"K",1);
if (flags & NOTIFY_KEYEVENT) res = sdscatlen(res,"E",1);
@@ -100,12 +102,12 @@ void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {
int len = -1;
char buf[24];
- /* If any modules are interested in events, notify the module system now.
+ /* If any modules are interested in events, notify the module system now.
* This bypasses the notifications configuration, but the module engine
* will only call event subscribers if the event type matches the types
* they are interested in. */
moduleNotifyKeyspaceEvent(type, event, key, dbid);
-
+
/* If notifications for this class of events are off, return ASAP. */
if (!(server.notify_keyspace_events & type)) return;
diff --git a/src/object.c b/src/object.c
index 8614d6e9e..70022f897 100644
--- a/src/object.c
+++ b/src/object.c
@@ -113,7 +113,7 @@ robj *createEmbeddedStringObject(const char *ptr, size_t len) {
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
* used.
*
- * The current limit of 39 is chosen so that the biggest string object
+ * The current limit of 44 is chosen so that the biggest string object
* we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
@@ -123,9 +123,25 @@ robj *createStringObject(const char *ptr, size_t len) {
return createRawStringObject(ptr,len);
}
-robj *createStringObjectFromLongLong(long long value) {
+/* Create a string object from a long long value. When possible returns a
+ * shared integer object, or at least an integer encoded one.
+ *
+ * If valueobj is non zero, the function avoids returning a a shared
+ * integer, because the object is going to be used as value in the Redis key
+ * space (for instance when the INCR command is used), so we want LFU/LRU
+ * values specific for each key. */
+robj *createStringObjectFromLongLongWithOptions(long long value, int valueobj) {
robj *o;
- if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
+
+ if (server.maxmemory == 0 ||
+ !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS))
+ {
+ /* If the maxmemory policy permits, we can still return shared integers
+ * even if valueobj is true. */
+ valueobj = 0;
+ }
+
+ if (value >= 0 && value < OBJ_SHARED_INTEGERS && valueobj == 0) {
incrRefCount(shared.integers[value]);
o = shared.integers[value];
} else {
@@ -140,6 +156,20 @@ robj *createStringObjectFromLongLong(long long value) {
return o;
}
+/* Wrapper for createStringObjectFromLongLongWithOptions() always demanding
+ * to create a shared object if possible. */
+robj *createStringObjectFromLongLong(long long value) {
+ return createStringObjectFromLongLongWithOptions(value,0);
+}
+
+/* Wrapper for createStringObjectFromLongLongWithOptions() avoiding a shared
+ * object when LFU/LRU info are needed, that is, when the object is used
+ * as a value in the key space, and Redis is configured to evict based on
+ * LFU/LRU. */
+robj *createStringObjectFromLongLongForValue(long long value) {
+ return createStringObjectFromLongLongWithOptions(value,1);
+}
+
/* Create a string object from a long double. If humanfriendly is non-zero
* it does not use exponential format and trims trailing zeroes at the end,
* however this results in loss of precision. Otherwise exp format is used
@@ -155,7 +185,7 @@ robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
/* Duplicate a string object, with the guarantee that the returned object
* has the same encoding as the original one.
*
- * This function also guarantees that duplicating a small integere object
+ * This function also guarantees that duplicating a small integer object
* (or a string object that contains a representation of a small integer)
* will always result in a fresh object that is unshared (refcount == 1).
*
@@ -385,6 +415,18 @@ int isObjectRepresentableAsLongLong(robj *o, long long *llval) {
}
}
+/* Optimize the SDS string inside the string object to require little space,
+ * in case there is more than 10% of free space at the end of the SDS
+ * string. This happens because SDS strings tend to overallocate to avoid
+ * wasting too much time in allocations when appending to the string. */
+void trimStringObjectIfNeeded(robj *o) {
+ if (o->encoding == OBJ_ENCODING_RAW &&
+ sdsavail(o->ptr) > sdslen(o->ptr)/10)
+ {
+ o->ptr = sdsRemoveFreeSpace(o->ptr);
+ }
+}
+
/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
long value;
@@ -425,10 +467,15 @@ robj *tryObjectEncoding(robj *o) {
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
- if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
- o->encoding = OBJ_ENCODING_INT;
- o->ptr = (void*) value;
- return o;
+ if (o->encoding == OBJ_ENCODING_RAW) {
+ sdsfree(o->ptr);
+ o->encoding = OBJ_ENCODING_INT;
+ o->ptr = (void*) value;
+ return o;
+ } else if (o->encoding == OBJ_ENCODING_EMBSTR) {
+ decrRefCount(o);
+ return createStringObjectFromLongLongForValue(value);
+ }
}
}
@@ -454,11 +501,7 @@ robj *tryObjectEncoding(robj *o) {
* We do that only for relatively large strings as this branch
* is only entered if the length of the string is greater than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
- if (o->encoding == OBJ_ENCODING_RAW &&
- sdsavail(s) > len/10)
- {
- o->ptr = sdsRemoveFreeSpace(o->ptr);
- }
+ trimStringObjectIfNeeded(o);
/* Return the original object. */
return o;
@@ -708,7 +751,31 @@ char *strEncoding(int encoding) {
}
}
-/* =========================== Memory introspection ========================== */
+/* =========================== Memory introspection ========================= */
+
+
+/* This is an helper function with the goal of estimating the memory
+ * size of a radix tree that is used to store Stream IDs.
+ *
+ * Note: to guess the size of the radix tree is not trivial, so we
+ * approximate it considering 16 bytes of data overhead for each
+ * key (the ID), and then adding the number of bare nodes, plus some
+ * overhead due by the data and child pointers. This secret recipe
+ * was obtained by checking the average radix tree created by real
+ * workloads, and then adjusting the constants to get numbers that
+ * more or less match the real memory usage.
+ *
+ * Actually the number of nodes and keys may be different depending
+ * on the insertion speed and thus the ability of the radix tree
+ * to compress prefixes. */
+size_t streamRadixTreeMemoryUsage(rax *rax) {
+ size_t size;
+ size = rax->numele * sizeof(streamID);
+ size += rax->numnodes * sizeof(raxNode);
+ /* Add a fixed overhead due to the aux data pointer, children, ... */
+ size += rax->numnodes * sizeof(long)*30;
+ return size;
+}
/* Returns the size in bytes consumed by the key's value in RAM.
* Note that the returned value is just an approximation, especially in the
@@ -772,7 +839,9 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
d = ((zset*)o->ptr)->dict;
zskiplist *zsl = ((zset*)o->ptr)->zsl;
zskiplistNode *znode = zsl->header->level[0].forward;
- asize = sizeof(*o)+sizeof(zset)+(sizeof(struct dictEntry*)*dictSlots(d));
+ asize = sizeof(*o)+sizeof(zset)+sizeof(zskiplist)+sizeof(dict)+
+ (sizeof(struct dictEntry*)*dictSlots(d))+
+ zmalloc_size(zsl->header);
while(znode != NULL && samples < sample_size) {
elesize += sdsAllocSize(znode->ele);
elesize += sizeof(struct dictEntry) + zmalloc_size(znode);
@@ -804,21 +873,8 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
}
} else if (o->type == OBJ_STREAM) {
stream *s = o->ptr;
- /* Note: to guess the size of the radix tree is not trivial, so we
- * approximate it considering 64 bytes of data overhead for each
- * key (the ID), and then adding the number of bare nodes, plus some
- * overhead due by the data and child pointers. This secret recipe
- * was obtained by checking the average radix tree created by real
- * workloads, and then adjusting the constants to get numbers that
- * more or less match the real memory usage.
- *
- * Actually the number of nodes and keys may be different depending
- * on the insertion speed and thus the ability of the radix tree
- * to compress prefixes. */
asize = sizeof(*o);
- asize += s->rax->numele * 64;
- asize += s->rax->numnodes * sizeof(raxNode);
- asize += s->rax->numnodes * 32*7; /* Add a few child pointers... */
+ asize += streamRadixTreeMemoryUsage(s->rax);
/* Now we have to add the listpacks. The last listpack is often non
* complete, so we estimate the size of the first N listpacks, and
@@ -845,6 +901,37 @@ size_t objectComputeSize(robj *o, size_t sample_size) {
asize += lpBytes(ri.data);
}
raxStop(&ri);
+
+ /* Consumer groups also have a non trivial memory overhead if there
+ * are many consumers and many groups, let's count at least the
+ * overhead of the pending entries in the groups and consumers
+ * PELs. */
+ if (s->cgroups) {
+ raxStart(&ri,s->cgroups);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamCG *cg = ri.data;
+ asize += sizeof(*cg);
+ asize += streamRadixTreeMemoryUsage(cg->pel);
+ asize += sizeof(streamNACK)*raxSize(cg->pel);
+
+ /* For each consumer we also need to add the basic data
+ * structures and the PEL memory usage. */
+ raxIterator cri;
+ raxStart(&cri,cg->consumers);
+ raxSeek(&cri,"^",NULL,0);
+ while(raxNext(&cri)) {
+ streamConsumer *consumer = cri.data;
+ asize += sizeof(*consumer);
+ asize += sdslen(consumer->name);
+ asize += streamRadixTreeMemoryUsage(consumer->pel);
+ /* Don't count NACKs again, they are shared with the
+ * consumer group PEL. */
+ }
+ raxStop(&cri);
+ }
+ raxStop(&ri);
+ }
} else if (o->type == OBJ_MODULE) {
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
@@ -878,8 +965,23 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mh->total_allocated = zmalloc_used;
mh->startup_allocated = server.initial_memory_usage;
mh->peak_allocated = server.stat_peak_memory;
- mh->fragmentation =
- zmalloc_get_fragmentation_ratio(server.resident_set_size);
+ mh->total_frag =
+ (float)server.cron_malloc_stats.process_rss / server.cron_malloc_stats.zmalloc_used;
+ mh->total_frag_bytes =
+ server.cron_malloc_stats.process_rss - server.cron_malloc_stats.zmalloc_used;
+ mh->allocator_frag =
+ (float)server.cron_malloc_stats.allocator_active / server.cron_malloc_stats.allocator_allocated;
+ mh->allocator_frag_bytes =
+ server.cron_malloc_stats.allocator_active - server.cron_malloc_stats.allocator_allocated;
+ mh->allocator_rss =
+ (float)server.cron_malloc_stats.allocator_resident / server.cron_malloc_stats.allocator_active;
+ mh->allocator_rss_bytes =
+ server.cron_malloc_stats.allocator_resident - server.cron_malloc_stats.allocator_active;
+ mh->rss_extra =
+ (float)server.cron_malloc_stats.process_rss / server.cron_malloc_stats.allocator_resident;
+ mh->rss_extra_bytes =
+ server.cron_malloc_stats.process_rss - server.cron_malloc_stats.allocator_resident;
+
mem_total += server.initial_memory_usage;
mem = 0;
@@ -912,7 +1014,7 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
listRewind(server.clients,&li);
while((ln = listNext(&li))) {
client *c = listNodeValue(ln);
- if (c->flags & CLIENT_SLAVE)
+ if (c->flags & CLIENT_SLAVE && !(c->flags & CLIENT_MONITOR))
continue;
mem += getClientOutputBufferMemoryUsage(c);
mem += sdsAllocSize(c->querybuf);
@@ -924,12 +1026,24 @@ struct redisMemOverhead *getMemoryOverheadData(void) {
mem = 0;
if (server.aof_state != AOF_OFF) {
- mem += sdslen(server.aof_buf);
+ mem += sdsalloc(server.aof_buf);
mem += aofRewriteBufferSize();
}
mh->aof_buffer = mem;
mem_total+=mem;
+ mem = server.lua_scripts_mem;
+ mem += dictSize(server.lua_scripts) * sizeof(dictEntry) +
+ dictSlots(server.lua_scripts) * sizeof(dictEntry*);
+ mem += dictSize(server.repl_scriptcache_dict) * sizeof(dictEntry) +
+ dictSlots(server.repl_scriptcache_dict) * sizeof(dictEntry*);
+ if (listLength(server.repl_scriptcache_fifo) > 0) {
+ mem += listLength(server.repl_scriptcache_fifo) * (sizeof(listNode) +
+ sdsZmallocSize(listNodeValue(listFirst(server.repl_scriptcache_fifo))));
+ }
+ mh->lua_caches = mem;
+ mem_total+=mem;
+
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
long long keyscount = dictSize(db->dict);
@@ -982,8 +1096,12 @@ sds getMemoryDoctorReport(void) {
int empty = 0; /* Instance is empty or almost empty. */
int big_peak = 0; /* Memory peak is much larger than used mem. */
int high_frag = 0; /* High fragmentation. */
+ int high_alloc_frag = 0;/* High allocator fragmentation. */
+ int high_proc_rss = 0; /* High process rss overhead. */
+ int high_alloc_rss = 0; /* High rss overhead. */
int big_slave_buf = 0; /* Slave buffers are too big. */
int big_client_buf = 0; /* Client buffers are too big. */
+ int many_scripts = 0; /* Script cache has too many scripts. */
int num_reports = 0;
struct redisMemOverhead *mh = getMemoryOverheadData();
@@ -997,12 +1115,30 @@ sds getMemoryDoctorReport(void) {
num_reports++;
}
- /* Fragmentation is higher than 1.4? */
- if (mh->fragmentation > 1.4) {
+ /* Fragmentation is higher than 1.4 and 10MB ?*/
+ if (mh->total_frag > 1.4 && mh->total_frag_bytes > 10<<20) {
high_frag = 1;
num_reports++;
}
+ /* External fragmentation is higher than 1.1 and 10MB? */
+ if (mh->allocator_frag > 1.1 && mh->allocator_frag_bytes > 10<<20) {
+ high_alloc_frag = 1;
+ num_reports++;
+ }
+
+ /* Allocator fss is higher than 1.1 and 10MB ? */
+ if (mh->allocator_rss > 1.1 && mh->allocator_rss_bytes > 10<<20) {
+ high_alloc_rss = 1;
+ num_reports++;
+ }
+
+ /* Non-Allocator fss is higher than 1.1 and 10MB ? */
+ if (mh->rss_extra > 1.1 && mh->rss_extra_bytes > 10<<20) {
+ high_proc_rss = 1;
+ num_reports++;
+ }
+
/* Clients using more than 200k each average? */
long numslaves = listLength(server.slaves);
long numclients = listLength(server.clients)-numslaves;
@@ -1016,6 +1152,12 @@ sds getMemoryDoctorReport(void) {
big_slave_buf = 1;
num_reports++;
}
+
+ /* Too many scripts are cached? */
+ if (dictSize(server.lua_scripts) > 1000) {
+ many_scripts = 1;
+ num_reports++;
+ }
}
sds s;
@@ -1036,20 +1178,62 @@ sds getMemoryDoctorReport(void) {
s = sdscat(s," * Peak memory: In the past this instance used more than 150% the memory that is currently using. The allocator is normally not able to release memory after a peak, so you can expect to see a big fragmentation ratio, however this is actually harmless and is only due to the memory peak, and if the Redis instance Resident Set Size (RSS) is currently bigger than expected, the memory will be used as soon as you fill the Redis instance with more data. If the memory peak was only occasional and you want to try to reclaim memory, please try the MEMORY PURGE command, otherwise the only other option is to shutdown and restart the instance.\n\n");
}
if (high_frag) {
- s = sdscatprintf(s," * High fragmentation: This instance has a memory fragmentation greater than 1.4 (this means that the Resident Set Size of the Redis process is much larger than the sum of the logical allocations Redis performed). This problem is usually due either to a large peak memory (check if there is a peak memory entry above in the report) or may result from a workload that causes the allocator to fragment memory a lot. If the problem is a large peak memory, then there is no issue. Otherwise, make sure you are using the Jemalloc allocator and not the default libc malloc. Note: The currently used allocator is \"%s\".\n\n", ZMALLOC_LIB);
+ s = sdscatprintf(s," * High total RSS: This instance has a memory fragmentation and RSS overhead greater than 1.4 (this means that the Resident Set Size of the Redis process is much larger than the sum of the logical allocations Redis performed). This problem is usually due either to a large peak memory (check if there is a peak memory entry above in the report) or may result from a workload that causes the allocator to fragment memory a lot. If the problem is a large peak memory, then there is no issue. Otherwise, make sure you are using the Jemalloc allocator and not the default libc malloc. Note: The currently used allocator is \"%s\".\n\n", ZMALLOC_LIB);
+ }
+ if (high_alloc_frag) {
+ s = sdscatprintf(s," * High allocator fragmentation: This instance has an allocator external fragmentation greater than 1.1. This problem is usually due either to a large peak memory (check if there is a peak memory entry above in the report) or may result from a workload that causes the allocator to fragment memory a lot. You can try enabling 'activedefrag' config option.\n\n");
+ }
+ if (high_alloc_rss) {
+ s = sdscatprintf(s," * High allocator RSS overhead: This instance has an RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the allocator is much larger than the sum what the allocator actually holds). This problem is usually due to a large peak memory (check if there is a peak memory entry above in the report), you can try the MEMORY PURGE command to reclaim it.\n\n");
+ }
+ if (high_proc_rss) {
+ s = sdscatprintf(s," * High process RSS overhead: This instance has non-allocator RSS memory overhead is greater than 1.1 (this means that the Resident Set Size of the Redis process is much larger than the RSS the allocator holds). This problem may be due to Lua scripts or Modules.\n\n");
}
if (big_slave_buf) {
- s = sdscat(s," * Big slave buffers: The slave output buffers in this instance are greater than 10MB for each slave (on average). This likely means that there is some slave instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what slave is not receiving data correctly and why. You can use the INFO output in order to check the slaves delays and the CLIENT LIST command to check the output buffers of each slave.\n\n");
+ s = sdscat(s," * Big replica buffers: The replica output buffers in this instance are greater than 10MB for each replica (on average). This likely means that there is some replica instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what replica is not receiving data correctly and why. You can use the INFO output in order to check the replicas delays and the CLIENT LIST command to check the output buffers of each replica.\n\n");
}
if (big_client_buf) {
s = sdscat(s," * Big client buffers: The clients output buffers in this instance are greater than 200K per client (on average). This may result from different causes, like Pub/Sub clients subscribed to channels bot not receiving data fast enough, so that data piles on the Redis instance output buffer, or clients sending commands with large replies or very large sequences of commands in the same pipeline. Please use the CLIENT LIST command in order to investigate the issue if it causes problems in your instance, or to understand better why certain clients are using a big amount of memory.\n\n");
}
+ if (many_scripts) {
+ s = sdscat(s," * Many scripts: There seem to be many cached scripts in this instance (more than 1000). This may be because scripts are generated and `EVAL`ed, instead of being parameterized (with KEYS and ARGV), `SCRIPT LOAD`ed and `EVALSHA`ed. Unless `SCRIPT FLUSH` is called periodically, the scripts' caches may end up consuming most of your memory.\n\n");
+ }
s = sdscat(s,"I'm here to keep you safe, Sam. I want to help you.\n");
}
freeMemoryOverheadData(mh);
return s;
}
+/* Set the object LRU/LFU depending on server.maxmemory_policy.
+ * The lfu_freq arg is only relevant if policy is MAXMEMORY_FLAG_LFU.
+ * The lru_idle and lru_clock args are only relevant if policy
+ * is MAXMEMORY_FLAG_LRU.
+ * Either or both of them may be <0, in that case, nothing is set. */
+void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
+ long long lru_clock) {
+ if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
+ if (lfu_freq >= 0) {
+ serverAssert(lfu_freq <= 255);
+ val->lru = (LFUGetTimeInMinutes()<<8) | lfu_freq;
+ }
+ } else if (lru_idle >= 0) {
+ /* Provided LRU idle time is in seconds. Scale
+ * according to the LRU clock resolution this Redis
+ * instance was compiled with (normally 1000 ms, so the
+ * below statement will expand to lru_idle*1000/1000. */
+ lru_idle = lru_idle*1000/LRU_CLOCK_RESOLUTION;
+ long lru_abs = lru_clock - lru_idle; /* Absolute access time. */
+ /* If the LRU field underflows (since LRU it is a wrapping
+ * clock), the best we can do is to provide a large enough LRU
+ * that is half-way in the circlular LRU clock we use: this way
+ * the computed idle time for this object will stay high for quite
+ * some time. */
+ if (lru_abs < 0)
+ lru_abs = (lru_clock+(LRU_CLOCK_MAX/2)) % LRU_CLOCK_MAX;
+ val->lru = lru_abs;
+ }
+}
+
/* ======================= The OBJECT and MEMORY commands =================== */
/* This is a helper function for the OBJECT command. We need to lookup keys
@@ -1075,23 +1259,23 @@ void objectCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"encoding <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
-"freq <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.",
-"idletime <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
-"refcount <key> -- Return the number of references of the value associated with the specified key.",
+"ENCODING <key> -- Return the kind of internal representation used in order to store the value associated with a key.",
+"FREQ <key> -- Return the access frequency index of the key. The returned integer is proportional to the logarithm of the recent access frequency of the key.",
+"IDLETIME <key> -- Return the idle time of the key, that is the approximated number of seconds elapsed since the last access to the key.",
+"REFCOUNT <key> -- Return the number of references of the value associated with the specified key.",
NULL
};
addReplyHelp(c, help);
} else if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
- if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
+ if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
addReplyLongLong(c,o->refcount);
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
- if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
+ if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding));
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
- if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
+ if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
addReplyError(c,"An LFU maxmemory policy is selected, idle time not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
@@ -1099,7 +1283,7 @@ NULL
}
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
} else if (!strcasecmp(c->argv[1]->ptr,"freq") && c->argc == 3) {
- if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
+ if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.null[c->resp]))
== NULL) return;
if (!(server.maxmemory_policy & MAXMEMORY_FLAG_LFU)) {
addReplyError(c,"An LFU maxmemory policy is not selected, access frequency not tracked. Please note that when switching between policies at runtime LRU and LFU data will take some time to adjust.");
@@ -1111,7 +1295,7 @@ NULL
* when the key is read or overwritten. */
addReplyLongLong(c,LFUDecrAndReturn(o));
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try OBJECT help", (char *)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
}
}
@@ -1120,9 +1304,18 @@ NULL
*
* Usage: MEMORY usage <key> */
void memoryCommand(client *c) {
- robj *o;
-
- if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
+ if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
+ const char *help[] = {
+"DOCTOR - Return memory problems reports.",
+"MALLOC-STATS -- Return internal statistics report from the memory allocator.",
+"PURGE -- Attempt to purge dirty pages for reclamation by the allocator.",
+"STATS -- Return information about the memory usage of the server.",
+"USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).",
+NULL
+ };
+ addReplyHelp(c, help);
+ } else if (!strcasecmp(c->argv[1]->ptr,"usage") && c->argc >= 3) {
+ dictEntry *de;
long long samples = OBJ_COMPUTE_SIZE_DEF_SAMPLES;
for (int j = 3; j < c->argc; j++) {
if (!strcasecmp(c->argv[j]->ptr,"samples") &&
@@ -1141,16 +1334,18 @@ void memoryCommand(client *c) {
return;
}
}
- if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
- == NULL) return;
- size_t usage = objectComputeSize(o,samples);
- usage += sdsAllocSize(c->argv[2]->ptr);
+ if ((de = dictFind(c->db->dict,c->argv[2]->ptr)) == NULL) {
+ addReplyNull(c);
+ return;
+ }
+ size_t usage = objectComputeSize(dictGetVal(de),samples);
+ usage += sdsAllocSize(dictGetKey(de));
usage += sizeof(dictEntry);
addReplyLongLong(c,usage);
} else if (!strcasecmp(c->argv[1]->ptr,"stats") && c->argc == 2) {
struct redisMemOverhead *mh = getMemoryOverheadData();
- addReplyMultiBulkLen(c,(14+mh->num_dbs)*2);
+ addReplyMapLen(c,25+mh->num_dbs);
addReplyBulkCString(c,"peak.allocated");
addReplyLongLong(c,mh->peak_allocated);
@@ -1173,11 +1368,14 @@ void memoryCommand(client *c) {
addReplyBulkCString(c,"aof.buffer");
addReplyLongLong(c,mh->aof_buffer);
+ addReplyBulkCString(c,"lua.caches");
+ addReplyLongLong(c,mh->lua_caches);
+
for (size_t j = 0; j < mh->num_dbs; j++) {
char dbname[32];
snprintf(dbname,sizeof(dbname),"db.%zd",mh->db[j].dbid);
addReplyBulkCString(c,dbname);
- addReplyMultiBulkLen(c,4);
+ addReplyMapLen(c,2);
addReplyBulkCString(c,"overhead.hashtable.main");
addReplyLongLong(c,mh->db[j].overhead_ht_main);
@@ -1204,51 +1402,59 @@ void memoryCommand(client *c) {
addReplyBulkCString(c,"peak.percentage");
addReplyDouble(c,mh->peak_perc);
- addReplyBulkCString(c,"fragmentation");
- addReplyDouble(c,mh->fragmentation);
+ addReplyBulkCString(c,"allocator.allocated");
+ addReplyLongLong(c,server.cron_malloc_stats.allocator_allocated);
+
+ addReplyBulkCString(c,"allocator.active");
+ addReplyLongLong(c,server.cron_malloc_stats.allocator_active);
+
+ addReplyBulkCString(c,"allocator.resident");
+ addReplyLongLong(c,server.cron_malloc_stats.allocator_resident);
+
+ addReplyBulkCString(c,"allocator-fragmentation.ratio");
+ addReplyDouble(c,mh->allocator_frag);
+
+ addReplyBulkCString(c,"allocator-fragmentation.bytes");
+ addReplyLongLong(c,mh->allocator_frag_bytes);
+
+ addReplyBulkCString(c,"allocator-rss.ratio");
+ addReplyDouble(c,mh->allocator_rss);
+
+ addReplyBulkCString(c,"allocator-rss.bytes");
+ addReplyLongLong(c,mh->allocator_rss_bytes);
+
+ addReplyBulkCString(c,"rss-overhead.ratio");
+ addReplyDouble(c,mh->rss_extra);
+
+ addReplyBulkCString(c,"rss-overhead.bytes");
+ addReplyLongLong(c,mh->rss_extra_bytes);
+
+ addReplyBulkCString(c,"fragmentation"); /* this is the total RSS overhead, including fragmentation */
+ addReplyDouble(c,mh->total_frag); /* it is kept here for backwards compatibility */
+
+ addReplyBulkCString(c,"fragmentation.bytes");
+ addReplyLongLong(c,mh->total_frag_bytes);
freeMemoryOverheadData(mh);
} else if (!strcasecmp(c->argv[1]->ptr,"malloc-stats") && c->argc == 2) {
#if defined(USE_JEMALLOC)
sds info = sdsempty();
je_malloc_stats_print(inputCatSds, &info, NULL);
- addReplyBulkSds(c, info);
+ addReplyVerbatim(c,info,sdslen(info),"txt");
+ sdsfree(info);
#else
addReplyBulkCString(c,"Stats not supported for the current allocator");
#endif
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
sds report = getMemoryDoctorReport();
- addReplyBulkSds(c,report);
+ addReplyVerbatim(c,report,sdslen(report),"txt");
+ sdsfree(report);
} else if (!strcasecmp(c->argv[1]->ptr,"purge") && c->argc == 2) {
-#if defined(USE_JEMALLOC)
- char tmp[32];
- unsigned narenas = 0;
- size_t sz = sizeof(unsigned);
- if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
- sprintf(tmp, "arena.%d.purge", narenas);
- if (!je_mallctl(tmp, NULL, 0, NULL, 0)) {
- addReply(c, shared.ok);
- return;
- }
- }
- addReplyError(c, "Error purging dirty pages");
-#else
- addReply(c, shared.ok);
- /* Nothing to do for other allocators. */
-#endif
- } else if (!strcasecmp(c->argv[1]->ptr,"help") && c->argc == 2) {
- addReplyMultiBulkLen(c,5);
- addReplyBulkCString(c,
-"MEMORY DOCTOR - Outputs memory problems report");
- addReplyBulkCString(c,
-"MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key");
- addReplyBulkCString(c,
-"MEMORY STATS - Show memory usage details");
- addReplyBulkCString(c,
-"MEMORY PURGE - Ask the allocator to release memory");
- addReplyBulkCString(c,
-"MEMORY MALLOC-STATS - Show allocator internal stats");
+ if (jemalloc_purge() == 0)
+ addReply(c, shared.ok);
+ else
+ addReplyError(c, "Error purging dirty pages");
} else {
- addReplyError(c,"Syntax error. Try MEMORY HELP");
+ addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try MEMORY HELP", (char*)c->argv[1]->ptr);
}
}
diff --git a/src/pubsub.c b/src/pubsub.c
index d1fffa20a..994dd9734 100644
--- a/src/pubsub.c
+++ b/src/pubsub.c
@@ -29,6 +29,93 @@
#include "server.h"
+int clientSubscriptionsCount(client *c);
+
+/*-----------------------------------------------------------------------------
+ * Pubsub client replies API
+ *----------------------------------------------------------------------------*/
+
+/* Send a pubsub message of type "message" to the client. */
+void addReplyPubsubMessage(client *c, robj *channel, robj *msg) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[3]);
+ else
+ addReplyPushLen(c,3);
+ addReply(c,shared.messagebulk);
+ addReplyBulk(c,channel);
+ addReplyBulk(c,msg);
+}
+
+/* Send a pubsub message of type "pmessage" to the client. The difference
+ * with the "message" type delivered by addReplyPubsubMessage() is that
+ * this message format also includes the pattern that matched the message. */
+void addReplyPubsubPatMessage(client *c, robj *pat, robj *channel, robj *msg) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[4]);
+ else
+ addReplyPushLen(c,4);
+ addReply(c,shared.pmessagebulk);
+ addReplyBulk(c,pat);
+ addReplyBulk(c,channel);
+ addReplyBulk(c,msg);
+}
+
+/* Send the pubsub subscription notification to the client. */
+void addReplyPubsubSubscribed(client *c, robj *channel) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[3]);
+ else
+ addReplyPushLen(c,3);
+ addReply(c,shared.subscribebulk);
+ addReplyBulk(c,channel);
+ addReplyLongLong(c,clientSubscriptionsCount(c));
+}
+
+/* Send the pubsub unsubscription notification to the client.
+ * Channel can be NULL: this is useful when the client sends a mass
+ * unsubscribe command but there are no channels to unsubscribe from: we
+ * still send a notification. */
+void addReplyPubsubUnsubscribed(client *c, robj *channel) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[3]);
+ else
+ addReplyPushLen(c,3);
+ addReply(c,shared.unsubscribebulk);
+ if (channel)
+ addReplyBulk(c,channel);
+ else
+ addReplyNull(c);
+ addReplyLongLong(c,clientSubscriptionsCount(c));
+}
+
+/* Send the pubsub pattern subscription notification to the client. */
+void addReplyPubsubPatSubscribed(client *c, robj *pattern) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[3]);
+ else
+ addReplyPushLen(c,3);
+ addReply(c,shared.psubscribebulk);
+ addReplyBulk(c,pattern);
+ addReplyLongLong(c,clientSubscriptionsCount(c));
+}
+
+/* Send the pubsub pattern unsubscription notification to the client.
+ * Pattern can be NULL: this is useful when the client sends a mass
+ * punsubscribe command but there are no pattern to unsubscribe from: we
+ * still send a notification. */
+void addReplyPubsubPatUnsubscribed(client *c, robj *pattern) {
+ if (c->resp == 2)
+ addReply(c,shared.mbulkhdr[3]);
+ else
+ addReplyPushLen(c,3);
+ addReply(c,shared.punsubscribebulk);
+ if (pattern)
+ addReplyBulk(c,pattern);
+ else
+ addReplyNull(c);
+ addReplyLongLong(c,clientSubscriptionsCount(c));
+}
+
/*-----------------------------------------------------------------------------
* Pubsub low level API
*----------------------------------------------------------------------------*/
@@ -76,10 +163,7 @@ int pubsubSubscribeChannel(client *c, robj *channel) {
listAddNodeTail(clients,c);
}
/* Notify the client */
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.subscribebulk);
- addReplyBulk(c,channel);
- addReplyLongLong(c,clientSubscriptionsCount(c));
+ addReplyPubsubSubscribed(c,channel);
return retval;
}
@@ -111,14 +195,7 @@ int pubsubUnsubscribeChannel(client *c, robj *channel, int notify) {
}
}
/* Notify the client */
- if (notify) {
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.unsubscribebulk);
- addReplyBulk(c,channel);
- addReplyLongLong(c,dictSize(c->pubsub_channels)+
- listLength(c->pubsub_patterns));
-
- }
+ if (notify) addReplyPubsubUnsubscribed(c,channel);
decrRefCount(channel); /* it is finally safe to release it */
return retval;
}
@@ -138,10 +215,7 @@ int pubsubSubscribePattern(client *c, robj *pattern) {
listAddNodeTail(server.pubsub_patterns,pat);
}
/* Notify the client */
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.psubscribebulk);
- addReplyBulk(c,pattern);
- addReplyLongLong(c,clientSubscriptionsCount(c));
+ addReplyPubsubPatSubscribed(c,pattern);
return retval;
}
@@ -162,13 +236,7 @@ int pubsubUnsubscribePattern(client *c, robj *pattern, int notify) {
listDelNode(server.pubsub_patterns,ln);
}
/* Notify the client */
- if (notify) {
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.punsubscribebulk);
- addReplyBulk(c,pattern);
- addReplyLongLong(c,dictSize(c->pubsub_channels)+
- listLength(c->pubsub_patterns));
- }
+ if (notify) addReplyPubsubPatUnsubscribed(c,pattern);
decrRefCount(pattern);
return retval;
}
@@ -186,13 +254,7 @@ int pubsubUnsubscribeAllChannels(client *c, int notify) {
count += pubsubUnsubscribeChannel(c,channel,notify);
}
/* We were subscribed to nothing? Still reply to the client. */
- if (notify && count == 0) {
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.unsubscribebulk);
- addReply(c,shared.nullbulk);
- addReplyLongLong(c,dictSize(c->pubsub_channels)+
- listLength(c->pubsub_patterns));
- }
+ if (notify && count == 0) addReplyPubsubUnsubscribed(c,NULL);
dictReleaseIterator(di);
return count;
}
@@ -210,14 +272,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify) {
count += pubsubUnsubscribePattern(c,pattern,notify);
}
- if (notify && count == 0) {
- /* We were subscribed to nothing? Still reply to the client. */
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.punsubscribebulk);
- addReply(c,shared.nullbulk);
- addReplyLongLong(c,dictSize(c->pubsub_channels)+
- listLength(c->pubsub_patterns));
- }
+ if (notify && count == 0) addReplyPubsubPatUnsubscribed(c,NULL);
return count;
}
@@ -238,11 +293,7 @@ int pubsubPublishMessage(robj *channel, robj *message) {
listRewind(list,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = ln->value;
-
- addReply(c,shared.mbulkhdr[3]);
- addReply(c,shared.messagebulk);
- addReplyBulk(c,channel);
- addReplyBulk(c,message);
+ addReplyPubsubMessage(c,channel,message);
receivers++;
}
}
@@ -256,12 +307,10 @@ int pubsubPublishMessage(robj *channel, robj *message) {
if (stringmatchlen((char*)pat->pattern->ptr,
sdslen(pat->pattern->ptr),
(char*)channel->ptr,
- sdslen(channel->ptr),0)) {
- addReply(pat->client,shared.mbulkhdr[4]);
- addReply(pat->client,shared.pmessagebulk);
- addReplyBulk(pat->client,pat->pattern);
- addReplyBulk(pat->client,channel);
- addReplyBulk(pat->client,message);
+ sdslen(channel->ptr),0))
+ {
+ addReplyPubsubPatMessage(pat->client,
+ pat->pattern,channel,message);
receivers++;
}
}
@@ -327,9 +376,9 @@ void publishCommand(client *c) {
void pubsubCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"channels [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
-"numpat -- Return number of subscriptions to patterns.",
-"numsub [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
+"CHANNELS [<pattern>] -- Return the currently active channels matching a pattern (default: all).",
+"NUMPAT -- Return number of subscriptions to patterns.",
+"NUMSUB [channel-1 .. channel-N] -- Returns the number of subscribers for the specified channels (excluding patterns, default: none).",
NULL
};
addReplyHelp(c, help);
@@ -343,7 +392,7 @@ NULL
long mblen = 0;
void *replylen;
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
while((de = dictNext(di)) != NULL) {
robj *cobj = dictGetKey(de);
sds channel = cobj->ptr;
@@ -356,12 +405,12 @@ NULL
}
}
dictReleaseIterator(di);
- setDeferredMultiBulkLength(c,replylen,mblen);
+ setDeferredArrayLen(c,replylen,mblen);
} else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) {
/* PUBSUB NUMSUB [Channel_1 ... Channel_N] */
int j;
- addReplyMultiBulkLen(c,(c->argc-2)*2);
+ addReplyArrayLen(c,(c->argc-2)*2);
for (j = 2; j < c->argc; j++) {
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
@@ -372,7 +421,6 @@ NULL
/* PUBSUB NUMPAT */
addReplyLongLong(c,listLength(server.pubsub_patterns));
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try PUBSUB HELP",
- (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
}
}
diff --git a/src/quicklist.c b/src/quicklist.c
index faa08c65f..7b5484116 100644
--- a/src/quicklist.c
+++ b/src/quicklist.c
@@ -1636,7 +1636,7 @@ int quicklistTest(int argc, char *argv[]) {
TEST("add to tail of empty list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushTail(ql, "hello", 6);
- /* 1 for head and 1 for tail beacuse 1 node = head = tail */
+ /* 1 for head and 1 for tail because 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
@@ -1644,7 +1644,7 @@ int quicklistTest(int argc, char *argv[]) {
TEST("add to head of empty list") {
quicklist *ql = quicklistNew(-2, options[_i]);
quicklistPushHead(ql, "hello", 6);
- /* 1 for head and 1 for tail beacuse 1 node = head = tail */
+ /* 1 for head and 1 for tail because 1 node = head = tail */
ql_verify(ql, 1, 1, 1, 1);
quicklistRelease(ql);
}
diff --git a/src/quicklist.h b/src/quicklist.h
index 955a22cfa..a7e27a2dd 100644
--- a/src/quicklist.h
+++ b/src/quicklist.h
@@ -40,7 +40,7 @@
* container: 2 bits, NONE=1, ZIPLIST=2.
* recompress: 1 bit, bool, true if node is temporarry decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
- * extra: 12 bits, free for future use; pads out the remainder of 32 bits */
+ * extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
diff --git a/src/rax.c b/src/rax.c
index 4e45fd2c3..be474b058 100644
--- a/src/rax.c
+++ b/src/rax.c
@@ -1,6 +1,6 @@
/* Rax -- A radix tree implementation.
*
- * Copyright (c) 2017, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -51,14 +51,18 @@ void *raxNotFound = (void*)"rax-not-found-pointer";
void raxDebugShowNode(const char *msg, raxNode *n);
-/* Turn debugging messages on/off. */
-#if 0
+/* Turn debugging messages on/off by compiling with RAX_DEBUG_MSG macro on.
+ * When RAX_DEBUG_MSG is defined by default Rax operations will emit a lot
+ * of debugging info to the standard output, however you can still turn
+ * debugging on/off in order to enable it only when you suspect there is an
+ * operation causing a bug using the function raxSetDebugMsg(). */
+#ifdef RAX_DEBUG_MSG
#define debugf(...) \
- do { \
+ if (raxDebugMsg) { \
printf("%s:%s:%d:\t", __FILE__, __FUNCTION__, __LINE__); \
printf(__VA_ARGS__); \
fflush(stdout); \
- } while (0);
+ }
#define debugnode(msg,n) raxDebugShowNode(msg,n)
#else
@@ -66,6 +70,16 @@ void raxDebugShowNode(const char *msg, raxNode *n);
#define debugnode(msg,n)
#endif
+/* By default log debug info if RAX_DEBUG_MSG is defined. */
+static int raxDebugMsg = 1;
+
+/* When debug messages are enabled, turn them on/off dynamically. By
+ * default they are enabled. Set the state to 0 to disable, and 1 to
+ * re-enable. */
+void raxSetDebugMsg(int onoff) {
+ raxDebugMsg = onoff;
+}
+
/* ------------------------- raxStack functions --------------------------
* The raxStack is a simple stack of pointers that is capable of switching
* from using a stack-allocated array to dynamic heap once a given number of
@@ -134,12 +148,43 @@ static inline void raxStackFree(raxStack *ts) {
* Radix tree implementation
* --------------------------------------------------------------------------*/
+/* Return the padding needed in the characters section of a node having size
+ * 'nodesize'. The padding is needed to store the child pointers to aligned
+ * addresses. Note that we add 4 to the node size because the node has a four
+ * bytes header. */
+#define raxPadding(nodesize) ((sizeof(void*)-((nodesize+4) % sizeof(void*))) & (sizeof(void*)-1))
+
+/* Return the pointer to the last child pointer in a node. For the compressed
+ * nodes this is the only child pointer. */
+#define raxNodeLastChildPtr(n) ((raxNode**) ( \
+ ((char*)(n)) + \
+ raxNodeCurrentLength(n) - \
+ sizeof(raxNode*) - \
+ (((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
+))
+
+/* Return the pointer to the first child pointer. */
+#define raxNodeFirstChildPtr(n) ((raxNode**) ( \
+ (n)->data + \
+ (n)->size + \
+ raxPadding((n)->size)))
+
+/* Return the current total size of the node. Note that the second line
+ * computes the padding after the string of characters, needed in order to
+ * save pointers to aligned addresses. */
+#define raxNodeCurrentLength(n) ( \
+ sizeof(raxNode)+(n)->size+ \
+ raxPadding((n)->size)+ \
+ ((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
+ (((n)->iskey && !(n)->isnull)*sizeof(void*)) \
+)
+
/* Allocate a new non compressed node with the specified number of children.
* If datafiled is true, the allocation is made large enough to hold the
* associated data pointer.
* Returns the new node pointer. On out of memory NULL is returned. */
raxNode *raxNewNode(size_t children, int datafield) {
- size_t nodesize = sizeof(raxNode)+children+
+ size_t nodesize = sizeof(raxNode)+children+raxPadding(children)+
sizeof(raxNode*)*children;
if (datafield) nodesize += sizeof(void*);
raxNode *node = rax_malloc(nodesize);
@@ -167,13 +212,6 @@ rax *raxNew(void) {
}
}
-/* Return the current total size of the node. */
-#define raxNodeCurrentLength(n) ( \
- sizeof(raxNode)+(n)->size+ \
- ((n)->iscompr ? sizeof(raxNode*) : sizeof(raxNode*)*(n)->size)+ \
- (((n)->iskey && !(n)->isnull)*sizeof(void*)) \
-)
-
/* realloc the node to make room for auxiliary data in order
* to store an item in that node. On out of memory NULL is returned. */
raxNode *raxReallocForData(raxNode *n, void *data) {
@@ -216,18 +254,17 @@ void *raxGetData(raxNode *n) {
raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode ***parentlink) {
assert(n->iscompr == 0);
- size_t curlen = sizeof(raxNode)+
- n->size+
- sizeof(raxNode*)*n->size;
- size_t newlen;
+ size_t curlen = raxNodeCurrentLength(n);
+ n->size++;
+ size_t newlen = raxNodeCurrentLength(n);
+ n->size--; /* For now restore the orignal size. We'll update it only on
+ success at the end. */
/* Alloc the new child we will link to 'n'. */
raxNode *child = raxNewNode(0,0);
if (child == NULL) return NULL;
/* Make space in the original node. */
- if (n->iskey) curlen += sizeof(void*);
- newlen = curlen+sizeof(raxNode*)+1; /* Add 1 char and 1 pointer. */
raxNode *newn = rax_realloc(n,newlen);
if (newn == NULL) {
rax_free(child);
@@ -235,14 +272,34 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
}
n = newn;
- /* After the reallocation, we have 5/9 (depending on the system
- * pointer size) bytes at the end, that is, the additional char
- * in the 'data' section, plus one pointer to the new child:
+ /* After the reallocation, we have up to 8/16 (depending on the system
+ * pointer size, and the required node padding) bytes at the end, that is,
+ * the additional char in the 'data' section, plus one pointer to the new
+ * child, plus the padding needed in order to store addresses into aligned
+ * locations.
+ *
+ * So if we start with the following node, having "abde" edges.
+ *
+ * Note:
+ * - We assume 4 bytes pointer for simplicity.
+ * - Each space below corresponds to one byte
+ *
+ * [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|
+ *
+ * After the reallocation we need: 1 byte for the new edge character
+ * plus 4 bytes for a new child pointer (assuming 32 bit machine).
+ * However after adding 1 byte to the edge char, the header + the edge
+ * characters are no longer aligned, so we also need 3 bytes of padding.
+ * In total the reallocation will add 1+4+3 bytes = 8 bytes:
*
- * [numc][abx][ap][bp][xp]|auxp|.....
+ * (Blank bytes are represented by ".")
+ *
+ * [HDR*][abde][Aptr][Bptr][Dptr][Eptr]|AUXP|[....][....]
*
* Let's find where to insert the new child in order to make sure
- * it is inserted in-place lexicographically. */
+ * it is inserted in-place lexicographically. Assuming we are adding
+ * a child "c" in our case pos will be = 2 after the end of the following
+ * loop. */
int pos;
for (pos = 0; pos < n->size; pos++) {
if (n->data[pos] > c) break;
@@ -252,55 +309,81 @@ raxNode *raxAddChild(raxNode *n, unsigned char c, raxNode **childptr, raxNode **
* so that we can mess with the other data without overwriting it.
* We will obtain something like that:
*
- * [numc][abx][ap][bp][xp].....|auxp| */
- unsigned char *src;
+ * [HDR*][abde][Aptr][Bptr][Dptr][Eptr][....][....]|AUXP|
+ */
+ unsigned char *src, *dst;
if (n->iskey && !n->isnull) {
- src = n->data+n->size+sizeof(raxNode*)*n->size;
- memmove(src+1+sizeof(raxNode*),src,sizeof(void*));
+ src = ((unsigned char*)n+curlen-sizeof(void*));
+ dst = ((unsigned char*)n+newlen-sizeof(void*));
+ memmove(dst,src,sizeof(void*));
}
- /* Now imagine we are adding a node with edge 'c'. The insertion
- * point is between 'b' and 'x', so the 'pos' variable value is
+ /* Compute the "shift", that is, how many bytes we need to move the
+ * pointers section forward because of the addition of the new child
+ * byte in the string section. Note that if we had no padding, that
+ * would be always "1", since we are adding a single byte in the string
+ * section of the node (where now there is "abde" basically).
+ *
+ * However we have padding, so it could be zero, or up to 8.
+ *
+ * Another way to think at the shift is, how many bytes we need to
+ * move child pointers forward *other than* the obvious sizeof(void*)
+ * needed for the additional pointer itself. */
+ size_t shift = newlen - curlen - sizeof(void*);
+
+ /* We said we are adding a node with edge 'c'. The insertion
+ * point is between 'b' and 'd', so the 'pos' variable value is
+ * the index of the first child pointer that we need to move forward
+ * to make space for our new pointer.
+ *
* To start, move all the child pointers after the insertion point
- * of 1+sizeof(pointer) bytes on the right, to obtain:
+ * of shift+sizeof(pointer) bytes on the right, to obtain:
*
- * [numc][abx][ap][bp].....[xp]|auxp| */
- src = n->data+n->size+sizeof(raxNode*)*pos;
- memmove(src+1+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
+ * [HDR*][abde][Aptr][Bptr][....][....][Dptr][Eptr]|AUXP|
+ */
+ src = n->data+n->size+
+ raxPadding(n->size)+
+ sizeof(raxNode*)*pos;
+ memmove(src+shift+sizeof(raxNode*),src,sizeof(raxNode*)*(n->size-pos));
+
+ /* Move the pointers to the left of the insertion position as well. Often
+ * we don't need to do anything if there was already some padding to use. In
+ * that case the final destination of the pointers will be the same, however
+ * in our example there was no pre-existing padding, so we added one byte
+ * plus thre bytes of padding. After the next memmove() things will look
+ * like thata:
+ *
+ * [HDR*][abde][....][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
+ */
+ if (shift) {
+ src = (unsigned char*) raxNodeFirstChildPtr(n);
+ memmove(src+shift,src,sizeof(raxNode*)*pos);
+ }
/* Now make the space for the additional char in the data section,
- * but also move the pointers before the insertion point in the right
- * by 1 byte, in order to obtain the following:
+ * but also move the pointers before the insertion point to the right
+ * by shift bytes, in order to obtain the following:
*
- * [numc][ab.x][ap][bp]....[xp]|auxp| */
+ * [HDR*][ab.d][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
+ */
src = n->data+pos;
- memmove(src+1,src,n->size-pos+sizeof(raxNode*)*pos);
+ memmove(src+1,src,n->size-pos);
/* We can now set the character and its child node pointer to get:
*
- * [numc][abcx][ap][bp][cp]....|auxp|
- * [numc][abcx][ap][bp][cp][xp]|auxp| */
+ * [HDR*][abcd][e...][Aptr][Bptr][....][Dptr][Eptr]|AUXP|
+ * [HDR*][abcd][e...][Aptr][Bptr][Cptr][Dptr][Eptr]|AUXP|
+ */
n->data[pos] = c;
n->size++;
- raxNode **childfield = (raxNode**)(n->data+n->size+sizeof(raxNode*)*pos);
+ src = (unsigned char*) raxNodeFirstChildPtr(n);
+ raxNode **childfield = (raxNode**)(src+sizeof(raxNode*)*pos);
memcpy(childfield,&child,sizeof(child));
*childptr = child;
*parentlink = childfield;
return n;
}
-/* Return the pointer to the last child pointer in a node. For the compressed
- * nodes this is the only child pointer. */
-#define raxNodeLastChildPtr(n) ((raxNode**) ( \
- ((char*)(n)) + \
- raxNodeCurrentLength(n) - \
- sizeof(raxNode*) - \
- (((n)->iskey && !(n)->isnull) ? sizeof(void*) : 0) \
-))
-
-/* Return the pointer to the first child pointer. */
-#define raxNodeFirstChildPtr(n) ((raxNode**)((n)->data+(n)->size))
-
/* Turn the node 'n', that must be a node without any children, into a
* compressed node representing a set of nodes linked one after the other
* and having exactly one child each. The node can be a key or not: this
@@ -321,7 +404,7 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi
if (*child == NULL) return NULL;
/* Make space in the parent node. */
- newsize = sizeof(raxNode)+len+sizeof(raxNode*);
+ newsize = sizeof(raxNode)+len+raxPadding(len)+sizeof(raxNode*);
if (n->iskey) {
data = raxGetData(n); /* To restore it later. */
if (!n->isnull) newsize += sizeof(void*);
@@ -359,7 +442,18 @@ raxNode *raxCompressNode(raxNode *n, unsigned char *s, size_t len, raxNode **chi
* parent's node is returned as '*plink' if not NULL. Finally, if the
* search stopped in a compressed node, '*splitpos' returns the index
* inside the compressed node where the search ended. This is useful to
- * know where to split the node for insertion. */
+ * know where to split the node for insertion.
+ *
+ * Note that when we stop in the middle of a compressed node with
+ * a perfect match, this function will return a length equal to the
+ * 'len' argument (all the key matched), and will return a *splitpos which is
+ * always positive (that will represent the index of the character immediately
+ * *after* the last match in the current compressed node).
+ *
+ * When instead we stop at a compressed node and *splitpos is zero, it
+ * means that the current node represents the key (that is, none of the
+ * compressed node characters are needed to represent the key, just all
+ * its parents nodes). */
static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode **stopnode, raxNode ***plink, int *splitpos, raxStack *ts) {
raxNode *h = rax->head;
raxNode **parentlink = &rax->head;
@@ -405,10 +499,12 @@ static inline size_t raxLowWalk(rax *rax, unsigned char *s, size_t len, raxNode
/* Insert the element 's' of size 'len', setting as auxiliary data
* the pointer 'data'. If the element is already present, the associated
- * data is updated, and 0 is returned, otherwise the element is inserted
- * and 1 is returned. On out of memory the function returns 0 as well but
- * sets errno to ENOMEM, otherwise errno will be set to 0. */
-int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
+ * data is updated (only if 'overwrite' is set to 1), and 0 is returned,
+ * otherwise the element is inserted and 1 is returned. On out of memory the
+ * function returns 0 as well but sets errno to ENOMEM, otherwise errno will
+ * be set to 0.
+ */
+int raxGenericInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old, int overwrite) {
size_t i;
int j = 0; /* Split position. If raxLowWalk() stops in a compressed
node, the index 'j' represents the char we stopped within the
@@ -426,7 +522,8 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
* data pointer. */
if (i == len && (!h->iscompr || j == 0 /* not in the middle if j is 0 */)) {
debugf("### Insert: node representing key exists\n");
- if (!h->iskey || h->isnull) {
+ /* Make space for the value pointer if needed. */
+ if (!h->iskey || (h->isnull && overwrite)) {
h = raxReallocForData(h,data);
if (h) memcpy(parentlink,&h,sizeof(h));
}
@@ -434,12 +531,17 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
errno = ENOMEM;
return 0;
}
+
+ /* Update the existing key if there is already one. */
if (h->iskey) {
if (old) *old = raxGetData(h);
- raxSetData(h,data);
+ if (overwrite) raxSetData(h,data);
errno = 0;
return 0; /* Element already exists. */
}
+
+ /* Otherwise set the node as a key. Note that raxSetData()
+ * will set h->iskey. */
raxSetData(h,data);
rax->numele++;
return 1; /* Element inserted. */
@@ -448,7 +550,7 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
/* If the node we stopped at is a compressed node, we need to
* split it before to continue.
*
- * Splitting a compressed node have a few possibile cases.
+ * Splitting a compressed node have a few possible cases.
* Imagine that the node 'h' we are currently at is a compressed
* node contaning the string "ANNIBALE" (it means that it represents
* nodes A -> N -> N -> I -> B -> A -> L -> E with the only child
@@ -600,13 +702,14 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
raxNode *postfix = NULL;
if (trimmedlen) {
- nodesize = sizeof(raxNode)+trimmedlen+sizeof(raxNode*);
+ nodesize = sizeof(raxNode)+trimmedlen+raxPadding(trimmedlen)+
+ sizeof(raxNode*);
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
trimmed = rax_malloc(nodesize);
}
if (postfixlen) {
- nodesize = sizeof(raxNode)+postfixlen+
+ nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
sizeof(raxNode*);
postfix = rax_malloc(nodesize);
}
@@ -682,11 +785,12 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
/* Allocate postfix & trimmed nodes ASAP to fail for OOM gracefully. */
size_t postfixlen = h->size - j;
- size_t nodesize = sizeof(raxNode)+postfixlen+sizeof(raxNode*);
+ size_t nodesize = sizeof(raxNode)+postfixlen+raxPadding(postfixlen)+
+ sizeof(raxNode*);
if (data != NULL) nodesize += sizeof(void*);
raxNode *postfix = rax_malloc(nodesize);
- nodesize = sizeof(raxNode)+j+sizeof(raxNode*);
+ nodesize = sizeof(raxNode)+j+raxPadding(j)+sizeof(raxNode*);
if (h->iskey && !h->isnull) nodesize += sizeof(void*);
raxNode *trimmed = rax_malloc(nodesize);
@@ -730,7 +834,7 @@ int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
cp = raxNodeLastChildPtr(trimmed);
memcpy(cp,&postfix,sizeof(postfix));
- /* Finish! We don't need to contine with the insertion
+ /* Finish! We don't need to continue with the insertion
* algorithm for ALGO 2. The key is already inserted. */
rax->numele++;
rax_free(h);
@@ -793,6 +897,19 @@ oom:
return 0;
}
+/* Overwriting insert. Just a wrapper for raxGenericInsert() that will
+ * update the element if there is already one for the same key. */
+int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
+ return raxGenericInsert(rax,s,len,data,old,1);
+}
+
+/* Non overwriting insert function: this if an element with the same key
+ * exists, the value is not updated and the function returns 0.
+ * This is a just a wrapper for raxGenericInsert(). */
+int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old) {
+ return raxGenericInsert(rax,s,len,data,old,0);
+}
+
/* Find a key in the rax, returns raxNotFound special void pointer value
* if the item was not found, otherwise the value associated with the
* item is returned. */
@@ -843,7 +960,7 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
return parent;
}
- /* Otherwise we need to scan for the children pointer and memmove()
+ /* Otherwise we need to scan for the child pointer and memmove()
* accordingly.
*
* 1. To start we seek the first element in both the children
@@ -868,13 +985,21 @@ raxNode *raxRemoveChild(raxNode *parent, raxNode *child) {
debugf("raxRemoveChild tail len: %d\n", taillen);
memmove(e,e+1,taillen);
- /* Since we have one data byte less, also child pointers start one byte
- * before now. */
- memmove(((char*)cp)-1,cp,(parent->size-taillen-1)*sizeof(raxNode**));
+ /* Compute the shift, that is the amount of bytes we should move our
+ * child pointers to the left, since the removal of one edge character
+ * and the corresponding padding change, may change the layout.
+ * We just check if in the old version of the node there was at the
+ * end just a single byte and all padding: in that case removing one char
+ * will remove a whole sizeof(void*) word. */
+ size_t shift = ((parent->size+4) % sizeof(void*)) == 1 ? sizeof(void*) : 0;
+
+ /* Move the children pointers before the deletion point. */
+ if (shift)
+ memmove(((char*)cp)-shift,cp,(parent->size-taillen-1)*sizeof(raxNode**));
- /* Move the remaining "tail" pointer at the right position as well. */
+ /* Move the remaining "tail" pointers at the right position as well. */
size_t valuelen = (parent->iskey && !parent->isnull) ? sizeof(void*) : 0;
- memmove(((char*)c)-1,c+1,taillen*sizeof(raxNode**)+valuelen);
+ memmove(((char*)c)-shift,c+1,taillen*sizeof(raxNode**)+valuelen);
/* 4. Update size. */
parent->size--;
@@ -1040,7 +1165,7 @@ int raxRemove(rax *rax, unsigned char *s, size_t len, void **old) {
if (nodes > 1) {
/* If we can compress, create the new node and populate it. */
size_t nodesize =
- sizeof(raxNode)+comprsize+sizeof(raxNode*);
+ sizeof(raxNode)+comprsize+raxPadding(comprsize)+sizeof(raxNode*);
raxNode *new = rax_malloc(nodesize);
/* An out of memory here just means we cannot optimize this
* node, but the tree is left in a consistent state. */
@@ -1135,6 +1260,7 @@ void raxStart(raxIterator *it, rax *rt) {
it->key = it->key_static_string;
it->key_max = RAX_ITER_STATIC_LEN;
it->data = NULL;
+ it->node_cb = NULL;
raxStackInit(&it->stack);
}
@@ -1208,6 +1334,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
if (!raxIteratorAddChars(it,it->node->data,
it->node->iscompr ? it->node->size : 1)) return 0;
memcpy(&it->node,cp,sizeof(it->node));
+ /* Call the node callback if any, and replace the node pointer
+ * if the callback returns true. */
+ if (it->node_cb && it->node_cb(&it->node))
+ memcpy(cp,&it->node,sizeof(it->node));
/* For "next" step, stop every time we find a key along the
* way, since the key is lexicograhically smaller compared to
* what follows in the sub-children. */
@@ -1260,6 +1390,10 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
raxIteratorAddChars(it,it->node->data+i,1);
if (!raxStackPush(&it->stack,it->node)) return 0;
memcpy(&it->node,cp,sizeof(it->node));
+ /* Call the node callback if any, and replace the node
+ * pointer if the callback returns true. */
+ if (it->node_cb && it->node_cb(&it->node))
+ memcpy(cp,&it->node,sizeof(it->node));
if (it->node->iskey) {
it->data = raxGetData(it->node);
return 1;
@@ -1272,7 +1406,7 @@ int raxIteratorNextStep(raxIterator *it, int noup) {
}
}
-/* Seek the grestest key in the subtree at the current node. Return 0 on
+/* Seek the greatest key in the subtree at the current node. Return 0 on
* out of memory, otherwise 1. This is an helper function for different
* iteration functions below. */
int raxSeekGreatest(raxIterator *it) {
@@ -1293,7 +1427,7 @@ int raxSeekGreatest(raxIterator *it) {
/* Like raxIteratorNextStep() but implements an iteration step moving
* to the lexicographically previous element. The 'noup' option has a similar
- * effect to the one of raxIteratorPrevSte(). */
+ * effect to the one of raxIteratorNextStep(). */
int raxIteratorPrevStep(raxIterator *it, int noup) {
if (it->flags & RAX_ITER_EOF) {
return 1;
@@ -1523,11 +1657,26 @@ int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len) {
/* If there was no mismatch we are into a node representing the
* key, (but which is not a key or the seek operator does not
* include 'eq'), or we stopped in the middle of a compressed node
- * after processing all the key. Cotinue iterating as this was
+ * after processing all the key. Continue iterating as this was
* a legitimate key we stopped at. */
it->flags &= ~RAX_ITER_JUST_SEEKED;
- if (gt && !raxIteratorNextStep(it,0)) return 0;
- if (lt && !raxIteratorPrevStep(it,0)) return 0;
+ if (it->node->iscompr && it->node->iskey && splitpos && lt) {
+ /* If we stopped in the middle of a compressed node with
+ * perfect match, and the condition is to seek a key "<" than
+ * the specified one, then if this node is a key it already
+ * represents our match. For instance we may have nodes:
+ *
+ * "f" -> "oobar" = 1 -> "" = 2
+ *
+ * Representing keys "f" = 1, "foobar" = 2. A seek for
+ * the key < "foo" will stop in the middle of the "oobar"
+ * node, but will be our match, representing the key "f".
+ *
+ * So in that case, we don't seek backward. */
+ } else {
+ if (gt && !raxIteratorNextStep(it,0)) return 0;
+ if (lt && !raxIteratorPrevStep(it,0)) return 0;
+ }
it->flags |= RAX_ITER_JUST_SEEKED; /* Ignore next call. */
}
} else {
@@ -1642,7 +1791,8 @@ int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key
if (eq && key_len == iter->key_len) return 1;
else if (lt) return iter->key_len < key_len;
else if (gt) return iter->key_len > key_len;
- } if (cmp > 0) {
+ return 0;
+ } else if (cmp > 0) {
return gt ? 1 : 0;
} else /* (cmp < 0) */ {
return lt ? 1 : 0;
@@ -1737,6 +1887,7 @@ void raxShow(rax *rax) {
/* Used by debugnode() macro to show info about a given node. */
void raxDebugShowNode(const char *msg, raxNode *n) {
+ if (raxDebugMsg == 0) return;
printf("%s: %p [%.*s] key:%d size:%d children:",
msg, (void*)n, (int)n->size, (char*)n->data, n->iskey, n->size);
int numcld = n->iscompr ? 1 : n->size;
@@ -1751,4 +1902,43 @@ void raxDebugShowNode(const char *msg, raxNode *n) {
fflush(stdout);
}
+/* Touch all the nodes of a tree returning a check sum. This is useful
+ * in order to make Valgrind detect if there is something wrong while
+ * reading the data structure.
+ *
+ * This function was used in order to identify Rax bugs after a big refactoring
+ * using this technique:
+ *
+ * 1. The rax-test is executed using Valgrind, adding a printf() so that for
+ * the fuzz tester we see what iteration in the loop we are in.
+ * 2. After every modification of the radix tree made by the fuzz tester
+ * in rax-test.c, we add a call to raxTouch().
+ * 3. Now as soon as an operation will corrupt the tree, raxTouch() will
+ * detect it (via Valgrind) immediately. We can add more calls to narrow
+ * the state.
+ * 4. At this point a good idea is to enable Rax debugging messages immediately
+ * before the moment the tree is corrupted, to see what happens.
+ */
+unsigned long raxTouch(raxNode *n) {
+ debugf("Touching %p\n", (void*)n);
+ unsigned long sum = 0;
+ if (n->iskey) {
+ sum += (unsigned long)raxGetData(n);
+ }
+ int numchildren = n->iscompr ? 1 : n->size;
+ raxNode **cp = raxNodeFirstChildPtr(n);
+ int count = 0;
+ for (int i = 0; i < numchildren; i++) {
+ if (numchildren > 1) {
+ sum += (long)n->data[i];
+ }
+ raxNode *child;
+ memcpy(&child,cp,sizeof(child));
+ if (child == (void*)0x65d1760) count++;
+ if (count > 1) exit(1);
+ sum += raxTouch(child);
+ cp++;
+ }
+ return sum;
+}
diff --git a/src/rax.h b/src/rax.h
index b4e2fd91e..f2521d14a 100644
--- a/src/rax.h
+++ b/src/rax.h
@@ -1,3 +1,33 @@
+/* Rax -- A radix tree implementation.
+ *
+ * Copyright (c) 2017-2018, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
#ifndef RAX_H
#define RAX_H
@@ -77,16 +107,16 @@ typedef struct raxNode {
* Note how the character is not stored in the children but in the
* edge of the parents:
*
- * [header strlen=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
+ * [header iscompr=0][abc][a-ptr][b-ptr][c-ptr](value-ptr?)
*
- * if node is compressed (strlen != 0) the node has 1 children.
+ * if node is compressed (iscompr bit is 1) the node has 1 children.
* In that case the 'size' bytes of the string stored immediately at
* the start of the data section, represent a sequence of successive
* nodes linked one after the other, for which only the last one in
* the sequence is actually represented as a node, and pointed to by
* the current compressed node.
*
- * [header strlen=3][xyz][z-ptr](value-ptr?)
+ * [header iscompr=1][xyz][z-ptr](value-ptr?)
*
* Both compressed and not compressed nodes can represent a key
* with associated data in the radix tree at any level (not just terminal
@@ -94,7 +124,7 @@ typedef struct raxNode {
*
* If the node has an associated key (iskey=1) and is not NULL
* (isnull=0), then after the raxNode pointers poiting to the
- * childen, an additional value pointer is present (as you can see
+ * children, an additional value pointer is present (as you can see
* in the representation above as "value-ptr" field).
*/
unsigned char data[];
@@ -119,6 +149,21 @@ typedef struct raxStack {
int oom; /* True if pushing into this stack failed for OOM at some point. */
} raxStack;
+/* Optional callback used for iterators and be notified on each rax node,
+ * including nodes not representing keys. If the callback returns true
+ * the callback changed the node pointer in the iterator structure, and the
+ * iterator implementation will have to replace the pointer in the radix tree
+ * internals. This allows the callback to reallocate the node to perform
+ * very special operations, normally not needed by normal applications.
+ *
+ * This callback is used to perform very low level analysis of the radix tree
+ * structure, scanning each possible node (but the root node), or in order to
+ * reallocate the nodes to reduce the allocation fragmentation (this is the
+ * Redis application for this callback).
+ *
+ * This is currently only supported in forward iterations (raxNext) */
+typedef int (*raxNodeCallback)(raxNode **noderef);
+
/* Radix tree iterator state is encapsulated into this data structure. */
#define RAX_ITER_STATIC_LEN 128
#define RAX_ITER_JUST_SEEKED (1<<0) /* Iterator was just seeked. Return current
@@ -137,6 +182,7 @@ typedef struct raxIterator {
unsigned char key_static_string[RAX_ITER_STATIC_LEN];
raxNode *node; /* Current node. Only for unsafe iteration. */
raxStack stack; /* Stack used for unsafe iteration. */
+ raxNodeCallback node_cb; /* Optional node callback. Normally set to NULL. */
} raxIterator;
/* A special pointer returned for not found items. */
@@ -145,6 +191,7 @@ extern void *raxNotFound;
/* Exported API. */
rax *raxNew(void);
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
+int raxTryInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
void *raxFind(rax *rax, unsigned char *s, size_t len);
void raxFree(rax *rax);
@@ -159,5 +206,11 @@ void raxStop(raxIterator *it);
int raxEOF(raxIterator *it);
void raxShow(rax *rax);
uint64_t raxSize(rax *rax);
+unsigned long raxTouch(raxNode *n);
+void raxSetDebugMsg(int onoff);
+
+/* Internal API. May be used by the node callback in order to access rax nodes
+ * in a low level way, so this function is exported as well. */
+void raxSetData(raxNode *n, void *data);
#endif
diff --git a/src/rdb.c b/src/rdb.c
index 35448b624..c92b9f4a1 100644
--- a/src/rdb.c
+++ b/src/rdb.c
@@ -42,30 +42,41 @@
#include <sys/stat.h>
#include <sys/param.h>
-#define rdbExitReportCorruptRDB(...) rdbCheckThenExit(__LINE__,__VA_ARGS__)
+/* This macro is called when the internal RDB stracture is corrupt */
+#define rdbExitReportCorruptRDB(...) rdbReportError(1, __LINE__,__VA_ARGS__)
+/* This macro is called when RDB read failed (possibly a short read) */
+#define rdbReportReadError(...) rdbReportError(0, __LINE__,__VA_ARGS__)
+char* rdbFileBeingLoaded = NULL; /* used for rdb checking on read error */
extern int rdbCheckMode;
void rdbCheckError(const char *fmt, ...);
void rdbCheckSetError(const char *fmt, ...);
-void rdbCheckThenExit(int linenum, char *reason, ...) {
+void rdbReportError(int corruption_error, int linenum, char *reason, ...) {
va_list ap;
char msg[1024];
int len;
len = snprintf(msg,sizeof(msg),
- "Internal error in RDB reading function at rdb.c:%d -> ", linenum);
+ "Internal error in RDB reading offset %llu, function at rdb.c:%d -> ",
+ (unsigned long long)server.loading_loaded_bytes, linenum);
va_start(ap,reason);
vsnprintf(msg+len,sizeof(msg)-len,reason,ap);
va_end(ap);
if (!rdbCheckMode) {
- serverLog(LL_WARNING, "%s", msg);
- char *argv[2] = {"",server.rdb_filename};
- redis_check_rdb_main(2,argv,NULL);
+ if (rdbFileBeingLoaded || corruption_error) {
+ serverLog(LL_WARNING, "%s", msg);
+ char *argv[2] = {"",rdbFileBeingLoaded};
+ redis_check_rdb_main(2,argv,NULL);
+ } else {
+ serverLog(LL_WARNING, "%s. Failure loading rdb format from socket, assuming connection error, resuming operation.", msg);
+ return;
+ }
} else {
rdbCheckError("%s",msg);
}
+ serverLog(LL_WARNING, "Terminating server after rdb file reading failure.");
exit(1);
}
@@ -88,6 +99,11 @@ int rdbLoadType(rio *rdb) {
return type;
}
+/* This is only used to load old databases stored with the RDB_OPCODE_EXPIRETIME
+ * opcode. New versions of Redis store using the RDB_OPCODE_EXPIRETIME_MS
+ * opcode. On error -1 is returned, however this could be a valid time, so
+ * to check for loading errors the caller should call rioGetReadError() after
+ * calling this function. */
time_t rdbLoadTime(rio *rdb) {
int32_t t32;
if (rioRead(rdb,&t32,4) == 0) return -1;
@@ -96,12 +112,30 @@ time_t rdbLoadTime(rio *rdb) {
int rdbSaveMillisecondTime(rio *rdb, long long t) {
int64_t t64 = (int64_t) t;
+ memrev64ifbe(&t64); /* Store in little endian. */
return rdbWriteRaw(rdb,&t64,8);
}
-long long rdbLoadMillisecondTime(rio *rdb) {
+/* This function loads a time from the RDB file. It gets the version of the
+ * RDB because, unfortunately, before Redis 5 (RDB version 9), the function
+ * failed to convert data to/from little endian, so RDB files with keys having
+ * expires could not be shared between big endian and little endian systems
+ * (because the expire time will be totally wrong). The fix for this is just
+ * to call memrev64ifbe(), however if we fix this for all the RDB versions,
+ * this call will introduce an incompatibility for big endian systems:
+ * after upgrading to Redis version 5 they will no longer be able to load their
+ * own old RDB files. Because of that, we instead fix the function only for new
+ * RDB versions, and load older RDB versions as we used to do in the past,
+ * allowing big endian systems to load their own old RDB files.
+ *
+ * On I/O error the function returns LLONG_MAX, however if this is also a
+ * valid stored value, the caller should use rioGetReadError() to check for
+ * errors after calling this function. */
+long long rdbLoadMillisecondTime(rio *rdb, int rdbver) {
int64_t t64;
- if (rioRead(rdb,&t64,8) == 0) return -1;
+ if (rioRead(rdb,&t64,8) == 0) return LLONG_MAX;
+ if (rdbver >= 9) /* Check the top comment of this function. */
+ memrev64ifbe(&t64); /* Convert in big endian if the system is BE. */
return (long long)t64;
}
@@ -226,7 +260,7 @@ int rdbEncodeInteger(long long value, unsigned char *enc) {
/* Loads an integer-encoded object with the specified encoding type "enctype".
* The returned value changes according to the flags, see
- * rdbGenerincLoadStringObject() for more info. */
+ * rdbGenericLoadStringObject() for more info. */
void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
int plain = flags & RDB_LOAD_PLAIN;
int sds = flags & RDB_LOAD_SDS;
@@ -248,8 +282,8 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
v = enc[0]|(enc[1]<<8)|(enc[2]<<16)|(enc[3]<<24);
val = (int32_t)v;
} else {
- val = 0; /* anti-warning */
rdbExitReportCorruptRDB("Unknown RDB integer encoding type %d",enctype);
+ return NULL; /* Never reached. */
}
if (plain || sds) {
char buf[LONG_STR_SIZE], *p;
@@ -259,7 +293,7 @@ void *rdbLoadIntegerObject(rio *rdb, int enctype, int flags, size_t *lenptr) {
memcpy(p,buf,len);
return p;
} else if (encode) {
- return createStringObjectFromLongLong(val);
+ return createStringObjectFromLongLongForValue(val);
} else {
return createObject(OBJ_STRING,sdsfromlonglong(val));
}
@@ -352,8 +386,7 @@ void *rdbLoadLzfStringObject(rio *rdb, int flags, size_t *lenptr) {
/* Load the compressed representation and uncompress it to target. */
if (rioRead(rdb,c,clen) == 0) goto err;
if (lzf_decompress(c,clen,val,len) == 0) {
- if (rdbCheckMode) rdbCheckSetError("Invalid LZF compressed string");
- goto err;
+ rdbExitReportCorruptRDB("Invalid LZF compressed string");
}
zfree(c);
@@ -467,6 +500,7 @@ void *rdbGenericLoadStringObject(rio *rdb, int flags, size_t *lenptr) {
return rdbLoadLzfStringObject(rdb,flags,lenptr);
default:
rdbExitReportCorruptRDB("Unknown RDB string encoding type %d",len);
+ return NULL; /* Never reached. */
}
}
@@ -642,8 +676,87 @@ int rdbLoadObjectType(rio *rdb) {
return type;
}
-/* Save a Redis object. Returns -1 on error, number of bytes written on success. */
-ssize_t rdbSaveObject(rio *rdb, robj *o) {
+/* This helper function serializes a consumer group Pending Entries List (PEL)
+ * into the RDB file. The 'nacks' argument tells the function if also persist
+ * the informations about the not acknowledged message, or if to persist
+ * just the IDs: this is useful because for the global consumer group PEL
+ * we serialized the NACKs as well, but when serializing the local consumer
+ * PELs we just add the ID, that will be resolved inside the global PEL to
+ * put a reference to the same structure. */
+ssize_t rdbSaveStreamPEL(rio *rdb, rax *pel, int nacks) {
+ ssize_t n, nwritten = 0;
+
+ /* Number of entries in the PEL. */
+ if ((n = rdbSaveLen(rdb,raxSize(pel))) == -1) return -1;
+ nwritten += n;
+
+ /* Save each entry. */
+ raxIterator ri;
+ raxStart(&ri,pel);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ /* We store IDs in raw form as 128 big big endian numbers, like
+ * they are inside the radix tree key. */
+ if ((n = rdbWriteRaw(rdb,ri.key,sizeof(streamID))) == -1) return -1;
+ nwritten += n;
+
+ if (nacks) {
+ streamNACK *nack = ri.data;
+ if ((n = rdbSaveMillisecondTime(rdb,nack->delivery_time)) == -1)
+ return -1;
+ nwritten += n;
+ if ((n = rdbSaveLen(rdb,nack->delivery_count)) == -1) return -1;
+ nwritten += n;
+ /* We don't save the consumer name: we'll save the pending IDs
+ * for each consumer in the consumer PEL, and resolve the consumer
+ * at loading time. */
+ }
+ }
+ raxStop(&ri);
+ return nwritten;
+}
+
+/* Serialize the consumers of a stream consumer group into the RDB. Helper
+ * function for the stream data type serialization. What we do here is to
+ * persist the consumer metadata, and it's PEL, for each consumer. */
+size_t rdbSaveStreamConsumers(rio *rdb, streamCG *cg) {
+ ssize_t n, nwritten = 0;
+
+ /* Number of consumers in this consumer group. */
+ if ((n = rdbSaveLen(rdb,raxSize(cg->consumers))) == -1) return -1;
+ nwritten += n;
+
+ /* Save each consumer. */
+ raxIterator ri;
+ raxStart(&ri,cg->consumers);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamConsumer *consumer = ri.data;
+
+ /* Consumer name. */
+ if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1) return -1;
+ nwritten += n;
+
+ /* Last seen time. */
+ if ((n = rdbSaveMillisecondTime(rdb,consumer->seen_time)) == -1)
+ return -1;
+ nwritten += n;
+
+ /* Consumer PEL, without the ACKs (see last parameter of the function
+ * passed with value of 0), at loading time we'll lookup the ID
+ * in the consumer group global PEL and will put a reference in the
+ * consumer local PEL. */
+ if ((n = rdbSaveStreamPEL(rdb,consumer->pel,0)) == -1)
+ return -1;
+ nwritten += n;
+ }
+ raxStop(&ri);
+ return nwritten;
+}
+
+/* Save a Redis object.
+ * Returns -1 on error, number of bytes written on success. */
+ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key) {
ssize_t n = 0, nwritten = 0;
if (o->type == OBJ_STRING) {
@@ -681,13 +794,20 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
dictIterator *di = dictGetIterator(set);
dictEntry *de;
- if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) return -1;
+ if ((n = rdbSaveLen(rdb,dictSize(set))) == -1) {
+ dictReleaseIterator(di);
+ return -1;
+ }
nwritten += n;
while((de = dictNext(di)) != NULL) {
sds ele = dictGetKey(de);
if ((n = rdbSaveRawString(rdb,(unsigned char*)ele,sdslen(ele)))
- == -1) return -1;
+ == -1)
+ {
+ dictReleaseIterator(di);
+ return -1;
+ }
nwritten += n;
}
dictReleaseIterator(di);
@@ -747,7 +867,10 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
dictIterator *di = dictGetIterator(o->ptr);
dictEntry *de;
- if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) return -1;
+ if ((n = rdbSaveLen(rdb,dictSize((dict*)o->ptr))) == -1) {
+ dictReleaseIterator(di);
+ return -1;
+ }
nwritten += n;
while((de = dictNext(di)) != NULL) {
@@ -755,10 +878,18 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
sds value = dictGetVal(de);
if ((n = rdbSaveRawString(rdb,(unsigned char*)field,
- sdslen(field))) == -1) return -1;
+ sdslen(field))) == -1)
+ {
+ dictReleaseIterator(di);
+ return -1;
+ }
nwritten += n;
if ((n = rdbSaveRawString(rdb,(unsigned char*)value,
- sdslen(value))) == -1) return -1;
+ sdslen(value))) == -1)
+ {
+ dictReleaseIterator(di);
+ return -1;
+ }
nwritten += n;
}
dictReleaseIterator(di);
@@ -798,12 +929,48 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
nwritten += n;
if ((n = rdbSaveLen(rdb,s->last_id.seq)) == -1) return -1;
nwritten += n;
+
+ /* The consumer groups and their clients are part of the stream
+ * type, so serialize every consumer group. */
+
+ /* Save the number of groups. */
+ size_t num_cgroups = s->cgroups ? raxSize(s->cgroups) : 0;
+ if ((n = rdbSaveLen(rdb,num_cgroups)) == -1) return -1;
+ nwritten += n;
+
+ if (num_cgroups) {
+ /* Serialize each consumer group. */
+ raxStart(&ri,s->cgroups);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamCG *cg = ri.data;
+
+ /* Save the group name. */
+ if ((n = rdbSaveRawString(rdb,ri.key,ri.key_len)) == -1)
+ return -1;
+ nwritten += n;
+
+ /* Last ID. */
+ if ((n = rdbSaveLen(rdb,cg->last_id.ms)) == -1) return -1;
+ nwritten += n;
+ if ((n = rdbSaveLen(rdb,cg->last_id.seq)) == -1) return -1;
+ nwritten += n;
+
+ /* Save the global PEL. */
+ if ((n = rdbSaveStreamPEL(rdb,cg->pel,1)) == -1) return -1;
+ nwritten += n;
+
+ /* Save the consumers of this group. */
+ if ((n = rdbSaveStreamConsumers(rdb,cg)) == -1) return -1;
+ nwritten += n;
+ }
+ raxStop(&ri);
+ }
} else if (o->type == OBJ_MODULE) {
/* Save a module-specific value. */
RedisModuleIO io;
moduleValue *mv = o->ptr;
moduleType *mt = mv->type;
- moduleInitIOContext(io,mt,rdb);
/* Write the "module" identifier as prefix, so that we'll be able
* to call the right module during loading. */
@@ -812,10 +979,13 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
io.bytes += retval;
/* Then write the module-specific representation + EOF marker. */
+ moduleInitIOContext(io,mt,rdb,key);
mt->rdb_save(&io,mv->value);
retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
- if (retval == -1) return -1;
- io.bytes += retval;
+ if (retval == -1)
+ io.error = 1;
+ else
+ io.bytes += retval;
if (io.ctx) {
moduleFreeContext(io.ctx);
@@ -833,7 +1003,7 @@ ssize_t rdbSaveObject(rio *rdb, robj *o) {
* this length with very little changes to the code. In the future
* we could switch to a faster solution. */
size_t rdbSavedObjectLen(robj *o) {
- ssize_t len = rdbSaveObject(NULL,o);
+ ssize_t len = rdbSaveObject(NULL,o,NULL);
serverAssertWithInfo(NULL,o,len != -1);
return len;
}
@@ -842,21 +1012,45 @@ size_t rdbSavedObjectLen(robj *o) {
* On error -1 is returned.
* On success if the key was actually saved 1 is returned, otherwise 0
* is returned (the key was already expired). */
-int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val,
- long long expiretime, long long now)
-{
+int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime) {
+ int savelru = server.maxmemory_policy & MAXMEMORY_FLAG_LRU;
+ int savelfu = server.maxmemory_policy & MAXMEMORY_FLAG_LFU;
+
/* Save the expire time */
if (expiretime != -1) {
- /* If this key is already expired skip it */
- if (expiretime < now) return 0;
if (rdbSaveType(rdb,RDB_OPCODE_EXPIRETIME_MS) == -1) return -1;
if (rdbSaveMillisecondTime(rdb,expiretime) == -1) return -1;
}
+ /* Save the LRU info. */
+ if (savelru) {
+ uint64_t idletime = estimateObjectIdleTime(val);
+ idletime /= 1000; /* Using seconds is enough and requires less space.*/
+ if (rdbSaveType(rdb,RDB_OPCODE_IDLE) == -1) return -1;
+ if (rdbSaveLen(rdb,idletime) == -1) return -1;
+ }
+
+ /* Save the LFU info. */
+ if (savelfu) {
+ uint8_t buf[1];
+ buf[0] = LFUDecrAndReturn(val);
+ /* We can encode this in exactly two bytes: the opcode and an 8
+ * bit counter, since the frequency is logarithmic with a 0-255 range.
+ * Note that we do not store the halving time because to reset it
+ * a single time when loading does not affect the frequency much. */
+ if (rdbSaveType(rdb,RDB_OPCODE_FREQ) == -1) return -1;
+ if (rdbWriteRaw(rdb,buf,1) == -1) return -1;
+ }
+
/* Save type, key, value */
if (rdbSaveObjectType(rdb,val) == -1) return -1;
if (rdbSaveStringObject(rdb,key) == -1) return -1;
- if (rdbSaveObject(rdb,val) == -1) return -1;
+ if (rdbSaveObject(rdb,val,key) == -1) return -1;
+
+ /* Delay return if required (for testing) */
+ if (server.rdb_key_save_delay)
+ usleep(server.rdb_key_save_delay);
+
return 1;
}
@@ -909,6 +1103,45 @@ int rdbSaveInfoAuxFields(rio *rdb, int flags, rdbSaveInfo *rsi) {
return 1;
}
+ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt) {
+ /* Save a module-specific aux value. */
+ RedisModuleIO io;
+ int retval = rdbSaveType(rdb, RDB_OPCODE_MODULE_AUX);
+
+ /* Write the "module" identifier as prefix, so that we'll be able
+ * to call the right module during loading. */
+ retval = rdbSaveLen(rdb,mt->id);
+ if (retval == -1) return -1;
+ io.bytes += retval;
+
+ /* write the 'when' so that we can provide it on loading. add a UINT opcode
+ * for backwards compatibility, everything after the MT needs to be prefixed
+ * by an opcode. */
+ retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_UINT);
+ if (retval == -1) return -1;
+ io.bytes += retval;
+ retval = rdbSaveLen(rdb,when);
+ if (retval == -1) return -1;
+ io.bytes += retval;
+
+ /* Then write the module-specific representation + EOF marker. */
+ moduleInitIOContext(io,mt,rdb,NULL);
+ mt->aux_save(&io,when);
+ retval = rdbSaveLen(rdb,RDB_MODULE_OPCODE_EOF);
+ if (retval == -1)
+ io.error = 1;
+ else
+ io.bytes += retval;
+
+ if (io.ctx) {
+ moduleFreeContext(io.ctx);
+ zfree(io.ctx);
+ }
+ if (io.error)
+ return -1;
+ return io.bytes;
+}
+
/* Produces a dump of the database in RDB format sending it to the specified
* Redis I/O channel. On success C_OK is returned, otherwise C_ERR
* is returned and part of the output, or all the output, can be
@@ -922,7 +1155,6 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
dictEntry *de;
char magic[10];
int j;
- long long now = mstime();
uint64_t cksum;
size_t processed = 0;
@@ -931,13 +1163,13 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
snprintf(magic,sizeof(magic),"REDIS%04d",RDB_VERSION);
if (rdbWriteRaw(rdb,magic,9) == -1) goto werr;
if (rdbSaveInfoAuxFields(rdb,flags,rsi) == -1) goto werr;
+ if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_BEFORE_RDB) == -1) goto werr;
for (j = 0; j < server.dbnum; j++) {
redisDb *db = server.db+j;
dict *d = db->dict;
if (dictSize(d) == 0) continue;
di = dictGetSafeIterator(d);
- if (!di) return C_ERR;
/* Write the SELECT DB opcode */
if (rdbSaveType(rdb,RDB_OPCODE_SELECTDB) == -1) goto werr;
@@ -947,13 +1179,9 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
* is currently the largest type we are able to represent in RDB sizes.
* However this does not limit the actual size of the DB to load since
* these sizes are just hints to resize the hash tables. */
- uint32_t db_size, expires_size;
- db_size = (dictSize(db->dict) <= UINT32_MAX) ?
- dictSize(db->dict) :
- UINT32_MAX;
- expires_size = (dictSize(db->expires) <= UINT32_MAX) ?
- dictSize(db->expires) :
- UINT32_MAX;
+ uint64_t db_size, expires_size;
+ db_size = dictSize(db->dict);
+ expires_size = dictSize(db->expires);
if (rdbSaveType(rdb,RDB_OPCODE_RESIZEDB) == -1) goto werr;
if (rdbSaveLen(rdb,db_size) == -1) goto werr;
if (rdbSaveLen(rdb,expires_size) == -1) goto werr;
@@ -966,7 +1194,7 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
initStaticStringObject(key,keystr);
expire = getExpire(db,&key);
- if (rdbSaveKeyValuePair(rdb,&key,o,expire,now) == -1) goto werr;
+ if (rdbSaveKeyValuePair(rdb,&key,o,expire) == -1) goto werr;
/* When this RDB is produced as part of an AOF rewrite, move
* accumulated diff from parent to child while rewriting in
@@ -979,8 +1207,8 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
}
}
dictReleaseIterator(di);
+ di = NULL; /* So that we don't release it again on error. */
}
- di = NULL; /* So that we don't release it again on error. */
/* If we are storing the replication information on disk, persist
* the script cache as well: on successful PSYNC after a restart, we need
@@ -994,8 +1222,11 @@ int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi) {
goto werr;
}
dictReleaseIterator(di);
+ di = NULL; /* So that we don't release it again on error. */
}
+ if (rdbSaveModulesAux(rdb, REDISMODULE_AUX_AFTER_RDB) == -1) goto werr;
+
/* EOF opcode */
if (rdbSaveType(rdb,RDB_OPCODE_EOF) == -1) goto werr;
@@ -1060,6 +1291,10 @@ int rdbSave(char *filename, rdbSaveInfo *rsi) {
}
rioInitWithFile(&rdb,fp);
+
+ if (server.rdb_save_incremental_fsync)
+ rioSetAutoSync(&rdb,REDIS_AUTOSYNC_BYTES);
+
if (rdbSaveRio(&rdb,&error,RDB_SAVE_NONE,rsi) == C_ERR) {
errno = error;
goto werr;
@@ -1100,40 +1335,25 @@ werr:
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
pid_t childpid;
- long long start;
- if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
+ if (hasActiveChildProcess()) return C_ERR;
server.dirty_before_bgsave = server.dirty;
server.lastbgsave_try = time(NULL);
openChildInfoPipe();
- start = ustime();
- if ((childpid = fork()) == 0) {
+ if ((childpid = redisFork()) == 0) {
int retval;
/* Child */
- closeListeningSockets(0);
redisSetProcTitle("redis-rdb-bgsave");
retval = rdbSave(filename,rsi);
if (retval == C_OK) {
- size_t private_dirty = zmalloc_get_private_dirty(-1);
-
- if (private_dirty) {
- serverLog(LL_NOTICE,
- "RDB: %zu MB of memory used by copy-on-write",
- private_dirty/(1024*1024));
- }
-
- server.child_info_data.cow_size = private_dirty;
- sendChildInfo(CHILD_INFO_TYPE_RDB);
+ sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
}
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
- server.stat_fork_time = ustime()-start;
- server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
- latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
if (childpid == -1) {
closeChildInfoPipe();
server.lastbgsave_status = C_ERR;
@@ -1145,7 +1365,6 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
server.rdb_child_type = RDB_CHILD_TYPE_DISK;
- updateDictResizePolicy();
return C_OK;
}
return C_OK; /* unreached */
@@ -1199,7 +1418,7 @@ robj *rdbLoadCheckModuleValue(rio *rdb, char *modulename) {
/* Load a Redis object of the specified type from the specified file.
* On success a newly allocated object is returned, otherwise NULL. */
-robj *rdbLoadObject(int rdbtype, rio *rdb) {
+robj *rdbLoadObject(int rdbtype, rio *rdb, robj *key) {
robj *o = NULL, *ele, *dec;
uint64_t len;
unsigned int i;
@@ -1276,6 +1495,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
o = createZsetObject();
zs = o->ptr;
+ if (zsetlen > DICT_HT_INITIAL_SIZE)
+ dictExpand(zs->dict,zsetlen);
+
/* Load every single element of the sorted set. */
while(zsetlen--) {
sds sdsele;
@@ -1344,6 +1566,9 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
sdsfree(value);
}
+ if (o->encoding == OBJ_ENCODING_HT && len > DICT_HT_INITIAL_SIZE)
+ dictExpand(o->ptr,len);
+
/* Load remaining fields and values into the hash table */
while (o->encoding == OBJ_ENCODING_HT && len > 0) {
len--;
@@ -1445,6 +1670,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
hashTypeConvert(o, OBJ_ENCODING_HT);
break;
default:
+ /* totally unreachable */
rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
break;
}
@@ -1452,12 +1678,22 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
o = createStreamObject();
stream *s = o->ptr;
uint64_t listpacks = rdbLoadLen(rdb,NULL);
+ if (listpacks == RDB_LENERR) {
+ rdbReportReadError("Stream listpacks len loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
while(listpacks--) {
/* Get the master ID, the one we'll use as key of the radix tree
* node: the entries inside the listpack itself are delta-encoded
* relatively to this ID. */
sds nodekey = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
+ if (nodekey == NULL) {
+ rdbReportReadError("Stream master ID loading failed: invalid encoding or I/O error.");
+ decrRefCount(o);
+ return NULL;
+ }
if (sdslen(nodekey) != sizeof(streamID)) {
rdbExitReportCorruptRDB("Stream node key entry is not the "
"size of a stream ID");
@@ -1466,12 +1702,17 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Load the listpack. */
unsigned char *lp =
rdbGenericLoadStringObject(rdb,RDB_LOAD_PLAIN,NULL);
- if (lp == NULL) return NULL;
+ if (lp == NULL) {
+ rdbReportReadError("Stream listpacks loading failed.");
+ sdsfree(nodekey);
+ decrRefCount(o);
+ return NULL;
+ }
unsigned char *first = lpFirst(lp);
if (first == NULL) {
/* Serialized listpacks should never be empty, since on
* deletion we should remove the radix tree key if the
- * resulting listpack is emtpy. */
+ * resulting listpack is empty. */
rdbExitReportCorruptRDB("Empty listpack inside stream");
}
@@ -1484,16 +1725,153 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
}
/* Load total number of items inside the stream. */
s->length = rdbLoadLen(rdb,NULL);
+
/* Load the last entry ID. */
s->last_id.ms = rdbLoadLen(rdb,NULL);
s->last_id.seq = rdbLoadLen(rdb,NULL);
+
+ if (rioGetReadError(rdb)) {
+ rdbReportReadError("Stream object metadata loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+
+ /* Consumer groups loading */
+ uint64_t cgroups_count = rdbLoadLen(rdb,NULL);
+ if (cgroups_count == RDB_LENERR) {
+ rdbReportReadError("Stream cgroup count loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+ while(cgroups_count--) {
+ /* Get the consumer group name and ID. We can then create the
+ * consumer group ASAP and populate its structure as
+ * we read more data. */
+ streamID cg_id;
+ sds cgname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
+ if (cgname == NULL) {
+ rdbReportReadError(
+ "Error reading the consumer group name from Stream");
+ decrRefCount(o);
+ return NULL;
+ }
+
+ cg_id.ms = rdbLoadLen(rdb,NULL);
+ cg_id.seq = rdbLoadLen(rdb,NULL);
+ if (rioGetReadError(rdb)) {
+ rdbReportReadError("Stream cgroup ID loading failed.");
+ sdsfree(cgname);
+ decrRefCount(o);
+ return NULL;
+ }
+
+ streamCG *cgroup = streamCreateCG(s,cgname,sdslen(cgname),&cg_id);
+ if (cgroup == NULL)
+ rdbExitReportCorruptRDB("Duplicated consumer group name %s",
+ cgname);
+ sdsfree(cgname);
+
+ /* Load the global PEL for this consumer group, however we'll
+ * not yet populate the NACK structures with the message
+ * owner, since consumers for this group and their messages will
+ * be read as a next step. So for now leave them not resolved
+ * and later populate it. */
+ uint64_t pel_size = rdbLoadLen(rdb,NULL);
+ if (pel_size == RDB_LENERR) {
+ rdbReportReadError("Stream PEL size loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+ while(pel_size--) {
+ unsigned char rawid[sizeof(streamID)];
+ if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
+ rdbReportReadError("Stream PEL ID loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+ streamNACK *nack = streamCreateNACK(NULL);
+ nack->delivery_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
+ nack->delivery_count = rdbLoadLen(rdb,NULL);
+ if (rioGetReadError(rdb)) {
+ rdbReportReadError("Stream PEL NACK loading failed.");
+ decrRefCount(o);
+ streamFreeNACK(nack);
+ return NULL;
+ }
+ if (!raxInsert(cgroup->pel,rawid,sizeof(rawid),nack,NULL))
+ rdbExitReportCorruptRDB("Duplicated gobal PEL entry "
+ "loading stream consumer group");
+ }
+
+ /* Now that we loaded our global PEL, we need to load the
+ * consumers and their local PELs. */
+ uint64_t consumers_num = rdbLoadLen(rdb,NULL);
+ if (consumers_num == RDB_LENERR) {
+ rdbReportReadError("Stream consumers num loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+ while(consumers_num--) {
+ sds cname = rdbGenericLoadStringObject(rdb,RDB_LOAD_SDS,NULL);
+ if (cname == NULL) {
+ rdbReportReadError(
+ "Error reading the consumer name from Stream group.");
+ decrRefCount(o);
+ return NULL;
+ }
+ streamConsumer *consumer = streamLookupConsumer(cgroup,cname,
+ 1);
+ sdsfree(cname);
+ consumer->seen_time = rdbLoadMillisecondTime(rdb,RDB_VERSION);
+ if (rioGetReadError(rdb)) {
+ rdbReportReadError("Stream short read reading seen time.");
+ decrRefCount(o);
+ return NULL;
+ }
+
+ /* Load the PEL about entries owned by this specific
+ * consumer. */
+ pel_size = rdbLoadLen(rdb,NULL);
+ if (pel_size == RDB_LENERR) {
+ rdbReportReadError(
+ "Stream consumer PEL num loading failed.");
+ decrRefCount(o);
+ return NULL;
+ }
+ while(pel_size--) {
+ unsigned char rawid[sizeof(streamID)];
+ if (rioRead(rdb,rawid,sizeof(rawid)) == 0) {
+ rdbReportReadError(
+ "Stream short read reading PEL streamID.");
+ decrRefCount(o);
+ return NULL;
+ }
+ streamNACK *nack = raxFind(cgroup->pel,rawid,sizeof(rawid));
+ if (nack == raxNotFound)
+ rdbExitReportCorruptRDB("Consumer entry not found in "
+ "group global PEL");
+
+ /* Set the NACK consumer, that was left to NULL when
+ * loading the global PEL. Then set the same shared
+ * NACK structure also in the consumer-specific PEL. */
+ nack->consumer = consumer;
+ if (!raxInsert(consumer->pel,rawid,sizeof(rawid),nack,NULL))
+ rdbExitReportCorruptRDB("Duplicated consumer PEL entry "
+ " loading a stream consumer "
+ "group");
+ }
+ }
+ }
} else if (rdbtype == RDB_TYPE_MODULE || rdbtype == RDB_TYPE_MODULE_2) {
uint64_t moduleid = rdbLoadLen(rdb,NULL);
+ if (rioGetReadError(rdb)) return NULL;
moduleType *mt = moduleTypeLookupModuleByID(moduleid);
char name[10];
- if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2)
+ if (rdbCheckMode && rdbtype == RDB_TYPE_MODULE_2) {
+ moduleTypeNameByID(name,moduleid);
return rdbLoadCheckModuleValue(rdb,name);
+ }
if (mt == NULL) {
moduleTypeNameByID(name,moduleid);
@@ -1501,7 +1879,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
exit(1);
}
RedisModuleIO io;
- moduleInitIOContext(io,mt,rdb);
+ moduleInitIOContext(io,mt,rdb,key);
io.ver = (rdbtype == RDB_TYPE_MODULE) ? 1 : 2;
/* Call the rdb_load method of the module providing the 10 bit
* encoding version in the lower 10 bits of the module ID. */
@@ -1514,6 +1892,11 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
/* Module v2 serialization has an EOF mark at the end. */
if (io.ver == 2) {
uint64_t eof = rdbLoadLen(rdb,NULL);
+ if (eof == RDB_LENERR) {
+ o = createModuleObject(mt,ptr); /* creating just in order to easily destroy */
+ decrRefCount(o);
+ return NULL;
+ }
if (eof != RDB_MODULE_OPCODE_EOF) {
serverLog(LL_WARNING,"The RDB file contains module data for the module '%s' that is not terminated by the proper module value EOF marker", name);
exit(1);
@@ -1527,25 +1910,31 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
}
o = createModuleObject(mt,ptr);
} else {
- rdbExitReportCorruptRDB("Unknown RDB encoding type %d",rdbtype);
+ rdbReportReadError("Unknown RDB encoding type %d",rdbtype);
+ return NULL;
}
return o;
}
/* Mark that we are loading in the global state and setup the fields
* needed to provide loading stats. */
-void startLoading(FILE *fp) {
- struct stat sb;
-
+void startLoading(size_t size) {
/* Load the DB */
server.loading = 1;
server.loading_start_time = time(NULL);
server.loading_loaded_bytes = 0;
- if (fstat(fileno(fp), &sb) == -1) {
- server.loading_total_bytes = 0;
- } else {
- server.loading_total_bytes = sb.st_size;
- }
+ server.loading_total_bytes = size;
+}
+
+/* Mark that we are loading in the global state and setup the fields
+ * needed to provide loading stats.
+ * 'filename' is optional and used for rdb-check on error */
+void startLoadingFile(FILE *fp, char* filename) {
+ struct stat sb;
+ if (fstat(fileno(fp), &sb) == -1)
+ sb.st_size = 0;
+ rdbFileBeingLoaded = filename;
+ startLoading(sb.st_size);
}
/* Refresh the loading progress info */
@@ -1558,6 +1947,7 @@ void loadingProgress(off_t pos) {
/* Loading finished */
void stopLoading(void) {
server.loading = 0;
+ rdbFileBeingLoaded = NULL;
}
/* Track loading progress in order to serve client's from time to time
@@ -1581,12 +1971,11 @@ void rdbLoadProgressCallback(rio *r, const void *buf, size_t len) {
/* Load an RDB file from the rio stream 'rdb'. On success C_OK is returned,
* otherwise C_ERR is returned and 'errno' is set accordingly. */
-int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
+int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof) {
uint64_t dbid;
int type, rdbver;
redisDb *db = server.db+0;
char buf[1024];
- long long expiretime, now = mstime();
rdb->update_cksum = rdbLoadProgressCallback;
rdb->max_processing_chunk = server.loading_process_events_interval_bytes;
@@ -1604,9 +1993,12 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
return C_ERR;
}
+ /* Key-specific attributes, set by opcodes before the key type. */
+ long long lru_idle = -1, lfu_freq = -1, expiretime = -1, now = mstime();
+ long long lru_clock = LRU_CLOCK();
+
while(1) {
robj *key, *val;
- expiretime = -1;
/* Read type. */
if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
@@ -1616,25 +2008,34 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
- if ((expiretime = rdbLoadTime(rdb)) == -1) goto eoferr;
- /* We read the time so we need to read the object type again. */
- if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
- /* the EXPIRETIME opcode specifies time in seconds, so convert
- * into milliseconds. */
+ expiretime = rdbLoadTime(rdb);
expiretime *= 1000;
+ if (rioGetReadError(rdb)) goto eoferr;
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
- if ((expiretime = rdbLoadMillisecondTime(rdb)) == -1) goto eoferr;
- /* We read the time so we need to read the object type again. */
- if ((type = rdbLoadType(rdb)) == -1) goto eoferr;
+ expiretime = rdbLoadMillisecondTime(rdb,rdbver);
+ if (rioGetReadError(rdb)) goto eoferr;
+ continue; /* Read next opcode. */
+ } else if (type == RDB_OPCODE_FREQ) {
+ /* FREQ: LFU frequency. */
+ uint8_t byte;
+ if (rioRead(rdb,&byte,1) == 0) goto eoferr;
+ lfu_freq = byte;
+ continue; /* Read next opcode. */
+ } else if (type == RDB_OPCODE_IDLE) {
+ /* IDLE: LRU idle time. */
+ uint64_t qword;
+ if ((qword = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
+ lru_idle = qword;
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
break;
} else if (type == RDB_OPCODE_SELECTDB) {
/* SELECTDB: Select the specified database. */
- if ((dbid = rdbLoadLen(rdb,NULL)) == RDB_LENERR)
- goto eoferr;
+ if ((dbid = rdbLoadLen(rdb,NULL)) == RDB_LENERR) goto eoferr;
if (dbid >= (unsigned)server.dbnum) {
serverLog(LL_WARNING,
"FATAL: Data file was created with a Redis "
@@ -1643,7 +2044,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
exit(1);
}
db = server.db+dbid;
- continue; /* Read type again. */
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_RESIZEDB) {
/* RESIZEDB: Hint about the size of the keys in the currently
* selected data base, in order to avoid useless rehashing. */
@@ -1654,7 +2055,7 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
goto eoferr;
dictExpand(db->dict,db_size);
dictExpand(db->expires,expires_size);
- continue; /* Read type again. */
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_AUX) {
/* AUX: generic string-string fields. Use to add state to RDB
* which is backward compatible. Implementations of RDB loading
@@ -1688,6 +2089,23 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
"Can't load Lua script from RDB file! "
"BODY: %s", auxval->ptr);
}
+ } else if (!strcasecmp(auxkey->ptr,"redis-ver")) {
+ serverLog(LL_NOTICE,"Loading RDB produced by version %s",
+ (char*)auxval->ptr);
+ } else if (!strcasecmp(auxkey->ptr,"ctime")) {
+ time_t age = time(NULL)-strtol(auxval->ptr,NULL,10);
+ if (age < 0) age = 0;
+ serverLog(LL_NOTICE,"RDB age %ld seconds",
+ (unsigned long) age);
+ } else if (!strcasecmp(auxkey->ptr,"used-mem")) {
+ long long usedmem = strtoll(auxval->ptr,NULL,10);
+ serverLog(LL_NOTICE,"RDB memory usage when created %.2f Mb",
+ (double) usedmem / (1024*1024));
+ } else if (!strcasecmp(auxkey->ptr,"aof-preamble")) {
+ long long haspreamble = strtoll(auxval->ptr,NULL,10);
+ if (haspreamble) serverLog(LL_NOTICE,"RDB has an AOF tail");
+ } else if (!strcasecmp(auxkey->ptr,"redis-bits")) {
+ /* Just ignored. */
} else {
/* We ignore fields we don't understand, as by AUX field
* contract. */
@@ -1698,49 +2116,120 @@ int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi) {
decrRefCount(auxkey);
decrRefCount(auxval);
continue; /* Read type again. */
+ } else if (type == RDB_OPCODE_MODULE_AUX) {
+ /* Load module data that is not related to the Redis key space.
+ * Such data can be potentially be stored both before and after the
+ * RDB keys-values section. */
+ uint64_t moduleid = rdbLoadLen(rdb,NULL);
+ int when_opcode = rdbLoadLen(rdb,NULL);
+ int when = rdbLoadLen(rdb,NULL);
+ if (rioGetReadError(rdb)) goto eoferr;
+ if (when_opcode != RDB_MODULE_OPCODE_UINT)
+ rdbReportReadError("bad when_opcode");
+ moduleType *mt = moduleTypeLookupModuleByID(moduleid);
+ char name[10];
+ moduleTypeNameByID(name,moduleid);
+
+ if (!rdbCheckMode && mt == NULL) {
+ /* Unknown module. */
+ serverLog(LL_WARNING,"The RDB file contains AUX module data I can't load: no matching module '%s'", name);
+ exit(1);
+ } else if (!rdbCheckMode && mt != NULL) {
+ if (!mt->aux_load) {
+ /* Module doesn't support AUX. */
+ serverLog(LL_WARNING,"The RDB file contains module AUX data, but the module '%s' doesn't seem to support it.", name);
+ exit(1);
+ }
+
+ RedisModuleIO io;
+ moduleInitIOContext(io,mt,rdb,NULL);
+ io.ver = 2;
+ /* Call the rdb_load method of the module providing the 10 bit
+ * encoding version in the lower 10 bits of the module ID. */
+ if (mt->aux_load(&io,moduleid&1023, when) || io.error) {
+ moduleTypeNameByID(name,moduleid);
+ serverLog(LL_WARNING,"The RDB file contains module AUX data for the module type '%s', that the responsible module is not able to load. Check for modules log above for additional clues.", name);
+ exit(1);
+ }
+ if (io.ctx) {
+ moduleFreeContext(io.ctx);
+ zfree(io.ctx);
+ }
+ uint64_t eof = rdbLoadLen(rdb,NULL);
+ if (eof != RDB_MODULE_OPCODE_EOF) {
+ serverLog(LL_WARNING,"The RDB file contains module AUX data for the module '%s' that is not terminated by the proper module value EOF marker", name);
+ exit(1);
+ }
+ continue;
+ } else {
+ /* RDB check mode. */
+ robj *aux = rdbLoadCheckModuleValue(rdb,name);
+ decrRefCount(aux);
+ continue; /* Read next opcode. */
+ }
}
/* Read key */
if ((key = rdbLoadStringObject(rdb)) == NULL) goto eoferr;
/* Read value */
- if ((val = rdbLoadObject(type,rdb)) == NULL) goto eoferr;
+ if ((val = rdbLoadObject(type,rdb,key)) == NULL) goto eoferr;
/* Check if the key already expired. This function is used when loading
* an RDB file from disk, either at startup, or when an RDB was
* received from the master. In the latter case, the master is
* responsible for key expiry. If we would expire keys here, the
* snapshot taken by the master may not be reflected on the slave. */
- if (server.masterhost == NULL && expiretime != -1 && expiretime < now) {
+ if (server.masterhost == NULL && !loading_aof && expiretime != -1 && expiretime < now) {
decrRefCount(key);
decrRefCount(val);
- continue;
- }
- /* Add the new object in the hash table */
- dbAdd(db,key,val);
+ } else {
+ /* Add the new object in the hash table */
+ dbAdd(db,key,val);
+
+ /* Set the expire time if needed */
+ if (expiretime != -1) setExpire(NULL,db,key,expiretime);
+
+ /* Set usage information (for eviction). */
+ objectSetLRUOrLFU(val,lfu_freq,lru_idle,lru_clock);
- /* Set the expire time if needed */
- if (expiretime != -1) setExpire(NULL,db,key,expiretime);
+ /* Decrement the key refcount since dbAdd() will take its
+ * own reference. */
+ decrRefCount(key);
+ }
+ if (server.key_load_delay)
+ usleep(server.key_load_delay);
- decrRefCount(key);
+ /* Reset the state that is key-specified and is populated by
+ * opcodes before the key, so that we start from scratch again. */
+ expiretime = -1;
+ lfu_freq = -1;
+ lru_idle = -1;
}
/* Verify the checksum if RDB version is >= 5 */
- if (rdbver >= 5 && server.rdb_checksum) {
+ if (rdbver >= 5) {
uint64_t cksum, expected = rdb->cksum;
if (rioRead(rdb,&cksum,8) == 0) goto eoferr;
- memrev64ifbe(&cksum);
- if (cksum == 0) {
- serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
- } else if (cksum != expected) {
- serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
- rdbExitReportCorruptRDB("RDB CRC error");
+ if (server.rdb_checksum) {
+ memrev64ifbe(&cksum);
+ if (cksum == 0) {
+ serverLog(LL_WARNING,"RDB file was saved with checksum disabled: no check performed.");
+ } else if (cksum != expected) {
+ serverLog(LL_WARNING,"Wrong RDB checksum. Aborting now.");
+ rdbExitReportCorruptRDB("RDB CRC error");
+ }
}
}
return C_OK;
-eoferr: /* unexpected end of file is handled here with a fatal exit */
- serverLog(LL_WARNING,"Short read or OOM loading DB. Unrecoverable error, aborting now.");
- rdbExitReportCorruptRDB("Unexpected EOF reading RDB file");
- return C_ERR; /* Just to avoid warning */
+ /* Unexpected end of file is handled here calling rdbReportReadError():
+ * this will in turn either abort Redis in most cases, or if we are loading
+ * the RDB file from a socket during initial SYNC (diskless replica mode),
+ * we'll report the error to the caller, so that we can retry. */
+eoferr:
+ serverLog(LL_WARNING,
+ "Short read or OOM loading DB. Unrecoverable error, aborting now.");
+ rdbReportReadError("Unexpected EOF reading RDB file");
+ return C_ERR;
}
/* Like rdbLoadRio() but takes a filename instead of a rio stream. The
@@ -1756,9 +2245,9 @@ int rdbLoad(char *filename, rdbSaveInfo *rsi) {
int retval;
if ((fp = fopen(filename,"r")) == NULL) return C_ERR;
- startLoading(fp);
+ startLoadingFile(fp, filename);
rioInitWithFile(&rdb,fp);
- retval = rdbLoadRio(&rdb,rsi);
+ retval = rdbLoadRio(&rdb,rsi,0);
fclose(fp);
stopLoading();
return retval;
@@ -1786,7 +2275,7 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
- * tirggering an error conditon. */
+ * tirggering an error condition. */
if (bysignal != SIGUSR1)
server.lastbgsave_status = C_ERR;
}
@@ -1803,8 +2292,6 @@ void backgroundSaveDoneHandlerDisk(int exitcode, int bysignal) {
* This function covers the case of RDB -> Slaves socket transfers for
* diskless replication. */
void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
- uint64_t *ok_slaves;
-
if (!bysignal && exitcode == 0) {
serverLog(LL_NOTICE,
"Background RDB transfer terminated with success");
@@ -1818,79 +2305,6 @@ void backgroundSaveDoneHandlerSocket(int exitcode, int bysignal) {
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
server.rdb_save_time_start = -1;
- /* If the child returns an OK exit code, read the set of slave client
- * IDs and the associated status code. We'll terminate all the slaves
- * in error state.
- *
- * If the process returned an error, consider the list of slaves that
- * can continue to be emtpy, so that it's just a special case of the
- * normal code path. */
- ok_slaves = zmalloc(sizeof(uint64_t)); /* Make space for the count. */
- ok_slaves[0] = 0;
- if (!bysignal && exitcode == 0) {
- int readlen = sizeof(uint64_t);
-
- if (read(server.rdb_pipe_read_result_from_child, ok_slaves, readlen) ==
- readlen)
- {
- readlen = ok_slaves[0]*sizeof(uint64_t)*2;
-
- /* Make space for enough elements as specified by the first
- * uint64_t element in the array. */
- ok_slaves = zrealloc(ok_slaves,sizeof(uint64_t)+readlen);
- if (readlen &&
- read(server.rdb_pipe_read_result_from_child, ok_slaves+1,
- readlen) != readlen)
- {
- ok_slaves[0] = 0;
- }
- }
- }
-
- close(server.rdb_pipe_read_result_from_child);
- close(server.rdb_pipe_write_result_to_parent);
-
- /* We can continue the replication process with all the slaves that
- * correctly received the full payload. Others are terminated. */
- listNode *ln;
- listIter li;
-
- listRewind(server.slaves,&li);
- while((ln = listNext(&li))) {
- client *slave = ln->value;
-
- if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
- uint64_t j;
- int errorcode = 0;
-
- /* Search for the slave ID in the reply. In order for a slave to
- * continue the replication process, we need to find it in the list,
- * and it must have an error code set to 0 (which means success). */
- for (j = 0; j < ok_slaves[0]; j++) {
- if (slave->id == ok_slaves[2*j+1]) {
- errorcode = ok_slaves[2*j+2];
- break; /* Found in slaves list. */
- }
- }
- if (j == ok_slaves[0] || errorcode != 0) {
- serverLog(LL_WARNING,
- "Closing slave %s: child->slave RDB transfer failed: %s",
- replicationGetSlaveName(slave),
- (errorcode == 0) ? "RDB transfer child aborted"
- : strerror(errorcode));
- freeClient(slave);
- } else {
- serverLog(LL_WARNING,
- "Slave %s correctly received the streamed RDB file.",
- replicationGetSlaveName(slave));
- /* Restore the socket as non-blocking. */
- anetNonBlock(NULL,slave->fd);
- anetSendTimeout(NULL,slave->fd,0);
- }
- }
- }
- zfree(ok_slaves);
-
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? C_OK : C_ERR, RDB_CHILD_TYPE_SOCKET);
}
@@ -1909,123 +2323,74 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
}
}
+/* Kill the RDB saving child using SIGUSR1 (so that the parent will know
+ * the child did not exit for an error, but because we wanted), and performs
+ * the cleanup needed. */
+void killRDBChild(void) {
+ kill(server.rdb_child_pid,SIGUSR1);
+ rdbRemoveTempFile(server.rdb_child_pid);
+ closeChildInfoPipe();
+ updateDictResizePolicy();
+}
+
/* Spawn an RDB child that writes the RDB to the sockets of the slaves
* that are currently in SLAVE_STATE_WAIT_BGSAVE_START state. */
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
- int *fds;
- uint64_t *clientids;
- int numfds;
listNode *ln;
listIter li;
pid_t childpid;
- long long start;
int pipefds[2];
- if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) return C_ERR;
+ if (hasActiveChildProcess()) return C_ERR;
+
+ /* Even if the previous fork child exited, don't start a new one until we
+ * drained the pipe. */
+ if (server.rdb_pipe_conns) return C_ERR;
- /* Before to fork, create a pipe that will be used in order to
- * send back to the parent the IDs of the slaves that successfully
- * received all the writes. */
+ /* Before to fork, create a pipe that is used to transfer the rdb bytes to
+ * the parent, we can't let it write directly to the sockets, since in case
+ * of TLS we must let the parent handle a continuous TLS state when the
+ * child terminates and parent takes over. */
if (pipe(pipefds) == -1) return C_ERR;
- server.rdb_pipe_read_result_from_child = pipefds[0];
- server.rdb_pipe_write_result_to_parent = pipefds[1];
+ server.rdb_pipe_read = pipefds[0];
+ server.rdb_pipe_write = pipefds[1];
+ anetNonBlock(NULL, server.rdb_pipe_read);
- /* Collect the file descriptors of the slaves we want to transfer
+ /* Collect the connections of the replicas we want to transfer
* the RDB to, which are i WAIT_BGSAVE_START state. */
- fds = zmalloc(sizeof(int)*listLength(server.slaves));
- /* We also allocate an array of corresponding client IDs. This will
- * be useful for the child process in order to build the report
- * (sent via unix pipe) that will be sent to the parent. */
- clientids = zmalloc(sizeof(uint64_t)*listLength(server.slaves));
- numfds = 0;
-
+ server.rdb_pipe_conns = zmalloc(sizeof(connection *)*listLength(server.slaves));
+ server.rdb_pipe_numconns = 0;
+ server.rdb_pipe_numconns_writing = 0;
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = ln->value;
-
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
- clientids[numfds] = slave->id;
- fds[numfds++] = slave->fd;
+ server.rdb_pipe_conns[server.rdb_pipe_numconns++] = slave->conn;
replicationSetupSlaveForFullResync(slave,getPsyncInitialOffset());
- /* Put the socket in blocking mode to simplify RDB transfer.
- * We'll restore it when the children returns (since duped socket
- * will share the O_NONBLOCK attribute with the parent). */
- anetBlock(NULL,slave->fd);
- anetSendTimeout(NULL,slave->fd,server.repl_timeout*1000);
}
}
/* Create the child process. */
openChildInfoPipe();
- start = ustime();
- if ((childpid = fork()) == 0) {
+ if ((childpid = redisFork()) == 0) {
/* Child */
int retval;
- rio slave_sockets;
+ rio rdb;
- rioInitWithFdset(&slave_sockets,fds,numfds);
- zfree(fds);
+ rioInitWithFd(&rdb,server.rdb_pipe_write);
- closeListeningSockets(0);
redisSetProcTitle("redis-rdb-to-slaves");
- retval = rdbSaveRioWithEOFMark(&slave_sockets,NULL,rsi);
- if (retval == C_OK && rioFlush(&slave_sockets) == 0)
+ retval = rdbSaveRioWithEOFMark(&rdb,NULL,rsi);
+ if (retval == C_OK && rioFlush(&rdb) == 0)
retval = C_ERR;
if (retval == C_OK) {
- size_t private_dirty = zmalloc_get_private_dirty(-1);
-
- if (private_dirty) {
- serverLog(LL_NOTICE,
- "RDB: %zu MB of memory used by copy-on-write",
- private_dirty/(1024*1024));
- }
-
- server.child_info_data.cow_size = private_dirty;
- sendChildInfo(CHILD_INFO_TYPE_RDB);
-
- /* If we are returning OK, at least one slave was served
- * with the RDB file as expected, so we need to send a report
- * to the parent via the pipe. The format of the message is:
- *
- * <len> <slave[0].id> <slave[0].error> ...
- *
- * len, slave IDs, and slave errors, are all uint64_t integers,
- * so basically the reply is composed of 64 bits for the len field
- * plus 2 additional 64 bit integers for each entry, for a total
- * of 'len' entries.
- *
- * The 'id' represents the slave's client ID, so that the master
- * can match the report with a specific slave, and 'error' is
- * set to 0 if the replication process terminated with a success
- * or the error code if an error occurred. */
- void *msg = zmalloc(sizeof(uint64_t)*(1+2*numfds));
- uint64_t *len = msg;
- uint64_t *ids = len+1;
- int j, msglen;
-
- *len = numfds;
- for (j = 0; j < numfds; j++) {
- *ids++ = clientids[j];
- *ids++ = slave_sockets.io.fdset.state[j];
- }
-
- /* Write the message to the parent. If we have no good slaves or
- * we are unable to transfer the message to the parent, we exit
- * with an error so that the parent will abort the replication
- * process with all the childre that were waiting. */
- msglen = sizeof(uint64_t)*(1+2*numfds);
- if (*len == 0 ||
- write(server.rdb_pipe_write_result_to_parent,msg,msglen)
- != msglen)
- {
- retval = C_ERR;
- }
- zfree(msg);
+ sendChildCOWInfo(CHILD_INFO_TYPE_RDB, "RDB");
}
- zfree(clientids);
- rioFreeFdset(&slave_sockets);
+
+ rioFreeFd(&rdb);
+ close(server.rdb_pipe_write); /* wake up the reader, tell it we're done. */
exitFromChild((retval == C_OK) ? 0 : 1);
} else {
/* Parent */
@@ -2039,32 +2404,28 @@ int rdbSaveToSlavesSockets(rdbSaveInfo *rsi) {
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = ln->value;
- int j;
-
- for (j = 0; j < numfds; j++) {
- if (slave->id == clientids[j]) {
- slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
- break;
- }
+ if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_END) {
+ slave->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
}
}
- close(pipefds[0]);
- close(pipefds[1]);
+ close(server.rdb_pipe_write);
+ close(server.rdb_pipe_read);
+ zfree(server.rdb_pipe_conns);
+ server.rdb_pipe_conns = NULL;
+ server.rdb_pipe_numconns = 0;
+ server.rdb_pipe_numconns_writing = 0;
closeChildInfoPipe();
} else {
- server.stat_fork_time = ustime()-start;
- server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
- latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
-
serverLog(LL_NOTICE,"Background RDB transfer started by pid %d",
childpid);
server.rdb_save_time_start = time(NULL);
server.rdb_child_pid = childpid;
server.rdb_child_type = RDB_CHILD_TYPE_SOCKET;
- updateDictResizePolicy();
+ close(server.rdb_pipe_write); /* close write in parent so that it can detect the close on the child. */
+ if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
+ serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
+ }
}
- zfree(clientids);
- zfree(fds);
return (childpid == -1) ? C_ERR : C_OK;
}
return C_OK; /* Unreached. */
@@ -2104,15 +2465,15 @@ void bgsaveCommand(client *c) {
if (server.rdb_child_pid != -1) {
addReplyError(c,"Background save already in progress");
- } else if (server.aof_child_pid != -1) {
+ } else if (hasActiveChildProcess()) {
if (schedule) {
server.rdb_bgsave_scheduled = 1;
addReplyStatus(c,"Background saving scheduled");
} else {
addReplyError(c,
- "An AOF log rewriting in progress: can't BGSAVE right now. "
- "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
- "possible.");
+ "Another child process is active (AOF?): can't BGSAVE right now. "
+ "Use BGSAVE SCHEDULE in order to schedule a BGSAVE whenever "
+ "possible.");
}
} else if (rdbSaveBackground(server.rdb_filename,rsiptr) == C_OK) {
addReplyStatus(c,"Background saving started");
diff --git a/src/rdb.h b/src/rdb.h
index 2456cfb58..40a50f7ba 100644
--- a/src/rdb.h
+++ b/src/rdb.h
@@ -38,7 +38,7 @@
/* The current RDB version. When the format changes in a way that is no longer
* backward compatible this number gets incremented. */
-#define RDB_VERSION 8
+#define RDB_VERSION 9
/* Defines related to the dump file format. To store 32 bits lengths for short
* keys requires a lot of space, so we check the most significant 2 bits of
@@ -97,12 +97,15 @@
#define rdbIsObjectType(t) ((t >= 0 && t <= 7) || (t >= 9 && t <= 15))
/* Special RDB opcodes (saved/loaded with rdbSaveType/rdbLoadType). */
-#define RDB_OPCODE_AUX 250
-#define RDB_OPCODE_RESIZEDB 251
-#define RDB_OPCODE_EXPIRETIME_MS 252
-#define RDB_OPCODE_EXPIRETIME 253
-#define RDB_OPCODE_SELECTDB 254
-#define RDB_OPCODE_EOF 255
+#define RDB_OPCODE_MODULE_AUX 247 /* Module auxiliary data. */
+#define RDB_OPCODE_IDLE 248 /* LRU idle time. */
+#define RDB_OPCODE_FREQ 249 /* LFU frequency. */
+#define RDB_OPCODE_AUX 250 /* RDB aux field. */
+#define RDB_OPCODE_RESIZEDB 251 /* Hash table resize hint. */
+#define RDB_OPCODE_EXPIRETIME_MS 252 /* Expire time in milliseconds. */
+#define RDB_OPCODE_EXPIRETIME 253 /* Old expire time in seconds. */
+#define RDB_OPCODE_SELECTDB 254 /* DB number of the following keys. */
+#define RDB_OPCODE_EOF 255 /* End of the RDB file. */
/* Module serialized values sub opcodes */
#define RDB_MODULE_OPCODE_EOF 0 /* End of module value. */
@@ -126,6 +129,8 @@ int rdbLoadType(rio *rdb);
int rdbSaveTime(rio *rdb, time_t t);
time_t rdbLoadTime(rio *rdb);
int rdbSaveLen(rio *rdb, uint64_t len);
+int rdbSaveMillisecondTime(rio *rdb, long long t);
+long long rdbLoadMillisecondTime(rio *rdb, int rdbver);
uint64_t rdbLoadLen(rio *rdb, int *isencoded);
int rdbLoadLenByRef(rio *rdb, int *isencoded, uint64_t *lenptr);
int rdbSaveObjectType(rio *rdb, robj *o);
@@ -135,11 +140,12 @@ int rdbSaveBackground(char *filename, rdbSaveInfo *rsi);
int rdbSaveToSlavesSockets(rdbSaveInfo *rsi);
void rdbRemoveTempFile(pid_t childpid);
int rdbSave(char *filename, rdbSaveInfo *rsi);
-ssize_t rdbSaveObject(rio *rdb, robj *o);
+ssize_t rdbSaveObject(rio *rdb, robj *o, robj *key);
size_t rdbSavedObjectLen(robj *o);
-robj *rdbLoadObject(int type, rio *rdb);
+robj *rdbLoadObject(int type, rio *rdb, robj *key);
void backgroundSaveDoneHandler(int exitcode, int bysignal);
-int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime, long long now);
+int rdbSaveKeyValuePair(rio *rdb, robj *key, robj *val, long long expiretime);
+ssize_t rdbSaveSingleModuleAux(rio *rdb, int when, moduleType *mt);
robj *rdbLoadStringObject(rio *rdb);
ssize_t rdbSaveStringObject(rio *rdb, robj *obj);
ssize_t rdbSaveRawString(rio *rdb, unsigned char *s, size_t len);
@@ -148,7 +154,7 @@ int rdbSaveBinaryDoubleValue(rio *rdb, double val);
int rdbLoadBinaryDoubleValue(rio *rdb, double *val);
int rdbSaveBinaryFloatValue(rio *rdb, float val);
int rdbLoadBinaryFloatValue(rio *rdb, float *val);
-int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi);
+int rdbLoadRio(rio *rdb, rdbSaveInfo *rsi, int loading_aof);
rdbSaveInfo *rdbPopulateSaveInfo(rdbSaveInfo *rsi);
#endif
diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c
index d30879dc4..2df41580b 100644
--- a/src/redis-benchmark.c
+++ b/src/redis-benchmark.c
@@ -1,4 +1,4 @@
-/* Redis benchmark utility.
+/* Redis benchmark utility.
*
* Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
@@ -39,15 +39,30 @@
#include <sys/time.h>
#include <signal.h>
#include <assert.h>
+#include <math.h>
+#include <pthread.h>
#include <sds.h> /* Use hiredis sds. */
#include "ae.h"
#include "hiredis.h"
#include "adlist.h"
+#include "dict.h"
#include "zmalloc.h"
+#include "atomicvar.h"
+#include "crc16_slottable.h"
#define UNUSED(V) ((void) V)
#define RANDPTR_INITIAL_SIZE 8
+#define MAX_LATENCY_PRECISION 3
+#define MAX_THREADS 500
+#define CLUSTER_SLOTS 16384
+
+#define CLIENT_GET_EVENTLOOP(c) \
+ (c->thread_id >= 0 ? config.threads[c->thread_id]->el : config.el)
+
+struct benchmarkThread;
+struct clusterNode;
+struct redisConfig;
static struct config {
aeEventLoop *el;
@@ -79,6 +94,25 @@ static struct config {
sds dbnumstr;
char *tests;
char *auth;
+ int precision;
+ int num_threads;
+ struct benchmarkThread **threads;
+ int cluster_mode;
+ int cluster_node_count;
+ struct clusterNode **cluster_nodes;
+ struct redisConfig *redis_config;
+ int is_fetching_slots;
+ int is_updating_slots;
+ int slots_last_update;
+ int enable_tracking;
+ /* Thread mutexes to be used as fallbacks by atomicvar.h */
+ pthread_mutex_t requests_issued_mutex;
+ pthread_mutex_t requests_finished_mutex;
+ pthread_mutex_t liveclients_mutex;
+ pthread_mutex_t is_fetching_slots_mutex;
+ pthread_mutex_t is_updating_slots_mutex;
+ pthread_mutex_t updating_slots_mutex;
+ pthread_mutex_t slots_last_update_mutex;
} config;
typedef struct _client {
@@ -87,6 +121,9 @@ typedef struct _client {
char **randptr; /* Pointers to :rand: strings inside the command buf */
size_t randlen; /* Number of pointers in client->randptr */
size_t randfree; /* Number of unused pointers in client->randptr */
+ char **stagptr; /* Pointers to slot hashtags (cluster mode only) */
+ size_t staglen; /* Number of pointers in client->stagptr */
+ size_t stagfree; /* Number of unused pointers in client->stagptr */
size_t written; /* Bytes of 'obuf' already written */
long long start; /* Start time of a request */
long long latency; /* Request latency */
@@ -95,11 +132,66 @@ typedef struct _client {
such as auth and select are prefixed to the pipeline of
benchmark commands and discarded after the first send. */
int prefixlen; /* Size in bytes of the pending prefix commands */
+ int thread_id;
+ struct clusterNode *cluster_node;
+ int slots_last_update;
} *client;
+/* Threads. */
+
+typedef struct benchmarkThread {
+ int index;
+ pthread_t thread;
+ aeEventLoop *el;
+} benchmarkThread;
+
+/* Cluster. */
+typedef struct clusterNode {
+ char *ip;
+ int port;
+ sds name;
+ int flags;
+ sds replicate; /* Master ID if node is a slave */
+ int *slots;
+ int slots_count;
+ int current_slot_index;
+ int *updated_slots; /* Used by updateClusterSlotsConfiguration */
+ int updated_slots_count; /* Used by updateClusterSlotsConfiguration */
+ int replicas_count;
+ sds *migrating; /* An array of sds where even strings are slots and odd
+ * strings are the destination node IDs. */
+ sds *importing; /* An array of sds where even strings are slots and odd
+ * strings are the source node IDs. */
+ int migrating_count; /* Length of the migrating array (migrating slots*2) */
+ int importing_count; /* Length of the importing array (importing slots*2) */
+ struct redisConfig *redis_config;
+} clusterNode;
+
+typedef struct redisConfig {
+ sds save;
+ sds appendonly;
+} redisConfig;
+
/* Prototypes */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
static void createMissingClients(client c);
+static benchmarkThread *createBenchmarkThread(int index);
+static void freeBenchmarkThread(benchmarkThread *thread);
+static void freeBenchmarkThreads();
+static void *execBenchmarkThread(void *ptr);
+static clusterNode *createClusterNode(char *ip, int port);
+static redisConfig *getRedisConfig(const char *ip, int port,
+ const char *hostsocket);
+static void freeRedisConfig(redisConfig *cfg);
+static int fetchClusterSlotsConfiguration(client c);
+static void updateClusterSlotsConfiguration();
+int showThroughput(struct aeEventLoop *eventLoop, long long id,
+ void *clientData);
+
+/* Dict callbacks */
+static uint64_t dictSdsHash(const void *key);
+static int dictSdsKeyCompare(void *privdata, const void *key1,
+ const void *key2);
/* Implementation */
static long long ustime(void) {
@@ -122,18 +214,123 @@ static long long mstime(void) {
return mst;
}
+static uint64_t dictSdsHash(const void *key) {
+ return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
+}
+
+static int dictSdsKeyCompare(void *privdata, const void *key1,
+ const void *key2)
+{
+ int l1,l2;
+ DICT_NOTUSED(privdata);
+
+ l1 = sdslen((sds)key1);
+ l2 = sdslen((sds)key2);
+ if (l1 != l2) return 0;
+ return memcmp(key1, key2, l1) == 0;
+}
+
+/* _serverAssert is needed by dict */
+void _serverAssert(const char *estr, const char *file, int line) {
+ fprintf(stderr, "=== ASSERTION FAILED ===");
+ fprintf(stderr, "==> %s:%d '%s' is not true",file,line,estr);
+ *((char*)-1) = 'x';
+}
+
+static redisConfig *getRedisConfig(const char *ip, int port,
+ const char *hostsocket)
+{
+ redisConfig *cfg = zcalloc(sizeof(*cfg));
+ if (!cfg) return NULL;
+ redisContext *c = NULL;
+ redisReply *reply = NULL, *sub_reply = NULL;
+ if (hostsocket == NULL)
+ c = redisConnect(ip, port);
+ else
+ c = redisConnectUnix(hostsocket);
+ if (c == NULL || c->err) {
+ fprintf(stderr,"Could not connect to Redis at ");
+ char *err = (c != NULL ? c->errstr : "");
+ if (hostsocket == NULL) fprintf(stderr,"%s:%d: %s\n",ip,port,err);
+ else fprintf(stderr,"%s: %s\n",hostsocket,err);
+ goto fail;
+ }
+
+ if(config.auth) {
+ void *authReply = NULL;
+ redisAppendCommand(c, "AUTH %s", config.auth);
+ if (REDIS_OK != redisGetReply(c, &authReply)) goto fail;
+ if (reply) freeReplyObject(reply);
+ reply = ((redisReply *) authReply);
+ if (reply->type == REDIS_REPLY_ERROR) {
+ fprintf(stderr, "ERROR: %s\n", reply->str);
+ goto fail;
+ }
+ }
+
+ redisAppendCommand(c, "CONFIG GET %s", "save");
+ redisAppendCommand(c, "CONFIG GET %s", "appendonly");
+ int i = 0;
+ void *r = NULL;
+ for (; i < 2; i++) {
+ int res = redisGetReply(c, &r);
+ if (reply) freeReplyObject(reply);
+ reply = ((redisReply *) r);
+ if (res != REDIS_OK || !r) goto fail;
+ if (reply->type == REDIS_REPLY_ERROR) {
+ fprintf(stderr, "ERROR: %s\n", reply->str);
+ goto fail;
+ }
+ if (reply->type != REDIS_REPLY_ARRAY || reply->elements < 2) goto fail;
+ sub_reply = reply->element[1];
+ char *value = sub_reply->str;
+ if (!value) value = "";
+ switch (i) {
+ case 0: cfg->save = sdsnew(value); break;
+ case 1: cfg->appendonly = sdsnew(value); break;
+ }
+ }
+ freeReplyObject(reply);
+ redisFree(c);
+ return cfg;
+fail:
+ fprintf(stderr, "ERROR: failed to fetch CONFIG from ");
+ if (hostsocket == NULL) fprintf(stderr, "%s:%d\n", ip, port);
+ else fprintf(stderr, "%s\n", hostsocket);
+ freeReplyObject(reply);
+ redisFree(c);
+ zfree(cfg);
+ return NULL;
+}
+static void freeRedisConfig(redisConfig *cfg) {
+ if (cfg->save) sdsfree(cfg->save);
+ if (cfg->appendonly) sdsfree(cfg->appendonly);
+ zfree(cfg);
+}
+
static void freeClient(client c) {
+ aeEventLoop *el = CLIENT_GET_EVENTLOOP(c);
listNode *ln;
- aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
- aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
+ aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
+ aeDeleteFileEvent(el,c->context->fd,AE_READABLE);
+ if (c->thread_id >= 0) {
+ int requests_finished = 0;
+ atomicGet(config.requests_finished, requests_finished);
+ if (requests_finished >= config.requests) {
+ aeStop(el);
+ }
+ }
redisFree(c->context);
sdsfree(c->obuf);
zfree(c->randptr);
+ zfree(c->stagptr);
zfree(c);
+ if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex));
config.liveclients--;
ln = listSearchKey(config.clients,c);
assert(ln != NULL);
listDelNode(config.clients,ln);
+ if (config.num_threads) pthread_mutex_unlock(&(config.liveclients_mutex));
}
static void freeAllClients(void) {
@@ -147,9 +344,10 @@ static void freeAllClients(void) {
}
static void resetClient(client c) {
- aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
- aeDeleteFileEvent(config.el,c->context->fd,AE_READABLE);
- aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
+ aeEventLoop *el = CLIENT_GET_EVENTLOOP(c);
+ aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
+ aeDeleteFileEvent(el,c->context->fd,AE_READABLE);
+ aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c);
c->written = 0;
c->pending = config.pipeline;
}
@@ -159,7 +357,9 @@ static void randomizeClientKey(client c) {
for (i = 0; i < c->randlen; i++) {
char *p = c->randptr[i]+11;
- size_t r = random() % config.randomkeys_keyspacelen;
+ size_t r = 0;
+ if (config.randomkeys_keyspacelen != 0)
+ r = random() % config.randomkeys_keyspacelen;
size_t j;
for (j = 0; j < 12; j++) {
@@ -170,18 +370,49 @@ static void randomizeClientKey(client c) {
}
}
+static void setClusterKeyHashTag(client c) {
+ assert(c->thread_id >= 0);
+ clusterNode *node = c->cluster_node;
+ assert(node);
+ assert(node->current_slot_index < node->slots_count);
+ int is_updating_slots = 0;
+ atomicGet(config.is_updating_slots, is_updating_slots);
+ /* If updateClusterSlotsConfiguration is updating the slots array,
+ * call updateClusterSlotsConfiguration is order to block the thread
+ * since the mutex is locked. When the slots will be updated by the
+ * thread that's actually performing the update, the execution of
+ * updateClusterSlotsConfiguration won't actually do anything, since
+ * the updated_slots_count array will be already NULL. */
+ if (is_updating_slots) updateClusterSlotsConfiguration();
+ int slot = node->slots[node->current_slot_index];
+ const char *tag = crc16_slot_table[slot];
+ int taglen = strlen(tag);
+ size_t i;
+ for (i = 0; i < c->staglen; i++) {
+ char *p = c->stagptr[i] + 1;
+ p[0] = tag[0];
+ p[1] = (taglen >= 2 ? tag[1] : '}');
+ p[2] = (taglen == 3 ? tag[2] : '}');
+ }
+}
+
static void clientDone(client c) {
- if (config.requests_finished == config.requests) {
+ int requests_finished = 0;
+ atomicGet(config.requests_finished, requests_finished);
+ if (requests_finished >= config.requests) {
freeClient(c);
- aeStop(config.el);
+ if (!config.num_threads && config.el) aeStop(config.el);
return;
}
if (config.keepalive) {
resetClient(c);
} else {
+ if (config.num_threads) pthread_mutex_lock(&(config.liveclients_mutex));
config.liveclients--;
createMissingClients(c);
config.liveclients++;
+ if (config.num_threads)
+ pthread_mutex_unlock(&(config.liveclients_mutex));
freeClient(c);
}
}
@@ -212,17 +443,47 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
fprintf(stderr,"Unexpected error reply, exiting...\n");
exit(1);
}
+ redisReply *r = reply;
+ int is_err = (r->type == REDIS_REPLY_ERROR);
- if (config.showerrors) {
+ if (is_err && config.showerrors) {
+ /* TODO: static lasterr_time not thread-safe */
static time_t lasterr_time = 0;
time_t now = time(NULL);
- redisReply *r = reply;
- if (r->type == REDIS_REPLY_ERROR && lasterr_time != now) {
+ if (lasterr_time != now) {
lasterr_time = now;
- printf("Error from server: %s\n", r->str);
+ if (c->cluster_node) {
+ printf("Error from server %s:%d: %s\n",
+ c->cluster_node->ip,
+ c->cluster_node->port,
+ r->str);
+ } else printf("Error from server: %s\n", r->str);
}
}
+ /* Try to update slots configuration if reply error is
+ * MOVED/ASK/CLUSTERDOWN and the key(s) used by the command
+ * contain(s) the slot hash tag. */
+ if (is_err && c->cluster_node && c->staglen) {
+ int fetch_slots = 0, do_wait = 0;
+ if (!strncmp(r->str,"MOVED",5) || !strncmp(r->str,"ASK",3))
+ fetch_slots = 1;
+ else if (!strncmp(r->str,"CLUSTERDOWN",11)) {
+ /* Usually the cluster is able to recover itself after
+ * a CLUSTERDOWN error, so try to sleep one second
+ * before requesting the new configuration. */
+ fetch_slots = 1;
+ do_wait = 1;
+ printf("Error from server %s:%d: %s\n",
+ c->cluster_node->ip,
+ c->cluster_node->port,
+ r->str);
+ }
+ if (do_wait) sleep(1);
+ if (fetch_slots && !fetchClusterSlotsConfiguration(c))
+ exit(1);
+ }
+
freeReplyObject(reply);
/* This is an OK for prefix commands such as auth and select.*/
if (c->prefix_pending > 0) {
@@ -240,9 +501,10 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
}
continue;
}
-
- if (config.requests_finished < config.requests)
- config.latency[config.requests_finished++] = c->latency;
+ int requests_finished = 0;
+ atomicGetIncr(config.requests_finished, requests_finished, 1);
+ if (requests_finished < config.requests)
+ config.latency[requests_finished] = c->latency;
c->pending--;
if (c->pending == 0) {
clientDone(c);
@@ -264,17 +526,20 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Initialize request when nothing was written. */
if (c->written == 0) {
/* Enforce upper bound to number of requests. */
- if (config.requests_issued++ >= config.requests) {
+ int requests_issued = 0;
+ atomicGetIncr(config.requests_issued, requests_issued, 1);
+ if (requests_issued >= config.requests) {
freeClient(c);
return;
}
/* Really initialize: randomize keys and set start time. */
if (config.randomkeys) randomizeClientKey(c);
+ if (config.cluster_mode && c->staglen > 0) setClusterKeyHashTag(c);
+ atomicGet(config.slots_last_update, c->slots_last_update);
c->start = ustime();
c->latency = -1;
}
-
if (sdslen(c->obuf) > c->written) {
void *ptr = c->obuf+c->written;
ssize_t nwritten = write(c->context->fd,ptr,sdslen(c->obuf)-c->written);
@@ -286,8 +551,8 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
}
c->written += nwritten;
if (sdslen(c->obuf) == c->written) {
- aeDeleteFileEvent(config.el,c->context->fd,AE_WRITABLE);
- aeCreateFileEvent(config.el,c->context->fd,AE_READABLE,readHandler,c);
+ aeDeleteFileEvent(el,c->context->fd,AE_WRITABLE);
+ aeCreateFileEvent(el,c->context->fd,AE_READABLE,readHandler,c);
}
}
}
@@ -313,23 +578,43 @@ static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
* for arguments randomization.
*
* Even when cloning another client, prefix commands are applied if needed.*/
-static client createClient(char *cmd, size_t len, client from) {
+static client createClient(char *cmd, size_t len, client from, int thread_id) {
int j;
+ int is_cluster_client = (config.cluster_mode && thread_id >= 0);
client c = zmalloc(sizeof(struct _client));
- if (config.hostsocket == NULL) {
- c->context = redisConnectNonBlock(config.hostip,config.hostport);
+ const char *ip = NULL;
+ int port = 0;
+ c->cluster_node = NULL;
+ if (config.hostsocket == NULL || is_cluster_client) {
+ if (!is_cluster_client) {
+ ip = config.hostip;
+ port = config.hostport;
+ } else {
+ int node_idx = 0;
+ if (config.num_threads < config.cluster_node_count)
+ node_idx = config.liveclients % config.cluster_node_count;
+ else
+ node_idx = thread_id % config.cluster_node_count;
+ clusterNode *node = config.cluster_nodes[node_idx];
+ assert(node != NULL);
+ ip = (const char *) node->ip;
+ port = node->port;
+ c->cluster_node = node;
+ }
+ c->context = redisConnectNonBlock(ip,port);
} else {
c->context = redisConnectUnixNonBlock(config.hostsocket);
}
if (c->context->err) {
fprintf(stderr,"Could not connect to Redis at ");
- if (config.hostsocket == NULL)
- fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,c->context->errstr);
+ if (config.hostsocket == NULL || is_cluster_client)
+ fprintf(stderr,"%s:%d: %s\n",ip,port,c->context->errstr);
else
fprintf(stderr,"%s: %s\n",config.hostsocket,c->context->errstr);
exit(1);
}
+ c->thread_id = thread_id;
/* Suppress hiredis cleanup of unused buffers for max speed. */
c->context->reader->maxbuf = 0;
@@ -349,11 +634,19 @@ static client createClient(char *cmd, size_t len, client from) {
c->prefix_pending++;
}
+ if (config.enable_tracking) {
+ char *buf = NULL;
+ int len = redisFormatCommand(&buf, "CLIENT TRACKING on");
+ c->obuf = sdscatlen(c->obuf, buf, len);
+ free(buf);
+ c->prefix_pending++;
+ }
+
/* If a DB number different than zero is selected, prefix our request
* buffer with the SELECT command, that will be discarded the first
* time the replies are received, so if the client is reused the
* SELECT command will not be used again. */
- if (config.dbnum != 0) {
+ if (config.dbnum != 0 && !is_cluster_client) {
c->obuf = sdscatprintf(c->obuf,"*2\r\n$6\r\nSELECT\r\n$%d\r\n%s\r\n",
(int)sdslen(config.dbnumstr),config.dbnumstr);
c->prefix_pending++;
@@ -373,6 +666,8 @@ static client createClient(char *cmd, size_t len, client from) {
c->pending = config.pipeline+c->prefix_pending;
c->randptr = NULL;
c->randlen = 0;
+ c->stagptr = NULL;
+ c->staglen = 0;
/* Find substrings in the output buffer that need to be randomized. */
if (config.randomkeys) {
@@ -403,18 +698,57 @@ static client createClient(char *cmd, size_t len, client from) {
}
}
}
+ /* If cluster mode is enabled, set slot hashtags pointers. */
+ if (config.cluster_mode) {
+ if (from) {
+ c->staglen = from->staglen;
+ c->stagfree = 0;
+ c->stagptr = zmalloc(sizeof(char*)*c->staglen);
+ /* copy the offsets. */
+ for (j = 0; j < (int)c->staglen; j++) {
+ c->stagptr[j] = c->obuf + (from->stagptr[j]-from->obuf);
+ /* Adjust for the different select prefix length. */
+ c->stagptr[j] += c->prefixlen - from->prefixlen;
+ }
+ } else {
+ char *p = c->obuf;
+
+ c->staglen = 0;
+ c->stagfree = RANDPTR_INITIAL_SIZE;
+ c->stagptr = zmalloc(sizeof(char*)*c->stagfree);
+ while ((p = strstr(p,"{tag}")) != NULL) {
+ if (c->stagfree == 0) {
+ c->stagptr = zrealloc(c->stagptr,
+ sizeof(char*) * c->staglen*2);
+ c->stagfree += c->staglen;
+ }
+ c->stagptr[c->staglen++] = p;
+ c->stagfree--;
+ p += 5; /* 12 is strlen("{tag}"). */
+ }
+ }
+ }
+ aeEventLoop *el = NULL;
+ if (thread_id < 0) el = config.el;
+ else {
+ benchmarkThread *thread = config.threads[thread_id];
+ el = thread->el;
+ }
if (config.idlemode == 0)
- aeCreateFileEvent(config.el,c->context->fd,AE_WRITABLE,writeHandler,c);
+ aeCreateFileEvent(el,c->context->fd,AE_WRITABLE,writeHandler,c);
listAddNodeTail(config.clients,c);
- config.liveclients++;
+ atomicIncr(config.liveclients, 1);
+ atomicGet(config.slots_last_update, c->slots_last_update);
return c;
}
static void createMissingClients(client c) {
int n = 0;
-
while(config.liveclients < config.numclients) {
- createClient(NULL,0,c);
+ int thread_id = -1;
+ if (config.num_threads)
+ thread_id = config.liveclients % config.num_threads;
+ createClient(NULL,0,c,thread_id);
/* Listen backlog is quite limited on most systems */
if (++n > 64) {
@@ -428,8 +762,19 @@ static int compareLatency(const void *a, const void *b) {
return (*(long long*)a)-(*(long long*)b);
}
+static int ipow(int base, int exp) {
+ int result = 1;
+ while (exp) {
+ if (exp & 1) result *= base;
+ exp /= 2;
+ base *= base;
+ }
+ return result;
+}
+
static void showLatencyReport(void) {
int i, curlat = 0;
+ int usbetweenlat = ipow(10, MAX_LATENCY_PRECISION-config.precision);
float perc, reqpersec;
reqpersec = (float)config.requests_finished/((float)config.totlatency/1000);
@@ -440,14 +785,50 @@ static void showLatencyReport(void) {
printf(" %d parallel clients\n", config.numclients);
printf(" %d bytes payload\n", config.datasize);
printf(" keep alive: %d\n", config.keepalive);
+ if (config.cluster_mode) {
+ printf(" cluster mode: yes (%d masters)\n",
+ config.cluster_node_count);
+ int m ;
+ for (m = 0; m < config.cluster_node_count; m++) {
+ clusterNode *node = config.cluster_nodes[m];
+ redisConfig *cfg = node->redis_config;
+ if (cfg == NULL) continue;
+ printf(" node [%d] configuration:\n",m );
+ printf(" save: %s\n",
+ sdslen(cfg->save) ? cfg->save : "NONE");
+ printf(" appendonly: %s\n", cfg->appendonly);
+ }
+ } else {
+ if (config.redis_config) {
+ printf(" host configuration \"save\": %s\n",
+ config.redis_config->save);
+ printf(" host configuration \"appendonly\": %s\n",
+ config.redis_config->appendonly);
+ }
+ }
+ printf(" multi-thread: %s\n", (config.num_threads ? "yes" : "no"));
+ if (config.num_threads)
+ printf(" threads: %d\n", config.num_threads);
+
printf("\n");
qsort(config.latency,config.requests,sizeof(long long),compareLatency);
for (i = 0; i < config.requests; i++) {
- if (config.latency[i]/1000 != curlat || i == (config.requests-1)) {
- curlat = config.latency[i]/1000;
+ if (config.latency[i]/usbetweenlat != curlat ||
+ i == (config.requests-1))
+ {
+ /* After the 2 milliseconds latency to have percentages split
+ * by decimals will just add a lot of noise to the output. */
+ if (config.latency[i] >= 2000) {
+ config.precision = 0;
+ usbetweenlat = ipow(10,
+ MAX_LATENCY_PRECISION-config.precision);
+ }
+
+ curlat = config.latency[i]/usbetweenlat;
perc = ((float)(i+1)*100)/config.requests;
- printf("%.2f%% <= %d milliseconds\n", perc, curlat);
+ printf("%.2f%% <= %.*f milliseconds\n", perc, config.precision,
+ curlat/pow(10.0, config.precision));
}
}
printf("%.2f requests per second\n\n", reqpersec);
@@ -458,6 +839,29 @@ static void showLatencyReport(void) {
}
}
+static void initBenchmarkThreads() {
+ int i;
+ if (config.threads) freeBenchmarkThreads();
+ config.threads = zmalloc(config.num_threads * sizeof(benchmarkThread*));
+ for (i = 0; i < config.num_threads; i++) {
+ benchmarkThread *thread = createBenchmarkThread(i);
+ config.threads[i] = thread;
+ }
+}
+
+static void startBenchmarkThreads() {
+ int i;
+ for (i = 0; i < config.num_threads; i++) {
+ benchmarkThread *t = config.threads[i];
+ if (pthread_create(&(t->thread), NULL, execBenchmarkThread, t)){
+ fprintf(stderr, "FATAL: Failed to start thread %d.\n", i);
+ exit(1);
+ }
+ }
+ for (i = 0; i < config.num_threads; i++)
+ pthread_join(config.threads[i]->thread, NULL);
+}
+
static void benchmark(char *title, char *cmd, int len) {
client c;
@@ -465,15 +869,404 @@ static void benchmark(char *title, char *cmd, int len) {
config.requests_issued = 0;
config.requests_finished = 0;
- c = createClient(cmd,len,NULL);
+ if (config.num_threads) initBenchmarkThreads();
+
+ int thread_id = config.num_threads > 0 ? 0 : -1;
+ c = createClient(cmd,len,NULL,thread_id);
createMissingClients(c);
config.start = mstime();
- aeMain(config.el);
+ if (!config.num_threads) aeMain(config.el);
+ else startBenchmarkThreads();
config.totlatency = mstime()-config.start;
showLatencyReport();
freeAllClients();
+ if (config.threads) freeBenchmarkThreads();
+}
+
+/* Thread functions. */
+
+static benchmarkThread *createBenchmarkThread(int index) {
+ benchmarkThread *thread = zmalloc(sizeof(*thread));
+ if (thread == NULL) return NULL;
+ thread->index = index;
+ thread->el = aeCreateEventLoop(1024*10);
+ aeCreateTimeEvent(thread->el,1,showThroughput,NULL,NULL);
+ return thread;
+}
+
+static void freeBenchmarkThread(benchmarkThread *thread) {
+ if (thread->el) aeDeleteEventLoop(thread->el);
+ zfree(thread);
+}
+
+static void freeBenchmarkThreads() {
+ int i = 0;
+ for (; i < config.num_threads; i++) {
+ benchmarkThread *thread = config.threads[i];
+ if (thread) freeBenchmarkThread(thread);
+ }
+ zfree(config.threads);
+ config.threads = NULL;
+}
+
+static void *execBenchmarkThread(void *ptr) {
+ benchmarkThread *thread = (benchmarkThread *) ptr;
+ aeMain(thread->el);
+ return NULL;
+}
+
+/* Cluster helper functions. */
+
+static clusterNode *createClusterNode(char *ip, int port) {
+ clusterNode *node = zmalloc(sizeof(*node));
+ if (!node) return NULL;
+ node->ip = ip;
+ node->port = port;
+ node->name = NULL;
+ node->flags = 0;
+ node->replicate = NULL;
+ node->replicas_count = 0;
+ node->slots = zmalloc(CLUSTER_SLOTS * sizeof(int));
+ node->slots_count = 0;
+ node->current_slot_index = 0;
+ node->updated_slots = NULL;
+ node->updated_slots_count = 0;
+ node->migrating = NULL;
+ node->importing = NULL;
+ node->migrating_count = 0;
+ node->importing_count = 0;
+ node->redis_config = NULL;
+ return node;
+}
+
+static void freeClusterNode(clusterNode *node) {
+ int i;
+ if (node->name) sdsfree(node->name);
+ if (node->replicate) sdsfree(node->replicate);
+ if (node->migrating != NULL) {
+ for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]);
+ zfree(node->migrating);
+ }
+ if (node->importing != NULL) {
+ for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]);
+ zfree(node->importing);
+ }
+ /* If the node is not the reference node, that uses the address from
+ * config.hostip and config.hostport, then the node ip has been
+ * allocated by fetchClusterConfiguration, so it must be freed. */
+ if (node->ip && strcmp(node->ip, config.hostip) != 0) sdsfree(node->ip);
+ if (node->redis_config != NULL) freeRedisConfig(node->redis_config);
+ zfree(node->slots);
+ zfree(node);
+}
+
+static void freeClusterNodes() {
+ int i = 0;
+ for (; i < config.cluster_node_count; i++) {
+ clusterNode *n = config.cluster_nodes[i];
+ if (n) freeClusterNode(n);
+ }
+ zfree(config.cluster_nodes);
+ config.cluster_nodes = NULL;
+}
+
+static clusterNode **addClusterNode(clusterNode *node) {
+ int count = config.cluster_node_count + 1;
+ config.cluster_nodes = zrealloc(config.cluster_nodes,
+ count * sizeof(*node));
+ if (!config.cluster_nodes) return NULL;
+ config.cluster_nodes[config.cluster_node_count++] = node;
+ return config.cluster_nodes;
+}
+
+static int fetchClusterConfiguration() {
+ int success = 1;
+ redisContext *ctx = NULL;
+ redisReply *reply = NULL;
+ if (config.hostsocket == NULL)
+ ctx = redisConnect(config.hostip,config.hostport);
+ else
+ ctx = redisConnectUnix(config.hostsocket);
+ if (ctx->err) {
+ fprintf(stderr,"Could not connect to Redis at ");
+ if (config.hostsocket == NULL) {
+ fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,
+ ctx->errstr);
+ } else fprintf(stderr,"%s: %s\n",config.hostsocket,ctx->errstr);
+ exit(1);
+ }
+ clusterNode *firstNode = createClusterNode((char *) config.hostip,
+ config.hostport);
+ if (!firstNode) {success = 0; goto cleanup;}
+ reply = redisCommand(ctx, "CLUSTER NODES");
+ success = (reply != NULL);
+ if (!success) goto cleanup;
+ success = (reply->type != REDIS_REPLY_ERROR);
+ if (!success) {
+ if (config.hostsocket == NULL) {
+ fprintf(stderr, "Cluster node %s:%d replied with error:\n%s\n",
+ config.hostip, config.hostport, reply->str);
+ } else {
+ fprintf(stderr, "Cluster node %s replied with error:\n%s\n",
+ config.hostsocket, reply->str);
+ }
+ goto cleanup;
+ }
+ char *lines = reply->str, *p, *line;
+ while ((p = strstr(lines, "\n")) != NULL) {
+ *p = '\0';
+ line = lines;
+ lines = p + 1;
+ char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL;
+ int i = 0;
+ while ((p = strchr(line, ' ')) != NULL) {
+ *p = '\0';
+ char *token = line;
+ line = p + 1;
+ switch(i++){
+ case 0: name = token; break;
+ case 1: addr = token; break;
+ case 2: flags = token; break;
+ case 3: master_id = token; break;
+ }
+ if (i == 8) break; // Slots
+ }
+ if (!flags) {
+ fprintf(stderr, "Invalid CLUSTER NODES reply: missing flags.\n");
+ success = 0;
+ goto cleanup;
+ }
+ int myself = (strstr(flags, "myself") != NULL);
+ int is_replica = (strstr(flags, "slave") != NULL ||
+ (master_id != NULL && master_id[0] != '-'));
+ if (is_replica) continue;
+ if (addr == NULL) {
+ fprintf(stderr, "Invalid CLUSTER NODES reply: missing addr.\n");
+ success = 0;
+ goto cleanup;
+ }
+ clusterNode *node = NULL;
+ char *ip = NULL;
+ int port = 0;
+ char *paddr = strchr(addr, ':');
+ if (paddr != NULL) {
+ *paddr = '\0';
+ ip = addr;
+ addr = paddr + 1;
+ /* If internal bus is specified, then just drop it. */
+ if ((paddr = strchr(addr, '@')) != NULL) *paddr = '\0';
+ port = atoi(addr);
+ }
+ if (myself) {
+ node = firstNode;
+ if (node->ip == NULL && ip != NULL) {
+ node->ip = ip;
+ node->port = port;
+ }
+ } else {
+ node = createClusterNode(sdsnew(ip), port);
+ }
+ if (node == NULL) {
+ success = 0;
+ goto cleanup;
+ }
+ if (name != NULL) node->name = sdsnew(name);
+ if (i == 8) {
+ int remaining = strlen(line);
+ while (remaining > 0) {
+ p = strchr(line, ' ');
+ if (p == NULL) p = line + remaining;
+ remaining -= (p - line);
+
+ char *slotsdef = line;
+ *p = '\0';
+ if (remaining) {
+ line = p + 1;
+ remaining--;
+ } else line = p;
+ char *dash = NULL;
+ if (slotsdef[0] == '[') {
+ slotsdef++;
+ if ((p = strstr(slotsdef, "->-"))) { // Migrating
+ *p = '\0';
+ p += 3;
+ char *closing_bracket = strchr(p, ']');
+ if (closing_bracket) *closing_bracket = '\0';
+ sds slot = sdsnew(slotsdef);
+ sds dst = sdsnew(p);
+ node->migrating_count += 2;
+ node->migrating =
+ zrealloc(node->migrating,
+ (node->migrating_count * sizeof(sds)));
+ node->migrating[node->migrating_count - 2] =
+ slot;
+ node->migrating[node->migrating_count - 1] =
+ dst;
+ } else if ((p = strstr(slotsdef, "-<-"))) {//Importing
+ *p = '\0';
+ p += 3;
+ char *closing_bracket = strchr(p, ']');
+ if (closing_bracket) *closing_bracket = '\0';
+ sds slot = sdsnew(slotsdef);
+ sds src = sdsnew(p);
+ node->importing_count += 2;
+ node->importing = zrealloc(node->importing,
+ (node->importing_count * sizeof(sds)));
+ node->importing[node->importing_count - 2] =
+ slot;
+ node->importing[node->importing_count - 1] =
+ src;
+ }
+ } else if ((dash = strchr(slotsdef, '-')) != NULL) {
+ p = dash;
+ int start, stop;
+ *p = '\0';
+ start = atoi(slotsdef);
+ stop = atoi(p + 1);
+ while (start <= stop) {
+ int slot = start++;
+ node->slots[node->slots_count++] = slot;
+ }
+ } else if (p > slotsdef) {
+ int slot = atoi(slotsdef);
+ node->slots[node->slots_count++] = slot;
+ }
+ }
+ }
+ if (node->slots_count == 0) {
+ printf("WARNING: master node %s:%d has no slots, skipping...\n",
+ node->ip, node->port);
+ continue;
+ }
+ if (!addClusterNode(node)) {
+ success = 0;
+ goto cleanup;
+ }
+ }
+cleanup:
+ if (ctx) redisFree(ctx);
+ if (!success) {
+ if (config.cluster_nodes) freeClusterNodes();
+ }
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+/* Request the current cluster slots configuration by calling CLUSTER SLOTS
+ * and atomically update the slots after a successful reply. */
+static int fetchClusterSlotsConfiguration(client c) {
+ UNUSED(c);
+ int success = 1, is_fetching_slots = 0, last_update = 0;
+ size_t i;
+ atomicGet(config.slots_last_update, last_update);
+ if (c->slots_last_update < last_update) {
+ c->slots_last_update = last_update;
+ return -1;
+ }
+ redisReply *reply = NULL;
+ atomicGetIncr(config.is_fetching_slots, is_fetching_slots, 1);
+ if (is_fetching_slots) return -1; //TODO: use other codes || errno ?
+ atomicSet(config.is_fetching_slots, 1);
+ if (config.showerrors)
+ printf("Cluster slots configuration changed, fetching new one...\n");
+ const char *errmsg = "Failed to update cluster slots configuration";
+ static dictType dtype = {
+ dictSdsHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCompare, /* key compare */
+ NULL, /* key destructor */
+ NULL /* val destructor */
+ };
+ /* printf("[%d] fetchClusterSlotsConfiguration\n", c->thread_id); */
+ dict *masters = dictCreate(&dtype, NULL);
+ redisContext *ctx = NULL;
+ for (i = 0; i < (size_t) config.cluster_node_count; i++) {
+ clusterNode *node = config.cluster_nodes[i];
+ assert(node->ip != NULL);
+ assert(node->name != NULL);
+ assert(node->port);
+ /* Use first node as entry point to connect to. */
+ if (ctx == NULL) {
+ ctx = redisConnect(node->ip, node->port);
+ if (!ctx || ctx->err) {
+ success = 0;
+ if (ctx && ctx->err)
+ fprintf(stderr, "REDIS CONNECTION ERROR: %s\n", ctx->errstr);
+ goto cleanup;
+ }
+ }
+ if (node->updated_slots != NULL)
+ zfree(node->updated_slots);
+ node->updated_slots = NULL;
+ node->updated_slots_count = 0;
+ dictReplace(masters, node->name, node) ;
+ }
+ reply = redisCommand(ctx, "CLUSTER SLOTS");
+ if (reply == NULL || reply->type == REDIS_REPLY_ERROR) {
+ success = 0;
+ if (reply)
+ fprintf(stderr,"%s\nCLUSTER SLOTS ERROR: %s\n",errmsg,reply->str);
+ goto cleanup;
+ }
+ assert(reply->type == REDIS_REPLY_ARRAY);
+ for (i = 0; i < reply->elements; i++) {
+ redisReply *r = reply->element[i];
+ assert(r->type == REDIS_REPLY_ARRAY);
+ assert(r->elements >= 3);
+ int from, to, slot;
+ from = r->element[0]->integer;
+ to = r->element[1]->integer;
+ redisReply *nr = r->element[2];
+ assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 3);
+ assert(nr->element[2]->str != NULL);
+ sds name = sdsnew(nr->element[2]->str);
+ dictEntry *entry = dictFind(masters, name);
+ if (entry == NULL) {
+ success = 0;
+ fprintf(stderr, "%s: could not find node with ID %s in current "
+ "configuration.\n", errmsg, name);
+ if (name) sdsfree(name);
+ goto cleanup;
+ }
+ sdsfree(name);
+ clusterNode *node = dictGetVal(entry);
+ if (node->updated_slots == NULL)
+ node->updated_slots = zcalloc(CLUSTER_SLOTS * sizeof(int));
+ for (slot = from; slot <= to; slot++)
+ node->updated_slots[node->updated_slots_count++] = slot;
+ }
+ updateClusterSlotsConfiguration();
+cleanup:
+ freeReplyObject(reply);
+ redisFree(ctx);
+ dictRelease(masters);
+ atomicSet(config.is_fetching_slots, 0);
+ return success;
+}
+
+/* Atomically update the new slots configuration. */
+static void updateClusterSlotsConfiguration() {
+ pthread_mutex_lock(&config.is_updating_slots_mutex);
+ atomicSet(config.is_updating_slots, 1);
+ int i;
+ for (i = 0; i < config.cluster_node_count; i++) {
+ clusterNode *node = config.cluster_nodes[i];
+ if (node->updated_slots != NULL) {
+ int *oldslots = node->slots;
+ node->slots = node->updated_slots;
+ node->slots_count = node->updated_slots_count;
+ node->current_slot_index = 0;
+ node->updated_slots = NULL;
+ node->updated_slots_count = 0;
+ zfree(oldslots);
+ }
+ }
+ atomicSet(config.is_updating_slots, 0);
+ atomicIncr(config.slots_last_update, 1);
+ pthread_mutex_unlock(&config.is_updating_slots_mutex);
}
/* Returns number of consumed options. */
@@ -517,8 +1310,13 @@ int parseOptions(int argc, const char **argv) {
if (config.pipeline <= 0) config.pipeline=1;
} else if (!strcmp(argv[i],"-r")) {
if (lastarg) goto invalid;
+ const char *next = argv[++i], *p = next;
+ if (*p == '-') {
+ p++;
+ if (*p < '0' || *p > '9') goto invalid;
+ }
config.randomkeys = 1;
- config.randomkeys_keyspacelen = atoi(argv[++i]);
+ config.randomkeys_keyspacelen = atoi(next);
if (config.randomkeys_keyspacelen < 0)
config.randomkeys_keyspacelen = 0;
} else if (!strcmp(argv[i],"-q")) {
@@ -546,6 +1344,23 @@ int parseOptions(int argc, const char **argv) {
if (lastarg) goto invalid;
config.dbnum = atoi(argv[++i]);
config.dbnumstr = sdsfromlonglong(config.dbnum);
+ } else if (!strcmp(argv[i],"--precision")) {
+ if (lastarg) goto invalid;
+ config.precision = atoi(argv[++i]);
+ if (config.precision < 0) config.precision = 0;
+ if (config.precision > MAX_LATENCY_PRECISION) config.precision = MAX_LATENCY_PRECISION;
+ } else if (!strcmp(argv[i],"--threads")) {
+ if (lastarg) goto invalid;
+ config.num_threads = atoi(argv[++i]);
+ if (config.num_threads > MAX_THREADS) {
+ printf("WARNING: too many threads, limiting threads to %d.\n",
+ MAX_THREADS);
+ config.num_threads = MAX_THREADS;
+ } else if (config.num_threads < 0) config.num_threads = 0;
+ } else if (!strcmp(argv[i],"--cluster")) {
+ config.cluster_mode = 1;
+ } else if (!strcmp(argv[i],"--enable-tracking")) {
+ config.enable_tracking = 1;
} else if (!strcmp(argv[i],"--help")) {
exit_status = 0;
goto usage;
@@ -574,6 +1389,9 @@ usage:
" -n <requests> Total number of requests (default 100000)\n"
" -d <size> Data size of SET/GET value in bytes (default 3)\n"
" --dbnum <db> SELECT the specified db number (default 0)\n"
+" --threads <num> Enable multi-thread mode.\n"
+" --cluster Enable cluster mode.\n"
+" --enable-tracking Send CLIENT TRACKING on before starting benchmark.\n"
" -k <boolean> 1=keep alive 0=reconnect (default 1)\n"
" -r <keyspacelen> Use random keys for SET/GET/INCR, random values for SADD\n"
" Using this option the benchmark will expand the string __rand_int__\n"
@@ -585,6 +1403,7 @@ usage:
" -e If server replies with errors, show them on stdout.\n"
" (no more than 1 error per second is displayed)\n"
" -q Quiet. Just show query/sec values\n"
+" --precision Number of decimal places to display in latency output (default 0)\n"
" --csv Output in CSV format\n"
" -l Loop. Run the tests forever\n"
" -t <tests> Only run the comma separated list of tests. The test\n"
@@ -613,11 +1432,19 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
UNUSED(eventLoop);
UNUSED(id);
UNUSED(clientData);
+ int liveclients = 0;
+ int requests_finished = 0;
+ atomicGet(config.liveclients, liveclients);
+ atomicGet(config.requests_finished, requests_finished);
- if (config.liveclients == 0 && config.requests_finished != config.requests) {
+ if (liveclients == 0 && requests_finished != config.requests) {
fprintf(stderr,"All clients disconnected... aborting.\n");
exit(1);
}
+ if (config.num_threads && requests_finished >= config.requests) {
+ aeStop(eventLoop);
+ return AE_NOMORE;
+ }
if (config.csv) return 250;
if (config.idlemode == 1) {
printf("clients: %d\r", config.liveclients);
@@ -625,7 +1452,7 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
return 250;
}
float dt = (float)(mstime()-config.start)/1000.0;
- float rps = (float)config.requests_finished/dt;
+ float rps = (float)requests_finished/dt;
printf("%s: %.2f\r", config.title, rps);
fflush(stdout);
return 250; /* every 250ms */
@@ -679,6 +1506,17 @@ int main(int argc, const char **argv) {
config.tests = NULL;
config.dbnum = 0;
config.auth = NULL;
+ config.precision = 1;
+ config.num_threads = 0;
+ config.threads = NULL;
+ config.cluster_mode = 0;
+ config.cluster_node_count = 0;
+ config.cluster_nodes = NULL;
+ config.redis_config = NULL;
+ config.is_fetching_slots = 0;
+ config.is_updating_slots = 0;
+ config.slots_last_update = 0;
+ config.enable_tracking = 0;
i = parseOptions(argc,argv);
argc -= i;
@@ -686,15 +1524,77 @@ int main(int argc, const char **argv) {
config.latency = zmalloc(sizeof(long long)*config.requests);
+ if (config.cluster_mode) {
+ /* Fetch cluster configuration. */
+ if (!fetchClusterConfiguration() || !config.cluster_nodes) {
+ if (!config.hostsocket) {
+ fprintf(stderr, "Failed to fetch cluster configuration from "
+ "%s:%d\n", config.hostip, config.hostport);
+ } else {
+ fprintf(stderr, "Failed to fetch cluster configuration from "
+ "%s\n", config.hostsocket);
+ }
+ exit(1);
+ }
+ if (config.cluster_node_count <= 1) {
+ fprintf(stderr, "Invalid cluster: %d node(s).\n",
+ config.cluster_node_count);
+ exit(1);
+ }
+ printf("Cluster has %d master nodes:\n\n", config.cluster_node_count);
+ int i = 0;
+ for (; i < config.cluster_node_count; i++) {
+ clusterNode *node = config.cluster_nodes[i];
+ if (!node) {
+ fprintf(stderr, "Invalid cluster node #%d\n", i);
+ exit(1);
+ }
+ printf("Master %d: ", i);
+ if (node->name) printf("%s ", node->name);
+ printf("%s:%d\n", node->ip, node->port);
+ node->redis_config = getRedisConfig(node->ip, node->port, NULL);
+ if (node->redis_config == NULL) {
+ fprintf(stderr, "WARN: could not fetch node CONFIG %s:%d\n",
+ node->ip, node->port);
+ }
+ }
+ printf("\n");
+ /* Automatically set thread number to node count if not specified
+ * by the user. */
+ if (config.num_threads == 0)
+ config.num_threads = config.cluster_node_count;
+ } else {
+ config.redis_config =
+ getRedisConfig(config.hostip, config.hostport, config.hostsocket);
+ if (config.redis_config == NULL)
+ fprintf(stderr, "WARN: could not fetch server CONFIG\n");
+ }
+
+ if (config.num_threads > 0) {
+ pthread_mutex_init(&(config.requests_issued_mutex), NULL);
+ pthread_mutex_init(&(config.requests_finished_mutex), NULL);
+ pthread_mutex_init(&(config.liveclients_mutex), NULL);
+ pthread_mutex_init(&(config.is_fetching_slots_mutex), NULL);
+ pthread_mutex_init(&(config.is_updating_slots_mutex), NULL);
+ pthread_mutex_init(&(config.updating_slots_mutex), NULL);
+ pthread_mutex_init(&(config.slots_last_update_mutex), NULL);
+ }
+
if (config.keepalive == 0) {
printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n");
}
if (config.idlemode) {
printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.numclients);
- c = createClient("",0,NULL); /* will never receive a reply */
+ int thread_id = -1, use_threads = (config.num_threads > 0);
+ if (use_threads) {
+ thread_id = 0;
+ initBenchmarkThreads();
+ }
+ c = createClient("",0,NULL,thread_id); /* will never receive a reply */
createMissingClients(c);
- aeMain(config.el);
+ if (use_threads) startBenchmarkThreads();
+ else aeMain(config.el);
/* and will wait for every */
}
@@ -712,6 +1612,7 @@ int main(int argc, const char **argv) {
free(cmd);
} while(config.loop);
+ if (config.redis_config != NULL) freeRedisConfig(config.redis_config);
return 0;
}
@@ -731,63 +1632,63 @@ int main(int argc, const char **argv) {
}
if (test_is_selected("set")) {
- len = redisFormatCommand(&cmd,"SET key:__rand_int__ %s",data);
+ len = redisFormatCommand(&cmd,"SET key:{tag}:__rand_int__ %s",data);
benchmark("SET",cmd,len);
free(cmd);
}
if (test_is_selected("get")) {
- len = redisFormatCommand(&cmd,"GET key:__rand_int__");
+ len = redisFormatCommand(&cmd,"GET key:{tag}:__rand_int__");
benchmark("GET",cmd,len);
free(cmd);
}
if (test_is_selected("incr")) {
- len = redisFormatCommand(&cmd,"INCR counter:__rand_int__");
+ len = redisFormatCommand(&cmd,"INCR counter:{tag}:__rand_int__");
benchmark("INCR",cmd,len);
free(cmd);
}
if (test_is_selected("lpush")) {
- len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
+ len = redisFormatCommand(&cmd,"LPUSH mylist:{tag} %s",data);
benchmark("LPUSH",cmd,len);
free(cmd);
}
if (test_is_selected("rpush")) {
- len = redisFormatCommand(&cmd,"RPUSH mylist %s",data);
+ len = redisFormatCommand(&cmd,"RPUSH mylist:{tag} %s",data);
benchmark("RPUSH",cmd,len);
free(cmd);
}
if (test_is_selected("lpop")) {
- len = redisFormatCommand(&cmd,"LPOP mylist");
+ len = redisFormatCommand(&cmd,"LPOP mylist:{tag}");
benchmark("LPOP",cmd,len);
free(cmd);
}
if (test_is_selected("rpop")) {
- len = redisFormatCommand(&cmd,"RPOP mylist");
+ len = redisFormatCommand(&cmd,"RPOP mylist:{tag}");
benchmark("RPOP",cmd,len);
free(cmd);
}
if (test_is_selected("sadd")) {
len = redisFormatCommand(&cmd,
- "SADD myset element:__rand_int__");
+ "SADD myset:{tag} element:__rand_int__");
benchmark("SADD",cmd,len);
free(cmd);
}
if (test_is_selected("hset")) {
len = redisFormatCommand(&cmd,
- "HSET myset:__rand_int__ element:__rand_int__ %s",data);
+ "HSET myhash:{tag}:__rand_int__ element:__rand_int__ %s",data);
benchmark("HSET",cmd,len);
free(cmd);
}
if (test_is_selected("spop")) {
- len = redisFormatCommand(&cmd,"SPOP myset");
+ len = redisFormatCommand(&cmd,"SPOP myset:{tag}");
benchmark("SPOP",cmd,len);
free(cmd);
}
@@ -798,31 +1699,31 @@ int main(int argc, const char **argv) {
test_is_selected("lrange_500") ||
test_is_selected("lrange_600"))
{
- len = redisFormatCommand(&cmd,"LPUSH mylist %s",data);
+ len = redisFormatCommand(&cmd,"LPUSH mylist:{tag} %s",data);
benchmark("LPUSH (needed to benchmark LRANGE)",cmd,len);
free(cmd);
}
if (test_is_selected("lrange") || test_is_selected("lrange_100")) {
- len = redisFormatCommand(&cmd,"LRANGE mylist 0 99");
+ len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 99");
benchmark("LRANGE_100 (first 100 elements)",cmd,len);
free(cmd);
}
if (test_is_selected("lrange") || test_is_selected("lrange_300")) {
- len = redisFormatCommand(&cmd,"LRANGE mylist 0 299");
+ len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 299");
benchmark("LRANGE_300 (first 300 elements)",cmd,len);
free(cmd);
}
if (test_is_selected("lrange") || test_is_selected("lrange_500")) {
- len = redisFormatCommand(&cmd,"LRANGE mylist 0 449");
+ len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 449");
benchmark("LRANGE_500 (first 450 elements)",cmd,len);
free(cmd);
}
if (test_is_selected("lrange") || test_is_selected("lrange_600")) {
- len = redisFormatCommand(&cmd,"LRANGE mylist 0 599");
+ len = redisFormatCommand(&cmd,"LRANGE mylist:{tag} 0 599");
benchmark("LRANGE_600 (first 600 elements)",cmd,len);
free(cmd);
}
@@ -831,7 +1732,7 @@ int main(int argc, const char **argv) {
const char *argv[21];
argv[0] = "MSET";
for (i = 1; i < 21; i += 2) {
- argv[i] = "key:__rand_int__";
+ argv[i] = "key:{tag}:__rand_int__";
argv[i+1] = data;
}
len = redisFormatCommandArgv(&cmd,21,argv,NULL);
@@ -842,5 +1743,7 @@ int main(int argc, const char **argv) {
if (!config.csv) printf("\n");
} while(config.loop);
+ if (config.redis_config != NULL) freeRedisConfig(config.redis_config);
+
return 0;
}
diff --git a/src/redis-check-aof.c b/src/redis-check-aof.c
index c4d5a225e..eedb09db5 100644
--- a/src/redis-check-aof.c
+++ b/src/redis-check-aof.c
@@ -33,11 +33,11 @@
#define ERROR(...) { \
char __buf[1024]; \
- sprintf(__buf, __VA_ARGS__); \
- sprintf(error, "0x%16llx: %s", (long long)epos, __buf); \
+ snprintf(__buf, sizeof(__buf), __VA_ARGS__); \
+ snprintf(error, sizeof(error), "0x%16llx: %s", (long long)epos, __buf); \
}
-static char error[1024];
+static char error[1044];
static off_t epos;
int consumeNewline(char *buf) {
diff --git a/src/redis-check-rdb.c b/src/redis-check-rdb.c
index 71ac50d03..5e7415046 100644
--- a/src/redis-check-rdb.c
+++ b/src/redis-check-rdb.c
@@ -34,7 +34,6 @@
void createSharedObjects(void);
void rdbLoadProgressCallback(rio *r, const void *buf, size_t len);
-long long rdbLoadMillisecondTime(rio *rdb);
int rdbCheckMode = 0;
struct {
@@ -85,7 +84,8 @@ char *rdb_type_string[] = {
"set-intset",
"zset-ziplist",
"hash-ziplist",
- "quicklist"
+ "quicklist",
+ "stream"
};
/* Show a few stats collected into 'rdbstate' */
@@ -201,10 +201,10 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
goto err;
}
- startLoading(fp);
+ expiretime = -1;
+ startLoadingFile(fp, rdbfilename);
while(1) {
robj *key, *val;
- expiretime = -1;
/* Read type. */
rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
@@ -216,21 +216,26 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
/* EXPIRETIME: load an expire associated with the next key
* to load. Note that after loading an expire we need to
* load the actual type, and continue. */
- if ((expiretime = rdbLoadTime(&rdb)) == -1) goto eoferr;
- /* We read the time so we need to read the object type again. */
- rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
- if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
- /* the EXPIRETIME opcode specifies time in seconds, so convert
- * into milliseconds. */
+ expiretime = rdbLoadTime(&rdb);
expiretime *= 1000;
+ if (rioGetReadError(&rdb)) goto eoferr;
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EXPIRETIME_MS) {
/* EXPIRETIME_MS: milliseconds precision expire times introduced
* with RDB v3. Like EXPIRETIME but no with more precision. */
rdbstate.doing = RDB_CHECK_DOING_READ_EXPIRE;
- if ((expiretime = rdbLoadMillisecondTime(&rdb)) == -1) goto eoferr;
- /* We read the time so we need to read the object type again. */
- rdbstate.doing = RDB_CHECK_DOING_READ_TYPE;
- if ((type = rdbLoadType(&rdb)) == -1) goto eoferr;
+ expiretime = rdbLoadMillisecondTime(&rdb, rdbver);
+ if (rioGetReadError(&rdb)) goto eoferr;
+ continue; /* Read next opcode. */
+ } else if (type == RDB_OPCODE_FREQ) {
+ /* FREQ: LFU frequency. */
+ uint8_t byte;
+ if (rioRead(&rdb,&byte,1) == 0) goto eoferr;
+ continue; /* Read next opcode. */
+ } else if (type == RDB_OPCODE_IDLE) {
+ /* IDLE: LRU idle time. */
+ if (rdbLoadLen(&rdb,NULL) == RDB_LENERR) goto eoferr;
+ continue; /* Read next opcode. */
} else if (type == RDB_OPCODE_EOF) {
/* EOF: End of file, exit the main loop. */
break;
@@ -282,19 +287,16 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
rdbstate.keys++;
/* Read value */
rdbstate.doing = RDB_CHECK_DOING_READ_OBJECT_VALUE;
- if ((val = rdbLoadObject(type,&rdb)) == NULL) goto eoferr;
- /* Check if the key already expired. This function is used when loading
- * an RDB file from disk, either at startup, or when an RDB was
- * received from the master. In the latter case, the master is
- * responsible for key expiry. If we would expire keys here, the
- * snapshot taken by the master may not be reflected on the slave. */
- if (server.masterhost == NULL && expiretime != -1 && expiretime < now)
+ if ((val = rdbLoadObject(type,&rdb,key)) == NULL) goto eoferr;
+ /* Check if the key already expired. */
+ if (expiretime != -1 && expiretime < now)
rdbstate.already_expired++;
if (expiretime != -1) rdbstate.expires++;
rdbstate.key = NULL;
decrRefCount(key);
decrRefCount(val);
rdbstate.key_type = -1;
+ expiretime = -1;
}
/* Verify the checksum if RDB version is >= 5 */
if (rdbver >= 5 && server.rdb_checksum) {
@@ -314,6 +316,7 @@ int redis_check_rdb(char *rdbfilename, FILE *fp) {
}
if (closefile) fclose(fp);
+ stopLoading();
return 0;
eoferr: /* unexpected end of file is handled here with a fatal exit */
@@ -324,6 +327,7 @@ eoferr: /* unexpected end of file is handled here with a fatal exit */
}
err:
if (closefile) fclose(fp);
+ stopLoading();
return 1;
}
diff --git a/src/redis-cli.c b/src/redis-cli.c
index d80973e75..6d07f7ba6 100644
--- a/src/redis-cli.c
+++ b/src/redis-cli.c
@@ -47,7 +47,13 @@
#include <math.h>
#include <hiredis.h>
+#ifdef USE_OPENSSL
+#include <openssl/ssl.h>
+#include <hiredis_ssl.h>
+#endif
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
+#include "dict.h"
+#include "adlist.h"
#include "zmalloc.h"
#include "linenoise.h"
#include "help.h"
@@ -65,6 +71,80 @@
#define REDIS_CLI_HISTFILE_DEFAULT ".rediscli_history"
#define REDIS_CLI_RCFILE_ENV "REDISCLI_RCFILE"
#define REDIS_CLI_RCFILE_DEFAULT ".redisclirc"
+#define REDIS_CLI_AUTH_ENV "REDISCLI_AUTH"
+
+#define CLUSTER_MANAGER_SLOTS 16384
+#define CLUSTER_MANAGER_MIGRATE_TIMEOUT 60000
+#define CLUSTER_MANAGER_MIGRATE_PIPELINE 10
+#define CLUSTER_MANAGER_REBALANCE_THRESHOLD 2
+
+#define CLUSTER_MANAGER_INVALID_HOST_ARG \
+ "[ERR] Invalid arguments: you need to pass either a valid " \
+ "address (ie. 120.0.0.1:7000) or space separated IP " \
+ "and port (ie. 120.0.0.1 7000)\n"
+#define CLUSTER_MANAGER_MODE() (config.cluster_manager_command.name != NULL)
+#define CLUSTER_MANAGER_MASTERS_COUNT(nodes, replicas) (nodes/(replicas + 1))
+#define CLUSTER_MANAGER_COMMAND(n,...) \
+ (redisCommand(n->context, __VA_ARGS__))
+
+#define CLUSTER_MANAGER_NODE_ARRAY_FREE(array) zfree(array->alloc)
+
+#define CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err) \
+ clusterManagerLogErr("Node %s:%d replied with error:\n%s\n", \
+ n->ip, n->port, err);
+
+#define clusterManagerLogInfo(...) \
+ clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_INFO,__VA_ARGS__)
+
+#define clusterManagerLogErr(...) \
+ clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_ERR,__VA_ARGS__)
+
+#define clusterManagerLogWarn(...) \
+ clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_WARN,__VA_ARGS__)
+
+#define clusterManagerLogOk(...) \
+ clusterManagerLog(CLUSTER_MANAGER_LOG_LVL_SUCCESS,__VA_ARGS__)
+
+#define CLUSTER_MANAGER_FLAG_MYSELF 1 << 0
+#define CLUSTER_MANAGER_FLAG_SLAVE 1 << 1
+#define CLUSTER_MANAGER_FLAG_FRIEND 1 << 2
+#define CLUSTER_MANAGER_FLAG_NOADDR 1 << 3
+#define CLUSTER_MANAGER_FLAG_DISCONNECT 1 << 4
+#define CLUSTER_MANAGER_FLAG_FAIL 1 << 5
+
+#define CLUSTER_MANAGER_CMD_FLAG_FIX 1 << 0
+#define CLUSTER_MANAGER_CMD_FLAG_SLAVE 1 << 1
+#define CLUSTER_MANAGER_CMD_FLAG_YES 1 << 2
+#define CLUSTER_MANAGER_CMD_FLAG_AUTOWEIGHTS 1 << 3
+#define CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER 1 << 4
+#define CLUSTER_MANAGER_CMD_FLAG_SIMULATE 1 << 5
+#define CLUSTER_MANAGER_CMD_FLAG_REPLACE 1 << 6
+#define CLUSTER_MANAGER_CMD_FLAG_COPY 1 << 7
+#define CLUSTER_MANAGER_CMD_FLAG_COLOR 1 << 8
+#define CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS 1 << 9
+
+#define CLUSTER_MANAGER_OPT_GETFRIENDS 1 << 0
+#define CLUSTER_MANAGER_OPT_COLD 1 << 1
+#define CLUSTER_MANAGER_OPT_UPDATE 1 << 2
+#define CLUSTER_MANAGER_OPT_QUIET 1 << 6
+#define CLUSTER_MANAGER_OPT_VERBOSE 1 << 7
+
+#define CLUSTER_MANAGER_LOG_LVL_INFO 1
+#define CLUSTER_MANAGER_LOG_LVL_WARN 2
+#define CLUSTER_MANAGER_LOG_LVL_ERR 3
+#define CLUSTER_MANAGER_LOG_LVL_SUCCESS 4
+
+#define CLUSTER_JOIN_CHECK_AFTER 20
+
+#define LOG_COLOR_BOLD "29;1m"
+#define LOG_COLOR_RED "31;1m"
+#define LOG_COLOR_GREEN "32;1m"
+#define LOG_COLOR_YELLOW "33;1m"
+#define LOG_COLOR_RESET "0m"
+
+/* cliConnect() flags. */
+#define CC_FORCE (1<<0) /* Re-connect if already connected. */
+#define CC_QUIET (1<<1) /* Don't log connecting errors. */
/* --latency-dist palettes. */
int spectrum_palette_color_size = 19;
@@ -77,11 +157,47 @@ int spectrum_palette_mono[] = {0,233,234,235,237,239,241,243,245,247,249,251,253
int *spectrum_palette;
int spectrum_palette_size;
+/* Dict Helpers */
+
+static uint64_t dictSdsHash(const void *key);
+static int dictSdsKeyCompare(void *privdata, const void *key1,
+ const void *key2);
+static void dictSdsDestructor(void *privdata, void *val);
+static void dictListDestructor(void *privdata, void *val);
+
+/* Cluster Manager Command Info */
+typedef struct clusterManagerCommand {
+ char *name;
+ int argc;
+ char **argv;
+ int flags;
+ int replicas;
+ char *from;
+ char *to;
+ char **weight;
+ int weight_argc;
+ char *master_id;
+ int slots;
+ int timeout;
+ int pipeline;
+ float threshold;
+ char *backup_dir;
+} clusterManagerCommand;
+
+static void createClusterManagerCommand(char *cmdname, int argc, char **argv);
+
+
static redisContext *context;
static struct config {
char *hostip;
int hostport;
char *hostsocket;
+ int tls;
+ char *sni;
+ char *cacert;
+ char *cacertdir;
+ char *cert;
+ char *key;
long repeat;
long interval;
int dbnum;
@@ -107,9 +223,12 @@ static struct config {
char *pattern;
char *rdb_filename;
int bigkeys;
+ int memkeys;
+ unsigned memkeys_samples;
int hotkeys;
int stdinarg; /* get last arg from stdin. (-x option) */
char *auth;
+ char *user;
int output; /* output mode, see OUTPUT_* defines */
sds mb_delim;
char prompt[128];
@@ -119,6 +238,10 @@ static struct config {
int eval_ldb_end; /* Lua debugging session ended. */
int enable_ldb_on_eval; /* Handle manual SCRIPT DEBUG + EVAL commands. */
int last_cmd_type;
+ int verbose;
+ clusterManagerCommand cluster_manager_command;
+ int no_auth_warning;
+ int resp3;
} config;
/* User preferences. */
@@ -133,10 +256,15 @@ char *redisGitSHA1(void);
char *redisGitDirty(void);
static int cliConnect(int force);
+static char *getInfoField(char *info, char *field);
+static long getLongInfoField(char *info, char *field);
+
/*------------------------------------------------------------------------------
* Utility functions
*--------------------------------------------------------------------------- */
+uint16_t crc16(const char *buf, int len);
+
static long long ustime(void) {
struct timeval tv;
long long ust;
@@ -152,20 +280,25 @@ static long long mstime(void) {
}
static void cliRefreshPrompt(void) {
- int len;
-
if (config.eval_ldb) return;
- if (config.hostsocket != NULL)
- len = snprintf(config.prompt,sizeof(config.prompt),"redis %s",
- config.hostsocket);
- else
- len = anetFormatAddr(config.prompt, sizeof(config.prompt),
- config.hostip, config.hostport);
+
+ sds prompt = sdsempty();
+ if (config.hostsocket != NULL) {
+ prompt = sdscatfmt(prompt,"redis %s",config.hostsocket);
+ } else {
+ char addr[256];
+ anetFormatAddr(addr, sizeof(addr), config.hostip, config.hostport);
+ prompt = sdscatlen(prompt,addr,strlen(addr));
+ }
+
/* Add [dbnum] if needed */
if (config.dbnum != 0)
- len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
- config.dbnum);
- snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
+ prompt = sdscatfmt(prompt,"[%i]",config.dbnum);
+
+ /* Copy the prompt in the static buffer. */
+ prompt = sdscatlen(prompt,"> ",2);
+ snprintf(config.prompt,sizeof(config.prompt),"%s",prompt);
+ sdsfree(prompt);
}
/* Return the name of the dotfile for the specified 'dotfilename'.
@@ -285,6 +418,41 @@ static void parseRedisUri(const char *uri) {
config.dbnum = atoi(curr);
}
+static uint64_t dictSdsHash(const void *key) {
+ return dictGenHashFunction((unsigned char*)key, sdslen((char*)key));
+}
+
+static int dictSdsKeyCompare(void *privdata, const void *key1,
+ const void *key2)
+{
+ int l1,l2;
+ DICT_NOTUSED(privdata);
+
+ l1 = sdslen((sds)key1);
+ l2 = sdslen((sds)key2);
+ if (l1 != l2) return 0;
+ return memcmp(key1, key2, l1) == 0;
+}
+
+static void dictSdsDestructor(void *privdata, void *val)
+{
+ DICT_NOTUSED(privdata);
+ sdsfree(val);
+}
+
+void dictListDestructor(void *privdata, void *val)
+{
+ DICT_NOTUSED(privdata);
+ listRelease((list*)val);
+}
+
+/* _serverAssert is needed by dict */
+void _serverAssert(const char *estr, const char *file, int line) {
+ fprintf(stderr, "=== ASSERTION FAILED ===");
+ fprintf(stderr, "==> %s:%d '%s' is not true",file,line,estr);
+ *((char*)-1) = 'x';
+}
+
/*------------------------------------------------------------------------------
* Help functions
*--------------------------------------------------------------------------- */
@@ -353,7 +521,7 @@ static void cliInitHelp(void) {
* entries with additional entries obtained using the COMMAND command
* available in recent versions of Redis. */
static void cliIntegrateHelp(void) {
- if (cliConnect(0) == REDIS_ERR) return;
+ if (cliConnect(CC_QUIET) == REDIS_ERR) return;
redisReply *reply = redisCommand(context, "COMMAND");
if(reply == NULL || reply->type != REDIS_REPLY_ARRAY) return;
@@ -391,11 +559,12 @@ static void cliIntegrateHelp(void) {
ch->name = new->argv[0];
ch->params = sdsempty();
int args = llabs(entry->element[1]->integer);
+ args--; /* Remove the command name itself. */
if (entry->element[3]->integer == 1) {
ch->params = sdscat(ch->params,"key ");
args--;
}
- while(args--) ch->params = sdscat(ch->params,"arg ");
+ while(args-- > 0) ch->params = sdscat(ch->params,"arg ");
if (entry->element[1]->integer < 0)
ch->params = sdscat(ch->params,"...options...");
ch->summary = "Help not available";
@@ -571,8 +740,13 @@ static int cliAuth(void) {
redisReply *reply;
if (config.auth == NULL) return REDIS_OK;
- reply = redisCommand(context,"AUTH %s",config.auth);
+ if (config.user == NULL)
+ reply = redisCommand(context,"AUTH %s",config.auth);
+ else
+ reply = redisCommand(context,"AUTH %s %s",config.user,config.auth);
if (reply != NULL) {
+ if (reply->type == REDIS_REPLY_ERROR)
+ fprintf(stderr,"Warning: AUTH failed\n");
freeReplyObject(reply);
return REDIS_OK;
}
@@ -594,10 +768,92 @@ static int cliSelect(void) {
return REDIS_ERR;
}
-/* Connect to the server. If force is not zero the connection is performed
- * even if there is already a connected socket. */
-static int cliConnect(int force) {
- if (context == NULL || force) {
+/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
+ * not building with TLS support.
+ */
+static int cliSecureConnection(redisContext *c, const char **err) {
+#ifdef USE_OPENSSL
+ static SSL_CTX *ssl_ctx = NULL;
+
+ if (!ssl_ctx) {
+ ssl_ctx = SSL_CTX_new(SSLv23_client_method());
+ if (!ssl_ctx) {
+ *err = "Failed to create SSL_CTX";
+ goto error;
+ }
+
+ SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
+
+ if (config.cacert || config.cacertdir) {
+ if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
+ *err = "Invalid CA Certificate File/Directory";
+ goto error;
+ }
+ } else {
+ if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
+ *err = "Failed to use default CA paths";
+ goto error;
+ }
+ }
+
+ if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
+ *err = "Invalid client certificate";
+ goto error;
+ }
+
+ if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
+ *err = "Invalid private key";
+ goto error;
+ }
+ }
+
+ SSL *ssl = SSL_new(ssl_ctx);
+ if (!ssl) {
+ *err = "Failed to create SSL object";
+ return REDIS_ERR;
+ }
+
+ if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
+ *err = "Failed to configure SNI";
+ SSL_free(ssl);
+ return REDIS_ERR;
+ }
+
+ return redisInitiateSSL(c, ssl);
+
+error:
+ SSL_CTX_free(ssl_ctx);
+ ssl_ctx = NULL;
+ return REDIS_ERR;
+#else
+ (void) c;
+ (void) err;
+ return REDIS_OK;
+#endif
+}
+
+/* Select RESP3 mode if redis-cli was started with the -3 option. */
+static int cliSwitchProto(void) {
+ redisReply *reply;
+ if (config.resp3 == 0) return REDIS_OK;
+
+ reply = redisCommand(context,"HELLO 3");
+ if (reply != NULL) {
+ int result = REDIS_OK;
+ if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
+ freeReplyObject(reply);
+ return result;
+ }
+ return REDIS_ERR;
+}
+
+/* Connect to the server. It is possible to pass certain flags to the function:
+ * CC_FORCE: The connection is performed even if there is already
+ * a connected socket.
+ * CC_QUIET: Don't print errors if connection fails. */
+static int cliConnect(int flags) {
+ if (context == NULL || flags & CC_FORCE) {
if (context != NULL) {
redisFree(context);
}
@@ -608,28 +864,45 @@ static int cliConnect(int force) {
context = redisConnectUnix(config.hostsocket);
}
+ if (!context->err && config.tls) {
+ const char *err = NULL;
+ if (cliSecureConnection(context, &err) == REDIS_ERR && err) {
+ fprintf(stderr, "Could not negotiate a TLS connection: %s\n", err);
+ context = NULL;
+ redisFree(context);
+ return REDIS_ERR;
+ }
+ }
+
if (context->err) {
- fprintf(stderr,"Could not connect to Redis at ");
- if (config.hostsocket == NULL)
- fprintf(stderr,"%s:%d: %s\n",config.hostip,config.hostport,context->errstr);
- else
- fprintf(stderr,"%s: %s\n",config.hostsocket,context->errstr);
+ if (!(flags & CC_QUIET)) {
+ fprintf(stderr,"Could not connect to Redis at ");
+ if (config.hostsocket == NULL)
+ fprintf(stderr,"%s:%d: %s\n",
+ config.hostip,config.hostport,context->errstr);
+ else
+ fprintf(stderr,"%s: %s\n",
+ config.hostsocket,context->errstr);
+ }
redisFree(context);
context = NULL;
return REDIS_ERR;
}
+
/* Set aggressive KEEP_ALIVE socket option in the Redis context socket
* in order to prevent timeouts caused by the execution of long
* commands. At the same time this improves the detection of real
* errors. */
anetKeepAlive(NULL, context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
- /* Do AUTH and select the right DB. */
+ /* Do AUTH, select the right DB, switch to RESP3 if needed. */
if (cliAuth() != REDIS_OK)
return REDIS_ERR;
if (cliSelect() != REDIS_OK)
return REDIS_ERR;
+ if (cliSwitchProto() != REDIS_OK)
+ return REDIS_ERR;
}
return REDIS_OK;
}
@@ -652,18 +925,40 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"(integer) %lld\n",r->integer);
break;
+ case REDIS_REPLY_DOUBLE:
+ out = sdscatprintf(out,"(double) %s\n",r->str);
+ break;
case REDIS_REPLY_STRING:
+ case REDIS_REPLY_VERB:
/* If you are producing output for the standard output we want
- * a more interesting output with quoted characters and so forth */
- out = sdscatrepr(out,r->str,r->len);
- out = sdscat(out,"\n");
+ * a more interesting output with quoted characters and so forth,
+ * unless it's a verbatim string type. */
+ if (r->type == REDIS_REPLY_STRING) {
+ out = sdscatrepr(out,r->str,r->len);
+ out = sdscat(out,"\n");
+ } else {
+ out = sdscatlen(out,r->str,r->len);
+ out = sdscat(out,"\n");
+ }
break;
case REDIS_REPLY_NIL:
out = sdscat(out,"(nil)\n");
break;
+ case REDIS_REPLY_BOOL:
+ out = sdscat(out,r->integer ? "(true)\n" : "(false)\n");
+ break;
case REDIS_REPLY_ARRAY:
+ case REDIS_REPLY_MAP:
+ case REDIS_REPLY_SET:
if (r->elements == 0) {
- out = sdscat(out,"(empty list or set)\n");
+ if (r->type == REDIS_REPLY_ARRAY)
+ out = sdscat(out,"(empty array)\n");
+ else if (r->type == REDIS_REPLY_MAP)
+ out = sdscat(out,"(empty hash)\n");
+ else if (r->type == REDIS_REPLY_SET)
+ out = sdscat(out,"(empty set)\n");
+ else
+ out = sdscat(out,"(empty aggregate type)\n");
} else {
unsigned int i, idxlen = 0;
char _prefixlen[16];
@@ -673,6 +968,7 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
/* Calculate chars needed to represent the largest index */
i = r->elements;
+ if (r->type == REDIS_REPLY_MAP) i /= 2;
do {
idxlen++;
i /= 10;
@@ -684,17 +980,35 @@ static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
_prefix = sdscat(sdsnew(prefix),_prefixlen);
/* Setup prefix format for every entry */
- snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud) ",idxlen);
+ char numsep;
+ if (r->type == REDIS_REPLY_SET) numsep = '~';
+ else if (r->type == REDIS_REPLY_MAP) numsep = '#';
+ else numsep = ')';
+ snprintf(_prefixfmt,sizeof(_prefixfmt),"%%s%%%ud%c ",idxlen,numsep);
for (i = 0; i < r->elements; i++) {
+ unsigned int human_idx = (r->type == REDIS_REPLY_MAP) ?
+ i/2 : i;
+ human_idx++; /* Make it 1-based. */
+
/* Don't use the prefix for the first element, as the parent
* caller already prepended the index number. */
- out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,i+1);
+ out = sdscatprintf(out,_prefixfmt,i == 0 ? "" : prefix,human_idx);
/* Format the multi bulk entry */
tmp = cliFormatReplyTTY(r->element[i],_prefix);
out = sdscatlen(out,tmp,sdslen(tmp));
sdsfree(tmp);
+
+ /* For maps, format the value as well. */
+ if (r->type == REDIS_REPLY_MAP) {
+ i++;
+ sdsrange(out,0,-2);
+ out = sdscat(out," => ");
+ tmp = cliFormatReplyTTY(r->element[i],_prefix);
+ out = sdscatlen(out,tmp,sdslen(tmp));
+ sdsfree(tmp);
+ }
}
sdsfree(_prefix);
}
@@ -764,6 +1078,7 @@ static sds cliFormatReplyRaw(redisReply *r) {
break;
case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING:
+ case REDIS_REPLY_VERB:
if (r->type == REDIS_REPLY_STATUS && config.eval_ldb) {
/* The Lua debugger replies with arrays of simple (status)
* strings. We colorize the output for more fun if this
@@ -783,9 +1098,15 @@ static sds cliFormatReplyRaw(redisReply *r) {
out = sdscatlen(out,r->str,r->len);
}
break;
+ case REDIS_REPLY_BOOL:
+ out = sdscat(out,r->integer ? "(true)" : "(false)");
+ break;
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer);
break;
+ case REDIS_REPLY_DOUBLE:
+ out = sdscatprintf(out,"%s",r->str);
+ break;
case REDIS_REPLY_ARRAY:
for (i = 0; i < r->elements; i++) {
if (i > 0) out = sdscat(out,config.mb_delim);
@@ -794,6 +1115,19 @@ static sds cliFormatReplyRaw(redisReply *r) {
sdsfree(tmp);
}
break;
+ case REDIS_REPLY_MAP:
+ for (i = 0; i < r->elements; i += 2) {
+ if (i > 0) out = sdscat(out,config.mb_delim);
+ tmp = cliFormatReplyRaw(r->element[i]);
+ out = sdscatlen(out,tmp,sdslen(tmp));
+ sdsfree(tmp);
+
+ out = sdscatlen(out," ",1);
+ tmp = cliFormatReplyRaw(r->element[i+1]);
+ out = sdscatlen(out,tmp,sdslen(tmp));
+ sdsfree(tmp);
+ }
+ break;
default:
fprintf(stderr,"Unknown reply type: %d\n", r->type);
exit(1);
@@ -816,13 +1150,21 @@ static sds cliFormatReplyCSV(redisReply *r) {
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"%lld",r->integer);
break;
+ case REDIS_REPLY_DOUBLE:
+ out = sdscatprintf(out,"%s",r->str);
+ break;
case REDIS_REPLY_STRING:
+ case REDIS_REPLY_VERB:
out = sdscatrepr(out,r->str,r->len);
break;
case REDIS_REPLY_NIL:
- out = sdscat(out,"NIL");
+ out = sdscat(out,"NULL");
+ break;
+ case REDIS_REPLY_BOOL:
+ out = sdscat(out,r->integer ? "true" : "false");
break;
case REDIS_REPLY_ARRAY:
+ case REDIS_REPLY_MAP: /* CSV has no map type, just output flat list. */
for (i = 0; i < r->elements; i++) {
sds tmp = cliFormatReplyCSV(r->element[i]);
out = sdscatlen(out,tmp,sdslen(tmp));
@@ -917,7 +1259,7 @@ static int cliReadReply(int output_raw_strings) {
return REDIS_OK;
}
-static int cliSendCommand(int argc, char **argv, int repeat) {
+static int cliSendCommand(int argc, char **argv, long repeat) {
char *command = argv[0];
size_t *argvlen;
int j, output_raw;
@@ -932,15 +1274,18 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
output_raw = 0;
if (!strcasecmp(command,"info") ||
+ !strcasecmp(command,"lolwut") ||
(argc >= 2 && !strcasecmp(command,"debug") &&
!strcasecmp(argv[1],"htstats")) ||
+ (argc >= 2 && !strcasecmp(command,"debug") &&
+ !strcasecmp(argv[1],"htstats-key")) ||
(argc >= 2 && !strcasecmp(command,"memory") &&
(!strcasecmp(argv[1],"malloc-stats") ||
!strcasecmp(argv[1],"doctor"))) ||
(argc == 2 && !strcasecmp(command,"cluster") &&
(!strcasecmp(argv[1],"nodes") ||
!strcasecmp(argv[1],"info"))) ||
- (argc == 2 && !strcasecmp(command,"client") &&
+ (argc >= 2 && !strcasecmp(command,"client") &&
!strcasecmp(argv[1],"list")) ||
(argc == 3 && !strcasecmp(command,"latency") &&
!strcasecmp(argv[1],"graph")) ||
@@ -980,7 +1325,9 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
for (j = 0; j < argc; j++)
argvlen[j] = sdslen(argv[j]);
- while(repeat--) {
+ /* Negative repeat is allowed and causes infinite loop,
+ works well with the interval option. */
+ while(repeat < 0 || repeat-- > 0) {
redisAppendCommandArgv(context,argc,(const char**)argv,argvlen);
while (config.monitor_mode) {
if (cliReadReply(output_raw) != REDIS_OK) exit(1);
@@ -996,7 +1343,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
}
if (config.slave_mode) {
- printf("Entering slave output mode... (press Ctrl-C to quit)\n");
+ printf("Entering replica output mode... (press Ctrl-C to quit)\n");
slaveMode();
config.slave_mode = 0;
zfree(argvlen);
@@ -1011,10 +1358,16 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
if (!strcasecmp(command,"select") && argc == 2 && config.last_cmd_type != REDIS_REPLY_ERROR) {
config.dbnum = atoi(argv[1]);
cliRefreshPrompt();
- } else if (!strcasecmp(command,"auth") && argc == 2) {
+ } else if (!strcasecmp(command,"auth") && (argc == 2 || argc == 3))
+ {
cliSelect();
}
}
+ if (config.cluster_reissue_command){
+ /* If we need to reissue the command, break to prevent a
+ further 'repeat' number of dud interations */
+ break;
+ }
if (config.interval) usleep(config.interval);
fflush(stdout); /* Make it grep friendly */
}
@@ -1038,6 +1391,13 @@ static redisReply *reconnectingRedisCommand(redisContext *c, const char *fmt, ..
redisFree(c);
c = redisConnect(config.hostip,config.hostport);
+ if (!c->err && config.tls) {
+ const char *err = NULL;
+ if (cliSecureConnection(c, &err) == REDIS_ERR && err) {
+ fprintf(stderr, "TLS Error: %s\n", err);
+ exit(1);
+ }
+ }
usleep(1000000);
}
@@ -1087,8 +1447,14 @@ static int parseOptions(int argc, char **argv) {
config.interval = seconds*1000000;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.dbnum = atoi(argv[++i]);
- } else if (!strcmp(argv[i],"-a") && !lastarg) {
+ } else if (!strcmp(argv[i], "--no-auth-warning")) {
+ config.no_auth_warning = 1;
+ } else if ((!strcmp(argv[i],"-a") || !strcmp(argv[i],"--pass"))
+ && !lastarg)
+ {
config.auth = argv[++i];
+ } else if (!strcmp(argv[i],"--user") && !lastarg) {
+ config.user = argv[++i];
} else if (!strcmp(argv[i],"-u") && !lastarg) {
parseRedisUri(argv[++i]);
} else if (!strcmp(argv[i],"--raw")) {
@@ -1112,6 +1478,8 @@ static int parseOptions(int argc, char **argv) {
config.lru_test_sample_size = strtoll(argv[++i],NULL,10);
} else if (!strcmp(argv[i],"--slave")) {
config.slave_mode = 1;
+ } else if (!strcmp(argv[i],"--replica")) {
+ config.slave_mode = 1;
} else if (!strcmp(argv[i],"--stat")) {
config.stat_mode = 1;
} else if (!strcmp(argv[i],"--scan")) {
@@ -1130,6 +1498,12 @@ static int parseOptions(int argc, char **argv) {
config.pipe_timeout = atoi(argv[++i]);
} else if (!strcmp(argv[i],"--bigkeys")) {
config.bigkeys = 1;
+ } else if (!strcmp(argv[i],"--memkeys")) {
+ config.memkeys = 1;
+ config.memkeys_samples = 0; /* use redis default */
+ } else if (!strcmp(argv[i],"--memkeys-samples")) {
+ config.memkeys = 1;
+ config.memkeys_samples = atoi(argv[++i]);
} else if (!strcmp(argv[i],"--hotkeys")) {
config.hotkeys = 1;
} else if (!strcmp(argv[i],"--eval") && !lastarg) {
@@ -1146,11 +1520,107 @@ static int parseOptions(int argc, char **argv) {
} else if (!strcmp(argv[i],"-d") && !lastarg) {
sdsfree(config.mb_delim);
config.mb_delim = sdsnew(argv[++i]);
+ } else if (!strcmp(argv[i],"--verbose")) {
+ config.verbose = 1;
+ } else if (!strcmp(argv[i],"--cluster") && !lastarg) {
+ if (CLUSTER_MANAGER_MODE()) usage();
+ char *cmd = argv[++i];
+ int j = i;
+ while (j < argc && argv[j][0] != '-') j++;
+ if (j > i) j--;
+ createClusterManagerCommand(cmd, j - i, argv + i + 1);
+ i = j;
+ } else if (!strcmp(argv[i],"--cluster") && lastarg) {
+ usage();
+ } else if (!strcmp(argv[i],"--cluster-replicas") && !lastarg) {
+ config.cluster_manager_command.replicas = atoi(argv[++i]);
+ } else if (!strcmp(argv[i],"--cluster-master-id") && !lastarg) {
+ config.cluster_manager_command.master_id = argv[++i];
+ } else if (!strcmp(argv[i],"--cluster-from") && !lastarg) {
+ config.cluster_manager_command.from = argv[++i];
+ } else if (!strcmp(argv[i],"--cluster-to") && !lastarg) {
+ config.cluster_manager_command.to = argv[++i];
+ } else if (!strcmp(argv[i],"--cluster-weight") && !lastarg) {
+ if (config.cluster_manager_command.weight != NULL) {
+ fprintf(stderr, "WARNING: you cannot use --cluster-weight "
+ "more than once.\n"
+ "You can set more weights by adding them "
+ "as a space-separated list, ie:\n"
+ "--cluster-weight n1=w n2=w\n");
+ exit(1);
+ }
+ int widx = i + 1;
+ char **weight = argv + widx;
+ int wargc = 0;
+ for (; widx < argc; widx++) {
+ if (strstr(argv[widx], "--") == argv[widx]) break;
+ if (strchr(argv[widx], '=') == NULL) break;
+ wargc++;
+ }
+ if (wargc > 0) {
+ config.cluster_manager_command.weight = weight;
+ config.cluster_manager_command.weight_argc = wargc;
+ i += wargc;
+ }
+ } else if (!strcmp(argv[i],"--cluster-slots") && !lastarg) {
+ config.cluster_manager_command.slots = atoi(argv[++i]);
+ } else if (!strcmp(argv[i],"--cluster-timeout") && !lastarg) {
+ config.cluster_manager_command.timeout = atoi(argv[++i]);
+ } else if (!strcmp(argv[i],"--cluster-pipeline") && !lastarg) {
+ config.cluster_manager_command.pipeline = atoi(argv[++i]);
+ } else if (!strcmp(argv[i],"--cluster-threshold") && !lastarg) {
+ config.cluster_manager_command.threshold = atof(argv[++i]);
+ } else if (!strcmp(argv[i],"--cluster-yes")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_YES;
+ } else if (!strcmp(argv[i],"--cluster-simulate")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
+ } else if (!strcmp(argv[i],"--cluster-replace")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_REPLACE;
+ } else if (!strcmp(argv[i],"--cluster-copy")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_COPY;
+ } else if (!strcmp(argv[i],"--cluster-slave")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_SLAVE;
+ } else if (!strcmp(argv[i],"--cluster-use-empty-masters")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
+ } else if (!strcmp(argv[i],"--cluster-search-multiple-owners")) {
+ config.cluster_manager_command.flags |=
+ CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
+#ifdef USE_OPENSSL
+ } else if (!strcmp(argv[i],"--tls")) {
+ config.tls = 1;
+ } else if (!strcmp(argv[i],"--sni")) {
+ config.sni = argv[++i];
+ } else if (!strcmp(argv[i],"--cacertdir")) {
+ config.cacertdir = argv[++i];
+ } else if (!strcmp(argv[i],"--cacert")) {
+ config.cacert = argv[++i];
+ } else if (!strcmp(argv[i],"--cert")) {
+ config.cert = argv[++i];
+ } else if (!strcmp(argv[i],"--key")) {
+ config.key = argv[++i];
+#endif
} else if (!strcmp(argv[i],"-v") || !strcmp(argv[i], "--version")) {
sds version = cliVersion();
printf("redis-cli %s\n", version);
sdsfree(version);
exit(0);
+ } else if (!strcmp(argv[i],"-3")) {
+ config.resp3 = 1;
+ } else if (CLUSTER_MANAGER_MODE() && argv[i][0] != '-') {
+ if (config.cluster_manager_command.argc == 0) {
+ int j = i + 1;
+ while (j < argc && argv[j][0] != '-') j++;
+ int cmd_argc = j - i;
+ config.cluster_manager_command.argc = cmd_argc;
+ config.cluster_manager_command.argv = argv + i;
+ if (cmd_argc > 1) i = j - 1;
+ }
} else {
if (argv[i][0] == '-') {
fprintf(stderr,
@@ -1170,9 +1640,23 @@ static int parseOptions(int argc, char **argv) {
fprintf(stderr,"Try %s --help for more information.\n", argv[0]);
exit(1);
}
+
+ if (!config.no_auth_warning && config.auth != NULL) {
+ fputs("Warning: Using a password with '-a' or '-u' option on the command"
+ " line interface may not be safe.\n", stderr);
+ }
+
return i;
}
+static void parseEnv() {
+ /* Set auth from env, but do not overwrite CLI arguments if passed */
+ char *auth = getenv(REDIS_CLI_AUTH_ENV);
+ if (auth != NULL && config.auth == NULL) {
+ config.auth = auth;
+ }
+}
+
static sds readArgFromStdin(void) {
char buf[1024];
sds arg = sdsempty();
@@ -1200,14 +1684,29 @@ static void usage(void) {
" -p <port> Server port (default: 6379).\n"
" -s <socket> Server socket (overrides hostname and port).\n"
" -a <password> Password to use when connecting to the server.\n"
+" You can also use the " REDIS_CLI_AUTH_ENV " environment\n"
+" variable to pass this password more safely\n"
+" (if both are used, this argument takes predecence).\n"
+" -user <username> Used to send ACL style 'AUTH username pass'. Needs -a.\n"
+" -pass <password> Alias of -a for consistency with the new --user option.\n"
" -u <uri> Server URI.\n"
" -r <repeat> Execute specified command N times.\n"
" -i <interval> When -r is used, waits <interval> seconds per command.\n"
" It is possible to specify sub-second times like -i 0.1.\n"
" -n <db> Database number.\n"
+" -3 Start session in RESP3 protocol mode.\n"
" -x Read last argument from STDIN.\n"
" -d <delimiter> Multi-bulk delimiter in for raw formatting (default: \\n).\n"
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
+#ifdef USE_OPENSSL
+" --tls Establish a secure TLS connection.\n"
+" --cacert CA Certificate file to verify with.\n"
+" --cacertdir Directory where trusted CA certificates are stored.\n"
+" If neither cacert nor cacertdir are specified, the default\n"
+" system-wide trusted root certs configuration will apply.\n"
+" --cert Client certificate to authenticate with.\n"
+" --key Private key file to authenticate with.\n"
+#endif
" --raw Use raw formatting for replies (default when STDOUT is\n"
" not a tty).\n"
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
@@ -1219,19 +1718,26 @@ static void usage(void) {
" --csv is specified, or if you redirect the output to a non\n"
" TTY, it samples the latency for 1 second (you can use\n"
" -i to change the interval), then produces a single output\n"
-" and exits.\n"
+" and exits.\n",version);
+
+ fprintf(stderr,
" --latency-history Like --latency but tracking latency changes over time.\n"
" Default time interval is 15 sec. Change it using -i.\n"
" --latency-dist Shows latency as a spectrum, requires xterm 256 colors.\n"
" Default time interval is 1 sec. Change it using -i.\n"
" --lru-test <keys> Simulate a cache workload with an 80-20 distribution.\n"
-" --slave Simulate a slave showing commands received from the master.\n"
+" --replica Simulate a replica showing commands received from the master.\n"
" --rdb <filename> Transfer an RDB dump from remote server to local file.\n"
" --pipe Transfer raw Redis protocol from stdin to server.\n"
" --pipe-timeout <n> In --pipe mode, abort with error if after sending all data.\n"
" no reply is received within <n> seconds.\n"
-" Default timeout: %d. Use 0 to wait forever.\n"
-" --bigkeys Sample Redis keys looking for big keys.\n"
+" Default timeout: %d. Use 0 to wait forever.\n",
+ REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
+ fprintf(stderr,
+" --bigkeys Sample Redis keys looking for keys with many elements (complexity).\n"
+" --memkeys Sample Redis keys looking for keys consuming a lot of memory.\n"
+" --memkeys-samples <n> Sample Redis keys looking for keys consuming a lot of memory.\n"
+" And define number of key elements to sample\n"
" --hotkeys Sample Redis keys looking for hot keys.\n"
" only works when maxmemory-policy is *lfu.\n"
" --scan List all keys using the SCAN command.\n"
@@ -1242,9 +1748,19 @@ static void usage(void) {
" --ldb Used with --eval enable the Redis Lua debugger.\n"
" --ldb-sync-mode Like --ldb but uses the synchronous Lua debugger, in\n"
" this mode the server is blocked and script changes are\n"
-" are not rolled back from the server memory.\n"
+" not rolled back from the server memory.\n"
+" --cluster <command> [args...] [opts...]\n"
+" Cluster Manager command and arguments (see below).\n"
+" --verbose Verbose mode.\n"
+" --no-auth-warning Don't show warning message when using password on command\n"
+" line interface.\n"
" --help Output this help and exit.\n"
" --version Output version and exit.\n"
+"\n");
+ /* Using another fprintf call to avoid -Woverlength-strings compile warning */
+ fprintf(stderr,
+"Cluster Manager Commands:\n"
+" Use --cluster help to list all available cluster manager commands.\n"
"\n"
"Examples:\n"
" cat /etc/passwd | redis-cli -x set mypasswd\n"
@@ -1259,12 +1775,20 @@ static void usage(void) {
"When no command is given, redis-cli starts in interactive mode.\n"
"Type \"help\" in interactive mode for information on available commands\n"
"and settings.\n"
-"\n",
- version, REDIS_CLI_DEFAULT_PIPE_TIMEOUT);
+"\n");
sdsfree(version);
exit(1);
}
+static int confirmWithYes(char *msg) {
+ printf("%s (type 'yes' to accept): ", msg);
+ fflush(stdout);
+ char buf[4];
+ int nread = read(fileno(stdin),buf,4);
+ buf[3] = '\0';
+ return (nread != 0 && !strcmp("yes", buf));
+}
+
/* Turn the plain C strings into Sds strings */
static char **convertToSds(int count, char** args) {
int j;
@@ -1280,7 +1804,7 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
while (1) {
config.cluster_reissue_command = 0;
if (cliSendCommand(argc,argv,repeat) != REDIS_OK) {
- cliConnect(1);
+ cliConnect(CC_FORCE);
/* If we still cannot send the command print error.
* We'll try to reconnect the next time. */
@@ -1288,12 +1812,12 @@ static int issueCommandRepeat(int argc, char **argv, long repeat) {
cliPrintContextError();
return REDIS_ERR;
}
- }
- /* Issue the command again if we got redirected in cluster mode */
- if (config.cluster_mode && config.cluster_reissue_command) {
- cliConnect(1);
- } else {
- break;
+ }
+ /* Issue the command again if we got redirected in cluster mode */
+ if (config.cluster_mode && config.cluster_reissue_command) {
+ cliConnect(CC_FORCE);
+ } else {
+ break;
}
}
return REDIS_OK;
@@ -1397,9 +1921,35 @@ static void repl(void) {
cliRefreshPrompt();
while((line = linenoise(context ? config.prompt : "not connected> ")) != NULL) {
if (line[0] != '\0') {
+ long repeat = 1;
+ int skipargs = 0;
+ char *endptr = NULL;
+
argv = cliSplitArgs(line,&argc);
- if (history) linenoiseHistoryAdd(line);
- if (historyfile) linenoiseHistorySave(historyfile);
+
+ /* check if we have a repeat command option and
+ * need to skip the first arg */
+ if (argv && argc > 0) {
+ errno = 0;
+ repeat = strtol(argv[0], &endptr, 10);
+ if (argc > 1 && *endptr == '\0') {
+ if (errno == ERANGE || errno == EINVAL || repeat <= 0) {
+ fputs("Invalid redis-cli repeat command option value.\n", stdout);
+ sdsfreesplitres(argv, argc);
+ linenoiseFree(line);
+ continue;
+ }
+ skipargs = 1;
+ } else {
+ repeat = 1;
+ }
+ }
+
+ /* Won't save auth command in history file */
+ if (!(argv && argc > 0 && !strcasecmp(argv[0+skipargs], "auth"))) {
+ if (history) linenoiseHistoryAdd(line);
+ if (historyfile) linenoiseHistorySave(historyfile);
+ }
if (argv == NULL) {
printf("Invalid argument(s)\n");
@@ -1412,6 +1962,8 @@ static void repl(void) {
exit(0);
} else if (argv[0][0] == ':') {
cliSetPreferences(argv,argc,1);
+ sdsfreesplitres(argv,argc);
+ linenoiseFree(line);
continue;
} else if (strcasecmp(argv[0],"restart") == 0) {
if (config.eval) {
@@ -1426,20 +1978,11 @@ static void repl(void) {
config.hostip = sdsnew(argv[1]);
config.hostport = atoi(argv[2]);
cliRefreshPrompt();
- cliConnect(1);
+ cliConnect(CC_FORCE);
} else if (argc == 1 && !strcasecmp(argv[0],"clear")) {
linenoiseClearScreen();
} else {
long long start_time = mstime(), elapsed;
- int repeat, skipargs = 0;
- char *endptr;
-
- repeat = strtol(argv[0], &endptr, 10);
- if (argc > 1 && *endptr == '\0' && repeat) {
- skipargs = 1;
- } else {
- repeat = 1;
- }
issueCommandRepeat(argc-skipargs, argv+skipargs, repeat);
@@ -1550,7 +2093,7 @@ static int evalMode(int argc, char **argv) {
if (eval_ldb) {
if (!config.eval_ldb) {
/* If the debugging session ended immediately, there was an
- * error compiling the script. Show it and don't enter
+ * error compiling the script. Show it and they don't enter
* the REPL at all. */
printf("Eval debugging session can't start:\n");
cliReadReply(0);
@@ -1559,7 +2102,7 @@ static int evalMode(int argc, char **argv) {
strncpy(config.prompt,"lua debugger> ",sizeof(config.prompt));
repl();
/* Restart the session if repl() returned. */
- cliConnect(1);
+ cliConnect(CC_FORCE);
printf("\n");
}
} else {
@@ -1570,6 +2113,4343 @@ static int evalMode(int argc, char **argv) {
}
/*------------------------------------------------------------------------------
+ * Cluster Manager
+ *--------------------------------------------------------------------------- */
+
+/* The Cluster Manager global structure */
+static struct clusterManager {
+ list *nodes; /* List of nodes in the configuration. */
+ list *errors;
+} cluster_manager;
+
+/* Used by clusterManagerFixSlotsCoverage */
+dict *clusterManagerUncoveredSlots = NULL;
+
+typedef struct clusterManagerNode {
+ redisContext *context;
+ sds name;
+ char *ip;
+ int port;
+ uint64_t current_epoch;
+ time_t ping_sent;
+ time_t ping_recv;
+ int flags;
+ list *flags_str; /* Flags string representations */
+ sds replicate; /* Master ID if node is a slave */
+ int dirty; /* Node has changes that can be flushed */
+ uint8_t slots[CLUSTER_MANAGER_SLOTS];
+ int slots_count;
+ int replicas_count;
+ list *friends;
+ sds *migrating; /* An array of sds where even strings are slots and odd
+ * strings are the destination node IDs. */
+ sds *importing; /* An array of sds where even strings are slots and odd
+ * strings are the source node IDs. */
+ int migrating_count; /* Length of the migrating array (migrating slots*2) */
+ int importing_count; /* Length of the importing array (importing slots*2) */
+ float weight; /* Weight used by rebalance */
+ int balance; /* Used by rebalance */
+} clusterManagerNode;
+
+/* Data structure used to represent a sequence of cluster nodes. */
+typedef struct clusterManagerNodeArray {
+ clusterManagerNode **nodes; /* Actual nodes array */
+ clusterManagerNode **alloc; /* Pointer to the allocated memory */
+ int len; /* Actual length of the array */
+ int count; /* Non-NULL nodes count */
+} clusterManagerNodeArray;
+
+/* Used for the reshard table. */
+typedef struct clusterManagerReshardTableItem {
+ clusterManagerNode *source;
+ int slot;
+} clusterManagerReshardTableItem;
+
+/* Info about a cluster internal link. */
+
+typedef struct clusterManagerLink {
+ sds node_name;
+ sds node_addr;
+ int connected;
+ int handshaking;
+} clusterManagerLink;
+
+static dictType clusterManagerDictType = {
+ dictSdsHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCompare, /* key compare */
+ NULL, /* key destructor */
+ dictSdsDestructor /* val destructor */
+};
+
+static dictType clusterManagerLinkDictType = {
+ dictSdsHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCompare, /* key compare */
+ dictSdsDestructor, /* key destructor */
+ dictListDestructor /* val destructor */
+};
+
+typedef int clusterManagerCommandProc(int argc, char **argv);
+typedef int (*clusterManagerOnReplyError)(redisReply *reply,
+ clusterManagerNode *n, int bulk_idx);
+
+/* Cluster Manager helper functions */
+
+static clusterManagerNode *clusterManagerNewNode(char *ip, int port);
+static clusterManagerNode *clusterManagerNodeByName(const char *name);
+static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char *n);
+static void clusterManagerNodeResetSlots(clusterManagerNode *node);
+static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err);
+static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
+ char *err);
+static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
+ char **err);
+static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts);
+static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err);
+static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
+ int ip_count, clusterManagerNode ***offending, int *offending_len);
+static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
+ int ip_count);
+static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent);
+static void clusterManagerShowNodes(void);
+static void clusterManagerShowClusterInfo(void);
+static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err);
+static void clusterManagerWaitForClusterJoin(void);
+static int clusterManagerCheckCluster(int quiet);
+static void clusterManagerLog(int level, const char* fmt, ...);
+static int clusterManagerIsConfigConsistent(void);
+static dict *clusterManagerGetLinkStatus(void);
+static void clusterManagerOnError(sds err);
+static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
+ int len);
+static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array);
+static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
+ clusterManagerNode **nodeptr);
+static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
+ clusterManagerNode *node);
+
+/* Cluster Manager commands. */
+
+static int clusterManagerCommandCreate(int argc, char **argv);
+static int clusterManagerCommandAddNode(int argc, char **argv);
+static int clusterManagerCommandDeleteNode(int argc, char **argv);
+static int clusterManagerCommandInfo(int argc, char **argv);
+static int clusterManagerCommandCheck(int argc, char **argv);
+static int clusterManagerCommandFix(int argc, char **argv);
+static int clusterManagerCommandReshard(int argc, char **argv);
+static int clusterManagerCommandRebalance(int argc, char **argv);
+static int clusterManagerCommandSetTimeout(int argc, char **argv);
+static int clusterManagerCommandImport(int argc, char **argv);
+static int clusterManagerCommandCall(int argc, char **argv);
+static int clusterManagerCommandHelp(int argc, char **argv);
+static int clusterManagerCommandBackup(int argc, char **argv);
+
+typedef struct clusterManagerCommandDef {
+ char *name;
+ clusterManagerCommandProc *proc;
+ int arity;
+ char *args;
+ char *options;
+} clusterManagerCommandDef;
+
+clusterManagerCommandDef clusterManagerCommands[] = {
+ {"create", clusterManagerCommandCreate, -2, "host1:port1 ... hostN:portN",
+ "replicas <arg>"},
+ {"check", clusterManagerCommandCheck, -1, "host:port",
+ "search-multiple-owners"},
+ {"info", clusterManagerCommandInfo, -1, "host:port", NULL},
+ {"fix", clusterManagerCommandFix, -1, "host:port",
+ "search-multiple-owners"},
+ {"reshard", clusterManagerCommandReshard, -1, "host:port",
+ "from <arg>,to <arg>,slots <arg>,yes,timeout <arg>,pipeline <arg>,"
+ "replace"},
+ {"rebalance", clusterManagerCommandRebalance, -1, "host:port",
+ "weight <node1=w1...nodeN=wN>,use-empty-masters,"
+ "timeout <arg>,simulate,pipeline <arg>,threshold <arg>,replace"},
+ {"add-node", clusterManagerCommandAddNode, 2,
+ "new_host:new_port existing_host:existing_port", "slave,master-id <arg>"},
+ {"del-node", clusterManagerCommandDeleteNode, 2, "host:port node_id",NULL},
+ {"call", clusterManagerCommandCall, -2,
+ "host:port command arg arg .. arg", NULL},
+ {"set-timeout", clusterManagerCommandSetTimeout, 2,
+ "host:port milliseconds", NULL},
+ {"import", clusterManagerCommandImport, 1, "host:port",
+ "from <arg>,copy,replace"},
+ {"backup", clusterManagerCommandBackup, 2, "host:port backup_directory",
+ NULL},
+ {"help", clusterManagerCommandHelp, 0, NULL, NULL}
+};
+
+static void getRDB(clusterManagerNode *node);
+
+static void createClusterManagerCommand(char *cmdname, int argc, char **argv) {
+ clusterManagerCommand *cmd = &config.cluster_manager_command;
+ cmd->name = cmdname;
+ cmd->argc = argc;
+ cmd->argv = argc ? argv : NULL;
+ if (isColorTerm()) cmd->flags |= CLUSTER_MANAGER_CMD_FLAG_COLOR;
+}
+
+
+static clusterManagerCommandProc *validateClusterManagerCommand(void) {
+ int i, commands_count = sizeof(clusterManagerCommands) /
+ sizeof(clusterManagerCommandDef);
+ clusterManagerCommandProc *proc = NULL;
+ char *cmdname = config.cluster_manager_command.name;
+ int argc = config.cluster_manager_command.argc;
+ for (i = 0; i < commands_count; i++) {
+ clusterManagerCommandDef cmddef = clusterManagerCommands[i];
+ if (!strcmp(cmddef.name, cmdname)) {
+ if ((cmddef.arity > 0 && argc != cmddef.arity) ||
+ (cmddef.arity < 0 && argc < (cmddef.arity * -1))) {
+ fprintf(stderr, "[ERR] Wrong number of arguments for "
+ "specified --cluster sub command\n");
+ return NULL;
+ }
+ proc = cmddef.proc;
+ }
+ }
+ if (!proc) fprintf(stderr, "Unknown --cluster subcommand\n");
+ return proc;
+}
+
+static int parseClusterNodeAddress(char *addr, char **ip_ptr, int *port_ptr,
+ int *bus_port_ptr)
+{
+ char *c = strrchr(addr, '@');
+ if (c != NULL) {
+ *c = '\0';
+ if (bus_port_ptr != NULL)
+ *bus_port_ptr = atoi(c + 1);
+ }
+ c = strrchr(addr, ':');
+ if (c != NULL) {
+ *c = '\0';
+ *ip_ptr = addr;
+ *port_ptr = atoi(++c);
+ } else return 0;
+ return 1;
+}
+
+/* Get host ip and port from command arguments. If only one argument has
+ * been provided it must be in the form of 'ip:port', elsewhere
+ * the first argument must be the ip and the second one the port.
+ * If host and port can be detected, it returns 1 and it stores host and
+ * port into variables referenced by'ip_ptr' and 'port_ptr' pointers,
+ * elsewhere it returns 0. */
+static int getClusterHostFromCmdArgs(int argc, char **argv,
+ char **ip_ptr, int *port_ptr) {
+ int port = 0;
+ char *ip = NULL;
+ if (argc == 1) {
+ char *addr = argv[0];
+ if (!parseClusterNodeAddress(addr, &ip, &port, NULL)) return 0;
+ } else {
+ ip = argv[0];
+ port = atoi(argv[1]);
+ }
+ if (!ip || !port) return 0;
+ else {
+ *ip_ptr = ip;
+ *port_ptr = port;
+ }
+ return 1;
+}
+
+static void freeClusterManagerNodeFlags(list *flags) {
+ listIter li;
+ listNode *ln;
+ listRewind(flags, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds flag = ln->value;
+ sdsfree(flag);
+ }
+ listRelease(flags);
+}
+
+static void freeClusterManagerNode(clusterManagerNode *node) {
+ if (node->context != NULL) redisFree(node->context);
+ if (node->friends != NULL) {
+ listIter li;
+ listNode *ln;
+ listRewind(node->friends,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *fn = ln->value;
+ freeClusterManagerNode(fn);
+ }
+ listRelease(node->friends);
+ node->friends = NULL;
+ }
+ if (node->name != NULL) sdsfree(node->name);
+ if (node->replicate != NULL) sdsfree(node->replicate);
+ if ((node->flags & CLUSTER_MANAGER_FLAG_FRIEND) && node->ip)
+ sdsfree(node->ip);
+ int i;
+ if (node->migrating != NULL) {
+ for (i = 0; i < node->migrating_count; i++) sdsfree(node->migrating[i]);
+ zfree(node->migrating);
+ }
+ if (node->importing != NULL) {
+ for (i = 0; i < node->importing_count; i++) sdsfree(node->importing[i]);
+ zfree(node->importing);
+ }
+ if (node->flags_str != NULL) {
+ freeClusterManagerNodeFlags(node->flags_str);
+ node->flags_str = NULL;
+ }
+ zfree(node);
+}
+
+static void freeClusterManager(void) {
+ listIter li;
+ listNode *ln;
+ if (cluster_manager.nodes != NULL) {
+ listRewind(cluster_manager.nodes,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ freeClusterManagerNode(n);
+ }
+ listRelease(cluster_manager.nodes);
+ cluster_manager.nodes = NULL;
+ }
+ if (cluster_manager.errors != NULL) {
+ listRewind(cluster_manager.errors,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds err = ln->value;
+ sdsfree(err);
+ }
+ listRelease(cluster_manager.errors);
+ cluster_manager.errors = NULL;
+ }
+ if (clusterManagerUncoveredSlots != NULL)
+ dictRelease(clusterManagerUncoveredSlots);
+}
+
+static clusterManagerNode *clusterManagerNewNode(char *ip, int port) {
+ clusterManagerNode *node = zmalloc(sizeof(*node));
+ node->context = NULL;
+ node->name = NULL;
+ node->ip = ip;
+ node->port = port;
+ node->current_epoch = 0;
+ node->ping_sent = 0;
+ node->ping_recv = 0;
+ node->flags = 0;
+ node->flags_str = NULL;
+ node->replicate = NULL;
+ node->dirty = 0;
+ node->friends = NULL;
+ node->migrating = NULL;
+ node->importing = NULL;
+ node->migrating_count = 0;
+ node->importing_count = 0;
+ node->replicas_count = 0;
+ node->weight = 1.0f;
+ node->balance = 0;
+ clusterManagerNodeResetSlots(node);
+ return node;
+}
+
+static sds clusterManagerGetNodeRDBFilename(clusterManagerNode *node) {
+ assert(config.cluster_manager_command.backup_dir);
+ sds filename = sdsnew(config.cluster_manager_command.backup_dir);
+ if (filename[sdslen(filename) - 1] != '/')
+ filename = sdscat(filename, "/");
+ filename = sdscatprintf(filename, "redis-node-%s-%d-%s.rdb", node->ip,
+ node->port, node->name);
+ return filename;
+}
+
+/* Check whether reply is NULL or its type is REDIS_REPLY_ERROR. In the
+ * latest case, if the 'err' arg is not NULL, it gets allocated with a copy
+ * of reply error (it's up to the caller function to free it), elsewhere
+ * the error is directly printed. */
+static int clusterManagerCheckRedisReply(clusterManagerNode *n,
+ redisReply *r, char **err)
+{
+ int is_err = 0;
+ if (!r || (is_err = (r->type == REDIS_REPLY_ERROR))) {
+ if (is_err) {
+ if (err != NULL) {
+ *err = zmalloc((r->len + 1) * sizeof(char));
+ strcpy(*err, r->str);
+ } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, r->str);
+ }
+ return 0;
+ }
+ return 1;
+}
+
+/* Call MULTI command on a cluster node. */
+static int clusterManagerStartTransaction(clusterManagerNode *node) {
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "MULTI");
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+/* Call EXEC command on a cluster node. */
+static int clusterManagerExecTransaction(clusterManagerNode *node,
+ clusterManagerOnReplyError onerror)
+{
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "EXEC");
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (success) {
+ if (reply->type != REDIS_REPLY_ARRAY) {
+ success = 0;
+ goto cleanup;
+ }
+ size_t i;
+ for (i = 0; i < reply->elements; i++) {
+ redisReply *r = reply->element[i];
+ char *err = NULL;
+ success = clusterManagerCheckRedisReply(node, r, &err);
+ if (!success && onerror) success = onerror(r, node, i);
+ if (err) {
+ if (!success)
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ zfree(err);
+ }
+ if (!success) break;
+ }
+ }
+cleanup:
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+static int clusterManagerNodeConnect(clusterManagerNode *node) {
+ if (node->context) redisFree(node->context);
+ node->context = redisConnect(node->ip, node->port);
+ if (!node->context->err && config.tls) {
+ const char *err = NULL;
+ if (cliSecureConnection(node->context, &err) == REDIS_ERR && err) {
+ fprintf(stderr,"TLS Error: %s\n", err);
+ redisFree(node->context);
+ node->context = NULL;
+ return 0;
+ }
+ }
+ if (node->context->err) {
+ fprintf(stderr,"Could not connect to Redis at ");
+ fprintf(stderr,"%s:%d: %s\n", node->ip, node->port,
+ node->context->errstr);
+ redisFree(node->context);
+ node->context = NULL;
+ return 0;
+ }
+ /* Set aggressive KEEP_ALIVE socket option in the Redis context socket
+ * in order to prevent timeouts caused by the execution of long
+ * commands. At the same time this improves the detection of real
+ * errors. */
+ anetKeepAlive(NULL, node->context->fd, REDIS_CLI_KEEPALIVE_INTERVAL);
+ if (config.auth) {
+ redisReply *reply;
+ if (config.user == NULL)
+ reply = redisCommand(node->context,"AUTH %s", config.auth);
+ else
+ reply = redisCommand(node->context,"AUTH %s %s",
+ config.user,config.auth);
+ int ok = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (reply != NULL) freeReplyObject(reply);
+ if (!ok) return 0;
+ }
+ return 1;
+}
+
+static void clusterManagerRemoveNodeFromList(list *nodelist,
+ clusterManagerNode *node) {
+ listIter li;
+ listNode *ln;
+ listRewind(nodelist, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ if (node == ln->value) {
+ listDelNode(nodelist, ln);
+ break;
+ }
+ }
+}
+
+/* Return the node with the specified name (ID) or NULL. */
+static clusterManagerNode *clusterManagerNodeByName(const char *name) {
+ if (cluster_manager.nodes == NULL) return NULL;
+ clusterManagerNode *found = NULL;
+ sds lcname = sdsempty();
+ lcname = sdscpy(lcname, name);
+ sdstolower(lcname);
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->name && !sdscmp(n->name, lcname)) {
+ found = n;
+ break;
+ }
+ }
+ sdsfree(lcname);
+ return found;
+}
+
+/* Like clusterManagerNodeByName but the specified name can be just the first
+ * part of the node ID as long as the prefix in unique across the
+ * cluster.
+ */
+static clusterManagerNode *clusterManagerNodeByAbbreviatedName(const char*name)
+{
+ if (cluster_manager.nodes == NULL) return NULL;
+ clusterManagerNode *found = NULL;
+ sds lcname = sdsempty();
+ lcname = sdscpy(lcname, name);
+ sdstolower(lcname);
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->name &&
+ strstr(n->name, lcname) == n->name) {
+ found = n;
+ break;
+ }
+ }
+ sdsfree(lcname);
+ return found;
+}
+
+static void clusterManagerNodeResetSlots(clusterManagerNode *node) {
+ memset(node->slots, 0, sizeof(node->slots));
+ node->slots_count = 0;
+}
+
+/* Call "INFO" redis command on the specified node and return the reply. */
+static redisReply *clusterManagerGetNodeRedisInfo(clusterManagerNode *node,
+ char **err)
+{
+ redisReply *info = CLUSTER_MANAGER_COMMAND(node, "INFO");
+ if (err != NULL) *err = NULL;
+ if (info == NULL) return NULL;
+ if (info->type == REDIS_REPLY_ERROR) {
+ if (err != NULL) {
+ *err = zmalloc((info->len + 1) * sizeof(char));
+ strcpy(*err, info->str);
+ }
+ freeReplyObject(info);
+ return NULL;
+ }
+ return info;
+}
+
+static int clusterManagerNodeIsCluster(clusterManagerNode *node, char **err) {
+ redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
+ if (info == NULL) return 0;
+ int is_cluster = (int) getLongInfoField(info->str, "cluster_enabled");
+ freeReplyObject(info);
+ return is_cluster;
+}
+
+/* Checks whether the node is empty. Node is considered not-empty if it has
+ * some key or if it already knows other nodes */
+static int clusterManagerNodeIsEmpty(clusterManagerNode *node, char **err) {
+ redisReply *info = clusterManagerGetNodeRedisInfo(node, err);
+ int is_empty = 1;
+ if (info == NULL) return 0;
+ if (strstr(info->str, "db0:") != NULL) {
+ is_empty = 0;
+ goto result;
+ }
+ freeReplyObject(info);
+ info = CLUSTER_MANAGER_COMMAND(node, "CLUSTER INFO");
+ if (err != NULL) *err = NULL;
+ if (!clusterManagerCheckRedisReply(node, info, err)) {
+ is_empty = 0;
+ goto result;
+ }
+ long known_nodes = getLongInfoField(info->str, "cluster_known_nodes");
+ is_empty = (known_nodes == 1);
+result:
+ freeReplyObject(info);
+ return is_empty;
+}
+
+/* Return the anti-affinity score, which is a measure of the amount of
+ * violations of anti-affinity in the current cluster layout, that is, how
+ * badly the masters and slaves are distributed in the different IP
+ * addresses so that slaves of the same master are not in the master
+ * host and are also in different hosts.
+ *
+ * The score is calculated as follows:
+ *
+ * SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
+ * SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
+ of the same master.
+ * FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
+ *
+ * So a greater score means a worse anti-affinity level, while zero
+ * means perfect anti-affinity.
+ *
+ * The anti affinity optimizator will try to get a score as low as
+ * possible. Since we do not want to sacrifice the fact that slaves should
+ * not be in the same host as the master, we assign 10000 times the score
+ * to this violation, so that we'll optimize for the second factor only
+ * if it does not impact the first one.
+ *
+ * The ipnodes argument is an array of clusterManagerNodeArray, one for
+ * each IP, while ip_count is the total number of IPs in the configuration.
+ *
+ * The function returns the above score, and the list of
+ * offending slaves can be stored into the 'offending' argument,
+ * so that the optimizer can try changing the configuration of the
+ * slaves violating the anti-affinity goals. */
+static int clusterManagerGetAntiAffinityScore(clusterManagerNodeArray *ipnodes,
+ int ip_count, clusterManagerNode ***offending, int *offending_len)
+{
+ int score = 0, i, j;
+ int node_len = cluster_manager.nodes->len;
+ clusterManagerNode **offending_p = NULL;
+ if (offending != NULL) {
+ *offending = zcalloc(node_len * sizeof(clusterManagerNode*));
+ offending_p = *offending;
+ }
+ /* For each set of nodes in the same host, split by
+ * related nodes (masters and slaves which are involved in
+ * replication of each other) */
+ for (i = 0; i < ip_count; i++) {
+ clusterManagerNodeArray *node_array = &(ipnodes[i]);
+ dict *related = dictCreate(&clusterManagerDictType, NULL);
+ char *ip = NULL;
+ for (j = 0; j < node_array->len; j++) {
+ clusterManagerNode *node = node_array->nodes[j];
+ if (node == NULL) continue;
+ if (!ip) ip = node->ip;
+ sds types;
+ /* We always use the Master ID as key. */
+ sds key = (!node->replicate ? node->name : node->replicate);
+ assert(key != NULL);
+ dictEntry *entry = dictFind(related, key);
+ if (entry) types = sdsdup((sds) dictGetVal(entry));
+ else types = sdsempty();
+ /* Master type 'm' is always set as the first character of the
+ * types string. */
+ if (!node->replicate) types = sdscatprintf(types, "m%s", types);
+ else types = sdscat(types, "s");
+ dictReplace(related, key, types);
+ }
+ /* Now it's trivial to check, for each related group having the
+ * same host, what is their local score. */
+ dictIterator *iter = dictGetIterator(related);
+ dictEntry *entry;
+ while ((entry = dictNext(iter)) != NULL) {
+ sds types = (sds) dictGetVal(entry);
+ sds name = (sds) dictGetKey(entry);
+ int typeslen = sdslen(types);
+ if (typeslen < 2) continue;
+ if (types[0] == 'm') score += (10000 * (typeslen - 1));
+ else score += (1 * typeslen);
+ if (offending == NULL) continue;
+ /* Populate the list of offending nodes. */
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->replicate == NULL) continue;
+ if (!strcmp(n->replicate, name) && !strcmp(n->ip, ip)) {
+ *(offending_p++) = n;
+ if (offending_len != NULL) (*offending_len)++;
+ break;
+ }
+ }
+ }
+ //if (offending_len != NULL) *offending_len = offending_p - *offending;
+ dictReleaseIterator(iter);
+ dictRelease(related);
+ }
+ return score;
+}
+
+static void clusterManagerOptimizeAntiAffinity(clusterManagerNodeArray *ipnodes,
+ int ip_count)
+{
+ clusterManagerNode **offenders = NULL;
+ int score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count,
+ NULL, NULL);
+ if (score == 0) goto cleanup;
+ clusterManagerLogInfo(">>> Trying to optimize slaves allocation "
+ "for anti-affinity\n");
+ int node_len = cluster_manager.nodes->len;
+ int maxiter = 500 * node_len; // Effort is proportional to cluster size...
+ srand(time(NULL));
+ while (maxiter > 0) {
+ int offending_len = 0;
+ if (offenders != NULL) {
+ zfree(offenders);
+ offenders = NULL;
+ }
+ score = clusterManagerGetAntiAffinityScore(ipnodes,
+ ip_count,
+ &offenders,
+ &offending_len);
+ if (score == 0) break; // Optimal anti affinity reached
+ /* We'll try to randomly swap a slave's assigned master causing
+ * an affinity problem with another random slave, to see if we
+ * can improve the affinity. */
+ int rand_idx = rand() % offending_len;
+ clusterManagerNode *first = offenders[rand_idx],
+ *second = NULL;
+ clusterManagerNode **other_replicas = zcalloc((node_len - 1) *
+ sizeof(*other_replicas));
+ int other_replicas_count = 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n != first && n->replicate != NULL)
+ other_replicas[other_replicas_count++] = n;
+ }
+ if (other_replicas_count == 0) {
+ zfree(other_replicas);
+ break;
+ }
+ rand_idx = rand() % other_replicas_count;
+ second = other_replicas[rand_idx];
+ char *first_master = first->replicate,
+ *second_master = second->replicate;
+ first->replicate = second_master, first->dirty = 1;
+ second->replicate = first_master, second->dirty = 1;
+ int new_score = clusterManagerGetAntiAffinityScore(ipnodes,
+ ip_count,
+ NULL, NULL);
+ /* If the change actually makes thing worse, revert. Otherwise
+ * leave as it is because the best solution may need a few
+ * combined swaps. */
+ if (new_score > score) {
+ first->replicate = first_master;
+ second->replicate = second_master;
+ }
+ zfree(other_replicas);
+ maxiter--;
+ }
+ score = clusterManagerGetAntiAffinityScore(ipnodes, ip_count, NULL, NULL);
+ char *msg;
+ int perfect = (score == 0);
+ int log_level = (perfect ? CLUSTER_MANAGER_LOG_LVL_SUCCESS :
+ CLUSTER_MANAGER_LOG_LVL_WARN);
+ if (perfect) msg = "[OK] Perfect anti-affinity obtained!";
+ else if (score >= 10000)
+ msg = ("[WARNING] Some slaves are in the same host as their master");
+ else
+ msg=("[WARNING] Some slaves of the same master are in the same host");
+ clusterManagerLog(log_level, "%s\n", msg);
+cleanup:
+ zfree(offenders);
+}
+
+/* Return a representable string of the node's flags */
+static sds clusterManagerNodeFlagString(clusterManagerNode *node) {
+ sds flags = sdsempty();
+ if (!node->flags_str) return flags;
+ int empty = 1;
+ listIter li;
+ listNode *ln;
+ listRewind(node->flags_str, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds flag = ln->value;
+ if (strcmp(flag, "myself") == 0) continue;
+ if (!empty) flags = sdscat(flags, ",");
+ flags = sdscatfmt(flags, "%S", flag);
+ empty = 0;
+ }
+ return flags;
+}
+
+/* Return a representable string of the node's slots */
+static sds clusterManagerNodeSlotsString(clusterManagerNode *node) {
+ sds slots = sdsempty();
+ int first_range_idx = -1, last_slot_idx = -1, i;
+ for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
+ int has_slot = node->slots[i];
+ if (has_slot) {
+ if (first_range_idx == -1) {
+ if (sdslen(slots)) slots = sdscat(slots, ",");
+ first_range_idx = i;
+ slots = sdscatfmt(slots, "[%u", i);
+ }
+ last_slot_idx = i;
+ } else {
+ if (last_slot_idx >= 0) {
+ if (first_range_idx == last_slot_idx)
+ slots = sdscat(slots, "]");
+ else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
+ }
+ last_slot_idx = -1;
+ first_range_idx = -1;
+ }
+ }
+ if (last_slot_idx >= 0) {
+ if (first_range_idx == last_slot_idx) slots = sdscat(slots, "]");
+ else slots = sdscatfmt(slots, "-%u]", last_slot_idx);
+ }
+ return slots;
+}
+
+static sds clusterManagerNodeGetJSON(clusterManagerNode *node,
+ unsigned long error_count)
+{
+ sds json = sdsempty();
+ sds replicate = sdsempty();
+ if (node->replicate)
+ replicate = sdscatprintf(replicate, "\"%s\"", node->replicate);
+ else
+ replicate = sdscat(replicate, "null");
+ sds slots = clusterManagerNodeSlotsString(node);
+ sds flags = clusterManagerNodeFlagString(node);
+ char *p = slots;
+ while ((p = strchr(p, '-')) != NULL)
+ *(p++) = ',';
+ json = sdscatprintf(json,
+ " {\n"
+ " \"name\": \"%s\",\n"
+ " \"host\": \"%s\",\n"
+ " \"port\": %d,\n"
+ " \"replicate\": %s,\n"
+ " \"slots\": [%s],\n"
+ " \"slots_count\": %d,\n"
+ " \"flags\": \"%s\",\n"
+ " \"current_epoch\": %llu",
+ node->name,
+ node->ip,
+ node->port,
+ replicate,
+ slots,
+ node->slots_count,
+ flags,
+ (unsigned long long)node->current_epoch
+ );
+ if (error_count > 0) {
+ json = sdscatprintf(json, ",\n \"cluster_errors\": %lu",
+ error_count);
+ }
+ if (node->migrating_count > 0 && node->migrating != NULL) {
+ int i = 0;
+ sds migrating = sdsempty();
+ for (; i < node->migrating_count; i += 2) {
+ sds slot = node->migrating[i];
+ sds dest = node->migrating[i + 1];
+ if (slot && dest) {
+ if (sdslen(migrating) > 0) migrating = sdscat(migrating, ",");
+ migrating = sdscatfmt(migrating, "\"%S\": \"%S\"", slot, dest);
+ }
+ }
+ if (sdslen(migrating) > 0)
+ json = sdscatfmt(json, ",\n \"migrating\": {%S}", migrating);
+ sdsfree(migrating);
+ }
+ if (node->importing_count > 0 && node->importing != NULL) {
+ int i = 0;
+ sds importing = sdsempty();
+ for (; i < node->importing_count; i += 2) {
+ sds slot = node->importing[i];
+ sds from = node->importing[i + 1];
+ if (slot && from) {
+ if (sdslen(importing) > 0) importing = sdscat(importing, ",");
+ importing = sdscatfmt(importing, "\"%S\": \"%S\"", slot, from);
+ }
+ }
+ if (sdslen(importing) > 0)
+ json = sdscatfmt(json, ",\n \"importing\": {%S}", importing);
+ sdsfree(importing);
+ }
+ json = sdscat(json, "\n }");
+ sdsfree(replicate);
+ sdsfree(slots);
+ sdsfree(flags);
+ return json;
+}
+
+
+/* -----------------------------------------------------------------------------
+ * Key space handling
+ * -------------------------------------------------------------------------- */
+
+/* We have 16384 hash slots. The hash slot of a given key is obtained
+ * as the least significant 14 bits of the crc16 of the key.
+ *
+ * However if the key contains the {...} pattern, only the part between
+ * { and } is hashed. This may be useful in the future to force certain
+ * keys to be in the same node (assuming no resharding is in progress). */
+static unsigned int clusterManagerKeyHashSlot(char *key, int keylen) {
+ int s, e; /* start-end indexes of { and } */
+
+ for (s = 0; s < keylen; s++)
+ if (key[s] == '{') break;
+
+ /* No '{' ? Hash the whole key. This is the base case. */
+ if (s == keylen) return crc16(key,keylen) & 0x3FFF;
+
+ /* '{' found? Check if we have the corresponding '}'. */
+ for (e = s+1; e < keylen; e++)
+ if (key[e] == '}') break;
+
+ /* No '}' or nothing between {} ? Hash the whole key. */
+ if (e == keylen || e == s+1) return crc16(key,keylen) & 0x3FFF;
+
+ /* If we are here there is both a { and a } on its right. Hash
+ * what is in the middle between { and }. */
+ return crc16(key+s+1,e-s-1) & 0x3FFF;
+}
+
+/* Return a string representation of the cluster node. */
+static sds clusterManagerNodeInfo(clusterManagerNode *node, int indent) {
+ sds info = sdsempty();
+ sds spaces = sdsempty();
+ int i;
+ for (i = 0; i < indent; i++) spaces = sdscat(spaces, " ");
+ if (indent) info = sdscat(info, spaces);
+ int is_master = !(node->flags & CLUSTER_MANAGER_FLAG_SLAVE);
+ char *role = (is_master ? "M" : "S");
+ sds slots = NULL;
+ if (node->dirty && node->replicate != NULL)
+ info = sdscatfmt(info, "S: %S %s:%u", node->name, node->ip, node->port);
+ else {
+ slots = clusterManagerNodeSlotsString(node);
+ sds flags = clusterManagerNodeFlagString(node);
+ info = sdscatfmt(info, "%s: %S %s:%u\n"
+ "%s slots:%S (%u slots) "
+ "%S",
+ role, node->name, node->ip, node->port, spaces,
+ slots, node->slots_count, flags);
+ sdsfree(slots);
+ sdsfree(flags);
+ }
+ if (node->replicate != NULL)
+ info = sdscatfmt(info, "\n%s replicates %S", spaces, node->replicate);
+ else if (node->replicas_count)
+ info = sdscatfmt(info, "\n%s %U additional replica(s)",
+ spaces, node->replicas_count);
+ sdsfree(spaces);
+ return info;
+}
+
+static void clusterManagerShowNodes(void) {
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ sds info = clusterManagerNodeInfo(node, 0);
+ printf("%s\n", (char *) info);
+ sdsfree(info);
+ }
+}
+
+static void clusterManagerShowClusterInfo(void) {
+ int masters = 0;
+ int keys = 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ if (!(node->flags & CLUSTER_MANAGER_FLAG_SLAVE)) {
+ if (!node->name) continue;
+ int replicas = 0;
+ int dbsize = -1;
+ char name[9];
+ memcpy(name, node->name, 8);
+ name[8] = '\0';
+ listIter ri;
+ listNode *rn;
+ listRewind(cluster_manager.nodes, &ri);
+ while ((rn = listNext(&ri)) != NULL) {
+ clusterManagerNode *n = rn->value;
+ if (n == node || !(n->flags & CLUSTER_MANAGER_FLAG_SLAVE))
+ continue;
+ if (n->replicate && !strcmp(n->replicate, node->name))
+ replicas++;
+ }
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "DBSIZE");
+ if (reply != NULL && reply->type == REDIS_REPLY_INTEGER)
+ dbsize = reply->integer;
+ if (dbsize < 0) {
+ char *err = "";
+ if (reply != NULL && reply->type == REDIS_REPLY_ERROR)
+ err = reply->str;
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ if (reply != NULL) freeReplyObject(reply);
+ return;
+ };
+ if (reply != NULL) freeReplyObject(reply);
+ printf("%s:%d (%s...) -> %d keys | %d slots | %d slaves.\n",
+ node->ip, node->port, name, dbsize,
+ node->slots_count, replicas);
+ masters++;
+ keys += dbsize;
+ }
+ }
+ clusterManagerLogOk("[OK] %d keys in %d masters.\n", keys, masters);
+ float keys_per_slot = keys / (float) CLUSTER_MANAGER_SLOTS;
+ printf("%.2f keys per slot on average.\n", keys_per_slot);
+}
+
+/* Flush dirty slots configuration of the node by calling CLUSTER ADDSLOTS */
+static int clusterManagerAddSlots(clusterManagerNode *node, char**err)
+{
+ redisReply *reply = NULL;
+ void *_reply = NULL;
+ int success = 1;
+ /* First two args are used for the command itself. */
+ int argc = node->slots_count + 2;
+ sds *argv = zmalloc(argc * sizeof(*argv));
+ size_t *argvlen = zmalloc(argc * sizeof(*argvlen));
+ argv[0] = "CLUSTER";
+ argv[1] = "ADDSLOTS";
+ argvlen[0] = 7;
+ argvlen[1] = 8;
+ *err = NULL;
+ int i, argv_idx = 2;
+ for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
+ if (argv_idx >= argc) break;
+ if (node->slots[i]) {
+ argv[argv_idx] = sdsfromlonglong((long long) i);
+ argvlen[argv_idx] = sdslen(argv[argv_idx]);
+ argv_idx++;
+ }
+ }
+ if (!argv_idx) {
+ success = 0;
+ goto cleanup;
+ }
+ redisAppendCommandArgv(node->context,argc,(const char**)argv,argvlen);
+ if (redisGetReply(node->context, &_reply) != REDIS_OK) {
+ success = 0;
+ goto cleanup;
+ }
+ reply = (redisReply*) _reply;
+ success = clusterManagerCheckRedisReply(node, reply, err);
+cleanup:
+ zfree(argvlen);
+ if (argv != NULL) {
+ for (i = 2; i < argc; i++) sdsfree(argv[i]);
+ zfree(argv);
+ }
+ if (reply != NULL) freeReplyObject(reply);
+ return success;
+}
+
+/* Get the node the slot is assigned to from the point of view of node *n.
+ * If the slot is unassigned or if the reply is an error, return NULL.
+ * Use the **err argument in order to check wether the slot is unassigned
+ * or the reply resulted in an error. */
+static clusterManagerNode *clusterManagerGetSlotOwner(clusterManagerNode *n,
+ int slot, char **err)
+{
+ assert(slot >= 0 && slot < CLUSTER_MANAGER_SLOTS);
+ clusterManagerNode *owner = NULL;
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SLOTS");
+ if (clusterManagerCheckRedisReply(n, reply, err)) {
+ assert(reply->type == REDIS_REPLY_ARRAY);
+ size_t i;
+ for (i = 0; i < reply->elements; i++) {
+ redisReply *r = reply->element[i];
+ assert(r->type == REDIS_REPLY_ARRAY && r->elements >= 3);
+ int from, to;
+ from = r->element[0]->integer;
+ to = r->element[1]->integer;
+ if (slot < from || slot > to) continue;
+ redisReply *nr = r->element[2];
+ assert(nr->type == REDIS_REPLY_ARRAY && nr->elements >= 2);
+ char *name = NULL;
+ if (nr->elements >= 3)
+ name = nr->element[2]->str;
+ if (name != NULL)
+ owner = clusterManagerNodeByName(name);
+ else {
+ char *ip = nr->element[0]->str;
+ assert(ip != NULL);
+ int port = (int) nr->element[1]->integer;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *nd = ln->value;
+ if (strcmp(nd->ip, ip) == 0 && port == nd->port) {
+ owner = nd;
+ break;
+ }
+ }
+ }
+ if (owner) break;
+ }
+ }
+ if (reply) freeReplyObject(reply);
+ return owner;
+}
+
+/* Set slot status to "importing" or "migrating" */
+static int clusterManagerSetSlot(clusterManagerNode *node1,
+ clusterManagerNode *node2,
+ int slot, const char *status, char **err) {
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node1, "CLUSTER "
+ "SETSLOT %d %s %s",
+ slot, status,
+ (char *) node2->name);
+ if (err != NULL) *err = NULL;
+ if (!reply) return 0;
+ int success = 1;
+ if (reply->type == REDIS_REPLY_ERROR) {
+ success = 0;
+ if (err != NULL) {
+ *err = zmalloc((reply->len + 1) * sizeof(char));
+ strcpy(*err, reply->str);
+ } else CLUSTER_MANAGER_PRINT_REPLY_ERROR(node1, reply->str);
+ goto cleanup;
+ }
+cleanup:
+ freeReplyObject(reply);
+ return success;
+}
+
+static int clusterManagerClearSlotStatus(clusterManagerNode *node, int slot) {
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
+ "CLUSTER SETSLOT %d %s", slot, "STABLE");
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+static int clusterManagerDelSlot(clusterManagerNode *node, int slot,
+ int ignore_unassigned_err)
+{
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
+ "CLUSTER DELSLOTS %d", slot);
+ char *err = NULL;
+ int success = clusterManagerCheckRedisReply(node, reply, &err);
+ if (!success && reply && reply->type == REDIS_REPLY_ERROR &&
+ ignore_unassigned_err)
+ {
+ char *get_owner_err = NULL;
+ clusterManagerNode *assigned_to =
+ clusterManagerGetSlotOwner(node, slot, &get_owner_err);
+ if (!assigned_to) {
+ if (get_owner_err == NULL) success = 1;
+ else {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, get_owner_err);
+ zfree(get_owner_err);
+ }
+ }
+ }
+ if (!success && err != NULL) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ zfree(err);
+ }
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+static int clusterManagerAddSlot(clusterManagerNode *node, int slot) {
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
+ "CLUSTER ADDSLOTS %d", slot);
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+static signed int clusterManagerCountKeysInSlot(clusterManagerNode *node,
+ int slot)
+{
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node,
+ "CLUSTER COUNTKEYSINSLOT %d", slot);
+ int count = -1;
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (success && reply->type == REDIS_REPLY_INTEGER) count = reply->integer;
+ if (reply) freeReplyObject(reply);
+ return count;
+}
+
+static int clusterManagerBumpEpoch(clusterManagerNode *node) {
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER BUMPEPOCH");
+ int success = clusterManagerCheckRedisReply(node, reply, NULL);
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+/* Callback used by clusterManagerSetSlotOwner transaction. It should ignore
+ * errors except for ADDSLOTS errors.
+ * Return 1 if the error should be ignored. */
+static int clusterManagerOnSetOwnerErr(redisReply *reply,
+ clusterManagerNode *n, int bulk_idx)
+{
+ UNUSED(reply);
+ UNUSED(n);
+ /* Only raise error when ADDSLOTS fail (bulk_idx == 1). */
+ return (bulk_idx != 1);
+}
+
+static int clusterManagerSetSlotOwner(clusterManagerNode *owner,
+ int slot,
+ int do_clear)
+{
+ int success = clusterManagerStartTransaction(owner);
+ if (!success) return 0;
+ /* Ensure the slot is not already assigned. */
+ clusterManagerDelSlot(owner, slot, 1);
+ /* Add the slot and bump epoch. */
+ clusterManagerAddSlot(owner, slot);
+ if (do_clear) clusterManagerClearSlotStatus(owner, slot);
+ clusterManagerBumpEpoch(owner);
+ success = clusterManagerExecTransaction(owner, clusterManagerOnSetOwnerErr);
+ return success;
+}
+
+/* Get the hash for the values of the specified keys in *keys_reply for the
+ * specified nodes *n1 and *n2, by calling DEBUG DIGEST-VALUE redis command
+ * on both nodes. Every key with same name on both nodes but having different
+ * values will be added to the *diffs list. Return 0 in case of reply
+ * error. */
+static int clusterManagerCompareKeysValues(clusterManagerNode *n1,
+ clusterManagerNode *n2,
+ redisReply *keys_reply,
+ list *diffs)
+{
+ size_t i, argc = keys_reply->elements + 2;
+ static const char *hash_zero = "0000000000000000000000000000000000000000";
+ char **argv = zcalloc(argc * sizeof(char *));
+ size_t *argv_len = zcalloc(argc * sizeof(size_t));
+ argv[0] = "DEBUG";
+ argv_len[0] = 5;
+ argv[1] = "DIGEST-VALUE";
+ argv_len[1] = 12;
+ for (i = 0; i < keys_reply->elements; i++) {
+ redisReply *entry = keys_reply->element[i];
+ int idx = i + 2;
+ argv[idx] = entry->str;
+ argv_len[idx] = entry->len;
+ }
+ int success = 0;
+ void *_reply1 = NULL, *_reply2 = NULL;
+ redisReply *r1 = NULL, *r2 = NULL;
+ redisAppendCommandArgv(n1->context,argc, (const char**)argv,argv_len);
+ success = (redisGetReply(n1->context, &_reply1) == REDIS_OK);
+ if (!success) goto cleanup;
+ r1 = (redisReply *) _reply1;
+ redisAppendCommandArgv(n2->context,argc, (const char**)argv,argv_len);
+ success = (redisGetReply(n2->context, &_reply2) == REDIS_OK);
+ if (!success) goto cleanup;
+ r2 = (redisReply *) _reply2;
+ success = (r1->type != REDIS_REPLY_ERROR && r2->type != REDIS_REPLY_ERROR);
+ if (r1->type == REDIS_REPLY_ERROR) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(n1, r1->str);
+ success = 0;
+ }
+ if (r2->type == REDIS_REPLY_ERROR) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(n2, r2->str);
+ success = 0;
+ }
+ if (!success) goto cleanup;
+ assert(keys_reply->elements == r1->elements &&
+ keys_reply->elements == r2->elements);
+ for (i = 0; i < keys_reply->elements; i++) {
+ char *key = keys_reply->element[i]->str;
+ char *hash1 = r1->element[i]->str;
+ char *hash2 = r2->element[i]->str;
+ /* Ignore keys that don't exist in both nodes. */
+ if (strcmp(hash1, hash_zero) == 0 || strcmp(hash2, hash_zero) == 0)
+ continue;
+ if (strcmp(hash1, hash2) != 0) listAddNodeTail(diffs, key);
+ }
+cleanup:
+ if (r1) freeReplyObject(r1);
+ if (r2) freeReplyObject(r2);
+ zfree(argv);
+ zfree(argv_len);
+ return success;
+}
+
+/* Migrate keys taken from reply->elements. It returns the reply from the
+ * MIGRATE command, or NULL if something goes wrong. If the argument 'dots'
+ * is not NULL, a dot will be printed for every migrated key. */
+static redisReply *clusterManagerMigrateKeysInReply(clusterManagerNode *source,
+ clusterManagerNode *target,
+ redisReply *reply,
+ int replace, int timeout,
+ char *dots)
+{
+ redisReply *migrate_reply = NULL;
+ char **argv = NULL;
+ size_t *argv_len = NULL;
+ int c = (replace ? 8 : 7);
+ if (config.auth) c += 2;
+ size_t argc = c + reply->elements;
+ size_t i, offset = 6; // Keys Offset
+ argv = zcalloc(argc * sizeof(char *));
+ argv_len = zcalloc(argc * sizeof(size_t));
+ char portstr[255];
+ char timeoutstr[255];
+ snprintf(portstr, 10, "%d", target->port);
+ snprintf(timeoutstr, 10, "%d", timeout);
+ argv[0] = "MIGRATE";
+ argv_len[0] = 7;
+ argv[1] = target->ip;
+ argv_len[1] = strlen(target->ip);
+ argv[2] = portstr;
+ argv_len[2] = strlen(portstr);
+ argv[3] = "";
+ argv_len[3] = 0;
+ argv[4] = "0";
+ argv_len[4] = 1;
+ argv[5] = timeoutstr;
+ argv_len[5] = strlen(timeoutstr);
+ if (replace) {
+ argv[offset] = "REPLACE";
+ argv_len[offset] = 7;
+ offset++;
+ }
+ if (config.auth) {
+ argv[offset] = "AUTH";
+ argv_len[offset] = 4;
+ offset++;
+ argv[offset] = config.auth;
+ argv_len[offset] = strlen(config.auth);
+ offset++;
+ }
+ argv[offset] = "KEYS";
+ argv_len[offset] = 4;
+ offset++;
+ for (i = 0; i < reply->elements; i++) {
+ redisReply *entry = reply->element[i];
+ size_t idx = i + offset;
+ assert(entry->type == REDIS_REPLY_STRING);
+ argv[idx] = (char *) sdsnewlen(entry->str, entry->len);
+ argv_len[idx] = entry->len;
+ if (dots) dots[i] = '.';
+ }
+ if (dots) dots[reply->elements] = '\0';
+ void *_reply = NULL;
+ redisAppendCommandArgv(source->context,argc,
+ (const char**)argv,argv_len);
+ int success = (redisGetReply(source->context, &_reply) == REDIS_OK);
+ for (i = 0; i < reply->elements; i++) sdsfree(argv[i + offset]);
+ if (!success) goto cleanup;
+ migrate_reply = (redisReply *) _reply;
+cleanup:
+ zfree(argv);
+ zfree(argv_len);
+ return migrate_reply;
+}
+
+/* Migrate all keys in the given slot from source to target.*/
+static int clusterManagerMigrateKeysInSlot(clusterManagerNode *source,
+ clusterManagerNode *target,
+ int slot, int timeout,
+ int pipeline, int verbose,
+ char **err)
+{
+ int success = 1;
+ int do_fix = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_FIX;
+ int do_replace = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_REPLACE;
+ while (1) {
+ char *dots = NULL;
+ redisReply *reply = NULL, *migrate_reply = NULL;
+ reply = CLUSTER_MANAGER_COMMAND(source, "CLUSTER "
+ "GETKEYSINSLOT %d %d", slot,
+ pipeline);
+ success = (reply != NULL);
+ if (!success) return 0;
+ if (reply->type == REDIS_REPLY_ERROR) {
+ success = 0;
+ if (err != NULL) {
+ *err = zmalloc((reply->len + 1) * sizeof(char));
+ strcpy(*err, reply->str);
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(source, *err);
+ }
+ goto next;
+ }
+ assert(reply->type == REDIS_REPLY_ARRAY);
+ size_t count = reply->elements;
+ if (count == 0) {
+ freeReplyObject(reply);
+ break;
+ }
+ if (verbose) dots = zmalloc((count+1) * sizeof(char));
+ /* Calling MIGRATE command. */
+ migrate_reply = clusterManagerMigrateKeysInReply(source, target,
+ reply, 0, timeout,
+ dots);
+ if (migrate_reply == NULL) goto next;
+ if (migrate_reply->type == REDIS_REPLY_ERROR) {
+ int is_busy = strstr(migrate_reply->str, "BUSYKEY") != NULL;
+ int not_served = 0;
+ if (!is_busy) {
+ /* Check if the slot is unassigned (not served) in the
+ * source node's configuration. */
+ char *get_owner_err = NULL;
+ clusterManagerNode *served_by =
+ clusterManagerGetSlotOwner(source, slot, &get_owner_err);
+ if (!served_by) {
+ if (get_owner_err == NULL) not_served = 1;
+ else {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
+ get_owner_err);
+ zfree(get_owner_err);
+ }
+ }
+ }
+ /* Try to handle errors. */
+ if (is_busy || not_served) {
+ /* If the key's slot is not served, try to assign slot
+ * to the target node. */
+ if (do_fix && not_served) {
+ clusterManagerLogWarn("*** Slot was not served, setting "
+ "owner to node %s:%d.\n",
+ target->ip, target->port);
+ clusterManagerSetSlot(source, target, slot, "node", NULL);
+ }
+ /* If the key already exists in the target node (BUSYKEY),
+ * check whether its value is the same in both nodes.
+ * In case of equal values, retry migration with the
+ * REPLACE option.
+ * In case of different values:
+ * - If the migration is requested by the fix command, stop
+ * and warn the user.
+ * - In other cases (ie. reshard), proceed only if the user
+ * launched the command with the --cluster-replace option.*/
+ if (is_busy) {
+ clusterManagerLogWarn("\n*** Target key exists\n");
+ if (!do_replace) {
+ clusterManagerLogWarn("*** Checking key values on "
+ "both nodes...\n");
+ list *diffs = listCreate();
+ success = clusterManagerCompareKeysValues(source,
+ target, reply, diffs);
+ if (!success) {
+ clusterManagerLogErr("*** Value check failed!\n");
+ listRelease(diffs);
+ goto next;
+ }
+ if (listLength(diffs) > 0) {
+ success = 0;
+ clusterManagerLogErr(
+ "*** Found %d key(s) in both source node and "
+ "target node having different values.\n"
+ " Source node: %s:%d\n"
+ " Target node: %s:%d\n"
+ " Keys(s):\n",
+ listLength(diffs),
+ source->ip, source->port,
+ target->ip, target->port);
+ listIter dli;
+ listNode *dln;
+ listRewind(diffs, &dli);
+ while((dln = listNext(&dli)) != NULL) {
+ char *k = dln->value;
+ clusterManagerLogErr(" - %s\n", k);
+ }
+ clusterManagerLogErr("Please fix the above key(s) "
+ "manually and try again "
+ "or relaunch the command \n"
+ "with --cluster-replace "
+ "option to force key "
+ "overriding.\n");
+ listRelease(diffs);
+ goto next;
+ }
+ listRelease(diffs);
+ }
+ clusterManagerLogWarn("*** Replacing target keys...\n");
+ }
+ freeReplyObject(migrate_reply);
+ migrate_reply = clusterManagerMigrateKeysInReply(source,
+ target,
+ reply,
+ is_busy,
+ timeout,
+ NULL);
+ success = (migrate_reply != NULL &&
+ migrate_reply->type != REDIS_REPLY_ERROR);
+ } else success = 0;
+ if (!success) {
+ if (migrate_reply != NULL) {
+ if (err) {
+ *err = zmalloc((migrate_reply->len + 1) * sizeof(char));
+ strcpy(*err, migrate_reply->str);
+ }
+ printf("\n");
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(source,
+ migrate_reply->str);
+ }
+ goto next;
+ }
+ }
+ if (verbose) {
+ printf("%s", dots);
+ fflush(stdout);
+ }
+next:
+ if (reply != NULL) freeReplyObject(reply);
+ if (migrate_reply != NULL) freeReplyObject(migrate_reply);
+ if (dots) zfree(dots);
+ if (!success) break;
+ }
+ return success;
+}
+
+/* Move slots between source and target nodes using MIGRATE.
+ *
+ * Options:
+ * CLUSTER_MANAGER_OPT_VERBOSE -- Print a dot for every moved key.
+ * CLUSTER_MANAGER_OPT_COLD -- Move keys without opening slots /
+ * reconfiguring the nodes.
+ * CLUSTER_MANAGER_OPT_UPDATE -- Update node->slots for source/target nodes.
+ * CLUSTER_MANAGER_OPT_QUIET -- Don't print info messages.
+*/
+static int clusterManagerMoveSlot(clusterManagerNode *source,
+ clusterManagerNode *target,
+ int slot, int opts, char**err)
+{
+ if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) {
+ printf("Moving slot %d from %s:%d to %s:%d: ", slot, source->ip,
+ source->port, target->ip, target->port);
+ fflush(stdout);
+ }
+ if (err != NULL) *err = NULL;
+ int pipeline = config.cluster_manager_command.pipeline,
+ timeout = config.cluster_manager_command.timeout,
+ print_dots = (opts & CLUSTER_MANAGER_OPT_VERBOSE),
+ option_cold = (opts & CLUSTER_MANAGER_OPT_COLD),
+ success = 1;
+ if (!option_cold) {
+ success = clusterManagerSetSlot(target, source, slot,
+ "importing", err);
+ if (!success) return 0;
+ success = clusterManagerSetSlot(source, target, slot,
+ "migrating", err);
+ if (!success) return 0;
+ }
+ success = clusterManagerMigrateKeysInSlot(source, target, slot, timeout,
+ pipeline, print_dots, err);
+ if (!(opts & CLUSTER_MANAGER_OPT_QUIET)) printf("\n");
+ if (!success) return 0;
+ /* Set the new node as the owner of the slot in all the known nodes. */
+ if (!option_cold) {
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER "
+ "SETSLOT %d %s %s",
+ slot, "node",
+ target->name);
+ success = (r != NULL);
+ if (!success) return 0;
+ if (r->type == REDIS_REPLY_ERROR) {
+ success = 0;
+ if (err != NULL) {
+ *err = zmalloc((r->len + 1) * sizeof(char));
+ strcpy(*err, r->str);
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, *err);
+ }
+ }
+ freeReplyObject(r);
+ if (!success) return 0;
+ }
+ }
+ /* Update the node logical config */
+ if (opts & CLUSTER_MANAGER_OPT_UPDATE) {
+ source->slots[slot] = 0;
+ target->slots[slot] = 1;
+ }
+ return 1;
+}
+
+/* Flush the dirty node configuration by calling replicate for slaves or
+ * adding the slots defined in the masters. */
+static int clusterManagerFlushNodeConfig(clusterManagerNode *node, char **err) {
+ if (!node->dirty) return 0;
+ redisReply *reply = NULL;
+ int is_err = 0, success = 1;
+ if (err != NULL) *err = NULL;
+ if (node->replicate != NULL) {
+ reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER REPLICATE %s",
+ node->replicate);
+ if (reply == NULL || (is_err = (reply->type == REDIS_REPLY_ERROR))) {
+ if (is_err && err != NULL) {
+ *err = zmalloc((reply->len + 1) * sizeof(char));
+ strcpy(*err, reply->str);
+ }
+ success = 0;
+ /* If the cluster did not already joined it is possible that
+ * the slave does not know the master node yet. So on errors
+ * we return ASAP leaving the dirty flag set, to flush the
+ * config later. */
+ goto cleanup;
+ }
+ } else {
+ int added = clusterManagerAddSlots(node, err);
+ if (!added || *err != NULL) success = 0;
+ }
+ node->dirty = 0;
+cleanup:
+ if (reply != NULL) freeReplyObject(reply);
+ return success;
+}
+
+/* Wait until the cluster configuration is consistent. */
+static void clusterManagerWaitForClusterJoin(void) {
+ printf("Waiting for the cluster to join\n");
+ int counter = 0,
+ check_after = CLUSTER_JOIN_CHECK_AFTER +
+ (int)(listLength(cluster_manager.nodes) * 0.15f);
+ while(!clusterManagerIsConfigConsistent()) {
+ printf(".");
+ fflush(stdout);
+ sleep(1);
+ if (++counter > check_after) {
+ dict *status = clusterManagerGetLinkStatus();
+ dictIterator *iter = NULL;
+ if (status != NULL && dictSize(status) > 0) {
+ printf("\n");
+ clusterManagerLogErr("Warning: %d node(s) may "
+ "be unreachable\n", dictSize(status));
+ iter = dictGetIterator(status);
+ dictEntry *entry;
+ while ((entry = dictNext(iter)) != NULL) {
+ sds nodeaddr = (sds) dictGetKey(entry);
+ char *node_ip = NULL;
+ int node_port = 0, node_bus_port = 0;
+ list *from = (list *) dictGetVal(entry);
+ if (parseClusterNodeAddress(nodeaddr, &node_ip,
+ &node_port, &node_bus_port) && node_bus_port) {
+ clusterManagerLogErr(" - The port %d of node %s may "
+ "be unreachable from:\n",
+ node_bus_port, node_ip);
+ } else {
+ clusterManagerLogErr(" - Node %s may be unreachable "
+ "from:\n", nodeaddr);
+ }
+ listIter li;
+ listNode *ln;
+ listRewind(from, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds from_addr = ln->value;
+ clusterManagerLogErr(" %s\n", from_addr);
+ sdsfree(from_addr);
+ }
+ clusterManagerLogErr("Cluster bus ports must be reachable "
+ "by every node.\nRemember that "
+ "cluster bus ports are different "
+ "from standard instance ports.\n");
+ listEmpty(from);
+ }
+ }
+ if (iter != NULL) dictReleaseIterator(iter);
+ if (status != NULL) dictRelease(status);
+ counter = 0;
+ }
+ }
+ printf("\n");
+}
+
+/* Load node's cluster configuration by calling "CLUSTER NODES" command.
+ * Node's configuration (name, replicate, slots, ...) is then updated.
+ * If CLUSTER_MANAGER_OPT_GETFRIENDS flag is set into 'opts' argument,
+ * and node already knows other nodes, the node's friends list is populated
+ * with the other nodes info. */
+static int clusterManagerNodeLoadInfo(clusterManagerNode *node, int opts,
+ char **err)
+{
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
+ int success = 1;
+ *err = NULL;
+ if (!clusterManagerCheckRedisReply(node, reply, err)) {
+ success = 0;
+ goto cleanup;
+ }
+ int getfriends = (opts & CLUSTER_MANAGER_OPT_GETFRIENDS);
+ char *lines = reply->str, *p, *line;
+ while ((p = strstr(lines, "\n")) != NULL) {
+ *p = '\0';
+ line = lines;
+ lines = p + 1;
+ char *name = NULL, *addr = NULL, *flags = NULL, *master_id = NULL,
+ *ping_sent = NULL, *ping_recv = NULL, *config_epoch = NULL,
+ *link_status = NULL;
+ UNUSED(link_status);
+ int i = 0;
+ while ((p = strchr(line, ' ')) != NULL) {
+ *p = '\0';
+ char *token = line;
+ line = p + 1;
+ switch(i++){
+ case 0: name = token; break;
+ case 1: addr = token; break;
+ case 2: flags = token; break;
+ case 3: master_id = token; break;
+ case 4: ping_sent = token; break;
+ case 5: ping_recv = token; break;
+ case 6: config_epoch = token; break;
+ case 7: link_status = token; break;
+ }
+ if (i == 8) break; // Slots
+ }
+ if (!flags) {
+ success = 0;
+ goto cleanup;
+ }
+ int myself = (strstr(flags, "myself") != NULL);
+ clusterManagerNode *currentNode = NULL;
+ if (myself) {
+ node->flags |= CLUSTER_MANAGER_FLAG_MYSELF;
+ currentNode = node;
+ clusterManagerNodeResetSlots(node);
+ if (i == 8) {
+ int remaining = strlen(line);
+ while (remaining > 0) {
+ p = strchr(line, ' ');
+ if (p == NULL) p = line + remaining;
+ remaining -= (p - line);
+
+ char *slotsdef = line;
+ *p = '\0';
+ if (remaining) {
+ line = p + 1;
+ remaining--;
+ } else line = p;
+ char *dash = NULL;
+ if (slotsdef[0] == '[') {
+ slotsdef++;
+ if ((p = strstr(slotsdef, "->-"))) { // Migrating
+ *p = '\0';
+ p += 3;
+ char *closing_bracket = strchr(p, ']');
+ if (closing_bracket) *closing_bracket = '\0';
+ sds slot = sdsnew(slotsdef);
+ sds dst = sdsnew(p);
+ node->migrating_count += 2;
+ node->migrating = zrealloc(node->migrating,
+ (node->migrating_count * sizeof(sds)));
+ node->migrating[node->migrating_count - 2] =
+ slot;
+ node->migrating[node->migrating_count - 1] =
+ dst;
+ } else if ((p = strstr(slotsdef, "-<-"))) {//Importing
+ *p = '\0';
+ p += 3;
+ char *closing_bracket = strchr(p, ']');
+ if (closing_bracket) *closing_bracket = '\0';
+ sds slot = sdsnew(slotsdef);
+ sds src = sdsnew(p);
+ node->importing_count += 2;
+ node->importing = zrealloc(node->importing,
+ (node->importing_count * sizeof(sds)));
+ node->importing[node->importing_count - 2] =
+ slot;
+ node->importing[node->importing_count - 1] =
+ src;
+ }
+ } else if ((dash = strchr(slotsdef, '-')) != NULL) {
+ p = dash;
+ int start, stop;
+ *p = '\0';
+ start = atoi(slotsdef);
+ stop = atoi(p + 1);
+ node->slots_count += (stop - (start - 1));
+ while (start <= stop) node->slots[start++] = 1;
+ } else if (p > slotsdef) {
+ node->slots[atoi(slotsdef)] = 1;
+ node->slots_count++;
+ }
+ }
+ }
+ node->dirty = 0;
+ } else if (!getfriends) {
+ if (!(node->flags & CLUSTER_MANAGER_FLAG_MYSELF)) continue;
+ else break;
+ } else {
+ if (addr == NULL) {
+ fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
+ success = 0;
+ goto cleanup;
+ }
+ char *c = strrchr(addr, '@');
+ if (c != NULL) *c = '\0';
+ c = strrchr(addr, ':');
+ if (c == NULL) {
+ fprintf(stderr, "Error: invalid CLUSTER NODES reply\n");
+ success = 0;
+ goto cleanup;
+ }
+ *c = '\0';
+ int port = atoi(++c);
+ currentNode = clusterManagerNewNode(sdsnew(addr), port);
+ currentNode->flags |= CLUSTER_MANAGER_FLAG_FRIEND;
+ if (node->friends == NULL) node->friends = listCreate();
+ listAddNodeTail(node->friends, currentNode);
+ }
+ if (name != NULL) {
+ if (currentNode->name) sdsfree(currentNode->name);
+ currentNode->name = sdsnew(name);
+ }
+ if (currentNode->flags_str != NULL)
+ freeClusterManagerNodeFlags(currentNode->flags_str);
+ currentNode->flags_str = listCreate();
+ int flag_len;
+ while ((flag_len = strlen(flags)) > 0) {
+ sds flag = NULL;
+ char *fp = strchr(flags, ',');
+ if (fp) {
+ *fp = '\0';
+ flag = sdsnew(flags);
+ flags = fp + 1;
+ } else {
+ flag = sdsnew(flags);
+ flags += flag_len;
+ }
+ if (strcmp(flag, "noaddr") == 0)
+ currentNode->flags |= CLUSTER_MANAGER_FLAG_NOADDR;
+ else if (strcmp(flag, "disconnected") == 0)
+ currentNode->flags |= CLUSTER_MANAGER_FLAG_DISCONNECT;
+ else if (strcmp(flag, "fail") == 0)
+ currentNode->flags |= CLUSTER_MANAGER_FLAG_FAIL;
+ else if (strcmp(flag, "slave") == 0) {
+ currentNode->flags |= CLUSTER_MANAGER_FLAG_SLAVE;
+ if (master_id != NULL) {
+ if (currentNode->replicate) sdsfree(currentNode->replicate);
+ currentNode->replicate = sdsnew(master_id);
+ }
+ }
+ listAddNodeTail(currentNode->flags_str, flag);
+ }
+ if (config_epoch != NULL)
+ currentNode->current_epoch = atoll(config_epoch);
+ if (ping_sent != NULL) currentNode->ping_sent = atoll(ping_sent);
+ if (ping_recv != NULL) currentNode->ping_recv = atoll(ping_recv);
+ if (!getfriends && myself) break;
+ }
+cleanup:
+ if (reply) freeReplyObject(reply);
+ return success;
+}
+
+/* Retrieves info about the cluster using argument 'node' as the starting
+ * point. All nodes will be loaded inside the cluster_manager.nodes list.
+ * Warning: if something goes wrong, it will free the starting node before
+ * returning 0. */
+static int clusterManagerLoadInfoFromNode(clusterManagerNode *node, int opts) {
+ if (node->context == NULL && !clusterManagerNodeConnect(node)) {
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ opts |= CLUSTER_MANAGER_OPT_GETFRIENDS;
+ char *e = NULL;
+ if (!clusterManagerNodeIsCluster(node, &e)) {
+ clusterManagerPrintNotClusterNodeError(node, e);
+ if (e) zfree(e);
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ e = NULL;
+ if (!clusterManagerNodeLoadInfo(node, opts, &e)) {
+ if (e) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, e);
+ zfree(e);
+ }
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ listIter li;
+ listNode *ln;
+ if (cluster_manager.nodes != NULL) {
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL)
+ freeClusterManagerNode((clusterManagerNode *) ln->value);
+ listRelease(cluster_manager.nodes);
+ }
+ cluster_manager.nodes = listCreate();
+ listAddNodeTail(cluster_manager.nodes, node);
+ if (node->friends != NULL) {
+ listRewind(node->friends, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *friend = ln->value;
+ if (!friend->ip || !friend->port) goto invalid_friend;
+ if (!friend->context && !clusterManagerNodeConnect(friend))
+ goto invalid_friend;
+ e = NULL;
+ if (clusterManagerNodeLoadInfo(friend, 0, &e)) {
+ if (friend->flags & (CLUSTER_MANAGER_FLAG_NOADDR |
+ CLUSTER_MANAGER_FLAG_DISCONNECT |
+ CLUSTER_MANAGER_FLAG_FAIL))
+ goto invalid_friend;
+ listAddNodeTail(cluster_manager.nodes, friend);
+ } else {
+ clusterManagerLogErr("[ERR] Unable to load info for "
+ "node %s:%d\n",
+ friend->ip, friend->port);
+ goto invalid_friend;
+ }
+ continue;
+invalid_friend:
+ freeClusterManagerNode(friend);
+ }
+ listRelease(node->friends);
+ node->friends = NULL;
+ }
+ // Count replicas for each node
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->replicate != NULL) {
+ clusterManagerNode *master = clusterManagerNodeByName(n->replicate);
+ if (master == NULL) {
+ clusterManagerLogWarn("*** WARNING: %s:%d claims to be "
+ "slave of unknown node ID %s.\n",
+ n->ip, n->port, n->replicate);
+ } else master->replicas_count++;
+ }
+ }
+ return 1;
+}
+
+/* Compare functions used by various sorting operations. */
+int clusterManagerSlotCompare(const void *slot1, const void *slot2) {
+ const char **i1 = (const char **)slot1;
+ const char **i2 = (const char **)slot2;
+ return strcmp(*i1, *i2);
+}
+
+int clusterManagerSlotCountCompareDesc(const void *n1, const void *n2) {
+ clusterManagerNode *node1 = *((clusterManagerNode **) n1);
+ clusterManagerNode *node2 = *((clusterManagerNode **) n2);
+ return node2->slots_count - node1->slots_count;
+}
+
+int clusterManagerCompareNodeBalance(const void *n1, const void *n2) {
+ clusterManagerNode *node1 = *((clusterManagerNode **) n1);
+ clusterManagerNode *node2 = *((clusterManagerNode **) n2);
+ return node1->balance - node2->balance;
+}
+
+static sds clusterManagerGetConfigSignature(clusterManagerNode *node) {
+ sds signature = NULL;
+ int node_count = 0, i = 0, name_len = 0;
+ char **node_configs = NULL;
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
+ if (reply == NULL || reply->type == REDIS_REPLY_ERROR)
+ goto cleanup;
+ char *lines = reply->str, *p, *line;
+ while ((p = strstr(lines, "\n")) != NULL) {
+ i = 0;
+ *p = '\0';
+ line = lines;
+ lines = p + 1;
+ char *nodename = NULL;
+ int tot_size = 0;
+ while ((p = strchr(line, ' ')) != NULL) {
+ *p = '\0';
+ char *token = line;
+ line = p + 1;
+ if (i == 0) {
+ nodename = token;
+ tot_size = (p - token);
+ name_len = tot_size++; // Make room for ':' in tot_size
+ }
+ if (++i == 8) break;
+ }
+ if (i != 8) continue;
+ if (nodename == NULL) continue;
+ int remaining = strlen(line);
+ if (remaining == 0) continue;
+ char **slots = NULL;
+ int c = 0;
+ while (remaining > 0) {
+ p = strchr(line, ' ');
+ if (p == NULL) p = line + remaining;
+ int size = (p - line);
+ remaining -= size;
+ tot_size += size;
+ char *slotsdef = line;
+ *p = '\0';
+ if (remaining) {
+ line = p + 1;
+ remaining--;
+ } else line = p;
+ if (slotsdef[0] != '[') {
+ c++;
+ slots = zrealloc(slots, (c * sizeof(char *)));
+ slots[c - 1] = slotsdef;
+ }
+ }
+ if (c > 0) {
+ if (c > 1)
+ qsort(slots, c, sizeof(char *), clusterManagerSlotCompare);
+ node_count++;
+ node_configs =
+ zrealloc(node_configs, (node_count * sizeof(char *)));
+ /* Make room for '|' separators. */
+ tot_size += (sizeof(char) * (c - 1));
+ char *cfg = zmalloc((sizeof(char) * tot_size) + 1);
+ memcpy(cfg, nodename, name_len);
+ char *sp = cfg + name_len;
+ *(sp++) = ':';
+ for (i = 0; i < c; i++) {
+ if (i > 0) *(sp++) = ',';
+ int slen = strlen(slots[i]);
+ memcpy(sp, slots[i], slen);
+ sp += slen;
+ }
+ *(sp++) = '\0';
+ node_configs[node_count - 1] = cfg;
+ }
+ zfree(slots);
+ }
+ if (node_count > 0) {
+ if (node_count > 1) {
+ qsort(node_configs, node_count, sizeof(char *),
+ clusterManagerSlotCompare);
+ }
+ signature = sdsempty();
+ for (i = 0; i < node_count; i++) {
+ if (i > 0) signature = sdscatprintf(signature, "%c", '|');
+ signature = sdscatfmt(signature, "%s", node_configs[i]);
+ }
+ }
+cleanup:
+ if (reply != NULL) freeReplyObject(reply);
+ if (node_configs != NULL) {
+ for (i = 0; i < node_count; i++) zfree(node_configs[i]);
+ zfree(node_configs);
+ }
+ return signature;
+}
+
+static int clusterManagerIsConfigConsistent(void) {
+ if (cluster_manager.nodes == NULL) return 0;
+ int consistent = (listLength(cluster_manager.nodes) <= 1);
+ // If the Cluster has only one node, it's always consistent
+ if (consistent) return 1;
+ sds first_cfg = NULL;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ sds cfg = clusterManagerGetConfigSignature(node);
+ if (cfg == NULL) {
+ consistent = 0;
+ break;
+ }
+ if (first_cfg == NULL) first_cfg = cfg;
+ else {
+ consistent = !sdscmp(first_cfg, cfg);
+ sdsfree(cfg);
+ if (!consistent) break;
+ }
+ }
+ if (first_cfg != NULL) sdsfree(first_cfg);
+ return consistent;
+}
+
+static list *clusterManagerGetDisconnectedLinks(clusterManagerNode *node) {
+ list *links = NULL;
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(node, "CLUSTER NODES");
+ if (!clusterManagerCheckRedisReply(node, reply, NULL)) goto cleanup;
+ links = listCreate();
+ char *lines = reply->str, *p, *line;
+ while ((p = strstr(lines, "\n")) != NULL) {
+ int i = 0;
+ *p = '\0';
+ line = lines;
+ lines = p + 1;
+ char *nodename = NULL, *addr = NULL, *flags = NULL, *link_status = NULL;
+ while ((p = strchr(line, ' ')) != NULL) {
+ *p = '\0';
+ char *token = line;
+ line = p + 1;
+ if (i == 0) nodename = token;
+ else if (i == 1) addr = token;
+ else if (i == 2) flags = token;
+ else if (i == 7) link_status = token;
+ else if (i == 8) break;
+ i++;
+ }
+ if (i == 7) link_status = line;
+ if (nodename == NULL || addr == NULL || flags == NULL ||
+ link_status == NULL) continue;
+ if (strstr(flags, "myself") != NULL) continue;
+ int disconnected = ((strstr(flags, "disconnected") != NULL) ||
+ (strstr(link_status, "disconnected")));
+ int handshaking = (strstr(flags, "handshake") != NULL);
+ if (disconnected || handshaking) {
+ clusterManagerLink *link = zmalloc(sizeof(*link));
+ link->node_name = sdsnew(nodename);
+ link->node_addr = sdsnew(addr);
+ link->connected = 0;
+ link->handshaking = handshaking;
+ listAddNodeTail(links, link);
+ }
+ }
+cleanup:
+ if (reply != NULL) freeReplyObject(reply);
+ return links;
+}
+
+/* Check for disconnected cluster links. It returns a dict whose keys
+ * are the unreachable node addresses and the values are lists of
+ * node addresses that cannot reach the unreachable node. */
+static dict *clusterManagerGetLinkStatus(void) {
+ if (cluster_manager.nodes == NULL) return NULL;
+ dict *status = dictCreate(&clusterManagerLinkDictType, NULL);
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ list *links = clusterManagerGetDisconnectedLinks(node);
+ if (links) {
+ listIter lli;
+ listNode *lln;
+ listRewind(links, &lli);
+ while ((lln = listNext(&lli)) != NULL) {
+ clusterManagerLink *link = lln->value;
+ list *from = NULL;
+ dictEntry *entry = dictFind(status, link->node_addr);
+ if (entry) from = dictGetVal(entry);
+ else {
+ from = listCreate();
+ dictAdd(status, sdsdup(link->node_addr), from);
+ }
+ sds myaddr = sdsempty();
+ myaddr = sdscatfmt(myaddr, "%s:%u", node->ip, node->port);
+ listAddNodeTail(from, myaddr);
+ sdsfree(link->node_name);
+ sdsfree(link->node_addr);
+ zfree(link);
+ }
+ listRelease(links);
+ }
+ }
+ return status;
+}
+
+/* Add the error string to cluster_manager.errors and print it. */
+static void clusterManagerOnError(sds err) {
+ if (cluster_manager.errors == NULL)
+ cluster_manager.errors = listCreate();
+ listAddNodeTail(cluster_manager.errors, err);
+ clusterManagerLogErr("%s\n", (char *) err);
+}
+
+/* Check the slots coverage of the cluster. The 'all_slots' argument must be
+ * and array of 16384 bytes. Every covered slot will be set to 1 in the
+ * 'all_slots' array. The function returns the total number if covered slots.*/
+static int clusterManagerGetCoveredSlots(char *all_slots) {
+ if (cluster_manager.nodes == NULL) return 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ int totslots = 0, i;
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
+ if (node->slots[i] && !all_slots[i]) {
+ all_slots[i] = 1;
+ totslots++;
+ }
+ }
+ }
+ return totslots;
+}
+
+static void clusterManagerPrintSlotsList(list *slots) {
+ listIter li;
+ listNode *ln;
+ listRewind(slots, &li);
+ sds first = NULL;
+ while ((ln = listNext(&li)) != NULL) {
+ sds slot = ln->value;
+ if (!first) first = slot;
+ else printf(", ");
+ printf("%s", slot);
+ }
+ printf("\n");
+}
+
+/* Return the node, among 'nodes' with the greatest number of keys
+ * in the specified slot. */
+static clusterManagerNode * clusterManagerGetNodeWithMostKeysInSlot(list *nodes,
+ int slot,
+ char **err)
+{
+ clusterManagerNode *node = NULL;
+ int numkeys = 0;
+ listIter li;
+ listNode *ln;
+ listRewind(nodes, &li);
+ if (err) *err = NULL;
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
+ continue;
+ redisReply *r =
+ CLUSTER_MANAGER_COMMAND(n, "CLUSTER COUNTKEYSINSLOT %d", slot);
+ int success = clusterManagerCheckRedisReply(n, r, err);
+ if (success) {
+ if (r->integer > numkeys || node == NULL) {
+ numkeys = r->integer;
+ node = n;
+ }
+ }
+ if (r != NULL) freeReplyObject(r);
+ /* If the reply contains errors */
+ if (!success) {
+ if (err != NULL && *err != NULL)
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(n, err);
+ node = NULL;
+ break;
+ }
+ }
+ return node;
+}
+
+/* This function returns the master that has the least number of replicas
+ * in the cluster. If there are multiple masters with the same smaller
+ * number of replicas, one at random is returned. */
+
+static clusterManagerNode *clusterManagerNodeWithLeastReplicas() {
+ clusterManagerNode *node = NULL;
+ int lowest_count = 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ if (node == NULL || n->replicas_count < lowest_count) {
+ node = n;
+ lowest_count = n->replicas_count;
+ }
+ }
+ return node;
+}
+
+/* This function returns a random master node, return NULL if none */
+
+static clusterManagerNode *clusterManagerNodeMasterRandom() {
+ int master_count = 0;
+ int idx;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ master_count++;
+ }
+
+ srand(time(NULL));
+ idx = rand() % master_count;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ if (!idx--) {
+ return n;
+ }
+ }
+ /* Can not be reached */
+ return NULL;
+}
+
+static int clusterManagerFixSlotsCoverage(char *all_slots) {
+ int i, fixed = 0;
+ list *none = NULL, *single = NULL, *multi = NULL;
+ clusterManagerLogInfo(">>> Fixing slots coverage...\n");
+ printf("List of not covered slots: \n");
+ int uncovered_count = 0;
+ sds log = sdsempty();
+ for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
+ int covered = all_slots[i];
+ if (!covered) {
+ sds key = sdsfromlonglong((long long) i);
+ if (uncovered_count++ > 0) printf(",");
+ printf("%s", (char *) key);
+ list *slot_nodes = listCreate();
+ sds slot_nodes_str = sdsempty();
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
+ continue;
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(n,
+ "CLUSTER GETKEYSINSLOT %d %d", i, 1);
+ if (!clusterManagerCheckRedisReply(n, reply, NULL)) {
+ fixed = -1;
+ if (reply) freeReplyObject(reply);
+ goto cleanup;
+ }
+ assert(reply->type == REDIS_REPLY_ARRAY);
+ if (reply->elements > 0) {
+ listAddNodeTail(slot_nodes, n);
+ if (listLength(slot_nodes) > 1)
+ slot_nodes_str = sdscat(slot_nodes_str, ", ");
+ slot_nodes_str = sdscatfmt(slot_nodes_str,
+ "%s:%u", n->ip, n->port);
+ }
+ freeReplyObject(reply);
+ }
+ log = sdscatfmt(log, "\nSlot %S has keys in %u nodes: %S",
+ key, listLength(slot_nodes), slot_nodes_str);
+ sdsfree(slot_nodes_str);
+ dictAdd(clusterManagerUncoveredSlots, key, slot_nodes);
+ }
+ }
+ printf("\n%s\n", log);
+ /* For every slot, take action depending on the actual condition:
+ * 1) No node has keys for this slot.
+ * 2) A single node has keys for this slot.
+ * 3) Multiple nodes have keys for this slot. */
+ none = listCreate();
+ single = listCreate();
+ multi = listCreate();
+ dictIterator *iter = dictGetIterator(clusterManagerUncoveredSlots);
+ dictEntry *entry;
+ while ((entry = dictNext(iter)) != NULL) {
+ sds slot = (sds) dictGetKey(entry);
+ list *nodes = (list *) dictGetVal(entry);
+ switch (listLength(nodes)){
+ case 0: listAddNodeTail(none, slot); break;
+ case 1: listAddNodeTail(single, slot); break;
+ default: listAddNodeTail(multi, slot); break;
+ }
+ }
+ dictReleaseIterator(iter);
+
+ /* Handle case "1": keys in no node. */
+ if (listLength(none) > 0) {
+ printf("The following uncovered slots have no keys "
+ "across the cluster:\n");
+ clusterManagerPrintSlotsList(none);
+ if (confirmWithYes("Fix these slots by covering with a random node?")){
+ listIter li;
+ listNode *ln;
+ listRewind(none, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds slot = ln->value;
+ int s = atoi(slot);
+ clusterManagerNode *n = clusterManagerNodeMasterRandom();
+ clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
+ slot, n->ip, n->port);
+ if (!clusterManagerSetSlotOwner(n, s, 0)) {
+ fixed = -1;
+ goto cleanup;
+ }
+ /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
+ * info into the node struct, in order to keep it synced */
+ n->slots[s] = 1;
+ fixed++;
+ }
+ }
+ }
+
+ /* Handle case "2": keys only in one node. */
+ if (listLength(single) > 0) {
+ printf("The following uncovered slots have keys in just one node:\n");
+ clusterManagerPrintSlotsList(single);
+ if (confirmWithYes("Fix these slots by covering with those nodes?")){
+ listIter li;
+ listNode *ln;
+ listRewind(single, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds slot = ln->value;
+ int s = atoi(slot);
+ dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
+ assert(entry != NULL);
+ list *nodes = (list *) dictGetVal(entry);
+ listNode *fn = listFirst(nodes);
+ assert(fn != NULL);
+ clusterManagerNode *n = fn->value;
+ clusterManagerLogInfo(">>> Covering slot %s with %s:%d\n",
+ slot, n->ip, n->port);
+ if (!clusterManagerSetSlotOwner(n, s, 0)) {
+ fixed = -1;
+ goto cleanup;
+ }
+ /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
+ * info into the node struct, in order to keep it synced */
+ n->slots[atoi(slot)] = 1;
+ fixed++;
+ }
+ }
+ }
+
+ /* Handle case "3": keys in multiple nodes. */
+ if (listLength(multi) > 0) {
+ printf("The following uncovered slots have keys in multiple nodes:\n");
+ clusterManagerPrintSlotsList(multi);
+ if (confirmWithYes("Fix these slots by moving keys "
+ "into a single node?")) {
+ listIter li;
+ listNode *ln;
+ listRewind(multi, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ sds slot = ln->value;
+ dictEntry *entry = dictFind(clusterManagerUncoveredSlots, slot);
+ assert(entry != NULL);
+ list *nodes = (list *) dictGetVal(entry);
+ int s = atoi(slot);
+ clusterManagerNode *target =
+ clusterManagerGetNodeWithMostKeysInSlot(nodes, s, NULL);
+ if (target == NULL) {
+ fixed = -1;
+ goto cleanup;
+ }
+ clusterManagerLogInfo(">>> Covering slot %s moving keys "
+ "to %s:%d\n", slot,
+ target->ip, target->port);
+ if (!clusterManagerSetSlotOwner(target, s, 1)) {
+ fixed = -1;
+ goto cleanup;
+ }
+ /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
+ * info into the node struct, in order to keep it synced */
+ target->slots[atoi(slot)] = 1;
+ listIter nli;
+ listNode *nln;
+ listRewind(nodes, &nli);
+ while ((nln = listNext(&nli)) != NULL) {
+ clusterManagerNode *src = nln->value;
+ if (src == target) continue;
+ /* Assign the slot to target node in the source node. */
+ if (!clusterManagerSetSlot(src, target, s, "NODE", NULL))
+ fixed = -1;
+ if (fixed < 0) goto cleanup;
+ /* Set the source node in 'importing' state
+ * (even if we will actually migrate keys away)
+ * in order to avoid receiving redirections
+ * for MIGRATE. */
+ if (!clusterManagerSetSlot(src, target, s,
+ "IMPORTING", NULL)) fixed = -1;
+ if (fixed < 0) goto cleanup;
+ int opts = CLUSTER_MANAGER_OPT_VERBOSE |
+ CLUSTER_MANAGER_OPT_COLD;
+ if (!clusterManagerMoveSlot(src, target, s, opts, NULL)) {
+ fixed = -1;
+ goto cleanup;
+ }
+ if (!clusterManagerClearSlotStatus(src, s))
+ fixed = -1;
+ if (fixed < 0) goto cleanup;
+ }
+ fixed++;
+ }
+ }
+ }
+cleanup:
+ sdsfree(log);
+ if (none) listRelease(none);
+ if (single) listRelease(single);
+ if (multi) listRelease(multi);
+ return fixed;
+}
+
+/* Slot 'slot' was found to be in importing or migrating state in one or
+ * more nodes. This function fixes this condition by migrating keys where
+ * it seems more sensible. */
+static int clusterManagerFixOpenSlot(int slot) {
+ clusterManagerLogInfo(">>> Fixing open slot %d\n", slot);
+ /* Try to obtain the current slot owner, according to the current
+ * nodes configuration. */
+ int success = 1;
+ list *owners = listCreate();
+ list *migrating = listCreate();
+ list *importing = listCreate();
+ sds migrating_str = sdsempty();
+ sds importing_str = sdsempty();
+ clusterManagerNode *owner = NULL;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ if (n->slots[slot]) listAddNodeTail(owners, n);
+ else {
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n,
+ "CLUSTER COUNTKEYSINSLOT %d", slot);
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (success && r->integer > 0) {
+ clusterManagerLogWarn("*** Found keys about slot %d "
+ "in non-owner node %s:%d!\n", slot,
+ n->ip, n->port);
+ listAddNodeTail(owners, n);
+ }
+ if (r) freeReplyObject(r);
+ if (!success) goto cleanup;
+ }
+ }
+ if (listLength(owners) == 1) owner = listFirst(owners)->value;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ int is_migrating = 0, is_importing = 0;
+ if (n->migrating) {
+ for (int i = 0; i < n->migrating_count; i += 2) {
+ sds migrating_slot = n->migrating[i];
+ if (atoi(migrating_slot) == slot) {
+ char *sep = (listLength(migrating) == 0 ? "" : ",");
+ migrating_str = sdscatfmt(migrating_str, "%s%s:%u",
+ sep, n->ip, n->port);
+ listAddNodeTail(migrating, n);
+ is_migrating = 1;
+ break;
+ }
+ }
+ }
+ if (!is_migrating && n->importing) {
+ for (int i = 0; i < n->importing_count; i += 2) {
+ sds importing_slot = n->importing[i];
+ if (atoi(importing_slot) == slot) {
+ char *sep = (listLength(importing) == 0 ? "" : ",");
+ importing_str = sdscatfmt(importing_str, "%s%s:%u",
+ sep, n->ip, n->port);
+ listAddNodeTail(importing, n);
+ is_importing = 1;
+ break;
+ }
+ }
+ }
+ /* If the node is neither migrating nor importing and it's not
+ * the owner, then is added to the importing list in case
+ * it has keys in the slot. */
+ if (!is_migrating && !is_importing && n != owner) {
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n,
+ "CLUSTER COUNTKEYSINSLOT %d", slot);
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (success && r->integer > 0) {
+ clusterManagerLogWarn("*** Found keys about slot %d "
+ "in node %s:%d!\n", slot, n->ip,
+ n->port);
+ char *sep = (listLength(importing) == 0 ? "" : ",");
+ importing_str = sdscatfmt(importing_str, "%s%S:%u",
+ sep, n->ip, n->port);
+ listAddNodeTail(importing, n);
+ }
+ if (r) freeReplyObject(r);
+ if (!success) goto cleanup;
+ }
+ }
+ if (sdslen(migrating_str) > 0)
+ printf("Set as migrating in: %s\n", migrating_str);
+ if (sdslen(importing_str) > 0)
+ printf("Set as importing in: %s\n", importing_str);
+ /* If there is no slot owner, set as owner the node with the biggest
+ * number of keys, among the set of migrating / importing nodes. */
+ if (owner == NULL) {
+ clusterManagerLogInfo(">>> Nobody claims ownership, "
+ "selecting an owner...\n");
+ owner = clusterManagerGetNodeWithMostKeysInSlot(cluster_manager.nodes,
+ slot, NULL);
+ // If we still don't have an owner, we can't fix it.
+ if (owner == NULL) {
+ clusterManagerLogErr("[ERR] Can't select a slot owner. "
+ "Impossible to fix.\n");
+ success = 0;
+ goto cleanup;
+ }
+
+ // Use ADDSLOTS to assign the slot.
+ clusterManagerLogWarn("*** Configuring %s:%d as the slot owner\n",
+ owner->ip, owner->port);
+ success = clusterManagerClearSlotStatus(owner, slot);
+ if (!success) goto cleanup;
+ success = clusterManagerSetSlotOwner(owner, slot, 0);
+ if (!success) goto cleanup;
+ /* Since CLUSTER ADDSLOTS succeeded, we also update the slot
+ * info into the node struct, in order to keep it synced */
+ owner->slots[slot] = 1;
+ /* Make sure this information will propagate. Not strictly needed
+ * since there is no past owner, so all the other nodes will accept
+ * whatever epoch this node will claim the slot with. */
+ success = clusterManagerBumpEpoch(owner);
+ if (!success) goto cleanup;
+ /* Remove the owner from the list of migrating/importing
+ * nodes. */
+ clusterManagerRemoveNodeFromList(migrating, owner);
+ clusterManagerRemoveNodeFromList(importing, owner);
+ }
+ /* If there are multiple owners of the slot, we need to fix it
+ * so that a single node is the owner and all the other nodes
+ * are in importing state. Later the fix can be handled by one
+ * of the base cases above.
+ *
+ * Note that this case also covers multiple nodes having the slot
+ * in migrating state, since migrating is a valid state only for
+ * slot owners. */
+ if (listLength(owners) > 1) {
+ /* Owner cannot be NULL at this point, since if there are more owners,
+ * the owner has been set in the previous condition (owner == NULL). */
+ assert(owner != NULL);
+ listRewind(owners, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n == owner) continue;
+ success = clusterManagerDelSlot(n, slot, 1);
+ if (!success) goto cleanup;
+ n->slots[slot] = 0;
+ /* Assign the slot to the owner in the node 'n' configuration.' */
+ success = clusterManagerSetSlot(n, owner, slot, "node", NULL);
+ if (!success) goto cleanup;
+ success = clusterManagerSetSlot(n, owner, slot, "importing", NULL);
+ if (!success) goto cleanup;
+ /* Avoid duplicates. */
+ clusterManagerRemoveNodeFromList(importing, n);
+ listAddNodeTail(importing, n);
+ /* Ensure that the node is not in the migrating list. */
+ clusterManagerRemoveNodeFromList(migrating, n);
+ }
+ }
+ int move_opts = CLUSTER_MANAGER_OPT_VERBOSE;
+ /* Case 1: The slot is in migrating state in one node, and in
+ * importing state in 1 node. That's trivial to address. */
+ if (listLength(migrating) == 1 && listLength(importing) == 1) {
+ clusterManagerNode *src = listFirst(migrating)->value;
+ clusterManagerNode *dst = listFirst(importing)->value;
+ clusterManagerLogInfo(">>> Case 1: Moving slot %d from "
+ "%s:%d to %s:%d\n", slot,
+ src->ip, src->port, dst->ip, dst->port);
+ move_opts |= CLUSTER_MANAGER_OPT_UPDATE;
+ success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
+ }
+ /* Case 2: There are multiple nodes that claim the slot as importing,
+ * they probably got keys about the slot after a restart so opened
+ * the slot. In this case we just move all the keys to the owner
+ * according to the configuration. */
+ else if (listLength(migrating) == 0 && listLength(importing) > 0) {
+ clusterManagerLogInfo(">>> Case 2: Moving all the %d slot keys to its "
+ "owner %s:%d\n", slot, owner->ip, owner->port);
+ move_opts |= CLUSTER_MANAGER_OPT_COLD;
+ listRewind(importing, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n == owner) continue;
+ success = clusterManagerMoveSlot(n, owner, slot, move_opts, NULL);
+ if (!success) goto cleanup;
+ clusterManagerLogInfo(">>> Setting %d as STABLE in "
+ "%s:%d\n", slot, n->ip, n->port);
+ success = clusterManagerClearSlotStatus(n, slot);
+ if (!success) goto cleanup;
+ }
+ /* Since the slot has been moved in "cold" mode, ensure that all the
+ * other nodes update their own configuration about the slot itself. */
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n == owner) continue;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ success = clusterManagerSetSlot(n, owner, slot, "NODE", NULL);
+ if (!success) goto cleanup;
+ }
+ }
+ /* Case 3: The slot is in migrating state in one node but multiple
+ * other nodes claim to be in importing state and don't have any key in
+ * the slot. We search for the importing node having the same ID as
+ * the destination node of the migrating node.
+ * In that case we move the slot from the migrating node to this node and
+ * we close the importing states on all the other importing nodes.
+ * If no importing node has the same ID as the destination node of the
+ * migrating node, the slot's state is closed on both the migrating node
+ * and the importing nodes. */
+ else if (listLength(migrating) == 1 && listLength(importing) > 1) {
+ int try_to_fix = 1;
+ clusterManagerNode *src = listFirst(migrating)->value;
+ clusterManagerNode *dst = NULL;
+ sds target_id = NULL;
+ for (int i = 0; i < src->migrating_count; i += 2) {
+ sds migrating_slot = src->migrating[i];
+ if (atoi(migrating_slot) == slot) {
+ target_id = src->migrating[i + 1];
+ break;
+ }
+ }
+ assert(target_id != NULL);
+ listIter li;
+ listNode *ln;
+ listRewind(importing, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ int count = clusterManagerCountKeysInSlot(n, slot);
+ if (count > 0) {
+ try_to_fix = 0;
+ break;
+ }
+ if (strcmp(n->name, target_id) == 0) dst = n;
+ }
+ if (!try_to_fix) goto unhandled_case;
+ if (dst != NULL) {
+ clusterManagerLogInfo(">>> Case 3: Moving slot %d from %s:%d to "
+ "%s:%d and closing it on all the other "
+ "importing nodes.\n",
+ slot, src->ip, src->port,
+ dst->ip, dst->port);
+ /* Move the slot to the destination node. */
+ success = clusterManagerMoveSlot(src, dst, slot, move_opts, NULL);
+ if (!success) goto cleanup;
+ /* Close slot on all the other importing nodes. */
+ listRewind(importing, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (dst == n) continue;
+ success = clusterManagerClearSlotStatus(n, slot);
+ if (!success) goto cleanup;
+ }
+ } else {
+ clusterManagerLogInfo(">>> Case 3: Closing slot %d on both "
+ "migrating and importing nodes.\n", slot);
+ /* Close the slot on both the migrating node and the importing
+ * nodes. */
+ success = clusterManagerClearSlotStatus(src, slot);
+ if (!success) goto cleanup;
+ listRewind(importing, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ success = clusterManagerClearSlotStatus(n, slot);
+ if (!success) goto cleanup;
+ }
+ }
+ } else {
+ int try_to_close_slot = (listLength(importing) == 0 &&
+ listLength(migrating) == 1);
+ if (try_to_close_slot) {
+ clusterManagerNode *n = listFirst(migrating)->value;
+ if (!owner || owner != n) {
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n,
+ "CLUSTER GETKEYSINSLOT %d %d", slot, 10);
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (r) {
+ if (success) try_to_close_slot = (r->elements == 0);
+ freeReplyObject(r);
+ }
+ if (!success) goto cleanup;
+ }
+ }
+ /* Case 4: There are no slots claiming to be in importing state, but
+ * there is a migrating node that actually don't have any key or is the
+ * slot owner. We can just close the slot, probably a reshard
+ * interrupted in the middle. */
+ if (try_to_close_slot) {
+ clusterManagerNode *n = listFirst(migrating)->value;
+ clusterManagerLogInfo(">>> Case 4: Closing slot %d on %s:%d\n",
+ slot, n->ip, n->port);
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER SETSLOT %d %s",
+ slot, "STABLE");
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (r) freeReplyObject(r);
+ if (!success) goto cleanup;
+ } else {
+unhandled_case:
+ success = 0;
+ clusterManagerLogErr("[ERR] Sorry, redis-cli can't fix this slot "
+ "yet (work in progress). Slot is set as "
+ "migrating in %s, as importing in %s, "
+ "owner is %s:%d\n", migrating_str,
+ importing_str, owner->ip, owner->port);
+ }
+ }
+cleanup:
+ listRelease(owners);
+ listRelease(migrating);
+ listRelease(importing);
+ sdsfree(migrating_str);
+ sdsfree(importing_str);
+ return success;
+}
+
+static int clusterManagerFixMultipleSlotOwners(int slot, list *owners) {
+ clusterManagerLogInfo(">>> Fixing multiple owners for slot %d...\n", slot);
+ int success = 0;
+ assert(listLength(owners) > 1);
+ clusterManagerNode *owner = clusterManagerGetNodeWithMostKeysInSlot(owners,
+ slot,
+ NULL);
+ if (!owner) owner = listFirst(owners)->value;
+ clusterManagerLogInfo(">>> Setting slot %d owner: %s:%d\n",
+ slot, owner->ip, owner->port);
+ /* Set the slot owner. */
+ if (!clusterManagerSetSlotOwner(owner, slot, 0)) return 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ /* Update configuration in all the other master nodes by assigning the slot
+ * itself to the new owner, and by eventually migrating keys if the node
+ * has keys for the slot. */
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n == owner) continue;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ int count = clusterManagerCountKeysInSlot(n, slot);
+ success = (count >= 0);
+ if (!success) break;
+ clusterManagerDelSlot(n, slot, 1);
+ if (!clusterManagerSetSlot(n, owner, slot, "node", NULL)) return 0;
+ if (count > 0) {
+ int opts = CLUSTER_MANAGER_OPT_VERBOSE |
+ CLUSTER_MANAGER_OPT_COLD;
+ success = clusterManagerMoveSlot(n, owner, slot, opts, NULL);
+ if (!success) break;
+ }
+ }
+ return success;
+}
+
+static int clusterManagerCheckCluster(int quiet) {
+ listNode *ln = listFirst(cluster_manager.nodes);
+ if (!ln) return 0;
+ clusterManagerNode *node = ln->value;
+ clusterManagerLogInfo(">>> Performing Cluster Check (using node %s:%d)\n",
+ node->ip, node->port);
+ int result = 1, consistent = 0;
+ int do_fix = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_FIX;
+ if (!quiet) clusterManagerShowNodes();
+ consistent = clusterManagerIsConfigConsistent();
+ if (!consistent) {
+ sds err = sdsnew("[ERR] Nodes don't agree about configuration!");
+ clusterManagerOnError(err);
+ result = 0;
+ } else {
+ clusterManagerLogOk("[OK] All nodes agree about slots "
+ "configuration.\n");
+ }
+ /* Check open slots */
+ clusterManagerLogInfo(">>> Check for open slots...\n");
+ listIter li;
+ listRewind(cluster_manager.nodes, &li);
+ int i;
+ dict *open_slots = NULL;
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->migrating != NULL) {
+ if (open_slots == NULL)
+ open_slots = dictCreate(&clusterManagerDictType, NULL);
+ sds errstr = sdsempty();
+ errstr = sdscatprintf(errstr,
+ "[WARNING] Node %s:%d has slots in "
+ "migrating state ",
+ n->ip,
+ n->port);
+ for (i = 0; i < n->migrating_count; i += 2) {
+ sds slot = n->migrating[i];
+ dictReplace(open_slots, slot, sdsdup(n->migrating[i + 1]));
+ char *fmt = (i > 0 ? ",%S" : "%S");
+ errstr = sdscatfmt(errstr, fmt, slot);
+ }
+ errstr = sdscat(errstr, ".");
+ clusterManagerOnError(errstr);
+ }
+ if (n->importing != NULL) {
+ if (open_slots == NULL)
+ open_slots = dictCreate(&clusterManagerDictType, NULL);
+ sds errstr = sdsempty();
+ errstr = sdscatprintf(errstr,
+ "[WARNING] Node %s:%d has slots in "
+ "importing state ",
+ n->ip,
+ n->port);
+ for (i = 0; i < n->importing_count; i += 2) {
+ sds slot = n->importing[i];
+ dictReplace(open_slots, slot, sdsdup(n->importing[i + 1]));
+ char *fmt = (i > 0 ? ",%S" : "%S");
+ errstr = sdscatfmt(errstr, fmt, slot);
+ }
+ errstr = sdscat(errstr, ".");
+ clusterManagerOnError(errstr);
+ }
+ }
+ if (open_slots != NULL) {
+ result = 0;
+ dictIterator *iter = dictGetIterator(open_slots);
+ dictEntry *entry;
+ sds errstr = sdsnew("[WARNING] The following slots are open: ");
+ i = 0;
+ while ((entry = dictNext(iter)) != NULL) {
+ sds slot = (sds) dictGetKey(entry);
+ char *fmt = (i++ > 0 ? ",%S" : "%S");
+ errstr = sdscatfmt(errstr, fmt, slot);
+ }
+ clusterManagerLogErr("%s.\n", (char *) errstr);
+ sdsfree(errstr);
+ if (do_fix) {
+ /* Fix open slots. */
+ dictReleaseIterator(iter);
+ iter = dictGetIterator(open_slots);
+ while ((entry = dictNext(iter)) != NULL) {
+ sds slot = (sds) dictGetKey(entry);
+ result = clusterManagerFixOpenSlot(atoi(slot));
+ if (!result) break;
+ }
+ }
+ dictReleaseIterator(iter);
+ dictRelease(open_slots);
+ }
+ clusterManagerLogInfo(">>> Check slots coverage...\n");
+ char slots[CLUSTER_MANAGER_SLOTS];
+ memset(slots, 0, CLUSTER_MANAGER_SLOTS);
+ int coverage = clusterManagerGetCoveredSlots(slots);
+ if (coverage == CLUSTER_MANAGER_SLOTS) {
+ clusterManagerLogOk("[OK] All %d slots covered.\n",
+ CLUSTER_MANAGER_SLOTS);
+ } else {
+ sds err = sdsempty();
+ err = sdscatprintf(err, "[ERR] Not all %d slots are "
+ "covered by nodes.\n",
+ CLUSTER_MANAGER_SLOTS);
+ clusterManagerOnError(err);
+ result = 0;
+ if (do_fix/* && result*/) {
+ dictType dtype = clusterManagerDictType;
+ dtype.keyDestructor = dictSdsDestructor;
+ dtype.valDestructor = dictListDestructor;
+ clusterManagerUncoveredSlots = dictCreate(&dtype, NULL);
+ int fixed = clusterManagerFixSlotsCoverage(slots);
+ if (fixed > 0) result = 1;
+ }
+ }
+ int search_multiple_owners = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_CHECK_OWNERS;
+ if (search_multiple_owners) {
+ /* Check whether there are multiple owners, even when slots are
+ * fully covered and there are no open slots. */
+ clusterManagerLogInfo(">>> Check for multiple slot owners...\n");
+ int slot = 0, slots_with_multiple_owners = 0;
+ for (; slot < CLUSTER_MANAGER_SLOTS; slot++) {
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ list *owners = listCreate();
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ if (n->slots[slot]) listAddNodeTail(owners, n);
+ else {
+ /* Nodes having keys for the slot will be considered
+ * owners too. */
+ int count = clusterManagerCountKeysInSlot(n, slot);
+ if (count > 0) listAddNodeTail(owners, n);
+ }
+ }
+ if (listLength(owners) > 1) {
+ result = 0;
+ clusterManagerLogErr("[WARNING] Slot %d has %d owners:\n",
+ slot, listLength(owners));
+ listRewind(owners, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ clusterManagerLogErr(" %s:%d\n", n->ip, n->port);
+ }
+ slots_with_multiple_owners++;
+ if (do_fix) {
+ result = clusterManagerFixMultipleSlotOwners(slot, owners);
+ if (!result) {
+ clusterManagerLogErr("Failed to fix multiple owners "
+ "for slot %d\n", slot);
+ listRelease(owners);
+ break;
+ } else slots_with_multiple_owners--;
+ }
+ }
+ listRelease(owners);
+ }
+ if (slots_with_multiple_owners == 0)
+ clusterManagerLogOk("[OK] No multiple owners found.\n");
+ }
+ return result;
+}
+
+static clusterManagerNode *clusterNodeForResharding(char *id,
+ clusterManagerNode *target,
+ int *raise_err)
+{
+ clusterManagerNode *node = NULL;
+ const char *invalid_node_msg = "*** The specified node (%s) is not known "
+ "or not a master, please retry.\n";
+ node = clusterManagerNodeByName(id);
+ *raise_err = 0;
+ if (!node || node->flags & CLUSTER_MANAGER_FLAG_SLAVE) {
+ clusterManagerLogErr(invalid_node_msg, id);
+ *raise_err = 1;
+ return NULL;
+ } else if (node != NULL && target != NULL) {
+ if (!strcmp(node->name, target->name)) {
+ clusterManagerLogErr( "*** It is not possible to use "
+ "the target node as "
+ "source node.\n");
+ return NULL;
+ }
+ }
+ return node;
+}
+
+static list *clusterManagerComputeReshardTable(list *sources, int numslots) {
+ list *moved = listCreate();
+ int src_count = listLength(sources), i = 0, tot_slots = 0, j;
+ clusterManagerNode **sorted = zmalloc(src_count * sizeof(*sorted));
+ listIter li;
+ listNode *ln;
+ listRewind(sources, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ tot_slots += node->slots_count;
+ sorted[i++] = node;
+ }
+ qsort(sorted, src_count, sizeof(clusterManagerNode *),
+ clusterManagerSlotCountCompareDesc);
+ for (i = 0; i < src_count; i++) {
+ clusterManagerNode *node = sorted[i];
+ float n = ((float) numslots / tot_slots * node->slots_count);
+ if (i == 0) n = ceil(n);
+ else n = floor(n);
+ int max = (int) n, count = 0;
+ for (j = 0; j < CLUSTER_MANAGER_SLOTS; j++) {
+ int slot = node->slots[j];
+ if (!slot) continue;
+ if (count >= max || (int)listLength(moved) >= numslots) break;
+ clusterManagerReshardTableItem *item = zmalloc(sizeof(*item));
+ item->source = node;
+ item->slot = j;
+ listAddNodeTail(moved, item);
+ count++;
+ }
+ }
+ zfree(sorted);
+ return moved;
+}
+
+static void clusterManagerShowReshardTable(list *table) {
+ listIter li;
+ listNode *ln;
+ listRewind(table, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerReshardTableItem *item = ln->value;
+ clusterManagerNode *n = item->source;
+ printf(" Moving slot %d from %s\n", item->slot, (char *) n->name);
+ }
+}
+
+static void clusterManagerReleaseReshardTable(list *table) {
+ if (table != NULL) {
+ listIter li;
+ listNode *ln;
+ listRewind(table, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerReshardTableItem *item = ln->value;
+ zfree(item);
+ }
+ listRelease(table);
+ }
+}
+
+static void clusterManagerLog(int level, const char* fmt, ...) {
+ int use_colors =
+ (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COLOR);
+ if (use_colors) {
+ printf("\033[");
+ switch (level) {
+ case CLUSTER_MANAGER_LOG_LVL_INFO: printf(LOG_COLOR_BOLD); break;
+ case CLUSTER_MANAGER_LOG_LVL_WARN: printf(LOG_COLOR_YELLOW); break;
+ case CLUSTER_MANAGER_LOG_LVL_ERR: printf(LOG_COLOR_RED); break;
+ case CLUSTER_MANAGER_LOG_LVL_SUCCESS: printf(LOG_COLOR_GREEN); break;
+ default: printf(LOG_COLOR_RESET); break;
+ }
+ }
+ va_list ap;
+ va_start(ap, fmt);
+ vprintf(fmt, ap);
+ va_end(ap);
+ if (use_colors) printf("\033[" LOG_COLOR_RESET);
+}
+
+static void clusterManagerNodeArrayInit(clusterManagerNodeArray *array,
+ int alloc_len)
+{
+ array->nodes = zcalloc(alloc_len * sizeof(clusterManagerNode*));
+ array->alloc = array->nodes;
+ array->len = alloc_len;
+ array->count = 0;
+}
+
+/* Reset array->nodes to the original array allocation and re-count non-NULL
+ * nodes. */
+static void clusterManagerNodeArrayReset(clusterManagerNodeArray *array) {
+ if (array->nodes > array->alloc) {
+ array->len = array->nodes - array->alloc;
+ array->nodes = array->alloc;
+ array->count = 0;
+ int i = 0;
+ for(; i < array->len; i++) {
+ if (array->nodes[i] != NULL) array->count++;
+ }
+ }
+}
+
+/* Shift array->nodes and store the shifted node into 'nodeptr'. */
+static void clusterManagerNodeArrayShift(clusterManagerNodeArray *array,
+ clusterManagerNode **nodeptr)
+{
+ assert(array->nodes < (array->nodes + array->len));
+ /* If the first node to be shifted is not NULL, decrement count. */
+ if (*array->nodes != NULL) array->count--;
+ /* Store the first node to be shifted into 'nodeptr'. */
+ *nodeptr = *array->nodes;
+ /* Shift the nodes array and decrement length. */
+ array->nodes++;
+ array->len--;
+}
+
+static void clusterManagerNodeArrayAdd(clusterManagerNodeArray *array,
+ clusterManagerNode *node)
+{
+ assert(array->nodes < (array->nodes + array->len));
+ assert(node != NULL);
+ assert(array->count < array->len);
+ array->nodes[array->count++] = node;
+}
+
+static void clusterManagerPrintNotEmptyNodeError(clusterManagerNode *node,
+ char *err)
+{
+ char *msg;
+ if (err) msg = err;
+ else {
+ msg = "is not empty. Either the node already knows other "
+ "nodes (check with CLUSTER NODES) or contains some "
+ "key in database 0.";
+ }
+ clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
+}
+
+static void clusterManagerPrintNotClusterNodeError(clusterManagerNode *node,
+ char *err)
+{
+ char *msg = (err ? err : "is not configured as a cluster node.");
+ clusterManagerLogErr("[ERR] Node %s:%d %s\n", node->ip, node->port, msg);
+}
+
+/* Execute redis-cli in Cluster Manager mode */
+static void clusterManagerMode(clusterManagerCommandProc *proc) {
+ int argc = config.cluster_manager_command.argc;
+ char **argv = config.cluster_manager_command.argv;
+ cluster_manager.nodes = NULL;
+ if (!proc(argc, argv)) goto cluster_manager_err;
+ freeClusterManager();
+ exit(0);
+cluster_manager_err:
+ freeClusterManager();
+ sdsfree(config.hostip);
+ sdsfree(config.mb_delim);
+ exit(1);
+}
+
+/* Cluster Manager Commands */
+
+static int clusterManagerCommandCreate(int argc, char **argv) {
+ int i, j, success = 1;
+ cluster_manager.nodes = listCreate();
+ for (i = 0; i < argc; i++) {
+ char *addr = argv[i];
+ char *c = strrchr(addr, '@');
+ if (c != NULL) *c = '\0';
+ c = strrchr(addr, ':');
+ if (c == NULL) {
+ fprintf(stderr, "Invalid address format: %s\n", addr);
+ return 0;
+ }
+ *c = '\0';
+ char *ip = addr;
+ int port = atoi(++c);
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerNodeConnect(node)) {
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ char *err = NULL;
+ if (!clusterManagerNodeIsCluster(node, &err)) {
+ clusterManagerPrintNotClusterNodeError(node, err);
+ if (err) zfree(err);
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ err = NULL;
+ if (!clusterManagerNodeLoadInfo(node, 0, &err)) {
+ if (err) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ zfree(err);
+ }
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ err = NULL;
+ if (!clusterManagerNodeIsEmpty(node, &err)) {
+ clusterManagerPrintNotEmptyNodeError(node, err);
+ if (err) zfree(err);
+ freeClusterManagerNode(node);
+ return 0;
+ }
+ listAddNodeTail(cluster_manager.nodes, node);
+ }
+ int node_len = cluster_manager.nodes->len;
+ int replicas = config.cluster_manager_command.replicas;
+ int masters_count = CLUSTER_MANAGER_MASTERS_COUNT(node_len, replicas);
+ if (masters_count < 3) {
+ clusterManagerLogErr(
+ "*** ERROR: Invalid configuration for cluster creation.\n"
+ "*** Redis Cluster requires at least 3 master nodes.\n"
+ "*** This is not possible with %d nodes and %d replicas per node.",
+ node_len, replicas);
+ clusterManagerLogErr("\n*** At least %d nodes are required.\n",
+ 3 * (replicas + 1));
+ return 0;
+ }
+ clusterManagerLogInfo(">>> Performing hash slots allocation "
+ "on %d nodes...\n", node_len);
+ int interleaved_len = 0, ip_count = 0;
+ clusterManagerNode **interleaved = zcalloc(node_len*sizeof(**interleaved));
+ char **ips = zcalloc(node_len * sizeof(char*));
+ clusterManagerNodeArray *ip_nodes = zcalloc(node_len * sizeof(*ip_nodes));
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ int found = 0;
+ for (i = 0; i < ip_count; i++) {
+ char *ip = ips[i];
+ if (!strcmp(ip, n->ip)) {
+ found = 1;
+ break;
+ }
+ }
+ if (!found) {
+ ips[ip_count++] = n->ip;
+ }
+ clusterManagerNodeArray *node_array = &(ip_nodes[i]);
+ if (node_array->nodes == NULL)
+ clusterManagerNodeArrayInit(node_array, node_len);
+ clusterManagerNodeArrayAdd(node_array, n);
+ }
+ while (interleaved_len < node_len) {
+ for (i = 0; i < ip_count; i++) {
+ clusterManagerNodeArray *node_array = &(ip_nodes[i]);
+ if (node_array->count > 0) {
+ clusterManagerNode *n = NULL;
+ clusterManagerNodeArrayShift(node_array, &n);
+ interleaved[interleaved_len++] = n;
+ }
+ }
+ }
+ clusterManagerNode **masters = interleaved;
+ interleaved += masters_count;
+ interleaved_len -= masters_count;
+ float slots_per_node = CLUSTER_MANAGER_SLOTS / (float) masters_count;
+ long first = 0;
+ float cursor = 0.0f;
+ for (i = 0; i < masters_count; i++) {
+ clusterManagerNode *master = masters[i];
+ long last = lround(cursor + slots_per_node - 1);
+ if (last > CLUSTER_MANAGER_SLOTS || i == (masters_count - 1))
+ last = CLUSTER_MANAGER_SLOTS - 1;
+ if (last < first) last = first;
+ printf("Master[%d] -> Slots %lu - %lu\n", i, first, last);
+ master->slots_count = 0;
+ for (j = first; j <= last; j++) {
+ master->slots[j] = 1;
+ master->slots_count++;
+ }
+ master->dirty = 1;
+ first = last + 1;
+ cursor += slots_per_node;
+ }
+
+ /* Rotating the list sometimes helps to get better initial
+ * anti-affinity before the optimizer runs. */
+ clusterManagerNode *first_node = interleaved[0];
+ for (i = 0; i < (interleaved_len - 1); i++)
+ interleaved[i] = interleaved[i + 1];
+ interleaved[interleaved_len - 1] = first_node;
+ int assign_unused = 0, available_count = interleaved_len;
+assign_replicas:
+ for (i = 0; i < masters_count; i++) {
+ clusterManagerNode *master = masters[i];
+ int assigned_replicas = 0;
+ while (assigned_replicas < replicas) {
+ if (available_count == 0) break;
+ clusterManagerNode *found = NULL, *slave = NULL;
+ int firstNodeIdx = -1;
+ for (j = 0; j < interleaved_len; j++) {
+ clusterManagerNode *n = interleaved[j];
+ if (n == NULL) continue;
+ if (strcmp(n->ip, master->ip)) {
+ found = n;
+ interleaved[j] = NULL;
+ break;
+ }
+ if (firstNodeIdx < 0) firstNodeIdx = j;
+ }
+ if (found) slave = found;
+ else if (firstNodeIdx >= 0) {
+ slave = interleaved[firstNodeIdx];
+ interleaved_len -= (interleaved - (interleaved + firstNodeIdx));
+ interleaved += (firstNodeIdx + 1);
+ }
+ if (slave != NULL) {
+ assigned_replicas++;
+ available_count--;
+ if (slave->replicate) sdsfree(slave->replicate);
+ slave->replicate = sdsnew(master->name);
+ slave->dirty = 1;
+ } else break;
+ printf("Adding replica %s:%d to %s:%d\n", slave->ip, slave->port,
+ master->ip, master->port);
+ if (assign_unused) break;
+ }
+ }
+ if (!assign_unused && available_count > 0) {
+ assign_unused = 1;
+ printf("Adding extra replicas...\n");
+ goto assign_replicas;
+ }
+ for (i = 0; i < ip_count; i++) {
+ clusterManagerNodeArray *node_array = ip_nodes + i;
+ clusterManagerNodeArrayReset(node_array);
+ }
+ clusterManagerOptimizeAntiAffinity(ip_nodes, ip_count);
+ clusterManagerShowNodes();
+ if (confirmWithYes("Can I set the above configuration?")) {
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ char *err = NULL;
+ int flushed = clusterManagerFlushNodeConfig(node, &err);
+ if (!flushed && node->dirty && !node->replicate) {
+ if (err != NULL) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ zfree(err);
+ }
+ success = 0;
+ goto cleanup;
+ } else if (err != NULL) zfree(err);
+ }
+ clusterManagerLogInfo(">>> Nodes configuration updated\n");
+ clusterManagerLogInfo(">>> Assign a different config epoch to "
+ "each node\n");
+ int config_epoch = 1;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ redisReply *reply = NULL;
+ reply = CLUSTER_MANAGER_COMMAND(node,
+ "cluster set-config-epoch %d",
+ config_epoch++);
+ if (reply != NULL) freeReplyObject(reply);
+ }
+ clusterManagerLogInfo(">>> Sending CLUSTER MEET messages to join "
+ "the cluster\n");
+ clusterManagerNode *first = NULL;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ if (first == NULL) {
+ first = node;
+ continue;
+ }
+ redisReply *reply = NULL;
+ reply = CLUSTER_MANAGER_COMMAND(node, "cluster meet %s %d",
+ first->ip, first->port);
+ int is_err = 0;
+ if (reply != NULL) {
+ if ((is_err = reply->type == REDIS_REPLY_ERROR))
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, reply->str);
+ freeReplyObject(reply);
+ } else {
+ is_err = 1;
+ fprintf(stderr, "Failed to send CLUSTER MEET command.\n");
+ }
+ if (is_err) {
+ success = 0;
+ goto cleanup;
+ }
+ }
+ /* Give one second for the join to start, in order to avoid that
+ * waiting for cluster join will find all the nodes agree about
+ * the config as they are still empty with unassigned slots. */
+ sleep(1);
+ clusterManagerWaitForClusterJoin();
+ /* Useful for the replicas */
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ if (!node->dirty) continue;
+ char *err = NULL;
+ int flushed = clusterManagerFlushNodeConfig(node, &err);
+ if (!flushed && !node->replicate) {
+ if (err != NULL) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(node, err);
+ zfree(err);
+ }
+ success = 0;
+ goto cleanup;
+ }
+ }
+ // Reset Nodes
+ listRewind(cluster_manager.nodes, &li);
+ clusterManagerNode *first_node = NULL;
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *node = ln->value;
+ if (!first_node) first_node = node;
+ else freeClusterManagerNode(node);
+ }
+ listEmpty(cluster_manager.nodes);
+ if (!clusterManagerLoadInfoFromNode(first_node, 0)) {
+ success = 0;
+ goto cleanup;
+ }
+ clusterManagerCheckCluster(0);
+ }
+cleanup:
+ /* Free everything */
+ zfree(masters);
+ zfree(ips);
+ for (i = 0; i < node_len; i++) {
+ clusterManagerNodeArray *node_array = ip_nodes + i;
+ CLUSTER_MANAGER_NODE_ARRAY_FREE(node_array);
+ }
+ zfree(ip_nodes);
+ return success;
+}
+
+static int clusterManagerCommandAddNode(int argc, char **argv) {
+ int success = 1;
+ redisReply *reply = NULL;
+ char *ref_ip = NULL, *ip = NULL;
+ int ref_port = 0, port = 0;
+ if (!getClusterHostFromCmdArgs(argc - 1, argv + 1, &ref_ip, &ref_port))
+ goto invalid_args;
+ if (!getClusterHostFromCmdArgs(1, argv, &ip, &port))
+ goto invalid_args;
+ clusterManagerLogInfo(">>> Adding node %s:%d to cluster %s:%d\n", ip, port,
+ ref_ip, ref_port);
+ // Check the existing cluster
+ clusterManagerNode *refnode = clusterManagerNewNode(ref_ip, ref_port);
+ if (!clusterManagerLoadInfoFromNode(refnode, 0)) return 0;
+ if (!clusterManagerCheckCluster(0)) return 0;
+
+ /* If --cluster-master-id was specified, try to resolve it now so that we
+ * abort before starting with the node configuration. */
+ clusterManagerNode *master_node = NULL;
+ if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_SLAVE) {
+ char *master_id = config.cluster_manager_command.master_id;
+ if (master_id != NULL) {
+ master_node = clusterManagerNodeByName(master_id);
+ if (master_node == NULL) {
+ clusterManagerLogErr("[ERR] No such master ID %s\n", master_id);
+ return 0;
+ }
+ } else {
+ master_node = clusterManagerNodeWithLeastReplicas();
+ assert(master_node != NULL);
+ printf("Automatically selected master %s:%d\n", master_node->ip,
+ master_node->port);
+ }
+ }
+
+ // Add the new node
+ clusterManagerNode *new_node = clusterManagerNewNode(ip, port);
+ int added = 0;
+ if (!clusterManagerNodeConnect(new_node)) {
+ clusterManagerLogErr("[ERR] Sorry, can't connect to node %s:%d\n",
+ ip, port);
+ success = 0;
+ goto cleanup;
+ }
+ char *err = NULL;
+ if (!(success = clusterManagerNodeIsCluster(new_node, &err))) {
+ clusterManagerPrintNotClusterNodeError(new_node, err);
+ if (err) zfree(err);
+ goto cleanup;
+ }
+ if (!clusterManagerNodeLoadInfo(new_node, 0, &err)) {
+ if (err) {
+ CLUSTER_MANAGER_PRINT_REPLY_ERROR(new_node, err);
+ zfree(err);
+ }
+ success = 0;
+ goto cleanup;
+ }
+ if (!(success = clusterManagerNodeIsEmpty(new_node, &err))) {
+ clusterManagerPrintNotEmptyNodeError(new_node, err);
+ if (err) zfree(err);
+ goto cleanup;
+ }
+ clusterManagerNode *first = listFirst(cluster_manager.nodes)->value;
+ listAddNodeTail(cluster_manager.nodes, new_node);
+ added = 1;
+
+ // Send CLUSTER MEET command to the new node
+ clusterManagerLogInfo(">>> Send CLUSTER MEET to node %s:%d to make it "
+ "join the cluster.\n", ip, port);
+ reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER MEET %s %d",
+ first->ip, first->port);
+ if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
+ goto cleanup;
+
+ /* Additional configuration is needed if the node is added as a slave. */
+ if (master_node) {
+ sleep(1);
+ clusterManagerWaitForClusterJoin();
+ clusterManagerLogInfo(">>> Configure node as replica of %s:%d.\n",
+ master_node->ip, master_node->port);
+ freeReplyObject(reply);
+ reply = CLUSTER_MANAGER_COMMAND(new_node, "CLUSTER REPLICATE %s",
+ master_node->name);
+ if (!(success = clusterManagerCheckRedisReply(new_node, reply, NULL)))
+ goto cleanup;
+ }
+ clusterManagerLogOk("[OK] New node added correctly.\n");
+cleanup:
+ if (!added && new_node) freeClusterManagerNode(new_node);
+ if (reply) freeReplyObject(reply);
+ return success;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandDeleteNode(int argc, char **argv) {
+ UNUSED(argc);
+ int success = 1;
+ int port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
+ char *node_id = argv[1];
+ clusterManagerLogInfo(">>> Removing node %s from cluster %s:%d\n",
+ node_id, ip, port);
+ clusterManagerNode *ref_node = clusterManagerNewNode(ip, port);
+ clusterManagerNode *node = NULL;
+
+ // Load cluster information
+ if (!clusterManagerLoadInfoFromNode(ref_node, 0)) return 0;
+
+ // Check if the node exists and is not empty
+ node = clusterManagerNodeByName(node_id);
+ if (node == NULL) {
+ clusterManagerLogErr("[ERR] No such node ID %s\n", node_id);
+ return 0;
+ }
+ if (node->slots_count != 0) {
+ clusterManagerLogErr("[ERR] Node %s:%d is not empty! Reshard data "
+ "away and try again.\n", node->ip, node->port);
+ return 0;
+ }
+
+ // Send CLUSTER FORGET to all the nodes but the node to remove
+ clusterManagerLogInfo(">>> Sending CLUSTER FORGET messages to the "
+ "cluster...\n");
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n == node) continue;
+ if (n->replicate && !strcasecmp(n->replicate, node_id)) {
+ // Reconfigure the slave to replicate with some other node
+ clusterManagerNode *master = clusterManagerNodeWithLeastReplicas();
+ assert(master != NULL);
+ clusterManagerLogInfo(">>> %s:%d as replica of %s:%d\n",
+ n->ip, n->port, master->ip, master->port);
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER REPLICATE %s",
+ master->name);
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (r) freeReplyObject(r);
+ if (!success) return 0;
+ }
+ redisReply *r = CLUSTER_MANAGER_COMMAND(n, "CLUSTER FORGET %s",
+ node_id);
+ success = clusterManagerCheckRedisReply(n, r, NULL);
+ if (r) freeReplyObject(r);
+ if (!success) return 0;
+ }
+
+ /* Finally send CLUSTER RESET to the node. */
+ clusterManagerLogInfo(">>> Sending CLUSTER RESET SOFT to the "
+ "deleted node.\n");
+ redisReply *r = redisCommand(node->context, "CLUSTER RESET %s", "SOFT");
+ success = clusterManagerCheckRedisReply(node, r, NULL);
+ if (r) freeReplyObject(r);
+ return success;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandInfo(int argc, char **argv) {
+ int port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
+ clusterManagerShowClusterInfo();
+ return 1;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandCheck(int argc, char **argv) {
+ int port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
+ clusterManagerShowClusterInfo();
+ return clusterManagerCheckCluster(0);
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandFix(int argc, char **argv) {
+ config.cluster_manager_command.flags |= CLUSTER_MANAGER_CMD_FLAG_FIX;
+ return clusterManagerCommandCheck(argc, argv);
+}
+
+static int clusterManagerCommandReshard(int argc, char **argv) {
+ int port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
+ clusterManagerCheckCluster(0);
+ if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
+ fflush(stdout);
+ fprintf(stderr,
+ "*** Please fix your cluster problems before resharding\n");
+ return 0;
+ }
+ int slots = config.cluster_manager_command.slots;
+ if (!slots) {
+ while (slots <= 0 || slots > CLUSTER_MANAGER_SLOTS) {
+ printf("How many slots do you want to move (from 1 to %d)? ",
+ CLUSTER_MANAGER_SLOTS);
+ fflush(stdout);
+ char buf[6];
+ int nread = read(fileno(stdin),buf,6);
+ if (nread <= 0) continue;
+ int last_idx = nread - 1;
+ if (buf[last_idx] != '\n') {
+ int ch;
+ while ((ch = getchar()) != '\n' && ch != EOF) {}
+ }
+ buf[last_idx] = '\0';
+ slots = atoi(buf);
+ }
+ }
+ char buf[255];
+ char *to = config.cluster_manager_command.to,
+ *from = config.cluster_manager_command.from;
+ while (to == NULL) {
+ printf("What is the receiving node ID? ");
+ fflush(stdout);
+ int nread = read(fileno(stdin),buf,255);
+ if (nread <= 0) continue;
+ int last_idx = nread - 1;
+ if (buf[last_idx] != '\n') {
+ int ch;
+ while ((ch = getchar()) != '\n' && ch != EOF) {}
+ }
+ buf[last_idx] = '\0';
+ if (strlen(buf) > 0) to = buf;
+ }
+ int raise_err = 0;
+ clusterManagerNode *target = clusterNodeForResharding(to, NULL, &raise_err);
+ if (target == NULL) return 0;
+ list *sources = listCreate();
+ list *table = NULL;
+ int all = 0, result = 1;
+ if (from == NULL) {
+ printf("Please enter all the source node IDs.\n");
+ printf(" Type 'all' to use all the nodes as source nodes for "
+ "the hash slots.\n");
+ printf(" Type 'done' once you entered all the source nodes IDs.\n");
+ while (1) {
+ printf("Source node #%lu: ", listLength(sources) + 1);
+ fflush(stdout);
+ int nread = read(fileno(stdin),buf,255);
+ if (nread <= 0) continue;
+ int last_idx = nread - 1;
+ if (buf[last_idx] != '\n') {
+ int ch;
+ while ((ch = getchar()) != '\n' && ch != EOF) {}
+ }
+ buf[last_idx] = '\0';
+ if (!strcmp(buf, "done")) break;
+ else if (!strcmp(buf, "all")) {
+ all = 1;
+ break;
+ } else {
+ clusterManagerNode *src =
+ clusterNodeForResharding(buf, target, &raise_err);
+ if (src != NULL) listAddNodeTail(sources, src);
+ else if (raise_err) {
+ result = 0;
+ goto cleanup;
+ }
+ }
+ }
+ } else {
+ char *p;
+ while((p = strchr(from, ',')) != NULL) {
+ *p = '\0';
+ if (!strcmp(from, "all")) {
+ all = 1;
+ break;
+ } else {
+ clusterManagerNode *src =
+ clusterNodeForResharding(from, target, &raise_err);
+ if (src != NULL) listAddNodeTail(sources, src);
+ else if (raise_err) {
+ result = 0;
+ goto cleanup;
+ }
+ }
+ from = p + 1;
+ }
+ /* Check if there's still another source to process. */
+ if (!all && strlen(from) > 0) {
+ if (!strcmp(from, "all")) all = 1;
+ if (!all) {
+ clusterManagerNode *src =
+ clusterNodeForResharding(from, target, &raise_err);
+ if (src != NULL) listAddNodeTail(sources, src);
+ else if (raise_err) {
+ result = 0;
+ goto cleanup;
+ }
+ }
+ }
+ }
+ listIter li;
+ listNode *ln;
+ if (all) {
+ listEmpty(sources);
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
+ continue;
+ if (!sdscmp(n->name, target->name)) continue;
+ listAddNodeTail(sources, n);
+ }
+ }
+ if (listLength(sources) == 0) {
+ fprintf(stderr, "*** No source nodes given, operation aborted.\n");
+ result = 0;
+ goto cleanup;
+ }
+ printf("\nReady to move %d slots.\n", slots);
+ printf(" Source nodes:\n");
+ listRewind(sources, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *src = ln->value;
+ sds info = clusterManagerNodeInfo(src, 4);
+ printf("%s\n", info);
+ sdsfree(info);
+ }
+ printf(" Destination node:\n");
+ sds info = clusterManagerNodeInfo(target, 4);
+ printf("%s\n", info);
+ sdsfree(info);
+ table = clusterManagerComputeReshardTable(sources, slots);
+ printf(" Resharding plan:\n");
+ clusterManagerShowReshardTable(table);
+ if (!(config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_YES))
+ {
+ printf("Do you want to proceed with the proposed "
+ "reshard plan (yes/no)? ");
+ fflush(stdout);
+ char buf[4];
+ int nread = read(fileno(stdin),buf,4);
+ buf[3] = '\0';
+ if (nread <= 0 || strcmp("yes", buf) != 0) {
+ result = 0;
+ goto cleanup;
+ }
+ }
+ int opts = CLUSTER_MANAGER_OPT_VERBOSE;
+ listRewind(table, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerReshardTableItem *item = ln->value;
+ char *err = NULL;
+ result = clusterManagerMoveSlot(item->source, target, item->slot,
+ opts, &err);
+ if (!result) {
+ if (err != NULL) {
+ //clusterManagerLogErr("\n%s\n", err);
+ zfree(err);
+ }
+ goto cleanup;
+ }
+ }
+cleanup:
+ listRelease(sources);
+ clusterManagerReleaseReshardTable(table);
+ return result;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandRebalance(int argc, char **argv) {
+ int port = 0;
+ char *ip = NULL;
+ clusterManagerNode **weightedNodes = NULL;
+ list *involved = NULL;
+ if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
+ int result = 1, i;
+ if (config.cluster_manager_command.weight != NULL) {
+ for (i = 0; i < config.cluster_manager_command.weight_argc; i++) {
+ char *name = config.cluster_manager_command.weight[i];
+ char *p = strchr(name, '=');
+ if (p == NULL) {
+ result = 0;
+ goto cleanup;
+ }
+ *p = '\0';
+ float w = atof(++p);
+ clusterManagerNode *n = clusterManagerNodeByAbbreviatedName(name);
+ if (n == NULL) {
+ clusterManagerLogErr("*** No such master node %s\n", name);
+ result = 0;
+ goto cleanup;
+ }
+ n->weight = w;
+ }
+ }
+ float total_weight = 0;
+ int nodes_involved = 0;
+ int use_empty = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_EMPTYMASTER;
+ involved = listCreate();
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ /* Compute the total cluster weight. */
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE || n->replicate)
+ continue;
+ if (!use_empty && n->slots_count == 0) {
+ n->weight = 0;
+ continue;
+ }
+ total_weight += n->weight;
+ nodes_involved++;
+ listAddNodeTail(involved, n);
+ }
+ weightedNodes = zmalloc(nodes_involved * sizeof(clusterManagerNode *));
+ if (weightedNodes == NULL) goto cleanup;
+ /* Check cluster, only proceed if it looks sane. */
+ clusterManagerCheckCluster(1);
+ if (cluster_manager.errors && listLength(cluster_manager.errors) > 0) {
+ clusterManagerLogErr("*** Please fix your cluster problems "
+ "before rebalancing\n");
+ result = 0;
+ goto cleanup;
+ }
+ /* Calculate the slots balance for each node. It's the number of
+ * slots the node should lose (if positive) or gain (if negative)
+ * in order to be balanced. */
+ int threshold_reached = 0, total_balance = 0;
+ float threshold = config.cluster_manager_command.threshold;
+ i = 0;
+ listRewind(involved, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ weightedNodes[i++] = n;
+ int expected = (int) (((float)CLUSTER_MANAGER_SLOTS / total_weight) *
+ n->weight);
+ n->balance = n->slots_count - expected;
+ total_balance += n->balance;
+ /* Compute the percentage of difference between the
+ * expected number of slots and the real one, to see
+ * if it's over the threshold specified by the user. */
+ int over_threshold = 0;
+ if (threshold > 0) {
+ if (n->slots_count > 0) {
+ float err_perc = fabs((100-(100.0*expected/n->slots_count)));
+ if (err_perc > threshold) over_threshold = 1;
+ } else if (expected > 1) {
+ over_threshold = 1;
+ }
+ }
+ if (over_threshold) threshold_reached = 1;
+ }
+ if (!threshold_reached) {
+ clusterManagerLogWarn("*** No rebalancing needed! "
+ "All nodes are within the %.2f%% threshold.\n",
+ config.cluster_manager_command.threshold);
+ goto cleanup;
+ }
+ /* Because of rounding, it is possible that the balance of all nodes
+ * summed does not give 0. Make sure that nodes that have to provide
+ * slots are always matched by nodes receiving slots. */
+ while (total_balance > 0) {
+ listRewind(involved, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->balance <= 0 && total_balance > 0) {
+ n->balance--;
+ total_balance--;
+ }
+ }
+ }
+ /* Sort nodes by their slots balance. */
+ qsort(weightedNodes, nodes_involved, sizeof(clusterManagerNode *),
+ clusterManagerCompareNodeBalance);
+ clusterManagerLogInfo(">>> Rebalancing across %d nodes. "
+ "Total weight = %.2f\n",
+ nodes_involved, total_weight);
+ if (config.verbose) {
+ for (i = 0; i < nodes_involved; i++) {
+ clusterManagerNode *n = weightedNodes[i];
+ printf("%s:%d balance is %d slots\n", n->ip, n->port, n->balance);
+ }
+ }
+ /* Now we have at the start of the 'sn' array nodes that should get
+ * slots, at the end nodes that must give slots.
+ * We take two indexes, one at the start, and one at the end,
+ * incrementing or decrementing the indexes accordingly til we
+ * find nodes that need to get/provide slots. */
+ int dst_idx = 0;
+ int src_idx = nodes_involved - 1;
+ int simulate = config.cluster_manager_command.flags &
+ CLUSTER_MANAGER_CMD_FLAG_SIMULATE;
+ while (dst_idx < src_idx) {
+ clusterManagerNode *dst = weightedNodes[dst_idx];
+ clusterManagerNode *src = weightedNodes[src_idx];
+ int db = abs(dst->balance);
+ int sb = abs(src->balance);
+ int numslots = (db < sb ? db : sb);
+ if (numslots > 0) {
+ printf("Moving %d slots from %s:%d to %s:%d\n", numslots,
+ src->ip,
+ src->port,
+ dst->ip,
+ dst->port);
+ /* Actually move the slots. */
+ list *lsrc = listCreate(), *table = NULL;
+ listAddNodeTail(lsrc, src);
+ table = clusterManagerComputeReshardTable(lsrc, numslots);
+ listRelease(lsrc);
+ int table_len = (int) listLength(table);
+ if (!table || table_len != numslots) {
+ clusterManagerLogErr("*** Assertion failed: Reshard table "
+ "!= number of slots");
+ result = 0;
+ goto end_move;
+ }
+ if (simulate) {
+ for (i = 0; i < table_len; i++) printf("#");
+ } else {
+ int opts = CLUSTER_MANAGER_OPT_QUIET |
+ CLUSTER_MANAGER_OPT_UPDATE;
+ listRewind(table, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerReshardTableItem *item = ln->value;
+ result = clusterManagerMoveSlot(item->source,
+ dst,
+ item->slot,
+ opts, NULL);
+ if (!result) goto end_move;
+ printf("#");
+ fflush(stdout);
+ }
+
+ }
+ printf("\n");
+end_move:
+ clusterManagerReleaseReshardTable(table);
+ if (!result) goto cleanup;
+ }
+ /* Update nodes balance. */
+ dst->balance += numslots;
+ src->balance -= numslots;
+ if (dst->balance == 0) dst_idx++;
+ if (src->balance == 0) src_idx --;
+ }
+cleanup:
+ if (involved != NULL) listRelease(involved);
+ if (weightedNodes != NULL) zfree(weightedNodes);
+ return result;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandSetTimeout(int argc, char **argv) {
+ UNUSED(argc);
+ int port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
+ int timeout = atoi(argv[1]);
+ if (timeout < 100) {
+ fprintf(stderr, "Setting a node timeout of less than 100 "
+ "milliseconds is a bad idea.\n");
+ return 0;
+ }
+ // Load cluster information
+ clusterManagerNode *node = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(node, 0)) return 0;
+ int ok_count = 0, err_count = 0;
+
+ clusterManagerLogInfo(">>> Reconfiguring node timeout in every "
+ "cluster node...\n");
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ char *err = NULL;
+ redisReply *reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s %s %d",
+ "SET",
+ "cluster-node-timeout",
+ timeout);
+ if (reply == NULL) goto reply_err;
+ int ok = clusterManagerCheckRedisReply(n, reply, &err);
+ freeReplyObject(reply);
+ if (!ok) goto reply_err;
+ reply = CLUSTER_MANAGER_COMMAND(n, "CONFIG %s", "REWRITE");
+ if (reply == NULL) goto reply_err;
+ ok = clusterManagerCheckRedisReply(n, reply, &err);
+ freeReplyObject(reply);
+ if (!ok) goto reply_err;
+ clusterManagerLogWarn("*** New timeout set for %s:%d\n", n->ip,
+ n->port);
+ ok_count++;
+ continue;
+reply_err:;
+ int need_free = 0;
+ if (err == NULL) err = "";
+ else need_free = 1;
+ clusterManagerLogErr("ERR setting node-timeot for %s:%d: %s\n", n->ip,
+ n->port, err);
+ if (need_free) zfree(err);
+ err_count++;
+ }
+ clusterManagerLogInfo(">>> New node timeout set. %d OK, %d ERR.\n",
+ ok_count, err_count);
+ return 1;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandImport(int argc, char **argv) {
+ int success = 1;
+ int port = 0, src_port = 0;
+ char *ip = NULL, *src_ip = NULL;
+ char *invalid_args_msg = NULL;
+ if (!getClusterHostFromCmdArgs(argc, argv, &ip, &port)) {
+ invalid_args_msg = CLUSTER_MANAGER_INVALID_HOST_ARG;
+ goto invalid_args;
+ }
+ if (config.cluster_manager_command.from == NULL) {
+ invalid_args_msg = "[ERR] Option '--cluster-from' is required for "
+ "subcommand 'import'.\n";
+ goto invalid_args;
+ }
+ char *src_host[] = {config.cluster_manager_command.from};
+ if (!getClusterHostFromCmdArgs(1, src_host, &src_ip, &src_port)) {
+ invalid_args_msg = "[ERR] Invalid --cluster-from host. You need to "
+ "pass a valid address (ie. 120.0.0.1:7000).\n";
+ goto invalid_args;
+ }
+ clusterManagerLogInfo(">>> Importing data from %s:%d to cluster %s:%d\n",
+ src_ip, src_port, ip, port);
+
+ clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(refnode, 0)) return 0;
+ if (!clusterManagerCheckCluster(0)) return 0;
+ char *reply_err = NULL;
+ redisReply *src_reply = NULL;
+ // Connect to the source node.
+ redisContext *src_ctx = redisConnect(src_ip, src_port);
+ if (src_ctx->err) {
+ success = 0;
+ fprintf(stderr,"Could not connect to Redis at %s:%d: %s.\n", src_ip,
+ src_port, src_ctx->errstr);
+ goto cleanup;
+ }
+ src_reply = reconnectingRedisCommand(src_ctx, "INFO");
+ if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
+ if (src_reply && src_reply->str) reply_err = src_reply->str;
+ success = 0;
+ goto cleanup;
+ }
+ if (getLongInfoField(src_reply->str, "cluster_enabled")) {
+ clusterManagerLogErr("[ERR] The source node should not be a "
+ "cluster node.\n");
+ success = 0;
+ goto cleanup;
+ }
+ freeReplyObject(src_reply);
+ src_reply = reconnectingRedisCommand(src_ctx, "DBSIZE");
+ if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
+ if (src_reply && src_reply->str) reply_err = src_reply->str;
+ success = 0;
+ goto cleanup;
+ }
+ int size = src_reply->integer, i;
+ clusterManagerLogWarn("*** Importing %d keys from DB 0\n", size);
+
+ // Build a slot -> node map
+ clusterManagerNode *slots_map[CLUSTER_MANAGER_SLOTS];
+ memset(slots_map, 0, sizeof(slots_map));
+ listIter li;
+ listNode *ln;
+ for (i = 0; i < CLUSTER_MANAGER_SLOTS; i++) {
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (n->flags & CLUSTER_MANAGER_FLAG_SLAVE) continue;
+ if (n->slots_count == 0) continue;
+ if (n->slots[i]) {
+ slots_map[i] = n;
+ break;
+ }
+ }
+ }
+
+ char cmdfmt[50] = "MIGRATE %s %d %s %d %d";
+ if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_COPY)
+ strcat(cmdfmt, " %s");
+ if (config.cluster_manager_command.flags & CLUSTER_MANAGER_CMD_FLAG_REPLACE)
+ strcat(cmdfmt, " %s");
+
+ /* Use SCAN to iterate over the keys, migrating to the
+ * right node as needed. */
+ int cursor = -999, timeout = config.cluster_manager_command.timeout;
+ while (cursor != 0) {
+ if (cursor < 0) cursor = 0;
+ freeReplyObject(src_reply);
+ src_reply = reconnectingRedisCommand(src_ctx, "SCAN %d COUNT %d",
+ cursor, 1000);
+ if (!src_reply || src_reply->type == REDIS_REPLY_ERROR) {
+ if (src_reply && src_reply->str) reply_err = src_reply->str;
+ success = 0;
+ goto cleanup;
+ }
+ assert(src_reply->type == REDIS_REPLY_ARRAY);
+ assert(src_reply->elements >= 2);
+ assert(src_reply->element[1]->type == REDIS_REPLY_ARRAY);
+ if (src_reply->element[0]->type == REDIS_REPLY_STRING)
+ cursor = atoi(src_reply->element[0]->str);
+ else if (src_reply->element[0]->type == REDIS_REPLY_INTEGER)
+ cursor = src_reply->element[0]->integer;
+ int keycount = src_reply->element[1]->elements;
+ for (i = 0; i < keycount; i++) {
+ redisReply *kr = src_reply->element[1]->element[i];
+ assert(kr->type == REDIS_REPLY_STRING);
+ char *key = kr->str;
+ uint16_t slot = clusterManagerKeyHashSlot(key, kr->len);
+ clusterManagerNode *target = slots_map[slot];
+ printf("Migrating %s to %s:%d: ", key, target->ip, target->port);
+ redisReply *r = reconnectingRedisCommand(src_ctx, cmdfmt,
+ target->ip, target->port,
+ key, 0, timeout,
+ "COPY", "REPLACE");
+ if (!r || r->type == REDIS_REPLY_ERROR) {
+ if (r && r->str) {
+ clusterManagerLogErr("Source %s:%d replied with "
+ "error:\n%s\n", src_ip, src_port,
+ r->str);
+ }
+ success = 0;
+ }
+ freeReplyObject(r);
+ if (!success) goto cleanup;
+ clusterManagerLogOk("OK\n");
+ }
+ }
+cleanup:
+ if (reply_err)
+ clusterManagerLogErr("Source %s:%d replied with error:\n%s\n",
+ src_ip, src_port, reply_err);
+ if (src_ctx) redisFree(src_ctx);
+ if (src_reply) freeReplyObject(src_reply);
+ return success;
+invalid_args:
+ fprintf(stderr, "%s", invalid_args_msg);
+ return 0;
+}
+
+static int clusterManagerCommandCall(int argc, char **argv) {
+ int port = 0, i;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(refnode, 0)) return 0;
+ argc--;
+ argv++;
+ size_t *argvlen = zmalloc(argc*sizeof(size_t));
+ clusterManagerLogInfo(">>> Calling");
+ for (i = 0; i < argc; i++) {
+ argvlen[i] = strlen(argv[i]);
+ printf(" %s", argv[i]);
+ }
+ printf("\n");
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ clusterManagerNode *n = ln->value;
+ if (!n->context && !clusterManagerNodeConnect(n)) continue;
+ redisReply *reply = NULL;
+ redisAppendCommandArgv(n->context, argc, (const char **) argv, argvlen);
+ int status = redisGetReply(n->context, (void **)(&reply));
+ if (status != REDIS_OK || reply == NULL )
+ printf("%s:%d: Failed!\n", n->ip, n->port);
+ else {
+ sds formatted_reply = cliFormatReplyRaw(reply);
+ printf("%s:%d: %s\n", n->ip, n->port, (char *) formatted_reply);
+ sdsfree(formatted_reply);
+ }
+ if (reply != NULL) freeReplyObject(reply);
+ }
+ zfree(argvlen);
+ return 1;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandBackup(int argc, char **argv) {
+ UNUSED(argc);
+ int success = 1, port = 0;
+ char *ip = NULL;
+ if (!getClusterHostFromCmdArgs(1, argv, &ip, &port)) goto invalid_args;
+ clusterManagerNode *refnode = clusterManagerNewNode(ip, port);
+ if (!clusterManagerLoadInfoFromNode(refnode, 0)) return 0;
+ int no_issues = clusterManagerCheckCluster(0);
+ int cluster_errors_count = (no_issues ? 0 :
+ listLength(cluster_manager.errors));
+ config.cluster_manager_command.backup_dir = argv[1];
+ /* TODO: check if backup_dir is a valid directory. */
+ sds json = sdsnew("[\n");
+ int first_node = 0;
+ listIter li;
+ listNode *ln;
+ listRewind(cluster_manager.nodes, &li);
+ while ((ln = listNext(&li)) != NULL) {
+ if (!first_node) first_node = 1;
+ else json = sdscat(json, ",\n");
+ clusterManagerNode *node = ln->value;
+ sds node_json = clusterManagerNodeGetJSON(node, cluster_errors_count);
+ json = sdscat(json, node_json);
+ sdsfree(node_json);
+ if (node->replicate)
+ continue;
+ clusterManagerLogInfo(">>> Node %s:%d -> Saving RDB...\n",
+ node->ip, node->port);
+ fflush(stdout);
+ getRDB(node);
+ }
+ json = sdscat(json, "\n]");
+ sds jsonpath = sdsnew(config.cluster_manager_command.backup_dir);
+ if (jsonpath[sdslen(jsonpath) - 1] != '/')
+ jsonpath = sdscat(jsonpath, "/");
+ jsonpath = sdscat(jsonpath, "nodes.json");
+ fflush(stdout);
+ clusterManagerLogInfo("Saving cluster configuration to: %s\n", jsonpath);
+ FILE *out = fopen(jsonpath, "w+");
+ if (!out) {
+ clusterManagerLogErr("Could not save nodes to: %s\n", jsonpath);
+ success = 0;
+ goto cleanup;
+ }
+ fputs(json, out);
+ fclose(out);
+cleanup:
+ sdsfree(json);
+ sdsfree(jsonpath);
+ if (success) {
+ if (!no_issues) {
+ clusterManagerLogWarn("*** Cluster seems to have some problems, "
+ "please be aware of it if you're going "
+ "to restore this backup.\n");
+ }
+ clusterManagerLogOk("[OK] Backup created into: %s\n",
+ config.cluster_manager_command.backup_dir);
+ } else clusterManagerLogOk("[ERR] Failed to back cluster!\n");
+ return success;
+invalid_args:
+ fprintf(stderr, CLUSTER_MANAGER_INVALID_HOST_ARG);
+ return 0;
+}
+
+static int clusterManagerCommandHelp(int argc, char **argv) {
+ UNUSED(argc);
+ UNUSED(argv);
+ int commands_count = sizeof(clusterManagerCommands) /
+ sizeof(clusterManagerCommandDef);
+ int i = 0, j;
+ fprintf(stderr, "Cluster Manager Commands:\n");
+ int padding = 15;
+ for (; i < commands_count; i++) {
+ clusterManagerCommandDef *def = &(clusterManagerCommands[i]);
+ int namelen = strlen(def->name), padlen = padding - namelen;
+ fprintf(stderr, " %s", def->name);
+ for (j = 0; j < padlen; j++) fprintf(stderr, " ");
+ fprintf(stderr, "%s\n", (def->args ? def->args : ""));
+ if (def->options != NULL) {
+ int optslen = strlen(def->options);
+ char *p = def->options, *eos = p + optslen;
+ char *comma = NULL;
+ while ((comma = strchr(p, ',')) != NULL) {
+ int deflen = (int)(comma - p);
+ char buf[255];
+ memcpy(buf, p, deflen);
+ buf[deflen] = '\0';
+ for (j = 0; j < padding; j++) fprintf(stderr, " ");
+ fprintf(stderr, " --cluster-%s\n", buf);
+ p = comma + 1;
+ if (p >= eos) break;
+ }
+ if (p < eos) {
+ for (j = 0; j < padding; j++) fprintf(stderr, " ");
+ fprintf(stderr, " --cluster-%s\n", p);
+ }
+ }
+ }
+ fprintf(stderr, "\nFor check, fix, reshard, del-node, set-timeout you "
+ "can specify the host and port of any working node in "
+ "the cluster.\n\n");
+ return 0;
+}
+
+/*------------------------------------------------------------------------------
* Latency and latency history modes
*--------------------------------------------------------------------------- */
@@ -1793,9 +6673,31 @@ static void latencyDistMode(void) {
* Slave mode
*--------------------------------------------------------------------------- */
+#define RDB_EOF_MARK_SIZE 40
+
+void sendReplconf(const char* arg1, const char* arg2) {
+ printf("sending REPLCONF %s %s\n", arg1, arg2);
+ redisReply *reply = redisCommand(context, "REPLCONF %s %s", arg1, arg2);
+
+ /* Handle any error conditions */
+ if(reply == NULL) {
+ fprintf(stderr, "\nI/O error\n");
+ exit(1);
+ } else if(reply->type == REDIS_REPLY_ERROR) {
+ fprintf(stderr, "REPLCONF %s error: %s\n", arg1, reply->str);
+ /* non fatal, old versions may not support it */
+ }
+ freeReplyObject(reply);
+}
+
+void sendCapa() {
+ sendReplconf("capa", "eof");
+}
+
/* Sends SYNC and reads the number of bytes in the payload. Used both by
- * slaveMode() and getRDB(). */
-unsigned long long sendSync(int fd) {
+ * slaveMode() and getRDB().
+ * returns 0 in case an EOF marker is used. */
+unsigned long long sendSync(int fd, char *out_eof) {
/* To start we need to send the SYNC command and return the payload.
* The hiredis client lib does not understand this part of the protocol
* and we don't want to mess with its buffers, so everything is performed
@@ -1825,17 +6727,33 @@ unsigned long long sendSync(int fd) {
printf("SYNC with master failed: %s\n", buf);
exit(1);
}
+ if (strncmp(buf+1,"EOF:",4) == 0 && strlen(buf+5) >= RDB_EOF_MARK_SIZE) {
+ memcpy(out_eof, buf+5, RDB_EOF_MARK_SIZE);
+ return 0;
+ }
return strtoull(buf+1,NULL,10);
}
static void slaveMode(void) {
int fd = context->fd;
- unsigned long long payload = sendSync(fd);
+ static char eofmark[RDB_EOF_MARK_SIZE];
+ static char lastbytes[RDB_EOF_MARK_SIZE];
+ static int usemark = 0;
+ unsigned long long payload = sendSync(fd, eofmark);
char buf[1024];
int original_output = config.output;
- fprintf(stderr,"SYNC with master, discarding %llu "
- "bytes of bulk transfer...\n", payload);
+ if (payload == 0) {
+ payload = ULLONG_MAX;
+ memset(lastbytes,0,RDB_EOF_MARK_SIZE);
+ usemark = 1;
+ fprintf(stderr,"SYNC with master, discarding "
+ "bytes of bulk transfer until EOF marker...\n");
+ } else {
+ fprintf(stderr,"SYNC with master, discarding %llu "
+ "bytes of bulk transfer...\n", payload);
+ }
+
/* Discard the payload. */
while(payload) {
@@ -1847,8 +6765,29 @@ static void slaveMode(void) {
exit(1);
}
payload -= nread;
+
+ if (usemark) {
+ /* Update the last bytes array, and check if it matches our delimiter.*/
+ if (nread >= RDB_EOF_MARK_SIZE) {
+ memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
+ } else {
+ int rem = RDB_EOF_MARK_SIZE-nread;
+ memmove(lastbytes,lastbytes+nread,rem);
+ memcpy(lastbytes+rem,buf,nread);
+ }
+ if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
+ break;
+ }
}
- fprintf(stderr,"SYNC done. Logging commands from master.\n");
+
+ if (usemark) {
+ unsigned long long offset = ULLONG_MAX - payload;
+ fprintf(stderr,"SYNC done after %llu bytes. Logging commands from master.\n", offset);
+ /* put the slave online */
+ sleep(1);
+ sendReplconf("ACK", "0");
+ } else
+ fprintf(stderr,"SYNC done. Logging commands from master.\n");
/* Now we can use hiredis to read the incoming protocol. */
config.output = OUTPUT_CSV;
@@ -1862,22 +6801,41 @@ static void slaveMode(void) {
/* This function implements --rdb, so it uses the replication protocol in order
* to fetch the RDB file from a remote server. */
-static void getRDB(void) {
- int s = context->fd;
- int fd;
- unsigned long long payload = sendSync(s);
+static void getRDB(clusterManagerNode *node) {
+ int s, fd;
+ char *filename;
+ if (node != NULL) {
+ assert(node->context);
+ s = node->context->fd;
+ filename = clusterManagerGetNodeRDBFilename(node);
+ } else {
+ s = context->fd;
+ filename = config.rdb_filename;
+ }
+ static char eofmark[RDB_EOF_MARK_SIZE];
+ static char lastbytes[RDB_EOF_MARK_SIZE];
+ static int usemark = 0;
+ unsigned long long payload = sendSync(s, eofmark);
char buf[4096];
- fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
- payload, config.rdb_filename);
+ if (payload == 0) {
+ payload = ULLONG_MAX;
+ memset(lastbytes,0,RDB_EOF_MARK_SIZE);
+ usemark = 1;
+ fprintf(stderr,"SYNC sent to master, writing bytes of bulk transfer "
+ "until EOF marker to '%s'\n", filename);
+ } else {
+ fprintf(stderr,"SYNC sent to master, writing %llu bytes to '%s'\n",
+ payload, filename);
+ }
/* Write to file. */
- if (!strcmp(config.rdb_filename,"-")) {
+ if (!strcmp(filename,"-")) {
fd = STDOUT_FILENO;
} else {
- fd = open(config.rdb_filename, O_CREAT|O_WRONLY, 0644);
+ fd = open(filename, O_CREAT|O_WRONLY, 0644);
if (fd == -1) {
- fprintf(stderr, "Error opening '%s': %s\n", config.rdb_filename,
+ fprintf(stderr, "Error opening '%s': %s\n", filename,
strerror(errno));
exit(1);
}
@@ -1894,15 +6852,40 @@ static void getRDB(void) {
nwritten = write(fd, buf, nread);
if (nwritten != nread) {
fprintf(stderr,"Error writing data to file: %s\n",
- strerror(errno));
+ (nwritten == -1) ? strerror(errno) : "short write");
exit(1);
}
payload -= nread;
+
+ if (usemark) {
+ /* Update the last bytes array, and check if it matches our delimiter.*/
+ if (nread >= RDB_EOF_MARK_SIZE) {
+ memcpy(lastbytes,buf+nread-RDB_EOF_MARK_SIZE,RDB_EOF_MARK_SIZE);
+ } else {
+ int rem = RDB_EOF_MARK_SIZE-nread;
+ memmove(lastbytes,lastbytes+nread,rem);
+ memcpy(lastbytes+rem,buf,nread);
+ }
+ if (memcmp(lastbytes,eofmark,RDB_EOF_MARK_SIZE) == 0)
+ break;
+ }
+ }
+ if (usemark) {
+ payload = ULLONG_MAX - payload - RDB_EOF_MARK_SIZE;
+ if (ftruncate(fd, payload) == -1)
+ fprintf(stderr,"ftruncate failed: %s.\n", strerror(errno));
+ fprintf(stderr,"Transfer finished with success after %llu bytes\n", payload);
+ } else {
+ fprintf(stderr,"Transfer finished with success.\n");
}
close(s); /* Close the file descriptor ASAP as fsync() may take time. */
fsync(fd);
close(fd);
fprintf(stderr,"Transfer finished with success.\n");
+ if (node) {
+ sdsfree(filename);
+ return;
+ }
exit(0);
}
@@ -1943,6 +6926,7 @@ static void pipeMode(void) {
/* Handle the readable state: we can read replies from the server. */
if (mask & AE_READABLE) {
ssize_t nread;
+ int read_error = 0;
/* Read from socket and feed the hiredis reader. */
do {
@@ -1950,7 +6934,8 @@ static void pipeMode(void) {
if (nread == -1 && errno != EAGAIN && errno != EINTR) {
fprintf(stderr, "Error reading from the server: %s\n",
strerror(errno));
- exit(1);
+ read_error = 1;
+ break;
}
if (nread > 0) {
redisReaderFeed(reader,ibuf,nread);
@@ -1983,6 +6968,11 @@ static void pipeMode(void) {
freeReplyObject(reply);
}
} while(reply);
+
+ /* Abort on read errors. We abort here because it is important
+ * to consume replies even after a read error: this way we can
+ * show a potential problem to the user. */
+ if (read_error) exit(1);
}
/* Handle the writable state: we can send protocol to the server. */
@@ -2070,15 +7060,6 @@ static void pipeMode(void) {
* Find big keys
*--------------------------------------------------------------------------- */
-#define TYPE_STRING 0
-#define TYPE_LIST 1
-#define TYPE_SET 2
-#define TYPE_HASH 3
-#define TYPE_ZSET 4
-#define TYPE_STREAM 5
-#define TYPE_NONE 6
-#define TYPE_COUNT 7
-
static redisReply *sendScan(unsigned long long *it) {
redisReply *reply = redisCommand(context, "SCAN %llu", *it);
@@ -2125,28 +7106,51 @@ static int getDbSize(void) {
return size;
}
-static int toIntType(char *key, char *type) {
- if(!strcmp(type, "string")) {
- return TYPE_STRING;
- } else if(!strcmp(type, "list")) {
- return TYPE_LIST;
- } else if(!strcmp(type, "set")) {
- return TYPE_SET;
- } else if(!strcmp(type, "hash")) {
- return TYPE_HASH;
- } else if(!strcmp(type, "zset")) {
- return TYPE_ZSET;
- } else if(!strcmp(type, "stream")) {
- return TYPE_STREAM;
- } else if(!strcmp(type, "none")) {
- return TYPE_NONE;
- } else {
- fprintf(stderr, "Unknown type '%s' for key '%s'\n", type, key);
- exit(1);
- }
+typedef struct {
+ char *name;
+ char *sizecmd;
+ char *sizeunit;
+ unsigned long long biggest;
+ unsigned long long count;
+ unsigned long long totalsize;
+ sds biggest_key;
+} typeinfo;
+
+typeinfo type_string = { "string", "STRLEN", "bytes" };
+typeinfo type_list = { "list", "LLEN", "items" };
+typeinfo type_set = { "set", "SCARD", "members" };
+typeinfo type_hash = { "hash", "HLEN", "fields" };
+typeinfo type_zset = { "zset", "ZCARD", "members" };
+typeinfo type_stream = { "stream", "XLEN", "entries" };
+typeinfo type_other = { "other", NULL, "?" };
+
+static typeinfo* typeinfo_add(dict *types, char* name, typeinfo* type_template) {
+ typeinfo *info = zmalloc(sizeof(typeinfo));
+ *info = *type_template;
+ info->name = sdsnew(name);
+ dictAdd(types, info->name, info);
+ return info;
}
-static void getKeyTypes(redisReply *keys, int *types) {
+void type_free(void* priv_data, void* val) {
+ typeinfo *info = val;
+ UNUSED(priv_data);
+ if (info->biggest_key)
+ sdsfree(info->biggest_key);
+ sdsfree(info->name);
+ zfree(info);
+}
+
+static dictType typeinfoDictType = {
+ dictSdsHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCompare, /* key compare */
+ NULL, /* key destructor (owned by the value)*/
+ type_free /* val destructor */
+};
+
+static void getKeyTypes(dict *types_dict, redisReply *keys, typeinfo **types) {
redisReply *reply;
unsigned int i;
@@ -2172,37 +7176,52 @@ static void getKeyTypes(redisReply *keys, int *types) {
exit(1);
}
- types[i] = toIntType(keys->element[i]->str, reply->str);
+ sds typereply = sdsnew(reply->str);
+ dictEntry *de = dictFind(types_dict, typereply);
+ sdsfree(typereply);
+ typeinfo *type = NULL;
+ if (de)
+ type = dictGetVal(de);
+ else if (strcmp(reply->str, "none")) /* create new types for modules, (but not for deleted keys) */
+ type = typeinfo_add(types_dict, reply->str, &type_other);
+ types[i] = type;
freeReplyObject(reply);
}
}
-static void getKeySizes(redisReply *keys, int *types,
- unsigned long long *sizes)
+static void getKeySizes(redisReply *keys, typeinfo **types,
+ unsigned long long *sizes, int memkeys,
+ unsigned memkeys_samples)
{
redisReply *reply;
- char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
unsigned int i;
/* Pipeline size commands */
for(i=0;i<keys->elements;i++) {
- /* Skip keys that were deleted */
- if(types[i]==TYPE_NONE)
+ /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
+ if(!types[i] || (!types[i]->sizecmd && !memkeys))
continue;
- redisAppendCommand(context, "%s %s", sizecmds[types[i]],
- keys->element[i]->str);
+ if (!memkeys)
+ redisAppendCommand(context, "%s %s",
+ types[i]->sizecmd, keys->element[i]->str);
+ else if (memkeys_samples==0)
+ redisAppendCommand(context, "%s %s %s",
+ "MEMORY", "USAGE", keys->element[i]->str);
+ else
+ redisAppendCommand(context, "%s %s %s SAMPLES %u",
+ "MEMORY", "USAGE", keys->element[i]->str, memkeys_samples);
}
- /* Retreive sizes */
+ /* Retrieve sizes */
for(i=0;i<keys->elements;i++) {
- /* Skip keys that dissapeared between SCAN and TYPE */
- if(types[i] == TYPE_NONE) {
+ /* Skip keys that disappeared between SCAN and TYPE (or unknown types when not in memkeys mode) */
+ if(!types[i] || (!types[i]->sizecmd && !memkeys)) {
sizes[i] = 0;
continue;
}
- /* Retreive size */
+ /* Retrieve size */
if(redisGetReply(context, (void**)&reply)!=REDIS_OK) {
fprintf(stderr, "Error getting size for key '%s' (%d: %s)\n",
keys->element[i]->str, context->err, context->errstr);
@@ -2212,7 +7231,8 @@ static void getKeySizes(redisReply *keys, int *types,
* added as a different type between TYPE and SIZE */
fprintf(stderr,
"Warning: %s on '%s' failed (may have changed type)\n",
- sizecmds[types[i]], keys->element[i]->str);
+ !memkeys? types[i]->sizecmd: "MEMORY USAGE",
+ keys->element[i]->str);
sizes[i] = 0;
} else {
sizes[i] = reply->integer;
@@ -2222,17 +7242,23 @@ static void getKeySizes(redisReply *keys, int *types,
}
}
-static void findBigKeys(void) {
- unsigned long long biggest[TYPE_COUNT] = {0}, counts[TYPE_COUNT] = {0}, totalsize[TYPE_COUNT] = {0};
+static void findBigKeys(int memkeys, unsigned memkeys_samples) {
unsigned long long sampled = 0, total_keys, totlen=0, *sizes=NULL, it=0;
- sds maxkeys[TYPE_COUNT] = {0};
- char *typename[] = {"string","list","set","hash","zset","stream","none"};
- char *typeunit[] = {"bytes","items","members","fields","members","entries",""};
redisReply *reply, *keys;
unsigned int arrsize=0, i;
- int type, *types=NULL;
+ dictIterator *di;
+ dictEntry *de;
+ typeinfo **types = NULL;
double pct;
+ dict *types_dict = dictCreate(&typeinfoDictType, NULL);
+ typeinfo_add(types_dict, "string", &type_string);
+ typeinfo_add(types_dict, "list", &type_list);
+ typeinfo_add(types_dict, "set", &type_set);
+ typeinfo_add(types_dict, "hash", &type_hash);
+ typeinfo_add(types_dict, "zset", &type_zset);
+ typeinfo_add(types_dict, "stream", &type_stream);
+
/* Total keys pre scanning */
total_keys = getDbSize();
@@ -2241,15 +7267,6 @@ static void findBigKeys(void) {
printf("# average sizes per key type. You can use -i 0.1 to sleep 0.1 sec\n");
printf("# per 100 SCAN commands (not usually needed).\n\n");
- /* New up sds strings to keep track of overall biggest per type */
- for(i=0;i<TYPE_NONE; i++) {
- maxkeys[i] = sdsempty();
- if(!maxkeys[i]) {
- fprintf(stderr, "Failed to allocate memory for largest key names!\n");
- exit(1);
- }
- }
-
/* SCAN loop */
do {
/* Calculate approximate percentage completion */
@@ -2261,7 +7278,7 @@ static void findBigKeys(void) {
/* Reallocate our type and size array if we need to */
if(keys->elements > arrsize) {
- types = zrealloc(types, sizeof(int)*keys->elements);
+ types = zrealloc(types, sizeof(typeinfo*)*keys->elements);
sizes = zrealloc(sizes, sizeof(unsigned long long)*keys->elements);
if(!types || !sizes) {
@@ -2272,35 +7289,39 @@ static void findBigKeys(void) {
arrsize = keys->elements;
}
- /* Retreive types and then sizes */
- getKeyTypes(keys, types);
- getKeySizes(keys, types, sizes);
+ /* Retrieve types and then sizes */
+ getKeyTypes(types_dict, keys, types);
+ getKeySizes(keys, types, sizes, memkeys, memkeys_samples);
/* Now update our stats */
for(i=0;i<keys->elements;i++) {
- if((type = types[i]) == TYPE_NONE)
+ typeinfo *type = types[i];
+ /* Skip keys that disappeared between SCAN and TYPE */
+ if(!type)
continue;
- totalsize[type] += sizes[i];
- counts[type]++;
+ type->totalsize += sizes[i];
+ type->count++;
totlen += keys->element[i]->len;
sampled++;
- if(biggest[type]<sizes[i]) {
+ if(type->biggest<sizes[i]) {
printf(
"[%05.2f%%] Biggest %-6s found so far '%s' with %llu %s\n",
- pct, typename[type], keys->element[i]->str, sizes[i],
- typeunit[type]);
+ pct, type->name, keys->element[i]->str, sizes[i],
+ !memkeys? type->sizeunit: "bytes");
/* Keep track of biggest key name for this type */
- maxkeys[type] = sdscpy(maxkeys[type], keys->element[i]->str);
- if(!maxkeys[type]) {
+ if (type->biggest_key)
+ sdsfree(type->biggest_key);
+ type->biggest_key = sdsnew(keys->element[i]->str);
+ if(!type->biggest_key) {
fprintf(stderr, "Failed to allocate memory for key!\n");
exit(1);
}
/* Keep track of the biggest size for this type */
- biggest[type] = sizes[i];
+ type->biggest = sizes[i];
}
/* Update overall progress */
@@ -2328,26 +7349,29 @@ static void findBigKeys(void) {
totlen, totlen ? (double)totlen/sampled : 0);
/* Output the biggest keys we found, for types we did find */
- for(i=0;i<TYPE_NONE;i++) {
- if(sdslen(maxkeys[i])>0) {
- printf("Biggest %6s found '%s' has %llu %s\n", typename[i], maxkeys[i],
- biggest[i], typeunit[i]);
+ di = dictGetIterator(types_dict);
+ while ((de = dictNext(di))) {
+ typeinfo *type = dictGetVal(de);
+ if(type->biggest_key) {
+ printf("Biggest %6s found '%s' has %llu %s\n", type->name, type->biggest_key,
+ type->biggest, !memkeys? type->sizeunit: "bytes");
}
}
+ dictReleaseIterator(di);
printf("\n");
- for(i=0;i<TYPE_NONE;i++) {
+ di = dictGetIterator(types_dict);
+ while ((de = dictNext(di))) {
+ typeinfo *type = dictGetVal(de);
printf("%llu %ss with %llu %s (%05.2f%% of keys, avg size %.2f)\n",
- counts[i], typename[i], totalsize[i], typeunit[i],
- sampled ? 100 * (double)counts[i]/sampled : 0,
- counts[i] ? (double)totalsize[i]/counts[i] : 0);
+ type->count, type->name, type->totalsize, !memkeys? type->sizeunit: "bytes",
+ sampled ? 100 * (double)type->count/sampled : 0,
+ type->count ? (double)type->totalsize/type->count : 0);
}
+ dictReleaseIterator(di);
- /* Free sds strings containing max keys */
- for(i=0;i<TYPE_NONE;i++) {
- sdsfree(maxkeys[i]);
- }
+ dictRelease(types_dict);
/* Success! */
exit(0);
@@ -2856,13 +7880,30 @@ int main(int argc, char **argv) {
config.hotkeys = 0;
config.stdinarg = 0;
config.auth = NULL;
+ config.user = NULL;
config.eval = NULL;
config.eval_ldb = 0;
config.eval_ldb_end = 0;
config.eval_ldb_sync = 0;
config.enable_ldb_on_eval = 0;
config.last_cmd_type = -1;
-
+ config.verbose = 0;
+ config.no_auth_warning = 0;
+ config.cluster_manager_command.name = NULL;
+ config.cluster_manager_command.argc = 0;
+ config.cluster_manager_command.argv = NULL;
+ config.cluster_manager_command.flags = 0;
+ config.cluster_manager_command.replicas = 0;
+ config.cluster_manager_command.from = NULL;
+ config.cluster_manager_command.to = NULL;
+ config.cluster_manager_command.weight = NULL;
+ config.cluster_manager_command.weight_argc = 0;
+ config.cluster_manager_command.slots = 0;
+ config.cluster_manager_command.timeout = CLUSTER_MANAGER_MIGRATE_TIMEOUT;
+ config.cluster_manager_command.pipeline = CLUSTER_MANAGER_MIGRATE_PIPELINE;
+ config.cluster_manager_command.threshold =
+ CLUSTER_MANAGER_REBALANCE_THRESHOLD;
+ config.cluster_manager_command.backup_dir = NULL;
pref.hints = 1;
spectrum_palette = spectrum_palette_color;
@@ -2878,6 +7919,19 @@ int main(int argc, char **argv) {
argc -= firstarg;
argv += firstarg;
+ parseEnv();
+
+ /* Cluster Manager mode */
+ if (CLUSTER_MANAGER_MODE()) {
+ clusterManagerCommandProc *proc = validateClusterManagerCommand();
+ if (!proc) {
+ sdsfree(config.hostip);
+ sdsfree(config.mb_delim);
+ exit(1);
+ }
+ clusterManagerMode(proc);
+ }
+
/* Latency mode */
if (config.latency_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
@@ -2893,13 +7947,15 @@ int main(int argc, char **argv) {
/* Slave mode */
if (config.slave_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
+ sendCapa();
slaveMode();
}
/* Get RDB mode. */
if (config.getrdb_mode) {
if (cliConnect(0) == REDIS_ERR) exit(1);
- getRDB();
+ sendCapa();
+ getRDB(NULL);
}
/* Pipe mode */
@@ -2911,7 +7967,13 @@ int main(int argc, char **argv) {
/* Find big keys */
if (config.bigkeys) {
if (cliConnect(0) == REDIS_ERR) exit(1);
- findBigKeys();
+ findBigKeys(0, 0);
+ }
+
+ /* Find large keys */
+ if (config.memkeys) {
+ if (cliConnect(0) == REDIS_ERR) exit(1);
+ findBigKeys(1, config.memkeys_samples);
}
/* Find hot keys */
diff --git a/src/redis-trib.rb b/src/redis-trib.rb
index 47b398ba6..b1af83069 100755
--- a/src/redis-trib.rb
+++ b/src/redis-trib.rb
@@ -1,1830 +1,129 @@
#!/usr/bin/env ruby
-# TODO (temporary here, we'll move this into the Github issues once
-# redis-trib initial implementation is completed).
-#
-# - Make sure that if the rehashing fails in the middle redis-trib will try
-# to recover.
-# - When redis-trib performs a cluster check, if it detects a slot move in
-# progress it should prompt the user to continue the move from where it
-# stopped.
-# - Gracefully handle Ctrl+C in move_slot to prompt the user if really stop
-# while rehashing, and performing the best cleanup possible if the user
-# forces the quit.
-# - When doing "fix" set a global Fix to true, and prompt the user to
-# fix the problem if automatically fixable every time there is something
-# to fix. For instance:
-# 1) If there is a node that pretend to receive a slot, or to migrate a
-# slot, but has no entries in that slot, fix it.
-# 2) If there is a node having keys in slots that are not owned by it
-# fix this condition moving the entries in the same node.
-# 3) Perform more possibly slow tests about the state of the cluster.
-# 4) When aborted slot migration is detected, fix it.
-
-require 'rubygems'
-require 'redis'
-
-ClusterHashSlots = 16384
-MigrateDefaultTimeout = 60000
-MigrateDefaultPipeline = 10
-RebalanceDefaultThreshold = 2
-
-$verbose = false
-
-def xputs(s)
- case s[0..2]
- when ">>>"
- color="29;1"
- when "[ER"
- color="31;1"
- when "[WA"
- color="31;1"
- when "[OK"
- color="32"
- when "[FA","***"
- color="33"
- else
- color=nil
- end
-
- color = nil if ENV['TERM'] != "xterm"
- print "\033[#{color}m" if color
- print s
- print "\033[0m" if color
- print "\n"
+def colorized(str, color)
+ return str if !(ENV['TERM'] || '')["xterm"]
+ color_code = {
+ white: 29,
+ bold: '29;1',
+ black: 30,
+ red: 31,
+ green: 32,
+ yellow: 33,
+ blue: 34,
+ magenta: 35,
+ cyan: 36,
+ gray: 37
+ }[color]
+ return str if !color_code
+ "\033[#{color_code}m#{str}\033[0m"
end
-class ClusterNode
- def initialize(addr)
- s = addr.split("@")[0].split(":")
- if s.length < 2
- puts "Invalid IP or Port (given as #{addr}) - use IP:Port format"
- exit 1
- end
- port = s.pop # removes port from split array
- ip = s.join(":") # if s.length > 1 here, it's IPv6, so restore address
- @r = nil
- @info = {}
- @info[:host] = ip
- @info[:port] = port
- @info[:slots] = {}
- @info[:migrating] = {}
- @info[:importing] = {}
- @info[:replicate] = false
- @dirty = false # True if we need to flush slots info into node.
- @friends = []
- end
-
- def friends
- @friends
- end
-
- def slots
- @info[:slots]
- end
-
- def has_flag?(flag)
- @info[:flags].index(flag)
- end
-
- def to_s
- "#{@info[:host]}:#{@info[:port]}"
- end
-
- def connect(o={})
- return if @r
- print "Connecting to node #{self}: " if $verbose
- STDOUT.flush
- begin
- @r = Redis.new(:host => @info[:host], :port => @info[:port], :timeout => 60)
- @r.ping
- rescue
- xputs "[ERR] Sorry, can't connect to node #{self}"
- exit 1 if o[:abort]
- @r = nil
- end
- xputs "OK" if $verbose
- end
-
- def assert_cluster
- info = @r.info
- if !info["cluster_enabled"] || info["cluster_enabled"].to_i == 0
- xputs "[ERR] Node #{self} is not configured as a cluster node."
- exit 1
- end
- end
-
- def assert_empty
- if !(@r.cluster("info").split("\r\n").index("cluster_known_nodes:1")) ||
- (@r.info['db0'])
- xputs "[ERR] Node #{self} is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0."
- exit 1
- end
- end
-
- def load_info(o={})
- self.connect
- nodes = @r.cluster("nodes").split("\n")
- nodes.each{|n|
- # name addr flags role ping_sent ping_recv link_status slots
- split = n.split
- name,addr,flags,master_id,ping_sent,ping_recv,config_epoch,link_status = split[0..6]
- slots = split[8..-1]
- info = {
- :name => name,
- :addr => addr,
- :flags => flags.split(","),
- :replicate => master_id,
- :ping_sent => ping_sent.to_i,
- :ping_recv => ping_recv.to_i,
- :link_status => link_status
- }
- info[:replicate] = false if master_id == "-"
-
- if info[:flags].index("myself")
- @info = @info.merge(info)
- @info[:slots] = {}
- slots.each{|s|
- if s[0..0] == '['
- if s.index("->-") # Migrating
- slot,dst = s[1..-1].split("->-")
- @info[:migrating][slot.to_i] = dst
- elsif s.index("-<-") # Importing
- slot,src = s[1..-1].split("-<-")
- @info[:importing][slot.to_i] = src
- end
- elsif s.index("-")
- start,stop = s.split("-")
- self.add_slots((start.to_i)..(stop.to_i))
- else
- self.add_slots((s.to_i)..(s.to_i))
- end
- } if slots
- @dirty = false
- @r.cluster("info").split("\n").each{|e|
- k,v=e.split(":")
- k = k.to_sym
- v.chop!
- if k != :cluster_state
- @info[k] = v.to_i
- else
- @info[k] = v
- end
- }
- elsif o[:getfriends]
- @friends << info
- end
- }
- end
-
- def add_slots(slots)
- slots.each{|s|
- @info[:slots][s] = :new
- }
- @dirty = true
- end
-
- def set_as_replica(node_id)
- @info[:replicate] = node_id
- @dirty = true
- end
-
- def flush_node_config
- return if !@dirty
- if @info[:replicate]
- begin
- @r.cluster("replicate",@info[:replicate])
- rescue
- # If the cluster did not already joined it is possible that
- # the slave does not know the master node yet. So on errors
- # we return ASAP leaving the dirty flag set, to flush the
- # config later.
- return
- end
- else
- new = []
- @info[:slots].each{|s,val|
- if val == :new
- new << s
- @info[:slots][s] = true
- end
- }
- @r.cluster("addslots",*new)
- end
- @dirty = false
- end
-
- def info_string
- # We want to display the hash slots assigned to this node
- # as ranges, like in: "1-5,8-9,20-25,30"
- #
- # Note: this could be easily written without side effects,
- # we use 'slots' just to split the computation into steps.
-
- # First step: we want an increasing array of integers
- # for instance: [1,2,3,4,5,8,9,20,21,22,23,24,25,30]
- slots = @info[:slots].keys.sort
-
- # As we want to aggregate adjacent slots we convert all the
- # slot integers into ranges (with just one element)
- # So we have something like [1..1,2..2, ... and so forth.
- slots.map!{|x| x..x}
-
- # Finally we group ranges with adjacent elements.
- slots = slots.reduce([]) {|a,b|
- if !a.empty? && b.first == (a[-1].last)+1
- a[0..-2] + [(a[-1].first)..(b.last)]
- else
- a + [b]
- end
- }
-
- # Now our task is easy, we just convert ranges with just one
- # element into a number, and a real range into a start-end format.
- # Finally we join the array using the comma as separator.
- slots = slots.map{|x|
- x.count == 1 ? x.first.to_s : "#{x.first}-#{x.last}"
- }.join(",")
-
- role = self.has_flag?("master") ? "M" : "S"
+class String
- if self.info[:replicate] and @dirty
- is = "S: #{self.info[:name]} #{self.to_s}"
- else
- is = "#{role}: #{self.info[:name]} #{self.to_s}\n"+
- " slots:#{slots} (#{self.slots.length} slots) "+
- "#{(self.info[:flags]-["myself"]).join(",")}"
- end
- if self.info[:replicate]
- is += "\n replicates #{info[:replicate]}"
- elsif self.has_flag?("master") && self.info[:replicas]
- is += "\n #{info[:replicas].length} additional replica(s)"
- end
- is
- end
-
- # Return a single string representing nodes and associated slots.
- # TODO: remove slaves from config when slaves will be handled
- # by Redis Cluster.
- def get_config_signature
- config = []
- @r.cluster("nodes").each_line{|l|
- s = l.split
- slots = s[8..-1].select {|x| x[0..0] != "["}
- next if slots.length == 0
- config << s[0]+":"+(slots.sort.join(","))
+ %w(white bold black red green yellow blue magenta cyan gray).each{|color|
+ color = :"#{color}"
+ define_method(color){
+ colorized(self, color)
}
- config.sort.join("|")
- end
-
- def info
- @info
- end
-
- def is_dirty?
- @dirty
- end
+ }
- def r
- @r
- end
end
-class RedisTrib
- def initialize
- @nodes = []
- @fix = false
- @errors = []
- @timeout = MigrateDefaultTimeout
- end
-
- def check_arity(req_args, num_args)
- if ((req_args > 0 and num_args != req_args) ||
- (req_args < 0 and num_args < req_args.abs))
- xputs "[ERR] Wrong number of arguments for specified sub command"
- exit 1
- end
- end
-
- def add_node(node)
- @nodes << node
- end
-
- def reset_nodes
- @nodes = []
- end
-
- def cluster_error(msg)
- @errors << msg
- xputs msg
- end
-
- # Return the node with the specified ID or Nil.
- def get_node_by_name(name)
- @nodes.each{|n|
- return n if n.info[:name] == name.downcase
- }
- return nil
- end
-
- # Like get_node_by_name but the specified name can be just the first
- # part of the node ID as long as the prefix in unique across the
- # cluster.
- def get_node_by_abbreviated_name(name)
- l = name.length
- candidates = []
- @nodes.each{|n|
- if n.info[:name][0...l] == name.downcase
- candidates << n
- end
- }
- return nil if candidates.length != 1
- candidates[0]
- end
-
- # This function returns the master that has the least number of replicas
- # in the cluster. If there are multiple masters with the same smaller
- # number of replicas, one at random is returned.
- def get_master_with_least_replicas
- masters = @nodes.select{|n| n.has_flag? "master"}
- sorted = masters.sort{|a,b|
- a.info[:replicas].length <=> b.info[:replicas].length
- }
- sorted[0]
- end
-
- def check_cluster(opt={})
- xputs ">>> Performing Cluster Check (using node #{@nodes[0]})"
- show_nodes if !opt[:quiet]
- check_config_consistency
- check_open_slots
- check_slots_coverage
- end
-
- def show_cluster_info
- masters = 0
- keys = 0
- @nodes.each{|n|
- if n.has_flag?("master")
- puts "#{n} (#{n.info[:name][0...8]}...) -> #{n.r.dbsize} keys | #{n.slots.length} slots | "+
- "#{n.info[:replicas].length} slaves."
- masters += 1
- keys += n.r.dbsize
- end
- }
- xputs "[OK] #{keys} keys in #{masters} masters."
- keys_per_slot = sprintf("%.2f",keys/16384.0)
- puts "#{keys_per_slot} keys per slot on average."
- end
-
- # Merge slots of every known node. If the resulting slots are equal
- # to ClusterHashSlots, then all slots are served.
- def covered_slots
- slots = {}
- @nodes.each{|n|
- slots = slots.merge(n.slots)
- }
- slots
- end
-
- def check_slots_coverage
- xputs ">>> Check slots coverage..."
- slots = covered_slots
- if slots.length == ClusterHashSlots
- xputs "[OK] All #{ClusterHashSlots} slots covered."
- else
- cluster_error \
- "[ERR] Not all #{ClusterHashSlots} slots are covered by nodes."
- fix_slots_coverage if @fix
- end
- end
-
- def check_open_slots
- xputs ">>> Check for open slots..."
- open_slots = []
- @nodes.each{|n|
- if n.info[:migrating].size > 0
- cluster_error \
- "[WARNING] Node #{n} has slots in migrating state (#{n.info[:migrating].keys.join(",")})."
- open_slots += n.info[:migrating].keys
- end
- if n.info[:importing].size > 0
- cluster_error \
- "[WARNING] Node #{n} has slots in importing state (#{n.info[:importing].keys.join(",")})."
- open_slots += n.info[:importing].keys
- end
- }
- open_slots.uniq!
- if open_slots.length > 0
- xputs "[WARNING] The following slots are open: #{open_slots.join(",")}"
- end
- if @fix
- open_slots.each{|slot| fix_open_slot slot}
- end
- end
-
- def nodes_with_keys_in_slot(slot)
- nodes = []
- @nodes.each{|n|
- next if n.has_flag?("slave")
- nodes << n if n.r.cluster("getkeysinslot",slot,1).length > 0
- }
- nodes
- end
-
- def fix_slots_coverage
- not_covered = (0...ClusterHashSlots).to_a - covered_slots.keys
- xputs ">>> Fixing slots coverage..."
- xputs "List of not covered slots: " + not_covered.join(",")
-
- # For every slot, take action depending on the actual condition:
- # 1) No node has keys for this slot.
- # 2) A single node has keys for this slot.
- # 3) Multiple nodes have keys for this slot.
- slots = {}
- not_covered.each{|slot|
- nodes = nodes_with_keys_in_slot(slot)
- slots[slot] = nodes
- xputs "Slot #{slot} has keys in #{nodes.length} nodes: #{nodes.join(", ")}"
- }
-
- none = slots.select {|k,v| v.length == 0}
- single = slots.select {|k,v| v.length == 1}
- multi = slots.select {|k,v| v.length > 1}
-
- # Handle case "1": keys in no node.
- if none.length > 0
- xputs "The folowing uncovered slots have no keys across the cluster:"
- xputs none.keys.join(",")
- yes_or_die "Fix these slots by covering with a random node?"
- none.each{|slot,nodes|
- node = @nodes.sample
- xputs ">>> Covering slot #{slot} with #{node}"
- node.r.cluster("addslots",slot)
- }
- end
-
- # Handle case "2": keys only in one node.
- if single.length > 0
- xputs "The folowing uncovered slots have keys in just one node:"
- puts single.keys.join(",")
- yes_or_die "Fix these slots by covering with those nodes?"
- single.each{|slot,nodes|
- xputs ">>> Covering slot #{slot} with #{nodes[0]}"
- nodes[0].r.cluster("addslots",slot)
- }
- end
-
- # Handle case "3": keys in multiple nodes.
- if multi.length > 0
- xputs "The folowing uncovered slots have keys in multiple nodes:"
- xputs multi.keys.join(",")
- yes_or_die "Fix these slots by moving keys into a single node?"
- multi.each{|slot,nodes|
- target = get_node_with_most_keys_in_slot(nodes,slot)
- xputs ">>> Covering slot #{slot} moving keys to #{target}"
-
- target.r.cluster('addslots',slot)
- target.r.cluster('setslot',slot,'stable')
- nodes.each{|src|
- next if src == target
- # Set the source node in 'importing' state (even if we will
- # actually migrate keys away) in order to avoid receiving
- # redirections for MIGRATE.
- src.r.cluster('setslot',slot,'importing',target.info[:name])
- move_slot(src,target,slot,:dots=>true,:fix=>true,:cold=>true)
- src.r.cluster('setslot',slot,'stable')
- }
- }
- end
- end
-
- # Return the owner of the specified slot
- def get_slot_owners(slot)
- owners = []
- @nodes.each{|n|
- next if n.has_flag?("slave")
- n.slots.each{|s,_|
- owners << n if s == slot
- }
- }
- owners
- end
-
- # Return the node, among 'nodes' with the greatest number of keys
- # in the specified slot.
- def get_node_with_most_keys_in_slot(nodes,slot)
- best = nil
- best_numkeys = 0
- @nodes.each{|n|
- next if n.has_flag?("slave")
- numkeys = n.r.cluster("countkeysinslot",slot)
- if numkeys > best_numkeys || best == nil
- best = n
- best_numkeys = numkeys
- end
- }
- return best
- end
-
- # Slot 'slot' was found to be in importing or migrating state in one or
- # more nodes. This function fixes this condition by migrating keys where
- # it seems more sensible.
- def fix_open_slot(slot)
- puts ">>> Fixing open slot #{slot}"
-
- # Try to obtain the current slot owner, according to the current
- # nodes configuration.
- owners = get_slot_owners(slot)
- owner = owners[0] if owners.length == 1
-
- migrating = []
- importing = []
- @nodes.each{|n|
- next if n.has_flag? "slave"
- if n.info[:migrating][slot]
- migrating << n
- elsif n.info[:importing][slot]
- importing << n
- elsif n.r.cluster("countkeysinslot",slot) > 0 && n != owner
- xputs "*** Found keys about slot #{slot} in node #{n}!"
- importing << n
- end
- }
- puts "Set as migrating in: #{migrating.join(",")}"
- puts "Set as importing in: #{importing.join(",")}"
-
- # If there is no slot owner, set as owner the slot with the biggest
- # number of keys, among the set of migrating / importing nodes.
- if !owner
- xputs ">>> Nobody claims ownership, selecting an owner..."
- owner = get_node_with_most_keys_in_slot(@nodes,slot)
-
- # If we still don't have an owner, we can't fix it.
- if !owner
- xputs "[ERR] Can't select a slot owner. Impossible to fix."
- exit 1
- end
-
- # Use ADDSLOTS to assign the slot.
- puts "*** Configuring #{owner} as the slot owner"
- owner.r.cluster("setslot",slot,"stable")
- owner.r.cluster("addslots",slot)
- # Make sure this information will propagate. Not strictly needed
- # since there is no past owner, so all the other nodes will accept
- # whatever epoch this node will claim the slot with.
- owner.r.cluster("bumpepoch")
-
- # Remove the owner from the list of migrating/importing
- # nodes.
- migrating.delete(owner)
- importing.delete(owner)
- end
-
- # If there are multiple owners of the slot, we need to fix it
- # so that a single node is the owner and all the other nodes
- # are in importing state. Later the fix can be handled by one
- # of the base cases above.
- #
- # Note that this case also covers multiple nodes having the slot
- # in migrating state, since migrating is a valid state only for
- # slot owners.
- if owners.length > 1
- owner = get_node_with_most_keys_in_slot(owners,slot)
- owners.each{|n|
- next if n == owner
- n.r.cluster('delslots',slot)
- n.r.cluster('setslot',slot,'importing',owner.info[:name])
- importing.delete(n) # Avoid duplciates
- importing << n
- }
- owner.r.cluster('bumpepoch')
- end
-
- # Case 1: The slot is in migrating state in one slot, and in
- # importing state in 1 slot. That's trivial to address.
- if migrating.length == 1 && importing.length == 1
- move_slot(migrating[0],importing[0],slot,:dots=>true,:fix=>true)
- # Case 2: There are multiple nodes that claim the slot as importing,
- # they probably got keys about the slot after a restart so opened
- # the slot. In this case we just move all the keys to the owner
- # according to the configuration.
- elsif migrating.length == 0 && importing.length > 0
- xputs ">>> Moving all the #{slot} slot keys to its owner #{owner}"
- importing.each {|node|
- next if node == owner
- move_slot(node,owner,slot,:dots=>true,:fix=>true,:cold=>true)
- xputs ">>> Setting #{slot} as STABLE in #{node}"
- node.r.cluster("setslot",slot,"stable")
- }
- # Case 3: There are no slots claiming to be in importing state, but
- # there is a migrating node that actually don't have any key. We
- # can just close the slot, probably a reshard interrupted in the middle.
- elsif importing.length == 0 && migrating.length == 1 &&
- migrating[0].r.cluster("getkeysinslot",slot,10).length == 0
- migrating[0].r.cluster("setslot",slot,"stable")
- else
- xputs "[ERR] Sorry, Redis-trib can't fix this slot yet (work in progress). Slot is set as migrating in #{migrating.join(",")}, as importing in #{importing.join(",")}, owner is #{owner}"
- end
- end
-
- # Check if all the nodes agree about the cluster configuration
- def check_config_consistency
- if !is_config_consistent?
- cluster_error "[ERR] Nodes don't agree about configuration!"
- else
- xputs "[OK] All nodes agree about slots configuration."
- end
- end
-
- def is_config_consistent?
- signatures=[]
- @nodes.each{|n|
- signatures << n.get_config_signature
- }
- return signatures.uniq.length == 1
- end
-
- def wait_cluster_join
- print "Waiting for the cluster to join"
- while !is_config_consistent?
- print "."
- STDOUT.flush
- sleep 1
- end
- print "\n"
- end
-
- def alloc_slots
- nodes_count = @nodes.length
- masters_count = @nodes.length / (@replicas+1)
- masters = []
-
- # The first step is to split instances by IP. This is useful as
- # we'll try to allocate master nodes in different physical machines
- # (as much as possible) and to allocate slaves of a given master in
- # different physical machines as well.
- #
- # This code assumes just that if the IP is different, than it is more
- # likely that the instance is running in a different physical host
- # or at least a different virtual machine.
- ips = {}
- @nodes.each{|n|
- ips[n.info[:host]] = [] if !ips[n.info[:host]]
- ips[n.info[:host]] << n
- }
-
- # Select master instances
- puts "Using #{masters_count} masters:"
- interleaved = []
- stop = false
- while not stop do
- # Take one node from each IP until we run out of nodes
- # across every IP.
- ips.each do |ip,nodes|
- if nodes.empty?
- # if this IP has no remaining nodes, check for termination
- if interleaved.length == nodes_count
- # stop when 'interleaved' has accumulated all nodes
- stop = true
- next
- end
- else
- # else, move one node from this IP to 'interleaved'
- interleaved.push nodes.shift
- end
- end
- end
-
- masters = interleaved.slice!(0, masters_count)
- nodes_count -= masters.length
-
- masters.each{|m| puts m}
-
- # Rotating the list sometimes helps to get better initial
- # anti-affinity before the optimizer runs.
- interleaved.push interleaved.shift
-
- # Alloc slots on masters. After interleaving to get just the first N
- # should be optimal. With slaves is more complex, see later...
- slots_per_node = ClusterHashSlots.to_f / masters_count
- first = 0
- cursor = 0.0
- masters.each_with_index{|n,masternum|
- last = (cursor+slots_per_node-1).round
- if last > ClusterHashSlots || masternum == masters.length-1
- last = ClusterHashSlots-1
- end
- last = first if last < first # Min step is 1.
- n.add_slots first..last
- first = last+1
- cursor += slots_per_node
- }
-
- # Select N replicas for every master.
- # We try to split the replicas among all the IPs with spare nodes
- # trying to avoid the host where the master is running, if possible.
- #
- # Note we loop two times. The first loop assigns the requested
- # number of replicas to each master. The second loop assigns any
- # remaining instances as extra replicas to masters. Some masters
- # may end up with more than their requested number of replicas, but
- # all nodes will be used.
- assignment_verbose = false
-
- [:requested,:unused].each do |assign|
- masters.each do |m|
- assigned_replicas = 0
- while assigned_replicas < @replicas
- break if nodes_count == 0
- if assignment_verbose
- if assign == :requested
- puts "Requesting total of #{@replicas} replicas " \
- "(#{assigned_replicas} replicas assigned " \
- "so far with #{nodes_count} total remaining)."
- elsif assign == :unused
- puts "Assigning extra instance to replication " \
- "role too (#{nodes_count} remaining)."
- end
- end
-
- # Return the first node not matching our current master
- node = interleaved.find{|n| n.info[:host] != m.info[:host]}
-
- # If we found a node, use it as a best-first match.
- # Otherwise, we didn't find a node on a different IP, so we
- # go ahead and use a same-IP replica.
- if node
- slave = node
- interleaved.delete node
- else
- slave = interleaved.shift
- end
- slave.set_as_replica(m.info[:name])
- nodes_count -= 1
- assigned_replicas += 1
- puts "Adding replica #{slave} to #{m}"
-
- # If we are in the "assign extra nodes" loop,
- # we want to assign one extra replica to each
- # master before repeating masters.
- # This break lets us assign extra replicas to masters
- # in a round-robin way.
- break if assign == :unused
- end
- end
- end
-
- optimize_anti_affinity
- end
-
- def optimize_anti_affinity
- score,aux = get_anti_affinity_score
- return if score == 0
-
- xputs ">>> Trying to optimize slaves allocation for anti-affinity"
-
- maxiter = 500*@nodes.length # Effort is proportional to cluster size...
- while maxiter > 0
- score,offenders = get_anti_affinity_score
- break if score == 0 # Optimal anti affinity reached
-
- # We'll try to randomly swap a slave's assigned master causing
- # an affinity problem with another random slave, to see if we
- # can improve the affinity.
- first = offenders.shuffle.first
- nodes = @nodes.select{|n| n != first && n.info[:replicate]}
- break if nodes.length == 0
- second = nodes.shuffle.first
-
- first_master = first.info[:replicate]
- second_master = second.info[:replicate]
- first.set_as_replica(second_master)
- second.set_as_replica(first_master)
-
- new_score,aux = get_anti_affinity_score
- # If the change actually makes thing worse, revert. Otherwise
- # leave as it is becuase the best solution may need a few
- # combined swaps.
- if new_score > score
- first.set_as_replica(first_master)
- second.set_as_replica(second_master)
- end
-
- maxiter -= 1
- end
-
- score,aux = get_anti_affinity_score
- if score == 0
- xputs "[OK] Perfect anti-affinity obtained!"
- elsif score >= 10000
- puts "[WARNING] Some slaves are in the same host as their master"
- else
- puts "[WARNING] Some slaves of the same master are in the same host"
- end
- end
-
- # Return the anti-affinity score, which is a measure of the amount of
- # violations of anti-affinity in the current cluster layout, that is, how
- # badly the masters and slaves are distributed in the different IP
- # addresses so that slaves of the same master are not in the master
- # host and are also in different hosts.
- #
- # The score is calculated as follows:
- #
- # SAME_AS_MASTER = 10000 * each slave in the same IP of its master.
- # SAME_AS_SLAVE = 1 * each slave having the same IP as another slave
- # of the same master.
- # FINAL_SCORE = SAME_AS_MASTER + SAME_AS_SLAVE
- #
- # So a greater score means a worse anti-affinity level, while zero
- # means perfect anti-affinity.
- #
- # The anti affinity optimizator will try to get a score as low as
- # possible. Since we do not want to sacrifice the fact that slaves should
- # not be in the same host as the master, we assign 10000 times the score
- # to this violation, so that we'll optimize for the second factor only
- # if it does not impact the first one.
- #
- # The function returns two things: the above score, and the list of
- # offending slaves, so that the optimizer can try changing the
- # configuration of the slaves violating the anti-affinity goals.
- def get_anti_affinity_score
- score = 0
- offending = [] # List of offending slaves to return to the caller
-
- # First, split nodes by host
- host_to_node = {}
- @nodes.each{|n|
- host = n.info[:host]
- host_to_node[host] = [] if host_to_node[host] == nil
- host_to_node[host] << n
- }
-
- # Then, for each set of nodes in the same host, split by
- # related nodes (masters and slaves which are involved in
- # replication of each other)
- host_to_node.each{|host,nodes|
- related = {}
- nodes.each{|n|
- if !n.info[:replicate]
- name = n.info[:name]
- related[name] = [] if related[name] == nil
- related[name] << :m
- else
- name = n.info[:replicate]
- related[name] = [] if related[name] == nil
- related[name] << :s
- end
- }
-
- # Now it's trivial to check, for each related group having the
- # same host, what is their local score.
- related.each{|id,types|
- next if types.length < 2
- types.sort! # Make sure :m if the first if any
- if types[0] == :m
- score += 10000 * (types.length-1)
- else
- score += 1 * types.length
- end
-
- # Populate the list of offending nodes
- @nodes.each{|n|
- if n.info[:replicate] == id &&
- n.info[:host] == host
- offending << n
- end
- }
- }
- }
- return score,offending
- end
-
- def flush_nodes_config
- @nodes.each{|n|
- n.flush_node_config
- }
- end
-
- def show_nodes
- @nodes.each{|n|
- xputs n.info_string
- }
- end
-
- # Redis Cluster config epoch collision resolution code is able to eventually
- # set a different epoch to each node after a new cluster is created, but
- # it is slow compared to assign a progressive config epoch to each node
- # before joining the cluster. However we do just a best-effort try here
- # since if we fail is not a problem.
- def assign_config_epoch
- config_epoch = 1
- @nodes.each{|n|
- begin
- n.r.cluster("set-config-epoch",config_epoch)
- rescue
- end
- config_epoch += 1
- }
- end
-
- def join_cluster
- # We use a brute force approach to make sure the node will meet
- # each other, that is, sending CLUSTER MEET messages to all the nodes
- # about the very same node.
- # Thanks to gossip this information should propagate across all the
- # cluster in a matter of seconds.
- first = false
- @nodes.each{|n|
- if !first then first = n.info; next; end # Skip the first node
- n.r.cluster("meet",first[:host],first[:port])
- }
- end
-
- def yes_or_die(msg)
- print "#{msg} (type 'yes' to accept): "
- STDOUT.flush
- if !(STDIN.gets.chomp.downcase == "yes")
- xputs "*** Aborting..."
- exit 1
- end
- end
-
- def load_cluster_info_from_node(nodeaddr)
- node = ClusterNode.new(nodeaddr)
- node.connect(:abort => true)
- node.assert_cluster
- node.load_info(:getfriends => true)
- add_node(node)
- node.friends.each{|f|
- next if f[:flags].index("noaddr") ||
- f[:flags].index("disconnected") ||
- f[:flags].index("fail")
- fnode = ClusterNode.new(f[:addr])
- fnode.connect()
- next if !fnode.r
- begin
- fnode.load_info()
- add_node(fnode)
- rescue => e
- xputs "[ERR] Unable to load info for node #{fnode}"
- end
- }
- populate_nodes_replicas_info
- end
-
- # This function is called by load_cluster_info_from_node in order to
- # add additional information to every node as a list of replicas.
- def populate_nodes_replicas_info
- # Start adding the new field to every node.
- @nodes.each{|n|
- n.info[:replicas] = []
- }
-
- # Populate the replicas field using the replicate field of slave
- # nodes.
- @nodes.each{|n|
- if n.info[:replicate]
- master = get_node_by_name(n.info[:replicate])
- if !master
- xputs "*** WARNING: #{n} claims to be slave of unknown node ID #{n.info[:replicate]}."
- else
- master.info[:replicas] << n
- end
- end
- }
- end
-
- # Given a list of source nodes return a "resharding plan"
- # with what slots to move in order to move "numslots" slots to another
- # instance.
- def compute_reshard_table(sources,numslots)
- moved = []
- # Sort from bigger to smaller instance, for two reasons:
- # 1) If we take less slots than instances it is better to start
- # getting from the biggest instances.
- # 2) We take one slot more from the first instance in the case of not
- # perfect divisibility. Like we have 3 nodes and need to get 10
- # slots, we take 4 from the first, and 3 from the rest. So the
- # biggest is always the first.
- sources = sources.sort{|a,b| b.slots.length <=> a.slots.length}
- source_tot_slots = sources.inject(0) {|sum,source|
- sum+source.slots.length
- }
- sources.each_with_index{|s,i|
- # Every node will provide a number of slots proportional to the
- # slots it has assigned.
- n = (numslots.to_f/source_tot_slots*s.slots.length)
- if i == 0
- n = n.ceil
- else
- n = n.floor
- end
- s.slots.keys.sort[(0...n)].each{|slot|
- if moved.length < numslots
- moved << {:source => s, :slot => slot}
- end
- }
- }
- return moved
- end
-
- def show_reshard_table(table)
- table.each{|e|
- puts " Moving slot #{e[:slot]} from #{e[:source].info[:name]}"
- }
- end
-
- # Move slots between source and target nodes using MIGRATE.
- #
- # Options:
- # :verbose -- Print a dot for every moved key.
- # :fix -- We are moving in the context of a fix. Use REPLACE.
- # :cold -- Move keys without opening slots / reconfiguring the nodes.
- # :update -- Update nodes.info[:slots] for source/target nodes.
- # :quiet -- Don't print info messages.
- def move_slot(source,target,slot,o={})
- o = {:pipeline => MigrateDefaultPipeline}.merge(o)
-
- # We start marking the slot as importing in the destination node,
- # and the slot as migrating in the target host. Note that the order of
- # the operations is important, as otherwise a client may be redirected
- # to the target node that does not yet know it is importing this slot.
- if !o[:quiet]
- print "Moving slot #{slot} from #{source} to #{target}: "
- STDOUT.flush
- end
-
- if !o[:cold]
- target.r.cluster("setslot",slot,"importing",source.info[:name])
- source.r.cluster("setslot",slot,"migrating",target.info[:name])
- end
- # Migrate all the keys from source to target using the MIGRATE command
- while true
- keys = source.r.cluster("getkeysinslot",slot,o[:pipeline])
- break if keys.length == 0
- begin
- source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:keys,*keys])
- rescue => e
- if o[:fix] && e.to_s =~ /BUSYKEY/
- xputs "*** Target key exists. Replacing it for FIX."
- source.r.client.call(["migrate",target.info[:host],target.info[:port],"",0,@timeout,:replace,:keys,*keys])
- else
- puts ""
- xputs "[ERR] Calling MIGRATE: #{e}"
- exit 1
- end
- end
- print "."*keys.length if o[:dots]
- STDOUT.flush
- end
-
- puts if !o[:quiet]
- # Set the new node as the owner of the slot in all the known nodes.
- if !o[:cold]
- @nodes.each{|n|
- next if n.has_flag?("slave")
- n.r.cluster("setslot",slot,"node",target.info[:name])
- }
- end
-
- # Update the node logical config
- if o[:update] then
- source.info[:slots].delete(slot)
- target.info[:slots][slot] = true
- end
- end
-
- # redis-trib subcommands implementations.
-
- def check_cluster_cmd(argv,opt)
- load_cluster_info_from_node(argv[0])
- check_cluster
- end
-
- def info_cluster_cmd(argv,opt)
- load_cluster_info_from_node(argv[0])
- show_cluster_info
- end
-
- def rebalance_cluster_cmd(argv,opt)
- opt = {
- 'pipeline' => MigrateDefaultPipeline,
- 'threshold' => RebalanceDefaultThreshold
- }.merge(opt)
-
- # Load nodes info before parsing options, otherwise we can't
- # handle --weight.
- load_cluster_info_from_node(argv[0])
-
- # Options parsing
- threshold = opt['threshold'].to_i
- autoweights = opt['auto-weights']
- weights = {}
- opt['weight'].each{|w|
- fields = w.split("=")
- node = get_node_by_abbreviated_name(fields[0])
- if !node || !node.has_flag?("master")
- puts "*** No such master node #{fields[0]}"
- exit 1
- end
- weights[node.info[:name]] = fields[1].to_f
- } if opt['weight']
- useempty = opt['use-empty-masters']
-
- # Assign a weight to each node, and compute the total cluster weight.
- total_weight = 0
- nodes_involved = 0
- @nodes.each{|n|
- if n.has_flag?("master")
- next if !useempty && n.slots.length == 0
- n.info[:w] = weights[n.info[:name]] ? weights[n.info[:name]] : 1
- total_weight += n.info[:w]
- nodes_involved += 1
- end
- }
-
- # Check cluster, only proceed if it looks sane.
- check_cluster(:quiet => true)
- if @errors.length != 0
- puts "*** Please fix your cluster problems before rebalancing"
- exit 1
- end
-
- # Calculate the slots balance for each node. It's the number of
- # slots the node should lose (if positive) or gain (if negative)
- # in order to be balanced.
- threshold = opt['threshold'].to_f
- threshold_reached = false
- @nodes.each{|n|
- if n.has_flag?("master")
- next if !n.info[:w]
- expected = ((ClusterHashSlots.to_f / total_weight) *
- n.info[:w]).to_i
- n.info[:balance] = n.slots.length - expected
- # Compute the percentage of difference between the
- # expected number of slots and the real one, to see
- # if it's over the threshold specified by the user.
- over_threshold = false
- if threshold > 0
- if n.slots.length > 0
- err_perc = (100-(100.0*expected/n.slots.length)).abs
- over_threshold = true if err_perc > threshold
- elsif expected > 0
- over_threshold = true
- end
- end
- threshold_reached = true if over_threshold
- end
- }
- if !threshold_reached
- xputs "*** No rebalancing needed! All nodes are within the #{threshold}% threshold."
- return
- end
-
- # Only consider nodes we want to change
- sn = @nodes.select{|n|
- n.has_flag?("master") && n.info[:w]
- }
-
- # Because of rounding, it is possible that the balance of all nodes
- # summed does not give 0. Make sure that nodes that have to provide
- # slots are always matched by nodes receiving slots.
- total_balance = sn.map{|x| x.info[:balance]}.reduce{|a,b| a+b}
- while total_balance > 0
- sn.each{|n|
- if n.info[:balance] < 0 && total_balance > 0
- n.info[:balance] -= 1
- total_balance -= 1
- end
- }
- end
-
- # Sort nodes by their slots balance.
- sn = sn.sort{|a,b|
- a.info[:balance] <=> b.info[:balance]
- }
-
- xputs ">>> Rebalancing across #{nodes_involved} nodes. Total weight = #{total_weight}"
-
- if $verbose
- sn.each{|n|
- puts "#{n} balance is #{n.info[:balance]} slots"
- }
- end
-
- # Now we have at the start of the 'sn' array nodes that should get
- # slots, at the end nodes that must give slots.
- # We take two indexes, one at the start, and one at the end,
- # incrementing or decrementing the indexes accordingly til we
- # find nodes that need to get/provide slots.
- dst_idx = 0
- src_idx = sn.length - 1
-
- while dst_idx < src_idx
- dst = sn[dst_idx]
- src = sn[src_idx]
- numslots = [dst.info[:balance],src.info[:balance]].map{|n|
- n.abs
- }.min
-
- if numslots > 0
- puts "Moving #{numslots} slots from #{src} to #{dst}"
-
- # Actaully move the slots.
- reshard_table = compute_reshard_table([src],numslots)
- if reshard_table.length != numslots
- xputs "*** Assertio failed: Reshard table != number of slots"
- exit 1
- end
- if opt['simulate']
- print "#"*reshard_table.length
- else
- reshard_table.each{|e|
- move_slot(e[:source],dst,e[:slot],
- :quiet=>true,
- :dots=>false,
- :update=>true,
- :pipeline=>opt['pipeline'])
- print "#"
- STDOUT.flush
- }
- end
- puts
- end
-
- # Update nodes balance.
- dst.info[:balance] += numslots
- src.info[:balance] -= numslots
- dst_idx += 1 if dst.info[:balance] == 0
- src_idx -= 1 if src.info[:balance] == 0
- end
- end
-
- def fix_cluster_cmd(argv,opt)
- @fix = true
- @timeout = opt['timeout'].to_i if opt['timeout']
-
- load_cluster_info_from_node(argv[0])
- check_cluster
- end
-
- def reshard_cluster_cmd(argv,opt)
- opt = {'pipeline' => MigrateDefaultPipeline}.merge(opt)
-
- load_cluster_info_from_node(argv[0])
- check_cluster
- if @errors.length != 0
- puts "*** Please fix your cluster problems before resharding"
- exit 1
- end
-
- @timeout = opt['timeout'].to_i if opt['timeout'].to_i
-
- # Get number of slots
- if opt['slots']
- numslots = opt['slots'].to_i
- else
- numslots = 0
- while numslots <= 0 or numslots > ClusterHashSlots
- print "How many slots do you want to move (from 1 to #{ClusterHashSlots})? "
- numslots = STDIN.gets.to_i
- end
- end
-
- # Get the target instance
- if opt['to']
- target = get_node_by_name(opt['to'])
- if !target || target.has_flag?("slave")
- xputs "*** The specified node is not known or not a master, please retry."
- exit 1
- end
- else
- target = nil
- while not target
- print "What is the receiving node ID? "
- target = get_node_by_name(STDIN.gets.chop)
- if !target || target.has_flag?("slave")
- xputs "*** The specified node is not known or not a master, please retry."
- target = nil
- end
- end
- end
-
- # Get the source instances
- sources = []
- if opt['from']
- opt['from'].split(',').each{|node_id|
- if node_id == "all"
- sources = "all"
- break
- end
- src = get_node_by_name(node_id)
- if !src || src.has_flag?("slave")
- xputs "*** The specified node is not known or is not a master, please retry."
- exit 1
- end
- sources << src
- }
- else
- xputs "Please enter all the source node IDs."
- xputs " Type 'all' to use all the nodes as source nodes for the hash slots."
- xputs " Type 'done' once you entered all the source nodes IDs."
- while true
- print "Source node ##{sources.length+1}:"
- line = STDIN.gets.chop
- src = get_node_by_name(line)
- if line == "done"
- break
- elsif line == "all"
- sources = "all"
- break
- elsif !src || src.has_flag?("slave")
- xputs "*** The specified node is not known or is not a master, please retry."
- elsif src.info[:name] == target.info[:name]
- xputs "*** It is not possible to use the target node as source node."
- else
- sources << src
- end
- end
- end
+COMMANDS = %w(create check info fix reshard rebalance add-node
+ del-node set-timeout call import help)
- if sources.length == 0
- puts "*** No source nodes given, operation aborted"
- exit 1
- end
-
- # Handle soures == all.
- if sources == "all"
- sources = []
- @nodes.each{|n|
- next if n.info[:name] == target.info[:name]
- next if n.has_flag?("slave")
- sources << n
- }
- end
-
- # Check if the destination node is the same of any source nodes.
- if sources.index(target)
- xputs "*** Target node is also listed among the source nodes!"
- exit 1
- end
-
- puts "\nReady to move #{numslots} slots."
- puts " Source nodes:"
- sources.each{|s| puts " "+s.info_string}
- puts " Destination node:"
- puts " #{target.info_string}"
- reshard_table = compute_reshard_table(sources,numslots)
- puts " Resharding plan:"
- show_reshard_table(reshard_table)
- if !opt['yes']
- print "Do you want to proceed with the proposed reshard plan (yes/no)? "
- yesno = STDIN.gets.chop
- exit(1) if (yesno != "yes")
- end
- reshard_table.each{|e|
- move_slot(e[:source],target,e[:slot],
- :dots=>true,
- :pipeline=>opt['pipeline'])
- }
- end
-
- # This is an helper function for create_cluster_cmd that verifies if
- # the number of nodes and the specified replicas have a valid configuration
- # where there are at least three master nodes and enough replicas per node.
- def check_create_parameters
- masters = @nodes.length/(@replicas+1)
- if masters < 3
- puts "*** ERROR: Invalid configuration for cluster creation."
- puts "*** Redis Cluster requires at least 3 master nodes."
- puts "*** This is not possible with #{@nodes.length} nodes and #{@replicas} replicas per node."
- puts "*** At least #{3*(@replicas+1)} nodes are required."
- exit 1
- end
- end
-
- def create_cluster_cmd(argv,opt)
- opt = {'replicas' => 0}.merge(opt)
- @replicas = opt['replicas'].to_i
-
- xputs ">>> Creating cluster"
- argv[0..-1].each{|n|
- node = ClusterNode.new(n)
- node.connect(:abort => true)
- node.assert_cluster
- node.load_info
- node.assert_empty
- add_node(node)
- }
- check_create_parameters
- xputs ">>> Performing hash slots allocation on #{@nodes.length} nodes..."
- alloc_slots
- show_nodes
- yes_or_die "Can I set the above configuration?"
- flush_nodes_config
- xputs ">>> Nodes configuration updated"
- xputs ">>> Assign a different config epoch to each node"
- assign_config_epoch
- xputs ">>> Sending CLUSTER MEET messages to join the cluster"
- join_cluster
- # Give one second for the join to start, in order to avoid that
- # wait_cluster_join will find all the nodes agree about the config as
- # they are still empty with unassigned slots.
- sleep 1
- wait_cluster_join
- flush_nodes_config # Useful for the replicas
- # Reset the node information, so that when the
- # final summary is listed in check_cluster about the newly created cluster
- # all the nodes would get properly listed as slaves or masters
- reset_nodes
- load_cluster_info_from_node(argv[0])
- check_cluster
- end
-
- def addnode_cluster_cmd(argv,opt)
- xputs ">>> Adding node #{argv[0]} to cluster #{argv[1]}"
-
- # Check the existing cluster
- load_cluster_info_from_node(argv[1])
- check_cluster
+ALLOWED_OPTIONS={
+ "create" => {"replicas" => true},
+ "add-node" => {"slave" => false, "master-id" => true},
+ "import" => {"from" => :required, "copy" => false, "replace" => false},
+ "reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true},
+ "rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
+ "fix" => {"timeout" => 0},
+}
- # If --master-id was specified, try to resolve it now so that we
- # abort before starting with the node configuration.
- if opt['slave']
- if opt['master-id']
- master = get_node_by_name(opt['master-id'])
- if !master
- xputs "[ERR] No such master ID #{opt['master-id']}"
- end
+def parse_options(cmd)
+ cmd = cmd.downcase
+ idx = 0
+ options = {}
+ args = []
+ while (arg = ARGV.shift)
+ if arg[0..1] == "--"
+ option = arg[2..-1]
+
+ # --verbose is a global option
+ if option == "--verbose"
+ options['verbose'] = true
+ next
+ end
+ if ALLOWED_OPTIONS[cmd] == nil ||
+ ALLOWED_OPTIONS[cmd][option] == nil
+ next
+ end
+ if ALLOWED_OPTIONS[cmd][option] != false
+ value = ARGV.shift
+ next if !value
else
- master = get_master_with_least_replicas
- xputs "Automatically selected master #{master}"
- end
- end
-
- # Add the new node
- new = ClusterNode.new(argv[0])
- new.connect(:abort => true)
- new.assert_cluster
- new.load_info
- new.assert_empty
- first = @nodes.first.info
- add_node(new)
-
- # Send CLUSTER MEET command to the new node
- xputs ">>> Send CLUSTER MEET to node #{new} to make it join the cluster."
- new.r.cluster("meet",first[:host],first[:port])
-
- # Additional configuration is needed if the node is added as
- # a slave.
- if opt['slave']
- wait_cluster_join
- xputs ">>> Configure node as replica of #{master}."
- new.r.cluster("replicate",master.info[:name])
- end
- xputs "[OK] New node added correctly."
- end
-
- def delnode_cluster_cmd(argv,opt)
- id = argv[1].downcase
- xputs ">>> Removing node #{id} from cluster #{argv[0]}"
-
- # Load cluster information
- load_cluster_info_from_node(argv[0])
-
- # Check if the node exists and is not empty
- node = get_node_by_name(id)
-
- if !node
- xputs "[ERR] No such node ID #{id}"
- exit 1
- end
-
- if node.slots.length != 0
- xputs "[ERR] Node #{node} is not empty! Reshard data away and try again."
- exit 1
- end
-
- # Send CLUSTER FORGET to all the nodes but the node to remove
- xputs ">>> Sending CLUSTER FORGET messages to the cluster..."
- @nodes.each{|n|
- next if n == node
- if n.info[:replicate] && n.info[:replicate].downcase == id
- # Reconfigure the slave to replicate with some other node
- master = get_master_with_least_replicas
- xputs ">>> #{n} as replica of #{master}"
- n.r.cluster("replicate",master.info[:name])
- end
- n.r.cluster("forget",argv[1])
- }
-
- # Finally shutdown the node
- xputs ">>> SHUTDOWN the node."
- node.r.shutdown
- end
-
- def set_timeout_cluster_cmd(argv,opt)
- timeout = argv[1].to_i
- if timeout < 100
- puts "Setting a node timeout of less than 100 milliseconds is a bad idea."
- exit 1
- end
-
- # Load cluster information
- load_cluster_info_from_node(argv[0])
- ok_count = 0
- err_count = 0
-
- # Send CLUSTER FORGET to all the nodes but the node to remove
- xputs ">>> Reconfiguring node timeout in every cluster node..."
- @nodes.each{|n|
- begin
- n.r.config("set","cluster-node-timeout",timeout)
- n.r.config("rewrite")
- ok_count += 1
- xputs "*** New timeout set for #{n}"
- rescue => e
- puts "ERR setting node-timeot for #{n}: #{e}"
- err_count += 1
- end
- }
- xputs ">>> New node timeout set. #{ok_count} OK, #{err_count} ERR."
- end
-
- def call_cluster_cmd(argv,opt)
- cmd = argv[1..-1]
- cmd[0] = cmd[0].upcase
-
- # Load cluster information
- load_cluster_info_from_node(argv[0])
- xputs ">>> Calling #{cmd.join(" ")}"
- @nodes.each{|n|
- begin
- res = n.r.send(*cmd)
- puts "#{n}: #{res}"
- rescue => e
- puts "#{n}: #{e}"
+ value = true
end
- }
- end
-
- def import_cluster_cmd(argv,opt)
- source_addr = opt['from']
- xputs ">>> Importing data from #{source_addr} to cluster #{argv[1]}"
- use_copy = opt['copy']
- use_replace = opt['replace']
- # Check the existing cluster.
- load_cluster_info_from_node(argv[0])
- check_cluster
-
- # Connect to the source node.
- xputs ">>> Connecting to the source Redis instance"
- src_host,src_port = source_addr.split(":")
- source = Redis.new(:host =>src_host, :port =>src_port)
- if source.info['cluster_enabled'].to_i == 1
- xputs "[ERR] The source node should not be a cluster node."
- end
- xputs "*** Importing #{source.dbsize} keys from DB 0"
-
- # Build a slot -> node map
- slots = {}
- @nodes.each{|n|
- n.slots.each{|s,_|
- slots[s] = n
- }
- }
-
- # Use SCAN to iterate over the keys, migrating to the
- # right node as needed.
- cursor = nil
- while cursor != 0
- cursor,keys = source.scan(cursor, :count => 1000)
- cursor = cursor.to_i
- keys.each{|k|
- # Migrate keys using the MIGRATE command.
- slot = key_to_slot(k)
- target = slots[slot]
- print "Migrating #{k} to #{target}: "
- STDOUT.flush
- begin
- cmd = ["migrate",target.info[:host],target.info[:port],k,0,@timeout]
- cmd << :copy if use_copy
- cmd << :replace if use_replace
- source.client.call(cmd)
- rescue => e
- puts e
- else
- puts "OK"
- end
- }
- end
- end
-
- def help_cluster_cmd(argv,opt)
- show_help
- exit 0
- end
-
- # Parse the options for the specific command "cmd".
- # Returns an hash populate with option => value pairs, and the index of
- # the first non-option argument in ARGV.
- def parse_options(cmd)
- idx = 1 ; # Current index into ARGV
- options={}
- while idx < ARGV.length && ARGV[idx][0..1] == '--'
- if ARGV[idx][0..1] == "--"
- option = ARGV[idx][2..-1]
- idx += 1
-
- # --verbose is a global option
- if option == "verbose"
- $verbose = true
- next
- end
-
- if ALLOWED_OPTIONS[cmd] == nil || ALLOWED_OPTIONS[cmd][option] == nil
- puts "Unknown option '#{option}' for command '#{cmd}'"
- exit 1
- end
- if ALLOWED_OPTIONS[cmd][option] != false
- value = ARGV[idx]
- idx += 1
- else
- value = true
- end
-
- # If the option is set to [], it's a multiple arguments
- # option. We just queue every new value into an array.
- if ALLOWED_OPTIONS[cmd][option] == []
- options[option] = [] if !options[option]
- options[option] << value
- else
- options[option] = value
- end
+ # If the option is set to [], it's a multiple arguments
+ # option. We just queue every new value into an array.
+ if ALLOWED_OPTIONS[cmd][option] == []
+ options[option] = [] if !options[option]
+ options[option] << value
else
- # Remaining arguments are not options.
- break
+ options[option] = value
end
+ else
+ next if arg[0,1] == '-'
+ args << arg
end
-
- # Enforce mandatory options
- if ALLOWED_OPTIONS[cmd]
- ALLOWED_OPTIONS[cmd].each {|option,val|
- if !options[option] && val == :required
- puts "Option '--#{option}' is required "+ \
- "for subcommand '#{cmd}'"
- exit 1
- end
- }
- end
- return options,idx
end
-end
-
-#################################################################################
-# Libraries
-#
-# We try to don't depend on external libs since this is a critical part
-# of Redis Cluster.
-#################################################################################
-# This is the CRC16 algorithm used by Redis Cluster to hash keys.
-# Implementation according to CCITT standards.
-#
-# This is actually the XMODEM CRC 16 algorithm, using the
-# following parameters:
-#
-# Name : "XMODEM", also known as "ZMODEM", "CRC-16/ACORN"
-# Width : 16 bit
-# Poly : 1021 (That is actually x^16 + x^12 + x^5 + 1)
-# Initialization : 0000
-# Reflect Input byte : False
-# Reflect Output CRC : False
-# Xor constant to output CRC : 0000
-# Output for "123456789" : 31C3
-
-module RedisClusterCRC16
- def RedisClusterCRC16.crc16(bytes)
- crc = 0
- bytes.each_byte{|b|
- crc = ((crc<<8) & 0xffff) ^ XMODEMCRC16Lookup[((crc>>8)^b) & 0xff]
- }
- crc
- end
-
-private
- XMODEMCRC16Lookup = [
- 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7,
- 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef,
- 0x1231,0x0210,0x3273,0x2252,0x52b5,0x4294,0x72f7,0x62d6,
- 0x9339,0x8318,0xb37b,0xa35a,0xd3bd,0xc39c,0xf3ff,0xe3de,
- 0x2462,0x3443,0x0420,0x1401,0x64e6,0x74c7,0x44a4,0x5485,
- 0xa56a,0xb54b,0x8528,0x9509,0xe5ee,0xf5cf,0xc5ac,0xd58d,
- 0x3653,0x2672,0x1611,0x0630,0x76d7,0x66f6,0x5695,0x46b4,
- 0xb75b,0xa77a,0x9719,0x8738,0xf7df,0xe7fe,0xd79d,0xc7bc,
- 0x48c4,0x58e5,0x6886,0x78a7,0x0840,0x1861,0x2802,0x3823,
- 0xc9cc,0xd9ed,0xe98e,0xf9af,0x8948,0x9969,0xa90a,0xb92b,
- 0x5af5,0x4ad4,0x7ab7,0x6a96,0x1a71,0x0a50,0x3a33,0x2a12,
- 0xdbfd,0xcbdc,0xfbbf,0xeb9e,0x9b79,0x8b58,0xbb3b,0xab1a,
- 0x6ca6,0x7c87,0x4ce4,0x5cc5,0x2c22,0x3c03,0x0c60,0x1c41,
- 0xedae,0xfd8f,0xcdec,0xddcd,0xad2a,0xbd0b,0x8d68,0x9d49,
- 0x7e97,0x6eb6,0x5ed5,0x4ef4,0x3e13,0x2e32,0x1e51,0x0e70,
- 0xff9f,0xefbe,0xdfdd,0xcffc,0xbf1b,0xaf3a,0x9f59,0x8f78,
- 0x9188,0x81a9,0xb1ca,0xa1eb,0xd10c,0xc12d,0xf14e,0xe16f,
- 0x1080,0x00a1,0x30c2,0x20e3,0x5004,0x4025,0x7046,0x6067,
- 0x83b9,0x9398,0xa3fb,0xb3da,0xc33d,0xd31c,0xe37f,0xf35e,
- 0x02b1,0x1290,0x22f3,0x32d2,0x4235,0x5214,0x6277,0x7256,
- 0xb5ea,0xa5cb,0x95a8,0x8589,0xf56e,0xe54f,0xd52c,0xc50d,
- 0x34e2,0x24c3,0x14a0,0x0481,0x7466,0x6447,0x5424,0x4405,
- 0xa7db,0xb7fa,0x8799,0x97b8,0xe75f,0xf77e,0xc71d,0xd73c,
- 0x26d3,0x36f2,0x0691,0x16b0,0x6657,0x7676,0x4615,0x5634,
- 0xd94c,0xc96d,0xf90e,0xe92f,0x99c8,0x89e9,0xb98a,0xa9ab,
- 0x5844,0x4865,0x7806,0x6827,0x18c0,0x08e1,0x3882,0x28a3,
- 0xcb7d,0xdb5c,0xeb3f,0xfb1e,0x8bf9,0x9bd8,0xabbb,0xbb9a,
- 0x4a75,0x5a54,0x6a37,0x7a16,0x0af1,0x1ad0,0x2ab3,0x3a92,
- 0xfd2e,0xed0f,0xdd6c,0xcd4d,0xbdaa,0xad8b,0x9de8,0x8dc9,
- 0x7c26,0x6c07,0x5c64,0x4c45,0x3ca2,0x2c83,0x1ce0,0x0cc1,
- 0xef1f,0xff3e,0xcf5d,0xdf7c,0xaf9b,0xbfba,0x8fd9,0x9ff8,
- 0x6e17,0x7e36,0x4e55,0x5e74,0x2e93,0x3eb2,0x0ed1,0x1ef0
- ]
-end
-
-# Turn a key name into the corrisponding Redis Cluster slot.
-def key_to_slot(key)
- # Only hash what is inside {...} if there is such a pattern in the key.
- # Note that the specification requires the content that is between
- # the first { and the first } after the first {. If we found {} without
- # nothing in the middle, the whole key is hashed as usually.
- s = key.index "{"
- if s
- e = key.index "}",s+1
- if e && e != s+1
- key = key[s+1..e-1]
- end
- end
- RedisClusterCRC16.crc16(key) % 16384
+ return options,args
end
-#################################################################################
-# Definition of commands
-#################################################################################
-
-COMMANDS={
- "create" => ["create_cluster_cmd", -2, "host1:port1 ... hostN:portN"],
- "check" => ["check_cluster_cmd", 2, "host:port"],
- "info" => ["info_cluster_cmd", 2, "host:port"],
- "fix" => ["fix_cluster_cmd", 2, "host:port"],
- "reshard" => ["reshard_cluster_cmd", 2, "host:port"],
- "rebalance" => ["rebalance_cluster_cmd", -2, "host:port"],
- "add-node" => ["addnode_cluster_cmd", 3, "new_host:new_port existing_host:existing_port"],
- "del-node" => ["delnode_cluster_cmd", 3, "host:port node_id"],
- "set-timeout" => ["set_timeout_cluster_cmd", 3, "host:port milliseconds"],
- "call" => ["call_cluster_cmd", -3, "host:port command arg arg .. arg"],
- "import" => ["import_cluster_cmd", 2, "host:port"],
- "help" => ["help_cluster_cmd", 1, "(show this help)"]
-}
-
-ALLOWED_OPTIONS={
- "create" => {"replicas" => true},
- "add-node" => {"slave" => false, "master-id" => true},
- "import" => {"from" => :required, "copy" => false, "replace" => false},
- "reshard" => {"from" => true, "to" => true, "slots" => true, "yes" => false, "timeout" => true, "pipeline" => true},
- "rebalance" => {"weight" => [], "auto-weights" => false, "use-empty-masters" => false, "timeout" => true, "simulate" => false, "pipeline" => true, "threshold" => true},
- "fix" => {"timeout" => MigrateDefaultTimeout},
-}
-
-def show_help
- puts "Usage: redis-trib <command> <options> <arguments ...>\n\n"
- COMMANDS.each{|k,v|
- puts " #{k.ljust(15)} #{v[2]}"
- if ALLOWED_OPTIONS[k]
- ALLOWED_OPTIONS[k].each{|optname,has_arg|
- puts " --#{optname}" + (has_arg ? " <arg>" : "")
- }
+def command_example(cmd, args, opts)
+ cmd = "redis-cli --cluster #{cmd}"
+ args.each{|a|
+ a = a.to_s
+ a = a.inspect if a[' ']
+ cmd << " #{a}"
+ }
+ opts.each{|opt, val|
+ opt = " --cluster-#{opt.downcase}"
+ if val != true
+ val = val.join(' ') if val.is_a? Array
+ opt << " #{val}"
end
+ cmd << opt
}
- puts "\nFor check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.\n"
-end
-
-# Sanity check
-if ARGV.length == 0
- show_help
- exit 1
+ cmd
end
-rt = RedisTrib.new
-cmd_spec = COMMANDS[ARGV[0].downcase]
-if !cmd_spec
- puts "Unknown redis-trib subcommand '#{ARGV[0]}'"
- exit 1
+$command = ARGV.shift
+$opts, $args = parse_options($command) if $command
+
+puts "WARNING: redis-trib.rb is not longer available!".yellow
+puts "You should use #{'redis-cli'.bold} instead."
+puts ''
+puts "All commands and features belonging to redis-trib.rb "+
+ "have been moved\nto redis-cli."
+puts "In order to use them you should call redis-cli with the #{'--cluster'.bold}"
+puts "option followed by the subcommand name, arguments and options."
+puts ''
+puts "Use the following syntax:"
+puts "redis-cli --cluster SUBCOMMAND [ARGUMENTS] [OPTIONS]".bold
+puts ''
+puts "Example:"
+if $command
+ example = command_example $command, $args, $opts
+else
+ example = "redis-cli --cluster info 127.0.0.1:7000"
end
-
-# Parse options
-cmd_options,first_non_option = rt.parse_options(ARGV[0].downcase)
-rt.check_arity(cmd_spec[1],ARGV.length-(first_non_option-1))
-
-# Dispatch
-rt.send(cmd_spec[0],ARGV[first_non_option..-1],cmd_options)
+puts example.bold
+puts ''
+puts "To get help about all subcommands, type:"
+puts "redis-cli --cluster help".bold
+puts ''
+exit 1
diff --git a/src/redisassert.h b/src/redisassert.h
index c9b78327c..61ab35a14 100644
--- a/src/redisassert.h
+++ b/src/redisassert.h
@@ -1,4 +1,4 @@
-/* redisassert.h -- Drop in replacemnet assert.h that prints the stack trace
+/* redisassert.h -- Drop in replacements assert.h that prints the stack trace
* in the Redis logs.
*
* This file should be included instead of "assert.h" inside libraries used by
diff --git a/src/redismodule.h b/src/redismodule.h
index 8c14fd0d1..377ea7e13 100644
--- a/src/redismodule.h
+++ b/src/redismodule.h
@@ -58,29 +58,53 @@
#define REDISMODULE_HASH_CFIELDS (1<<2)
#define REDISMODULE_HASH_EXISTS (1<<3)
-/* Context Flags: Info about the current context returned by RM_GetContextFlags */
+/* Context Flags: Info about the current context returned by
+ * RM_GetContextFlags(). */
/* The command is running in the context of a Lua script */
-#define REDISMODULE_CTX_FLAGS_LUA 0x0001
+#define REDISMODULE_CTX_FLAGS_LUA (1<<0)
/* The command is running inside a Redis transaction */
-#define REDISMODULE_CTX_FLAGS_MULTI 0x0002
+#define REDISMODULE_CTX_FLAGS_MULTI (1<<1)
/* The instance is a master */
-#define REDISMODULE_CTX_FLAGS_MASTER 0x0004
+#define REDISMODULE_CTX_FLAGS_MASTER (1<<2)
/* The instance is a slave */
-#define REDISMODULE_CTX_FLAGS_SLAVE 0x0008
+#define REDISMODULE_CTX_FLAGS_SLAVE (1<<3)
/* The instance is read-only (usually meaning it's a slave as well) */
-#define REDISMODULE_CTX_FLAGS_READONLY 0x0010
+#define REDISMODULE_CTX_FLAGS_READONLY (1<<4)
/* The instance is running in cluster mode */
-#define REDISMODULE_CTX_FLAGS_CLUSTER 0x0020
+#define REDISMODULE_CTX_FLAGS_CLUSTER (1<<5)
/* The instance has AOF enabled */
-#define REDISMODULE_CTX_FLAGS_AOF 0x0040 //
+#define REDISMODULE_CTX_FLAGS_AOF (1<<6)
/* The instance has RDB enabled */
-#define REDISMODULE_CTX_FLAGS_RDB 0x0080 //
+#define REDISMODULE_CTX_FLAGS_RDB (1<<7)
/* The instance has Maxmemory set */
-#define REDISMODULE_CTX_FLAGS_MAXMEMORY 0x0100
+#define REDISMODULE_CTX_FLAGS_MAXMEMORY (1<<8)
/* Maxmemory is set and has an eviction policy that may delete keys */
-#define REDISMODULE_CTX_FLAGS_EVICT 0x0200
-
+#define REDISMODULE_CTX_FLAGS_EVICT (1<<9)
+/* Redis is out of memory according to the maxmemory flag. */
+#define REDISMODULE_CTX_FLAGS_OOM (1<<10)
+/* Less than 25% of memory available according to maxmemory. */
+#define REDISMODULE_CTX_FLAGS_OOM_WARNING (1<<11)
+/* The command was sent over the replication link. */
+#define REDISMODULE_CTX_FLAGS_REPLICATED (1<<12)
+/* Redis is currently loading either from AOF or RDB. */
+#define REDISMODULE_CTX_FLAGS_LOADING (1<<13)
+/* The replica has no link with its master, note that
+ * there is the inverse flag as well:
+ *
+ * REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE
+ *
+ * The two flags are exclusive, one or the other can be set. */
+#define REDISMODULE_CTX_FLAGS_REPLICA_IS_STALE (1<<14)
+/* The replica is trying to connect with the master.
+ * (REPL_STATE_CONNECT and REPL_STATE_CONNECTING states) */
+#define REDISMODULE_CTX_FLAGS_REPLICA_IS_CONNECTING (1<<15)
+/* THe replica is receiving an RDB file from its master. */
+#define REDISMODULE_CTX_FLAGS_REPLICA_IS_TRANSFERRING (1<<16)
+/* The replica is online, receiving updates from its master. */
+#define REDISMODULE_CTX_FLAGS_REPLICA_IS_ONLINE (1<<17)
+/* There is currently some background process active. */
+#define REDISMODULE_CTX_FLAGS_ACTIVE_CHILD (1<<18)
#define REDISMODULE_NOTIFY_GENERIC (1<<2) /* g */
#define REDISMODULE_NOTIFY_STRING (1<<3) /* $ */
@@ -90,8 +114,9 @@
#define REDISMODULE_NOTIFY_ZSET (1<<7) /* z */
#define REDISMODULE_NOTIFY_EXPIRED (1<<8) /* x */
#define REDISMODULE_NOTIFY_EVICTED (1<<9) /* e */
-#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
-#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM) /* A */
+#define REDISMODULE_NOTIFY_STREAM (1<<10) /* t */
+#define REDISMODULE_NOTIFY_KEY_MISS (1<<11) /* m */
+#define REDISMODULE_NOTIFY_ALL (REDISMODULE_NOTIFY_GENERIC | REDISMODULE_NOTIFY_STRING | REDISMODULE_NOTIFY_LIST | REDISMODULE_NOTIFY_SET | REDISMODULE_NOTIFY_HASH | REDISMODULE_NOTIFY_ZSET | REDISMODULE_NOTIFY_EXPIRED | REDISMODULE_NOTIFY_EVICTED | REDISMODULE_NOTIFY_STREAM | REDISMODULE_NOTIFY_KEY_MISS) /* A */
/* A special pointer that we can use between the core and the module to signal
@@ -104,8 +129,165 @@
#define REDISMODULE_POSITIVE_INFINITE (1.0/0.0)
#define REDISMODULE_NEGATIVE_INFINITE (-1.0/0.0)
+/* Cluster API defines. */
+#define REDISMODULE_NODE_ID_LEN 40
+#define REDISMODULE_NODE_MYSELF (1<<0)
+#define REDISMODULE_NODE_MASTER (1<<1)
+#define REDISMODULE_NODE_SLAVE (1<<2)
+#define REDISMODULE_NODE_PFAIL (1<<3)
+#define REDISMODULE_NODE_FAIL (1<<4)
+#define REDISMODULE_NODE_NOFAILOVER (1<<5)
+
+#define REDISMODULE_CLUSTER_FLAG_NONE 0
+#define REDISMODULE_CLUSTER_FLAG_NO_FAILOVER (1<<1)
+#define REDISMODULE_CLUSTER_FLAG_NO_REDIRECTION (1<<2)
+
#define REDISMODULE_NOT_USED(V) ((void) V)
+/* Bit flags for aux_save_triggers and the aux_load and aux_save callbacks */
+#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
+#define REDISMODULE_AUX_AFTER_RDB (1<<1)
+
+/* This type represents a timer handle, and is returned when a timer is
+ * registered and used in order to invalidate a timer. It's just a 64 bit
+ * number, because this is how each timer is represented inside the radix tree
+ * of timers that are going to expire, sorted by expire time. */
+typedef uint64_t RedisModuleTimerID;
+
+/* CommandFilter Flags */
+
+/* Do filter RedisModule_Call() commands initiated by module itself. */
+#define REDISMODULE_CMDFILTER_NOSELF (1<<0)
+
+/* Declare that the module can handle errors with RedisModule_SetModuleOptions. */
+#define REDISMODULE_OPTIONS_HANDLE_IO_ERRORS (1<<0)
+
+/* Server events definitions. */
+#define REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED 0
+#define REDISMODULE_EVENT_PERSISTENCE 1
+#define REDISMODULE_EVENT_FLUSHDB 2
+#define REDISMODULE_EVENT_LOADING 3
+#define REDISMODULE_EVENT_CLIENT_CHANGE 4
+#define REDISMODULE_EVENT_SHUTDOWN 5
+#define REDISMODULE_EVENT_REPLICA_CHANGE 6
+#define REDISMODULE_EVENT_MASTER_LINK_CHANGE 7
+#define REDISMODULE_EVENT_CRON_LOOP 8
+
+typedef struct RedisModuleEvent {
+ uint64_t id; /* REDISMODULE_EVENT_... defines. */
+ uint64_t dataver; /* Version of the structure we pass as 'data'. */
+} RedisModuleEvent;
+
+struct RedisModuleCtx;
+typedef void (*RedisModuleEventCallback)(struct RedisModuleCtx *ctx, RedisModuleEvent eid, uint64_t subevent, void *data);
+
+static RedisModuleEvent
+ RedisModuleEvent_ReplicationRoleChanged = {
+ REDISMODULE_EVENT_REPLICATION_ROLE_CHANGED,
+ 1
+ },
+ RedisModuleEvent_Persistence = {
+ REDISMODULE_EVENT_PERSISTENCE,
+ 1
+ },
+ RedisModuleEvent_FlushDB = {
+ REDISMODULE_EVENT_FLUSHDB,
+ 1
+ },
+ RedisModuleEvent_Loading = {
+ REDISMODULE_EVENT_LOADING,
+ 1
+ },
+ RedisModuleEvent_ClientChange = {
+ REDISMODULE_EVENT_CLIENT_CHANGE,
+ 1
+ },
+ RedisModuleEvent_Shutdown = {
+ REDISMODULE_EVENT_SHUTDOWN,
+ 1
+ },
+ RedisModuleEvent_ReplicaChange = {
+ REDISMODULE_EVENT_REPLICA_CHANGE,
+ 1
+ },
+ RedisModuleEvent_CronLoop = {
+ REDISMODULE_EVENT_CRON_LOOP,
+ 1
+ },
+ RedisModuleEvent_MasterLinkChange = {
+ REDISMODULE_EVENT_MASTER_LINK_CHANGE,
+ 1
+ };
+
+/* Those are values that are used for the 'subevent' callback argument. */
+#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_START 0
+#define REDISMODULE_SUBEVENT_PERSISTENCE_RDB_END 1
+#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_START 2
+#define REDISMODULE_SUBEVENT_PERSISTENCE_AOF_END 3
+
+#define REDISMODULE_SUBEVENT_LOADING_RDB_START 0
+#define REDISMODULE_SUBEVENT_LOADING_RDB_END 1
+#define REDISMODULE_SUBEVENT_LOADING_AOF_START 2
+#define REDISMODULE_SUBEVENT_LOADING_AOF_END 3
+
+#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED 0
+#define REDISMODULE_SUBEVENT_CLIENT_CHANGE_DISCONNECTED 1
+
+#define REDISMODULE_SUBEVENT_MASTER_LINK_UP 0
+#define REDISMODULE_SUBEVENT_MASTER_LINK_DOWN 1
+
+#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_CONNECTED 0
+#define REDISMODULE_SUBEVENT_REPLICA_CHANGE_DISCONNECTED 1
+
+#define REDISMODULE_SUBEVENT_FLUSHDB_START 0
+#define REDISMODULE_SUBEVENT_FLUSHDB_END 1
+
+/* RedisModuleClientInfo flags. */
+#define REDISMODULE_CLIENTINFO_FLAG_SSL (1<<0)
+#define REDISMODULE_CLIENTINFO_FLAG_PUBSUB (1<<1)
+#define REDISMODULE_CLIENTINFO_FLAG_BLOCKED (1<<2)
+#define REDISMODULE_CLIENTINFO_FLAG_TRACKING (1<<3)
+#define REDISMODULE_CLIENTINFO_FLAG_UNIXSOCKET (1<<4)
+#define REDISMODULE_CLIENTINFO_FLAG_MULTI (1<<5)
+
+/* Here we take all the structures that the module pass to the core
+ * and the other way around. Notably the list here contains the structures
+ * used by the hooks API RedisModule_RegisterToServerEvent().
+ *
+ * The structures always start with a 'version' field. This is useful
+ * when we want to pass a reference to the structure to the core APIs,
+ * for the APIs to fill the structure. In that case, the structure 'version'
+ * field is initialized before passing it to the core, so that the core is
+ * able to cast the pointer to the appropriate structure version. In this
+ * way we obtain ABI compatibility.
+ *
+ * Here we'll list all the structure versions in case they evolve over time,
+ * however using a define, we'll make sure to use the last version as the
+ * public name for the module to use. */
+
+#define REDISMODULE_CLIENTINFO_VERSION 1
+typedef struct RedisModuleClientInfo {
+ uint64_t version; /* Version of this structure for ABI compat. */
+ uint64_t flags; /* REDISMODULE_CLIENTINFO_FLAG_* */
+ uint64_t id; /* Client ID. */
+ char addr[46]; /* IPv4 or IPv6 address. */
+ uint16_t port; /* TCP port. */
+ uint16_t db; /* Selected DB. */
+} RedisModuleClientInfoV1;
+
+#define RedisModuleClientInfo RedisModuleClientInfoV1
+
+#define REDISMODULE_FLUSHINFO_VERSION 1
+typedef struct RedisModuleFlushInfo {
+ uint64_t version; /* Not used since this structure is never passed
+ from the module to the core right now. Here
+ for future compatibility. */
+ int32_t sync; /* Synchronous or threaded flush?. */
+ int32_t dbnum; /* Flushed database number, -1 for ALL. */
+} RedisModuleFlushInfoV1;
+
+#define RedisModuleFlushInfo RedisModuleFlushInfoV1
+
/* ------------------------- End of common defines ------------------------ */
#ifndef REDISMODULE_CORE
@@ -121,18 +303,31 @@ typedef struct RedisModuleIO RedisModuleIO;
typedef struct RedisModuleType RedisModuleType;
typedef struct RedisModuleDigest RedisModuleDigest;
typedef struct RedisModuleBlockedClient RedisModuleBlockedClient;
-
-typedef int (*RedisModuleCmdFunc) (RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
-
-typedef int (*RedisModuleNotificationFunc) (RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
+typedef struct RedisModuleClusterInfo RedisModuleClusterInfo;
+typedef struct RedisModuleDict RedisModuleDict;
+typedef struct RedisModuleDictIter RedisModuleDictIter;
+typedef struct RedisModuleCommandFilterCtx RedisModuleCommandFilterCtx;
+typedef struct RedisModuleCommandFilter RedisModuleCommandFilter;
+typedef struct RedisModuleInfoCtx RedisModuleInfoCtx;
+
+typedef int (*RedisModuleCmdFunc)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
+typedef void (*RedisModuleDisconnectFunc)(RedisModuleCtx *ctx, RedisModuleBlockedClient *bc);
+typedef int (*RedisModuleNotificationFunc)(RedisModuleCtx *ctx, int type, const char *event, RedisModuleString *key);
typedef void *(*RedisModuleTypeLoadFunc)(RedisModuleIO *rdb, int encver);
typedef void (*RedisModuleTypeSaveFunc)(RedisModuleIO *rdb, void *value);
+typedef int (*RedisModuleTypeAuxLoadFunc)(RedisModuleIO *rdb, int encver, int when);
+typedef void (*RedisModuleTypeAuxSaveFunc)(RedisModuleIO *rdb, int when);
typedef void (*RedisModuleTypeRewriteFunc)(RedisModuleIO *aof, RedisModuleString *key, void *value);
typedef size_t (*RedisModuleTypeMemUsageFunc)(const void *value);
typedef void (*RedisModuleTypeDigestFunc)(RedisModuleDigest *digest, void *value);
typedef void (*RedisModuleTypeFreeFunc)(void *value);
+typedef void (*RedisModuleClusterMessageReceiver)(RedisModuleCtx *ctx, const char *sender_id, uint8_t type, const unsigned char *payload, uint32_t len);
+typedef void (*RedisModuleTimerProc)(RedisModuleCtx *ctx, void *data);
+typedef void (*RedisModuleCommandFilterFunc) (RedisModuleCommandFilterCtx *filter);
+typedef void (*RedisModuleForkDoneHandler) (int exitcode, int bysignal, void *user_data);
+typedef void (*RedisModuleInfoFunc)(RedisModuleInfoCtx *ctx, int for_crash_report);
-#define REDISMODULE_TYPE_METHOD_VERSION 1
+#define REDISMODULE_TYPE_METHOD_VERSION 2
typedef struct RedisModuleTypeMethods {
uint64_t version;
RedisModuleTypeLoadFunc rdb_load;
@@ -141,6 +336,9 @@ typedef struct RedisModuleTypeMethods {
RedisModuleTypeMemUsageFunc mem_usage;
RedisModuleTypeDigestFunc digest;
RedisModuleTypeFreeFunc free;
+ RedisModuleTypeAuxLoadFunc aux_load;
+ RedisModuleTypeAuxSaveFunc aux_save;
+ int aux_save_triggers;
} RedisModuleTypeMethods;
#define REDISMODULE_GET_API(name) \
@@ -186,6 +384,7 @@ int REDISMODULE_API_FUNC(RedisModule_ReplyWithSimpleString)(RedisModuleCtx *ctx,
int REDISMODULE_API_FUNC(RedisModule_ReplyWithArray)(RedisModuleCtx *ctx, long len);
void REDISMODULE_API_FUNC(RedisModule_ReplySetArrayLength)(RedisModuleCtx *ctx, long len);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithStringBuffer)(RedisModuleCtx *ctx, const char *buf, size_t len);
+int REDISMODULE_API_FUNC(RedisModule_ReplyWithCString)(RedisModuleCtx *ctx, const char *buf);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithNull)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_ReplyWithDouble)(RedisModuleCtx *ctx, double d);
@@ -222,12 +421,15 @@ int REDISMODULE_API_FUNC(RedisModule_HashGet)(RedisModuleKey *key, int flags, ..
int REDISMODULE_API_FUNC(RedisModule_IsKeysPositionRequest)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_KeyAtPos)(RedisModuleCtx *ctx, int pos);
unsigned long long REDISMODULE_API_FUNC(RedisModule_GetClientId)(RedisModuleCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_GetClientInfoById)(void *ci, uint64_t id);
int REDISMODULE_API_FUNC(RedisModule_GetContextFlags)(RedisModuleCtx *ctx);
void *REDISMODULE_API_FUNC(RedisModule_PoolAlloc)(RedisModuleCtx *ctx, size_t bytes);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_CreateDataType)(RedisModuleCtx *ctx, const char *name, int encver, RedisModuleTypeMethods *typemethods);
int REDISMODULE_API_FUNC(RedisModule_ModuleTypeSetValue)(RedisModuleKey *key, RedisModuleType *mt, void *value);
RedisModuleType *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetType)(RedisModuleKey *key);
void *REDISMODULE_API_FUNC(RedisModule_ModuleTypeGetValue)(RedisModuleKey *key);
+int REDISMODULE_API_FUNC(RedisModule_IsIOError)(RedisModuleIO *io);
+void REDISMODULE_API_FUNC(RedisModule_SetModuleOptions)(RedisModuleCtx *ctx, int options);
void REDISMODULE_API_FUNC(RedisModule_SaveUnsigned)(RedisModuleIO *io, uint64_t value);
uint64_t REDISMODULE_API_FUNC(RedisModule_LoadUnsigned)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_SaveSigned)(RedisModuleIO *io, int64_t value);
@@ -243,31 +445,95 @@ void REDISMODULE_API_FUNC(RedisModule_SaveFloat)(RedisModuleIO *io, float value)
float REDISMODULE_API_FUNC(RedisModule_LoadFloat)(RedisModuleIO *io);
void REDISMODULE_API_FUNC(RedisModule_Log)(RedisModuleCtx *ctx, const char *level, const char *fmt, ...);
void REDISMODULE_API_FUNC(RedisModule_LogIOError)(RedisModuleIO *io, const char *levelstr, const char *fmt, ...);
+void REDISMODULE_API_FUNC(RedisModule__Assert)(const char *estr, const char *file, int line);
int REDISMODULE_API_FUNC(RedisModule_StringAppendBuffer)(RedisModuleCtx *ctx, RedisModuleString *str, const char *buf, size_t len);
void REDISMODULE_API_FUNC(RedisModule_RetainString)(RedisModuleCtx *ctx, RedisModuleString *str);
int REDISMODULE_API_FUNC(RedisModule_StringCompare)(RedisModuleString *a, RedisModuleString *b);
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetContextFromIO)(RedisModuleIO *io);
+const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_GetKeyNameFromIO)(RedisModuleIO *io);
long long REDISMODULE_API_FUNC(RedisModule_Milliseconds)(void);
void REDISMODULE_API_FUNC(RedisModule_DigestAddStringBuffer)(RedisModuleDigest *md, unsigned char *ele, size_t len);
void REDISMODULE_API_FUNC(RedisModule_DigestAddLongLong)(RedisModuleDigest *md, long long ele);
void REDISMODULE_API_FUNC(RedisModule_DigestEndSequence)(RedisModuleDigest *md);
+RedisModuleDict *REDISMODULE_API_FUNC(RedisModule_CreateDict)(RedisModuleCtx *ctx);
+void REDISMODULE_API_FUNC(RedisModule_FreeDict)(RedisModuleCtx *ctx, RedisModuleDict *d);
+uint64_t REDISMODULE_API_FUNC(RedisModule_DictSize)(RedisModuleDict *d);
+int REDISMODULE_API_FUNC(RedisModule_DictSetC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
+int REDISMODULE_API_FUNC(RedisModule_DictReplaceC)(RedisModuleDict *d, void *key, size_t keylen, void *ptr);
+int REDISMODULE_API_FUNC(RedisModule_DictSet)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
+int REDISMODULE_API_FUNC(RedisModule_DictReplace)(RedisModuleDict *d, RedisModuleString *key, void *ptr);
+void *REDISMODULE_API_FUNC(RedisModule_DictGetC)(RedisModuleDict *d, void *key, size_t keylen, int *nokey);
+void *REDISMODULE_API_FUNC(RedisModule_DictGet)(RedisModuleDict *d, RedisModuleString *key, int *nokey);
+int REDISMODULE_API_FUNC(RedisModule_DictDelC)(RedisModuleDict *d, void *key, size_t keylen, void *oldval);
+int REDISMODULE_API_FUNC(RedisModule_DictDel)(RedisModuleDict *d, RedisModuleString *key, void *oldval);
+RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStartC)(RedisModuleDict *d, const char *op, void *key, size_t keylen);
+RedisModuleDictIter *REDISMODULE_API_FUNC(RedisModule_DictIteratorStart)(RedisModuleDict *d, const char *op, RedisModuleString *key);
+void REDISMODULE_API_FUNC(RedisModule_DictIteratorStop)(RedisModuleDictIter *di);
+int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseekC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
+int REDISMODULE_API_FUNC(RedisModule_DictIteratorReseek)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
+void *REDISMODULE_API_FUNC(RedisModule_DictNextC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
+void *REDISMODULE_API_FUNC(RedisModule_DictPrevC)(RedisModuleDictIter *di, size_t *keylen, void **dataptr);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictNext)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
+RedisModuleString *REDISMODULE_API_FUNC(RedisModule_DictPrev)(RedisModuleCtx *ctx, RedisModuleDictIter *di, void **dataptr);
+int REDISMODULE_API_FUNC(RedisModule_DictCompareC)(RedisModuleDictIter *di, const char *op, void *key, size_t keylen);
+int REDISMODULE_API_FUNC(RedisModule_DictCompare)(RedisModuleDictIter *di, const char *op, RedisModuleString *key);
+int REDISMODULE_API_FUNC(RedisModule_RegisterInfoFunc)(RedisModuleCtx *ctx, RedisModuleInfoFunc cb);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddSection)(RedisModuleInfoCtx *ctx, char *name);
+int REDISMODULE_API_FUNC(RedisModule_InfoBeginDictField)(RedisModuleInfoCtx *ctx, char *name);
+int REDISMODULE_API_FUNC(RedisModule_InfoEndDictField)(RedisModuleInfoCtx *ctx);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldString)(RedisModuleInfoCtx *ctx, char *field, RedisModuleString *value);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldCString)(RedisModuleInfoCtx *ctx, char *field, char *value);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldDouble)(RedisModuleInfoCtx *ctx, char *field, double value);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldLongLong)(RedisModuleInfoCtx *ctx, char *field, long long value);
+int REDISMODULE_API_FUNC(RedisModule_InfoAddFieldULongLong)(RedisModuleInfoCtx *ctx, char *field, unsigned long long value);
+int REDISMODULE_API_FUNC(RedisModule_SubscribeToServerEvent)(RedisModuleCtx *ctx, RedisModuleEvent event, RedisModuleEventCallback callback);
/* Experimental APIs */
#ifdef REDISMODULE_EXPERIMENTAL_API
-RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(void*), long long timeout_ms);
+#define REDISMODULE_EXPERIMENTAL_API_VERSION 3
+RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_BlockClient)(RedisModuleCtx *ctx, RedisModuleCmdFunc reply_callback, RedisModuleCmdFunc timeout_callback, void (*free_privdata)(RedisModuleCtx*,void*), long long timeout_ms);
int REDISMODULE_API_FUNC(RedisModule_UnblockClient)(RedisModuleBlockedClient *bc, void *privdata);
int REDISMODULE_API_FUNC(RedisModule_IsBlockedReplyRequest)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_IsBlockedTimeoutRequest)(RedisModuleCtx *ctx);
void *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientPrivateData)(RedisModuleCtx *ctx);
+RedisModuleBlockedClient *REDISMODULE_API_FUNC(RedisModule_GetBlockedClientHandle)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_AbortBlock)(RedisModuleBlockedClient *bc);
RedisModuleCtx *REDISMODULE_API_FUNC(RedisModule_GetThreadSafeContext)(RedisModuleBlockedClient *bc);
void REDISMODULE_API_FUNC(RedisModule_FreeThreadSafeContext)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextLock)(RedisModuleCtx *ctx);
void REDISMODULE_API_FUNC(RedisModule_ThreadSafeContextUnlock)(RedisModuleCtx *ctx);
int REDISMODULE_API_FUNC(RedisModule_SubscribeToKeyspaceEvents)(RedisModuleCtx *ctx, int types, RedisModuleNotificationFunc cb);
-
+int REDISMODULE_API_FUNC(RedisModule_BlockedClientDisconnected)(RedisModuleCtx *ctx);
+void REDISMODULE_API_FUNC(RedisModule_RegisterClusterMessageReceiver)(RedisModuleCtx *ctx, uint8_t type, RedisModuleClusterMessageReceiver callback);
+int REDISMODULE_API_FUNC(RedisModule_SendClusterMessage)(RedisModuleCtx *ctx, char *target_id, uint8_t type, unsigned char *msg, uint32_t len);
+int REDISMODULE_API_FUNC(RedisModule_GetClusterNodeInfo)(RedisModuleCtx *ctx, const char *id, char *ip, char *master_id, int *port, int *flags);
+char **REDISMODULE_API_FUNC(RedisModule_GetClusterNodesList)(RedisModuleCtx *ctx, size_t *numnodes);
+void REDISMODULE_API_FUNC(RedisModule_FreeClusterNodesList)(char **ids);
+RedisModuleTimerID REDISMODULE_API_FUNC(RedisModule_CreateTimer)(RedisModuleCtx *ctx, mstime_t period, RedisModuleTimerProc callback, void *data);
+int REDISMODULE_API_FUNC(RedisModule_StopTimer)(RedisModuleCtx *ctx, RedisModuleTimerID id, void **data);
+int REDISMODULE_API_FUNC(RedisModule_GetTimerInfo)(RedisModuleCtx *ctx, RedisModuleTimerID id, uint64_t *remaining, void **data);
+const char *REDISMODULE_API_FUNC(RedisModule_GetMyClusterID)(void);
+size_t REDISMODULE_API_FUNC(RedisModule_GetClusterSize)(void);
+void REDISMODULE_API_FUNC(RedisModule_GetRandomBytes)(unsigned char *dst, size_t len);
+void REDISMODULE_API_FUNC(RedisModule_GetRandomHexChars)(char *dst, size_t len);
+void REDISMODULE_API_FUNC(RedisModule_SetDisconnectCallback)(RedisModuleBlockedClient *bc, RedisModuleDisconnectFunc callback);
+void REDISMODULE_API_FUNC(RedisModule_SetClusterFlags)(RedisModuleCtx *ctx, uint64_t flags);
+int REDISMODULE_API_FUNC(RedisModule_ExportSharedAPI)(RedisModuleCtx *ctx, const char *apiname, void *func);
+void *REDISMODULE_API_FUNC(RedisModule_GetSharedAPI)(RedisModuleCtx *ctx, const char *apiname);
+RedisModuleCommandFilter *REDISMODULE_API_FUNC(RedisModule_RegisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilterFunc cb, int flags);
+int REDISMODULE_API_FUNC(RedisModule_UnregisterCommandFilter)(RedisModuleCtx *ctx, RedisModuleCommandFilter *filter);
+int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgsCount)(RedisModuleCommandFilterCtx *fctx);
+const RedisModuleString *REDISMODULE_API_FUNC(RedisModule_CommandFilterArgGet)(RedisModuleCommandFilterCtx *fctx, int pos);
+int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgInsert)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
+int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgReplace)(RedisModuleCommandFilterCtx *fctx, int pos, RedisModuleString *arg);
+int REDISMODULE_API_FUNC(RedisModule_CommandFilterArgDelete)(RedisModuleCommandFilterCtx *fctx, int pos);
+int REDISMODULE_API_FUNC(RedisModule_Fork)(RedisModuleForkDoneHandler cb, void *user_data);
+int REDISMODULE_API_FUNC(RedisModule_ExitFromChild)(int retcode);
+int REDISMODULE_API_FUNC(RedisModule_KillForkChild)(int child_pid);
#endif
+#define RedisModule_IsAOFClient(id) ((id) == UINT64_MAX)
+
/* This is included inline inside each Redis module. */
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) __attribute__((unused));
static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int apiver) {
@@ -288,6 +554,7 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ReplyWithArray);
REDISMODULE_GET_API(ReplySetArrayLength);
REDISMODULE_GET_API(ReplyWithStringBuffer);
+ REDISMODULE_GET_API(ReplyWithCString);
REDISMODULE_GET_API(ReplyWithString);
REDISMODULE_GET_API(ReplyWithNull);
REDISMODULE_GET_API(ReplyWithCallReply);
@@ -352,6 +619,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(ModuleTypeSetValue);
REDISMODULE_GET_API(ModuleTypeGetType);
REDISMODULE_GET_API(ModuleTypeGetValue);
+ REDISMODULE_GET_API(IsIOError);
+ REDISMODULE_GET_API(SetModuleOptions);
REDISMODULE_GET_API(SaveUnsigned);
REDISMODULE_GET_API(LoadUnsigned);
REDISMODULE_GET_API(SaveSigned);
@@ -367,14 +636,49 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(EmitAOF);
REDISMODULE_GET_API(Log);
REDISMODULE_GET_API(LogIOError);
+ REDISMODULE_GET_API(_Assert);
REDISMODULE_GET_API(StringAppendBuffer);
REDISMODULE_GET_API(RetainString);
REDISMODULE_GET_API(StringCompare);
REDISMODULE_GET_API(GetContextFromIO);
+ REDISMODULE_GET_API(GetKeyNameFromIO);
REDISMODULE_GET_API(Milliseconds);
REDISMODULE_GET_API(DigestAddStringBuffer);
REDISMODULE_GET_API(DigestAddLongLong);
REDISMODULE_GET_API(DigestEndSequence);
+ REDISMODULE_GET_API(CreateDict);
+ REDISMODULE_GET_API(FreeDict);
+ REDISMODULE_GET_API(DictSize);
+ REDISMODULE_GET_API(DictSetC);
+ REDISMODULE_GET_API(DictReplaceC);
+ REDISMODULE_GET_API(DictSet);
+ REDISMODULE_GET_API(DictReplace);
+ REDISMODULE_GET_API(DictGetC);
+ REDISMODULE_GET_API(DictGet);
+ REDISMODULE_GET_API(DictDelC);
+ REDISMODULE_GET_API(DictDel);
+ REDISMODULE_GET_API(DictIteratorStartC);
+ REDISMODULE_GET_API(DictIteratorStart);
+ REDISMODULE_GET_API(DictIteratorStop);
+ REDISMODULE_GET_API(DictIteratorReseekC);
+ REDISMODULE_GET_API(DictIteratorReseek);
+ REDISMODULE_GET_API(DictNextC);
+ REDISMODULE_GET_API(DictPrevC);
+ REDISMODULE_GET_API(DictNext);
+ REDISMODULE_GET_API(DictPrev);
+ REDISMODULE_GET_API(DictCompare);
+ REDISMODULE_GET_API(DictCompareC);
+ REDISMODULE_GET_API(RegisterInfoFunc);
+ REDISMODULE_GET_API(InfoAddSection);
+ REDISMODULE_GET_API(InfoBeginDictField);
+ REDISMODULE_GET_API(InfoEndDictField);
+ REDISMODULE_GET_API(InfoAddFieldString);
+ REDISMODULE_GET_API(InfoAddFieldCString);
+ REDISMODULE_GET_API(InfoAddFieldDouble);
+ REDISMODULE_GET_API(InfoAddFieldLongLong);
+ REDISMODULE_GET_API(InfoAddFieldULongLong);
+ REDISMODULE_GET_API(GetClientInfoById);
+ REDISMODULE_GET_API(SubscribeToServerEvent);
#ifdef REDISMODULE_EXPERIMENTAL_API
REDISMODULE_GET_API(GetThreadSafeContext);
@@ -386,9 +690,36 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
REDISMODULE_GET_API(IsBlockedReplyRequest);
REDISMODULE_GET_API(IsBlockedTimeoutRequest);
REDISMODULE_GET_API(GetBlockedClientPrivateData);
+ REDISMODULE_GET_API(GetBlockedClientHandle);
REDISMODULE_GET_API(AbortBlock);
+ REDISMODULE_GET_API(SetDisconnectCallback);
REDISMODULE_GET_API(SubscribeToKeyspaceEvents);
-
+ REDISMODULE_GET_API(BlockedClientDisconnected);
+ REDISMODULE_GET_API(RegisterClusterMessageReceiver);
+ REDISMODULE_GET_API(SendClusterMessage);
+ REDISMODULE_GET_API(GetClusterNodeInfo);
+ REDISMODULE_GET_API(GetClusterNodesList);
+ REDISMODULE_GET_API(FreeClusterNodesList);
+ REDISMODULE_GET_API(CreateTimer);
+ REDISMODULE_GET_API(StopTimer);
+ REDISMODULE_GET_API(GetTimerInfo);
+ REDISMODULE_GET_API(GetMyClusterID);
+ REDISMODULE_GET_API(GetClusterSize);
+ REDISMODULE_GET_API(GetRandomBytes);
+ REDISMODULE_GET_API(GetRandomHexChars);
+ REDISMODULE_GET_API(SetClusterFlags);
+ REDISMODULE_GET_API(ExportSharedAPI);
+ REDISMODULE_GET_API(GetSharedAPI);
+ REDISMODULE_GET_API(RegisterCommandFilter);
+ REDISMODULE_GET_API(UnregisterCommandFilter);
+ REDISMODULE_GET_API(CommandFilterArgsCount);
+ REDISMODULE_GET_API(CommandFilterArgGet);
+ REDISMODULE_GET_API(CommandFilterArgInsert);
+ REDISMODULE_GET_API(CommandFilterArgReplace);
+ REDISMODULE_GET_API(CommandFilterArgDelete);
+ REDISMODULE_GET_API(Fork);
+ REDISMODULE_GET_API(ExitFromChild);
+ REDISMODULE_GET_API(KillForkChild);
#endif
if (RedisModule_IsModuleNameBusy && RedisModule_IsModuleNameBusy(name)) return REDISMODULE_ERR;
@@ -396,6 +727,8 @@ static int RedisModule_Init(RedisModuleCtx *ctx, const char *name, int ver, int
return REDISMODULE_OK;
}
+#define RedisModule_Assert(_e) ((_e)?(void)0 : (RedisModule__Assert(#_e,__FILE__,__LINE__),exit(1)))
+
#else
/* Things only defined for the modules core, not exported to modules
diff --git a/src/release.c b/src/release.c
index 4e59c7474..e0bd018fc 100644
--- a/src/release.c
+++ b/src/release.c
@@ -32,6 +32,7 @@
* files using this functions. */
#include <string.h>
+#include <stdio.h>
#include "release.h"
#include "version.h"
@@ -50,3 +51,16 @@ uint64_t redisBuildId(void) {
return crc64(0,(unsigned char*)buildid,strlen(buildid));
}
+
+/* Return a cached value of the build string in order to avoid recomputing
+ * and converting it in hex every time: this string is shown in the INFO
+ * output that should be fast. */
+char *redisBuildIdString(void) {
+ static char buf[32];
+ static int cached = 0;
+ if (!cached) {
+ snprintf(buf,sizeof(buf),"%llx",(unsigned long long) redisBuildId());
+ cached = 1;
+ }
+ return buf;
+}
diff --git a/src/replication.c b/src/replication.c
index c5c3618a5..4550e6a83 100644
--- a/src/replication.c
+++ b/src/replication.c
@@ -30,6 +30,7 @@
#include "server.h"
+#include "cluster.h"
#include <sys/time.h>
#include <unistd.h>
@@ -38,7 +39,7 @@
#include <sys/stat.h>
void replicationDiscardCachedMaster(void);
-void replicationResurrectCachedMaster(int newfd);
+void replicationResurrectCachedMaster(connection *conn);
void replicationSendAck(void);
void putSlaveOnline(client *slave);
int cancelReplicationHandshake(void);
@@ -48,7 +49,7 @@ int cancelReplicationHandshake(void);
/* Return the pointer to a string representing the slave ip:listening_port
* pair. Mostly useful for logging, since we want to log a slave using its
* IP address and its listening port which is more clear for the user, for
- * example: "Closing connection with slave 10.1.2.3:6380". */
+ * example: "Closing connection with replica 10.1.2.3:6380". */
char *replicationGetSlaveName(client *c) {
static char buf[NET_PEER_ID_LEN];
char ip[NET_IP_STR_LEN];
@@ -56,7 +57,7 @@ char *replicationGetSlaveName(client *c) {
ip[0] = '\0';
buf[0] = '\0';
if (c->slave_ip[0] != '\0' ||
- anetPeerToString(c->fd,ip,sizeof(ip),NULL) != -1)
+ connPeerToString(c->conn,ip,sizeof(ip),NULL) != -1)
{
/* Note that the 'ip' buffer is always larger than 'c->slave_ip' */
if (c->slave_ip[0] != '\0') memcpy(ip,c->slave_ip,sizeof(c->slave_ip));
@@ -64,7 +65,7 @@ char *replicationGetSlaveName(client *c) {
if (c->slave_listening_port)
anetFormatAddr(buf,sizeof(buf),ip,c->slave_listening_port);
else
- snprintf(buf,sizeof(buf),"%s:<unknown-slave-port>",ip);
+ snprintf(buf,sizeof(buf),"%s:<unknown-replica-port>",ip);
} else {
snprintf(buf,sizeof(buf),"client id #%llu",
(unsigned long long) c->id);
@@ -263,7 +264,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
* or are already in sync with the master. */
/* Add the multi bulk length. */
- addReplyMultiBulkLen(slave,argc);
+ addReplyArrayLen(slave,argc);
/* Finally any additional argument that was not stored inside the
* static buffer if any (from j to argc). */
@@ -296,7 +297,7 @@ void replicationFeedSlavesFromMasterStream(list *slaves, char *buf, size_t bufle
/* Don't feed slaves that are still waiting for BGSAVE to start. */
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) continue;
- addReplyString(slave,buf,buflen);
+ addReplyProto(slave,buf,buflen);
}
}
@@ -344,7 +345,7 @@ void replicationFeedMonitors(client *c, list *monitors, int dictid, robj **argv,
long long addReplyReplicationBacklog(client *c, long long offset) {
long long j, skip, len;
- serverLog(LL_DEBUG, "[PSYNC] Slave request offset: %lld", offset);
+ serverLog(LL_DEBUG, "[PSYNC] Replica request offset: %lld", offset);
if (server.repl_backlog_histlen == 0) {
serverLog(LL_DEBUG, "[PSYNC] Backlog history len is zero");
@@ -431,7 +432,7 @@ int replicationSetupSlaveForFullResync(client *slave, long long offset) {
if (!(slave->flags & CLIENT_PRE_PSYNC)) {
buflen = snprintf(buf,sizeof(buf),"+FULLRESYNC %s %lld\r\n",
server.replid,offset);
- if (write(slave->fd,buf,buflen) != buflen) {
+ if (connWrite(slave->conn,buf,buflen) != buflen) {
freeClientAsync(slave);
return C_ERR;
}
@@ -472,7 +473,7 @@ int masterTryPartialResynchronization(client *c) {
strcasecmp(master_replid, server.replid2))
{
serverLog(LL_NOTICE,"Partial resynchronization not accepted: "
- "Replication ID mismatch (Slave asked for '%s', my "
+ "Replication ID mismatch (Replica asked for '%s', my "
"replication IDs are '%s' and '%s')",
master_replid, server.replid, server.replid2);
} else {
@@ -481,7 +482,7 @@ int masterTryPartialResynchronization(client *c) {
"up to %lld", psync_offset, server.second_replid_offset);
}
} else {
- serverLog(LL_NOTICE,"Full resync requested by slave %s",
+ serverLog(LL_NOTICE,"Full resync requested by replica %s",
replicationGetSlaveName(c));
}
goto need_full_resync;
@@ -493,10 +494,10 @@ int masterTryPartialResynchronization(client *c) {
psync_offset > (server.repl_backlog_off + server.repl_backlog_histlen))
{
serverLog(LL_NOTICE,
- "Unable to partial resync with slave %s for lack of backlog (Slave request was: %lld).", replicationGetSlaveName(c), psync_offset);
+ "Unable to partial resync with replica %s for lack of backlog (Replica request was: %lld).", replicationGetSlaveName(c), psync_offset);
if (psync_offset > server.master_repl_offset) {
serverLog(LL_WARNING,
- "Warning: slave %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
+ "Warning: replica %s tried to PSYNC with an offset that is greater than the master replication offset.", replicationGetSlaveName(c));
}
goto need_full_resync;
}
@@ -518,7 +519,7 @@ int masterTryPartialResynchronization(client *c) {
} else {
buflen = snprintf(buf,sizeof(buf),"+CONTINUE\r\n");
}
- if (write(c->fd,buf,buflen) != buflen) {
+ if (connWrite(c->conn,buf,buflen) != buflen) {
freeClientAsync(c);
return C_OK;
}
@@ -553,7 +554,7 @@ need_full_resync:
* Side effects, other than starting a BGSAVE:
*
* 1) Handle the slaves in WAIT_START state, by preparing them for a full
- * sync if the BGSAVE was succesfully started, or sending them an error
+ * sync if the BGSAVE was successfully started, or sending them an error
* and dropping them from the list of slaves.
*
* 2) Flush the Lua scripting script cache if the BGSAVE was actually
@@ -567,7 +568,7 @@ int startBgsaveForReplication(int mincapa) {
listNode *ln;
serverLog(LL_NOTICE,"Starting BGSAVE for SYNC with target: %s",
- socket_target ? "slaves sockets" : "disk");
+ socket_target ? "replicas sockets" : "disk");
rdbSaveInfo rsi, *rsiptr;
rsiptr = rdbPopulateSaveInfo(&rsi);
@@ -593,6 +594,7 @@ int startBgsaveForReplication(int mincapa) {
client *slave = ln->value;
if (slave->replstate == SLAVE_STATE_WAIT_BGSAVE_START) {
+ slave->replstate = REPL_STATE_NONE;
slave->flags &= ~CLIENT_SLAVE;
listDelNode(server.slaves,ln);
addReplyError(slave,
@@ -644,7 +646,7 @@ void syncCommand(client *c) {
return;
}
- serverLog(LL_NOTICE,"Slave %s asks for synchronization",
+ serverLog(LL_NOTICE,"Replica %s asks for synchronization",
replicationGetSlaveName(c));
/* Try a partial resynchronization if this is a PSYNC command.
@@ -683,7 +685,7 @@ void syncCommand(client *c) {
* paths will change the state if we handle the slave differently. */
c->replstate = SLAVE_STATE_WAIT_BGSAVE_START;
if (server.repl_disable_tcp_nodelay)
- anetDisableTcpNoDelay(NULL, c->fd); /* Non critical if it fails. */
+ connDisableTcpNoDelay(c->conn); /* Non critical if it fails. */
c->repldbfd = -1;
c->flags |= CLIENT_SLAVE;
listAddNodeTail(server.slaves,c);
@@ -725,7 +727,7 @@ void syncCommand(client *c) {
} else {
/* No way, we need to wait for the next BGSAVE in order to
* register differences. */
- serverLog(LL_NOTICE,"Can't attach the slave to the current BGSAVE. Waiting for next BGSAVE for SYNC");
+ serverLog(LL_NOTICE,"Can't attach the replica to the current BGSAVE. Waiting for next BGSAVE for SYNC");
}
/* CASE 2: BGSAVE is in progress, with socket target. */
@@ -749,11 +751,11 @@ void syncCommand(client *c) {
/* Target is disk (or the slave is not capable of supporting
* diskless replication) and we don't have a BGSAVE in progress,
* let's start one. */
- if (server.aof_child_pid == -1) {
+ if (!hasActiveChildProcess()) {
startBgsaveForReplication(c->slave_capa);
} else {
serverLog(LL_NOTICE,
- "No BGSAVE in progress, but an AOF rewrite is active. "
+ "No BGSAVE in progress, but another BG operation is active. "
"BGSAVE for replication delayed");
}
}
@@ -798,7 +800,7 @@ void replconfCommand(client *c) {
memcpy(c->slave_ip,ip,sdslen(ip)+1);
} else {
addReplyErrorFormat(c,"REPLCONF ip-address provided by "
- "slave instance is too long: %zd bytes", sdslen(ip));
+ "replica instance is too long: %zd bytes", sdslen(ip));
return;
}
} else if (!strcasecmp(c->argv[j]->ptr,"capa")) {
@@ -821,7 +823,9 @@ void replconfCommand(client *c) {
c->repl_ack_time = server.unixtime;
/* If this was a diskless replication, we need to really put
* the slave online when the first ACK is received (which
- * confirms slave is online and ready to get more data). */
+ * confirms slave is online and ready to get more data). This
+ * allows for simpler and less CPU intensive EOF detection
+ * when streaming RDB files. */
if (c->repl_put_online_on_ack && c->replstate == SLAVE_STATE_ONLINE)
putSlaveOnline(c);
/* Note: this command does not reply anything! */
@@ -840,37 +844,36 @@ void replconfCommand(client *c) {
addReply(c,shared.ok);
}
-/* This function puts a slave in the online state, and should be called just
- * after a slave received the RDB file for the initial synchronization, and
+/* This function puts a replica in the online state, and should be called just
+ * after a replica received the RDB file for the initial synchronization, and
* we are finally ready to send the incremental stream of commands.
*
* It does a few things:
*
- * 1) Put the slave in ONLINE state (useless when the function is called
- * because state is already ONLINE but repl_put_online_on_ack is true).
+ * 1) Put the slave in ONLINE state. Note that the function may also be called
+ * for a replicas that are already in ONLINE state, but having the flag
+ * repl_put_online_on_ack set to true: we still have to install the write
+ * handler in that case. This function will take care of that.
* 2) Make sure the writable event is re-installed, since calling the SYNC
* command disables it, so that we can accumulate output buffer without
- * sending it to the slave.
- * 3) Update the count of good slaves. */
+ * sending it to the replica.
+ * 3) Update the count of "good replicas". */
void putSlaveOnline(client *slave) {
slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 0;
slave->repl_ack_time = server.unixtime; /* Prevent false timeout. */
- if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE,
- sendReplyToClient, slave) == AE_ERR) {
- serverLog(LL_WARNING,"Unable to register writable event for slave bulk transfer: %s", strerror(errno));
+ if (connSetWriteHandler(slave->conn, sendReplyToClient) == C_ERR) {
+ serverLog(LL_WARNING,"Unable to register writable event for replica bulk transfer: %s", strerror(errno));
freeClient(slave);
return;
}
refreshGoodSlavesCount();
- serverLog(LL_NOTICE,"Synchronization with slave %s succeeded",
+ serverLog(LL_NOTICE,"Synchronization with replica %s succeeded",
replicationGetSlaveName(slave));
}
-void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
- client *slave = privdata;
- UNUSED(el);
- UNUSED(mask);
+void sendBulkToSlave(connection *conn) {
+ client *slave = connGetPrivateData(conn);
char buf[PROTO_IOBUF_LEN];
ssize_t nwritten, buflen;
@@ -878,10 +881,10 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
* replication process. Currently the preamble is just the bulk count of
* the file in the form "$<length>\r\n". */
if (slave->replpreamble) {
- nwritten = write(fd,slave->replpreamble,sdslen(slave->replpreamble));
+ nwritten = connWrite(conn,slave->replpreamble,sdslen(slave->replpreamble));
if (nwritten == -1) {
- serverLog(LL_VERBOSE,"Write error sending RDB preamble to slave: %s",
- strerror(errno));
+ serverLog(LL_VERBOSE,"Write error sending RDB preamble to replica: %s",
+ connGetLastError(conn));
freeClient(slave);
return;
}
@@ -896,19 +899,19 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
}
}
- /* If the preamble was already transfered, send the RDB bulk data. */
+ /* If the preamble was already transferred, send the RDB bulk data. */
lseek(slave->repldbfd,slave->repldboff,SEEK_SET);
buflen = read(slave->repldbfd,buf,PROTO_IOBUF_LEN);
if (buflen <= 0) {
- serverLog(LL_WARNING,"Read error sending DB to slave: %s",
+ serverLog(LL_WARNING,"Read error sending DB to replica: %s",
(buflen == 0) ? "premature EOF" : strerror(errno));
freeClient(slave);
return;
}
- if ((nwritten = write(fd,buf,buflen)) == -1) {
- if (errno != EAGAIN) {
- serverLog(LL_WARNING,"Write error sending DB to slave: %s",
- strerror(errno));
+ if ((nwritten = connWrite(conn,buf,buflen)) == -1) {
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_WARNING,"Write error sending DB to replica: %s",
+ connGetLastError(conn));
freeClient(slave);
}
return;
@@ -918,11 +921,157 @@ void sendBulkToSlave(aeEventLoop *el, int fd, void *privdata, int mask) {
if (slave->repldboff == slave->repldbsize) {
close(slave->repldbfd);
slave->repldbfd = -1;
- aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
+ connSetWriteHandler(slave->conn,NULL);
putSlaveOnline(slave);
}
}
+/* Remove one write handler from the list of connections waiting to be writable
+ * during rdb pipe transfer. */
+void rdbPipeWriteHandlerConnRemoved(struct connection *conn) {
+ if (!connHasWriteHandler(conn))
+ return;
+ connSetWriteHandler(conn, NULL);
+ server.rdb_pipe_numconns_writing--;
+ /* if there are no more writes for now for this conn, or write error: */
+ if (server.rdb_pipe_numconns_writing == 0) {
+ if (aeCreateFileEvent(server.el, server.rdb_pipe_read, AE_READABLE, rdbPipeReadHandler,NULL) == AE_ERR) {
+ serverPanic("Unrecoverable error creating server.rdb_pipe_read file event.");
+ }
+ }
+}
+
+/* Called in diskless master during transfer of data from the rdb pipe, when
+ * the replica becomes writable again. */
+void rdbPipeWriteHandler(struct connection *conn) {
+ serverAssert(server.rdb_pipe_bufflen>0);
+ client *slave = connGetPrivateData(conn);
+ int nwritten;
+ if ((nwritten = connWrite(conn, server.rdb_pipe_buff + slave->repldboff,
+ server.rdb_pipe_bufflen - slave->repldboff)) == -1)
+ {
+ if (connGetState(conn) == CONN_STATE_CONNECTED)
+ return; /* equivalent to EAGAIN */
+ serverLog(LL_WARNING,"Write error sending DB to replica: %s",
+ connGetLastError(conn));
+ freeClient(slave);
+ return;
+ } else {
+ slave->repldboff += nwritten;
+ server.stat_net_output_bytes += nwritten;
+ if (slave->repldboff < server.rdb_pipe_bufflen)
+ return; /* more data to write.. */
+ }
+ rdbPipeWriteHandlerConnRemoved(conn);
+}
+
+/* When the the pipe serving diskless rdb transfer is drained (write end was
+ * closed), we can clean up all the temporary variables, and cleanup after the
+ * fork child. */
+void RdbPipeCleanup() {
+ close(server.rdb_pipe_read);
+ zfree(server.rdb_pipe_conns);
+ server.rdb_pipe_conns = NULL;
+ server.rdb_pipe_numconns = 0;
+ server.rdb_pipe_numconns_writing = 0;
+ zfree(server.rdb_pipe_buff);
+ server.rdb_pipe_buff = NULL;
+ server.rdb_pipe_bufflen = 0;
+
+ /* Since we're avoiding to detect the child exited as long as the pipe is
+ * not drained, so now is the time to check. */
+ checkChildrenDone();
+}
+
+/* Called in diskless master, when there's data to read from the child's rdb pipe */
+void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask) {
+ UNUSED(mask);
+ UNUSED(clientData);
+ UNUSED(eventLoop);
+ int i;
+ if (!server.rdb_pipe_buff)
+ server.rdb_pipe_buff = zmalloc(PROTO_IOBUF_LEN);
+ serverAssert(server.rdb_pipe_numconns_writing==0);
+
+ while (1) {
+ server.rdb_pipe_bufflen = read(fd, server.rdb_pipe_buff, PROTO_IOBUF_LEN);
+ if (server.rdb_pipe_bufflen < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK)
+ return;
+ serverLog(LL_WARNING,"Diskless rdb transfer, read error sending DB to replicas: %s", strerror(errno));
+ for (i=0; i < server.rdb_pipe_numconns; i++) {
+ connection *conn = server.rdb_pipe_conns[i];
+ if (!conn)
+ continue;
+ client *slave = connGetPrivateData(conn);
+ freeClient(slave);
+ server.rdb_pipe_conns[i] = NULL;
+ }
+ killRDBChild();
+ return;
+ }
+
+ if (server.rdb_pipe_bufflen == 0) {
+ /* EOF - write end was closed. */
+ int stillUp = 0;
+ aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
+ for (i=0; i < server.rdb_pipe_numconns; i++)
+ {
+ connection *conn = server.rdb_pipe_conns[i];
+ if (!conn)
+ continue;
+ stillUp++;
+ }
+ serverLog(LL_WARNING,"Diskless rdb transfer, done reading from pipe, %d replicas still up.", stillUp);
+ RdbPipeCleanup();
+ return;
+ }
+
+ int stillAlive = 0;
+ for (i=0; i < server.rdb_pipe_numconns; i++)
+ {
+ int nwritten;
+ connection *conn = server.rdb_pipe_conns[i];
+ if (!conn)
+ continue;
+
+ client *slave = connGetPrivateData(conn);
+ if ((nwritten = connWrite(conn, server.rdb_pipe_buff, server.rdb_pipe_bufflen)) == -1) {
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
+ serverLog(LL_WARNING,"Diskless rdb transfer, write error sending DB to replica: %s",
+ connGetLastError(conn));
+ freeClient(slave);
+ server.rdb_pipe_conns[i] = NULL;
+ continue;
+ }
+ /* An error and still in connected state, is equivalent to EAGAIN */
+ slave->repldboff = 0;
+ } else {
+ slave->repldboff = nwritten;
+ server.stat_net_output_bytes += nwritten;
+ }
+ /* If we were unable to write all the data to one of the replicas,
+ * setup write handler (and disable pipe read handler, below) */
+ if (nwritten != server.rdb_pipe_bufflen) {
+ server.rdb_pipe_numconns_writing++;
+ connSetWriteHandler(conn, rdbPipeWriteHandler);
+ }
+ stillAlive++;
+ }
+
+ if (stillAlive == 0) {
+ serverLog(LL_WARNING,"Diskless rdb transfer, last replica dropped, killing fork child.");
+ killRDBChild();
+ RdbPipeCleanup();
+ }
+ /* Remove the pipe read handler if at least one write handler was set. */
+ if (server.rdb_pipe_numconns_writing || stillAlive == 0) {
+ aeDeleteFileEvent(server.el, server.rdb_pipe_read, AE_READABLE);
+ break;
+ }
+ }
+}
+
/* This function is called at the end of every background saving,
* or when the replication RDB transfer strategy is modified from
* disk to socket or the other way around.
@@ -961,13 +1110,33 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
* the slave online. */
if (type == RDB_CHILD_TYPE_SOCKET) {
serverLog(LL_NOTICE,
- "Streamed RDB transfer with slave %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
+ "Streamed RDB transfer with replica %s succeeded (socket). Waiting for REPLCONF ACK from slave to enable streaming",
replicationGetSlaveName(slave));
- /* Note: we wait for a REPLCONF ACK message from slave in
+ /* Note: we wait for a REPLCONF ACK message from the replica in
* order to really put it online (install the write handler
- * so that the accumulated data can be transfered). However
+ * so that the accumulated data can be transferred). However
* we change the replication state ASAP, since our slave
- * is technically online now. */
+ * is technically online now.
+ *
+ * So things work like that:
+ *
+ * 1. We end trasnferring the RDB file via socket.
+ * 2. The replica is put ONLINE but the write handler
+ * is not installed.
+ * 3. The replica however goes really online, and pings us
+ * back via REPLCONF ACK commands.
+ * 4. Now we finally install the write handler, and send
+ * the buffers accumulated so far to the replica.
+ *
+ * But why we do that? Because the replica, when we stream
+ * the RDB directly via the socket, must detect the RDB
+ * EOF (end of file), that is a special random string at the
+ * end of the RDB (for streamed RDBs we don't know the length
+ * in advance). Detecting such final EOF string is much
+ * simpler and less CPU intensive if no more data is sent
+ * after such final EOF. So we don't want to glue the end of
+ * the RDB trasfer with the start of the other replication
+ * data. */
slave->replstate = SLAVE_STATE_ONLINE;
slave->repl_put_online_on_ack = 1;
slave->repl_ack_time = server.unixtime; /* Timeout otherwise. */
@@ -989,8 +1158,8 @@ void updateSlavesWaitingBgsave(int bgsaveerr, int type) {
slave->replpreamble = sdscatprintf(sdsempty(),"$%lld\r\n",
(unsigned long long) slave->repldbsize);
- aeDeleteFileEvent(server.el,slave->fd,AE_WRITABLE);
- if (aeCreateFileEvent(server.el, slave->fd, AE_WRITABLE, sendBulkToSlave, slave) == AE_ERR) {
+ connSetWriteHandler(slave->conn,NULL);
+ if (connSetWriteHandler(slave->conn,sendBulkToSlave) == C_ERR) {
freeClient(slave);
continue;
}
@@ -1048,7 +1217,7 @@ int slaveIsInHandshakeState(void) {
/* Avoid the master to detect the slave is timing out while loading the
* RDB file in initial synchronization. We send a single newline character
- * that is valid protocol but is guaranteed to either be sent entierly or
+ * that is valid protocol but is guaranteed to either be sent entirely or
* not, since the byte is indivisible.
*
* The function is called in two contexts: while we flush the current
@@ -1058,9 +1227,8 @@ void replicationSendNewlineToMaster(void) {
static time_t newline_sent;
if (time(NULL) != newline_sent) {
newline_sent = time(NULL);
- if (write(server.repl_transfer_s,"\n",1) == -1) {
- /* Pinging back in this stage is best-effort. */
- }
+ /* Pinging back in this stage is best-effort. */
+ if (server.repl_transfer_s) connWrite(server.repl_transfer_s, "\n", 1);
}
}
@@ -1074,12 +1242,15 @@ void replicationEmptyDbCallback(void *privdata) {
/* Once we have a link with the master and the synchroniziation was
* performed, this function materializes the master client we store
* at server.master, starting from the specified file descriptor. */
-void replicationCreateMasterClient(int fd, int dbid) {
- server.master = createClient(fd);
+void replicationCreateMasterClient(connection *conn, int dbid) {
+ server.master = createClient(conn);
+ if (conn)
+ connSetReadHandler(server.master->conn, readQueryFromClient);
server.master->flags |= CLIENT_MASTER;
server.master->authenticated = 1;
server.master->reploff = server.master_initial_offset;
server.master->read_reploff = server.master->reploff;
+ server.master->user = NULL; /* This client can do everything. */
memcpy(server.master->replid, server.master_replid,
sizeof(server.master_replid));
/* If master offset is set to -1, this master is old and is not
@@ -1089,27 +1260,94 @@ void replicationCreateMasterClient(int fd, int dbid) {
if (dbid != -1) selectDb(server.master,dbid);
}
-void restartAOF() {
- int retry = 10;
- while (retry-- && startAppendOnly() == C_ERR) {
- serverLog(LL_WARNING,"Failed enabling the AOF after successful master synchronization! Trying it again in one second.");
+/* This function will try to re-enable the AOF file after the
+ * master-replica synchronization: if it fails after multiple attempts
+ * the replica cannot be considered reliable and exists with an
+ * error. */
+void restartAOFAfterSYNC() {
+ unsigned int tries, max_tries = 10;
+ for (tries = 0; tries < max_tries; ++tries) {
+ if (startAppendOnly() == C_OK) break;
+ serverLog(LL_WARNING,
+ "Failed enabling the AOF after successful master synchronization! "
+ "Trying it again in one second.");
sleep(1);
}
- if (!retry) {
- serverLog(LL_WARNING,"FATAL: this slave instance finished the synchronization with its master, but the AOF can't be turned on. Exiting now.");
+ if (tries == max_tries) {
+ serverLog(LL_WARNING,
+ "FATAL: this replica instance finished the synchronization with "
+ "its master, but the AOF can't be turned on. Exiting now.");
exit(1);
}
}
+static int useDisklessLoad() {
+ /* compute boolean decision to use diskless load */
+ int enabled = server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB ||
+ (server.repl_diskless_load == REPL_DISKLESS_LOAD_WHEN_DB_EMPTY && dbTotalServerKeyCount()==0);
+ /* Check all modules handle read errors, otherwise it's not safe to use diskless load. */
+ if (enabled && !moduleAllDatatypesHandleErrors()) {
+ serverLog(LL_WARNING,
+ "Skipping diskless-load because there are modules that don't handle read errors.");
+ enabled = 0;
+ }
+ return enabled;
+}
+
+/* Helper function for readSyncBulkPayload() to make backups of the current
+ * DBs before socket-loading the new ones. The backups may be restored later
+ * or freed by disklessLoadRestoreBackups(). */
+redisDb *disklessLoadMakeBackups(void) {
+ redisDb *backups = zmalloc(sizeof(redisDb)*server.dbnum);
+ for (int i=0; i<server.dbnum; i++) {
+ backups[i] = server.db[i];
+ server.db[i].dict = dictCreate(&dbDictType,NULL);
+ server.db[i].expires = dictCreate(&keyptrDictType,NULL);
+ }
+ return backups;
+}
+
+/* Helper function for readSyncBulkPayload(): when replica-side diskless
+ * database loading is used, Redis makes a backup of the existing databases
+ * before loading the new ones from the socket.
+ *
+ * If the socket loading went wrong, we want to restore the old backups
+ * into the server databases. This function does just that in the case
+ * the 'restore' argument (the number of DBs to replace) is non-zero.
+ *
+ * When instead the loading succeeded we want just to free our old backups,
+ * in that case the funciton will do just that when 'restore' is 0. */
+void disklessLoadRestoreBackups(redisDb *backup, int restore, int empty_db_flags)
+{
+ if (restore) {
+ /* Restore. */
+ emptyDbGeneric(server.db,-1,empty_db_flags,replicationEmptyDbCallback);
+ for (int i=0; i<server.dbnum; i++) {
+ dictRelease(server.db[i].dict);
+ dictRelease(server.db[i].expires);
+ server.db[i] = backup[i];
+ }
+ } else {
+ /* Delete. */
+ emptyDbGeneric(backup,-1,empty_db_flags,replicationEmptyDbCallback);
+ for (int i=0; i<server.dbnum; i++) {
+ dictRelease(backup[i].dict);
+ dictRelease(backup[i].expires);
+ }
+ }
+ zfree(backup);
+}
+
/* Asynchronously read the SYNC payload we receive from a master */
#define REPL_MAX_WRITTEN_BEFORE_FSYNC (1024*1024*8) /* 8 MB */
-void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
+void readSyncBulkPayload(connection *conn) {
char buf[4096];
- ssize_t nread, readlen;
+ ssize_t nread, readlen, nwritten;
+ int use_diskless_load;
+ redisDb *diskless_load_backup = NULL;
+ int empty_db_flags = server.repl_slave_lazy_flush ? EMPTYDB_ASYNC :
+ EMPTYDB_NO_FLAGS;
off_t left;
- UNUSED(el);
- UNUSED(privdata);
- UNUSED(mask);
/* Static vars used to hold the EOF mark, and the last bytes received
* form the server: when they match, we reached the end of the transfer. */
@@ -1120,7 +1358,7 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
/* If repl_transfer_size == -1 we still have to read the bulk length
* from the master reply. */
if (server.repl_transfer_size == -1) {
- if (syncReadLine(fd,buf,1024,server.repl_syncio_timeout*1000) == -1) {
+ if (connSyncReadLine(conn,buf,1024,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,
"I/O error reading bulk count from MASTER: %s",
strerror(errno));
@@ -1161,141 +1399,261 @@ void readSyncBulkPayload(aeEventLoop *el, int fd, void *privdata, int mask) {
* at the next call. */
server.repl_transfer_size = 0;
serverLog(LL_NOTICE,
- "MASTER <-> SLAVE sync: receiving streamed RDB from master");
+ "MASTER <-> REPLICA sync: receiving streamed RDB from master with EOF %s",
+ useDisklessLoad()? "to parser":"to disk");
} else {
usemark = 0;
server.repl_transfer_size = strtol(buf+1,NULL,10);
serverLog(LL_NOTICE,
- "MASTER <-> SLAVE sync: receiving %lld bytes from master",
- (long long) server.repl_transfer_size);
+ "MASTER <-> REPLICA sync: receiving %lld bytes from master %s",
+ (long long) server.repl_transfer_size,
+ useDisklessLoad()? "to parser":"to disk");
}
return;
}
- /* Read bulk data */
- if (usemark) {
- readlen = sizeof(buf);
- } else {
- left = server.repl_transfer_size - server.repl_transfer_read;
- readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
- }
-
- nread = read(fd,buf,readlen);
- if (nread <= 0) {
- serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
- (nread == -1) ? strerror(errno) : "connection lost");
- cancelReplicationHandshake();
- return;
- }
- server.stat_net_input_bytes += nread;
+ use_diskless_load = useDisklessLoad();
+ if (!use_diskless_load) {
+ /* Read the data from the socket, store it to a file and search
+ * for the EOF. */
+ if (usemark) {
+ readlen = sizeof(buf);
+ } else {
+ left = server.repl_transfer_size - server.repl_transfer_read;
+ readlen = (left < (signed)sizeof(buf)) ? left : (signed)sizeof(buf);
+ }
- /* When a mark is used, we want to detect EOF asap in order to avoid
- * writing the EOF mark into the file... */
- int eof_reached = 0;
+ nread = connRead(conn,buf,readlen);
+ if (nread <= 0) {
+ serverLog(LL_WARNING,"I/O error trying to sync with MASTER: %s",
+ (nread == -1) ? strerror(errno) : "connection lost");
+ cancelReplicationHandshake();
+ return;
+ }
+ server.stat_net_input_bytes += nread;
+
+ /* When a mark is used, we want to detect EOF asap in order to avoid
+ * writing the EOF mark into the file... */
+ int eof_reached = 0;
+
+ if (usemark) {
+ /* Update the last bytes array, and check if it matches our
+ * delimiter. */
+ if (nread >= CONFIG_RUN_ID_SIZE) {
+ memcpy(lastbytes,buf+nread-CONFIG_RUN_ID_SIZE,
+ CONFIG_RUN_ID_SIZE);
+ } else {
+ int rem = CONFIG_RUN_ID_SIZE-nread;
+ memmove(lastbytes,lastbytes+nread,rem);
+ memcpy(lastbytes+rem,buf,nread);
+ }
+ if (memcmp(lastbytes,eofmark,CONFIG_RUN_ID_SIZE) == 0)
+ eof_reached = 1;
+ }
- if (usemark) {
- /* Update the last bytes array, and check if it matches our delimiter.*/
- if (nread >= CONFIG_RUN_ID_SIZE) {
- memcpy(lastbytes,buf+nread-CONFIG_RUN_ID_SIZE,CONFIG_RUN_ID_SIZE);
- } else {
- int rem = CONFIG_RUN_ID_SIZE-nread;
- memmove(lastbytes,lastbytes+nread,rem);
- memcpy(lastbytes+rem,buf,nread);
+ /* Update the last I/O time for the replication transfer (used in
+ * order to detect timeouts during replication), and write what we
+ * got from the socket to the dump file on disk. */
+ server.repl_transfer_lastio = server.unixtime;
+ if ((nwritten = write(server.repl_transfer_fd,buf,nread)) != nread) {
+ serverLog(LL_WARNING,
+ "Write error or short write writing to the DB dump file "
+ "needed for MASTER <-> REPLICA synchronization: %s",
+ (nwritten == -1) ? strerror(errno) : "short write");
+ goto error;
}
- if (memcmp(lastbytes,eofmark,CONFIG_RUN_ID_SIZE) == 0) eof_reached = 1;
- }
+ server.repl_transfer_read += nread;
- server.repl_transfer_lastio = server.unixtime;
- if (write(server.repl_transfer_fd,buf,nread) != nread) {
- serverLog(LL_WARNING,"Write error or short write writing to the DB dump file needed for MASTER <-> SLAVE synchronization: %s", strerror(errno));
- goto error;
- }
- server.repl_transfer_read += nread;
+ /* Delete the last 40 bytes from the file if we reached EOF. */
+ if (usemark && eof_reached) {
+ if (ftruncate(server.repl_transfer_fd,
+ server.repl_transfer_read - CONFIG_RUN_ID_SIZE) == -1)
+ {
+ serverLog(LL_WARNING,
+ "Error truncating the RDB file received from the master "
+ "for SYNC: %s", strerror(errno));
+ goto error;
+ }
+ }
- /* Delete the last 40 bytes from the file if we reached EOF. */
- if (usemark && eof_reached) {
- if (ftruncate(server.repl_transfer_fd,
- server.repl_transfer_read - CONFIG_RUN_ID_SIZE) == -1)
+ /* Sync data on disk from time to time, otherwise at the end of the
+ * transfer we may suffer a big delay as the memory buffers are copied
+ * into the actual disk. */
+ if (server.repl_transfer_read >=
+ server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
{
- serverLog(LL_WARNING,"Error truncating the RDB file received from the master for SYNC: %s", strerror(errno));
- goto error;
+ off_t sync_size = server.repl_transfer_read -
+ server.repl_transfer_last_fsync_off;
+ rdb_fsync_range(server.repl_transfer_fd,
+ server.repl_transfer_last_fsync_off, sync_size);
+ server.repl_transfer_last_fsync_off += sync_size;
}
+
+ /* Check if the transfer is now complete */
+ if (!usemark) {
+ if (server.repl_transfer_read == server.repl_transfer_size)
+ eof_reached = 1;
+ }
+
+ /* If the transfer is yet not complete, we need to read more, so
+ * return ASAP and wait for the handler to be called again. */
+ if (!eof_reached) return;
}
- /* Sync data on disk from time to time, otherwise at the end of the transfer
- * we may suffer a big delay as the memory buffers are copied into the
- * actual disk. */
- if (server.repl_transfer_read >=
- server.repl_transfer_last_fsync_off + REPL_MAX_WRITTEN_BEFORE_FSYNC)
+ /* We reach this point in one of the following cases:
+ *
+ * 1. The replica is using diskless replication, that is, it reads data
+ * directly from the socket to the Redis memory, without using
+ * a temporary RDB file on disk. In that case we just block and
+ * read everything from the socket.
+ *
+ * 2. Or when we are done reading from the socket to the RDB file, in
+ * such case we want just to read the RDB file in memory. */
+ serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Flushing old data");
+
+ /* We need to stop any AOF rewriting child before flusing and parsing
+ * the RDB, otherwise we'll create a copy-on-write disaster. */
+ if (server.aof_state != AOF_OFF) stopAppendOnly();
+ signalFlushedDb(-1);
+
+ /* When diskless RDB loading is used by replicas, it may be configured
+ * in order to save the current DB instead of throwing it away,
+ * so that we can restore it in case of failed transfer. */
+ if (use_diskless_load &&
+ server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB)
{
- off_t sync_size = server.repl_transfer_read -
- server.repl_transfer_last_fsync_off;
- rdb_fsync_range(server.repl_transfer_fd,
- server.repl_transfer_last_fsync_off, sync_size);
- server.repl_transfer_last_fsync_off += sync_size;
- }
+ diskless_load_backup = disklessLoadMakeBackups();
+ } else {
+ emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
+ }
+
+ /* Before loading the DB into memory we need to delete the readable
+ * handler, otherwise it will get called recursively since
+ * rdbLoad() will call the event loop to process events from time to
+ * time for non blocking loading. */
+ connSetReadHandler(conn, NULL);
+ serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Loading DB in memory");
+ rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
+ if (use_diskless_load) {
+ rio rdb;
+ rioInitWithConn(&rdb,conn,server.repl_transfer_size);
+
+ /* Put the socket in blocking mode to simplify RDB transfer.
+ * We'll restore it when the RDB is received. */
+ connBlock(conn);
+ connRecvTimeout(conn, server.repl_timeout*1000);
+ startLoading(server.repl_transfer_size);
+
+ if (rdbLoadRio(&rdb,&rsi,0) != C_OK) {
+ /* RDB loading failed. */
+ stopLoading();
+ serverLog(LL_WARNING,
+ "Failed trying to load the MASTER synchronization DB "
+ "from socket");
+ cancelReplicationHandshake();
+ rioFreeConn(&rdb, NULL);
+ if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
+ /* Restore the backed up databases. */
+ disklessLoadRestoreBackups(diskless_load_backup,1,
+ empty_db_flags);
+ } else {
+ /* Remove the half-loaded data in case we started with
+ * an empty replica. */
+ emptyDb(-1,empty_db_flags,replicationEmptyDbCallback);
+ }
- /* Check if the transfer is now complete */
- if (!usemark) {
- if (server.repl_transfer_read == server.repl_transfer_size)
- eof_reached = 1;
- }
+ /* Note that there's no point in restarting the AOF on SYNC
+ * failure, it'll be restarted when sync succeeds or the replica
+ * gets promoted. */
+ return;
+ }
+ stopLoading();
+
+ /* RDB loading succeeded if we reach this point. */
+ if (server.repl_diskless_load == REPL_DISKLESS_LOAD_SWAPDB) {
+ /* Delete the backup databases we created before starting to load
+ * the new RDB. Now the RDB was loaded with success so the old
+ * data is useless. */
+ disklessLoadRestoreBackups(diskless_load_backup,0,empty_db_flags);
+ }
+
+ /* Verify the end mark is correct. */
+ if (usemark) {
+ if (!rioRead(&rdb,buf,CONFIG_RUN_ID_SIZE) ||
+ memcmp(buf,eofmark,CONFIG_RUN_ID_SIZE) != 0)
+ {
+ serverLog(LL_WARNING,"Replication stream EOF marker is broken");
+ cancelReplicationHandshake();
+ rioFreeConn(&rdb, NULL);
+ return;
+ }
+ }
- if (eof_reached) {
- int aof_is_enabled = server.aof_state != AOF_OFF;
+ /* Cleanup and restore the socket to the original state to continue
+ * with the normal replication. */
+ rioFreeConn(&rdb, NULL);
+ connNonBlock(conn);
+ connRecvTimeout(conn,0);
+ } else {
+ /* Ensure background save doesn't overwrite synced data */
+ if (server.rdb_child_pid != -1) {
+ serverLog(LL_NOTICE,
+ "Replica is about to load the RDB file received from the "
+ "master, but there is a pending RDB child running. "
+ "Killing process %ld and removing its temp file to avoid "
+ "any race",
+ (long) server.rdb_child_pid);
+ killRDBChild();
+ }
if (rename(server.repl_transfer_tmpfile,server.rdb_filename) == -1) {
- serverLog(LL_WARNING,"Failed trying to rename the temp DB into dump.rdb in MASTER <-> SLAVE synchronization: %s", strerror(errno));
+ serverLog(LL_WARNING,
+ "Failed trying to rename the temp DB into %s in "
+ "MASTER <-> REPLICA synchronization: %s",
+ server.rdb_filename, strerror(errno));
cancelReplicationHandshake();
return;
}
- serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Flushing old data");
- /* We need to stop any AOFRW fork before flusing and parsing
- * RDB, otherwise we'll create a copy-on-write disaster. */
- if(aof_is_enabled) stopAppendOnly();
- signalFlushedDb(-1);
- emptyDb(
- -1,
- server.repl_slave_lazy_flush ? EMPTYDB_ASYNC : EMPTYDB_NO_FLAGS,
- replicationEmptyDbCallback);
- /* Before loading the DB into memory we need to delete the readable
- * handler, otherwise it will get called recursively since
- * rdbLoad() will call the event loop to process events from time to
- * time for non blocking loading. */
- aeDeleteFileEvent(server.el,server.repl_transfer_s,AE_READABLE);
- serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Loading DB in memory");
- rdbSaveInfo rsi = RDB_SAVE_INFO_INIT;
if (rdbLoad(server.rdb_filename,&rsi) != C_OK) {
- serverLog(LL_WARNING,"Failed trying to load the MASTER synchronization DB from disk");
+ serverLog(LL_WARNING,
+ "Failed trying to load the MASTER synchronization "
+ "DB from disk");
cancelReplicationHandshake();
- /* Re-enable the AOF if we disabled it earlier, in order to restore
- * the original configuration. */
- if (aof_is_enabled) restartAOF();
+ /* Note that there's no point in restarting the AOF on sync failure,
+ it'll be restarted when sync succeeds or replica promoted. */
return;
}
- /* Final setup of the connected slave <- master link */
+
+ /* Cleanup. */
zfree(server.repl_transfer_tmpfile);
close(server.repl_transfer_fd);
- replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
- server.repl_state = REPL_STATE_CONNECTED;
- /* After a full resynchroniziation we use the replication ID and
- * offset of the master. The secondary ID / offset are cleared since
- * we are starting a new history. */
- memcpy(server.replid,server.master->replid,sizeof(server.replid));
- server.master_repl_offset = server.master->reploff;
- clearReplicationId2();
- /* Let's create the replication backlog if needed. Slaves need to
- * accumulate the backlog regardless of the fact they have sub-slaves
- * or not, in order to behave correctly if they are promoted to
- * masters after a failover. */
- if (server.repl_backlog == NULL) createReplicationBacklog();
-
- serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Finished with success");
- /* Restart the AOF subsystem now that we finished the sync. This
- * will trigger an AOF rewrite, and when done will start appending
- * to the new file. */
- if (aof_is_enabled) restartAOF();
+ server.repl_transfer_fd = -1;
+ server.repl_transfer_tmpfile = NULL;
}
+
+ /* Final setup of the connected slave <- master link */
+ replicationCreateMasterClient(server.repl_transfer_s,rsi.repl_stream_db);
+ server.repl_state = REPL_STATE_CONNECTED;
+ server.repl_down_since = 0;
+
+ /* After a full resynchroniziation we use the replication ID and
+ * offset of the master. The secondary ID / offset are cleared since
+ * we are starting a new history. */
+ memcpy(server.replid,server.master->replid,sizeof(server.replid));
+ server.master_repl_offset = server.master->reploff;
+ clearReplicationId2();
+
+ /* Let's create the replication backlog if needed. Slaves need to
+ * accumulate the backlog regardless of the fact they have sub-slaves
+ * or not, in order to behave correctly if they are promoted to
+ * masters after a failover. */
+ if (server.repl_backlog == NULL) createReplicationBacklog();
+ serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Finished with success");
+
+ /* Restart the AOF subsystem now that we finished the sync. This
+ * will trigger an AOF rewrite, and when done will start appending
+ * to the new file. */
+ if (server.aof_enabled) restartAOFAfterSYNC();
return;
error:
@@ -1312,33 +1670,40 @@ error:
#define SYNC_CMD_READ (1<<0)
#define SYNC_CMD_WRITE (1<<1)
#define SYNC_CMD_FULL (SYNC_CMD_READ|SYNC_CMD_WRITE)
-char *sendSynchronousCommand(int flags, int fd, ...) {
+char *sendSynchronousCommand(int flags, connection *conn, ...) {
- /* Create the command to send to the master, we use simple inline
- * protocol for simplicity as currently we only send simple strings. */
+ /* Create the command to send to the master, we use redis binary
+ * protocol to make sure correct arguments are sent. This function
+ * is not safe for all binary data. */
if (flags & SYNC_CMD_WRITE) {
char *arg;
va_list ap;
sds cmd = sdsempty();
- va_start(ap,fd);
+ sds cmdargs = sdsempty();
+ size_t argslen = 0;
+ va_start(ap,conn);
while(1) {
arg = va_arg(ap, char*);
if (arg == NULL) break;
- if (sdslen(cmd) != 0) cmd = sdscatlen(cmd," ",1);
- cmd = sdscat(cmd,arg);
+ cmdargs = sdscatprintf(cmdargs,"$%zu\r\n%s\r\n",strlen(arg),arg);
+ argslen++;
}
- cmd = sdscatlen(cmd,"\r\n",2);
+
va_end(ap);
-
+
+ cmd = sdscatprintf(cmd,"*%zu\r\n",argslen);
+ cmd = sdscatsds(cmd,cmdargs);
+ sdsfree(cmdargs);
+
/* Transfer command to the server. */
- if (syncWrite(fd,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
+ if (connSyncWrite(conn,cmd,sdslen(cmd),server.repl_syncio_timeout*1000)
== -1)
{
sdsfree(cmd);
return sdscatprintf(sdsempty(),"-Writing to master: %s",
- strerror(errno));
+ connGetLastError(conn));
}
sdsfree(cmd);
}
@@ -1347,7 +1712,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
if (flags & SYNC_CMD_READ) {
char buf[256];
- if (syncReadLine(fd,buf,sizeof(buf),server.repl_syncio_timeout*1000)
+ if (connSyncReadLine(conn,buf,sizeof(buf),server.repl_syncio_timeout*1000)
== -1)
{
return sdscatprintf(sdsempty(),"-Reading from master: %s",
@@ -1388,7 +1753,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
*
* The function returns:
*
- * PSYNC_CONTINUE: If the PSYNC command succeded and we can continue.
+ * PSYNC_CONTINUE: If the PSYNC command succeeded and we can continue.
* PSYNC_FULLRESYNC: If PSYNC is supported but a full resync is needed.
* In this case the master run_id and global replication
* offset is saved.
@@ -1413,7 +1778,7 @@ char *sendSynchronousCommand(int flags, int fd, ...) {
#define PSYNC_FULLRESYNC 3
#define PSYNC_NOT_SUPPORTED 4
#define PSYNC_TRY_LATER 5
-int slaveTryPartialResynchronization(int fd, int read_reply) {
+int slaveTryPartialResynchronization(connection *conn, int read_reply) {
char *psync_replid;
char psync_offset[32];
sds reply;
@@ -1438,18 +1803,18 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
}
/* Issue the PSYNC command */
- reply = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PSYNC",psync_replid,psync_offset,NULL);
+ reply = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PSYNC",psync_replid,psync_offset,NULL);
if (reply != NULL) {
serverLog(LL_WARNING,"Unable to send PSYNC to master: %s",reply);
sdsfree(reply);
- aeDeleteFileEvent(server.el,fd,AE_READABLE);
+ connSetReadHandler(conn, NULL);
return PSYNC_WRITE_ERROR;
}
return PSYNC_WAIT_REPLY;
}
/* Reading half */
- reply = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ reply = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (sdslen(reply) == 0) {
/* The master may send empty newlines after it receives PSYNC
* and before to reply, just to keep the connection alive. */
@@ -1457,7 +1822,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
return PSYNC_WAIT_REPLY;
}
- aeDeleteFileEvent(server.el,fd,AE_READABLE);
+ connSetReadHandler(conn, NULL);
if (!strncmp(reply,"+FULLRESYNC",11)) {
char *replid = NULL, *offset = NULL;
@@ -1531,7 +1896,7 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* Setup the replication to continue. */
sdsfree(reply);
- replicationResurrectCachedMaster(fd);
+ replicationResurrectCachedMaster(conn);
/* If this instance was restarted and we read the metadata to
* PSYNC from the persistence file, our replication backlog could
@@ -1573,29 +1938,23 @@ int slaveTryPartialResynchronization(int fd, int read_reply) {
/* This handler fires when the non blocking connect was able to
* establish a connection with the master. */
-void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
+void syncWithMaster(connection *conn) {
char tmpfile[256], *err = NULL;
int dfd = -1, maxtries = 5;
- int sockerr = 0, psync_result;
- socklen_t errlen = sizeof(sockerr);
- UNUSED(el);
- UNUSED(privdata);
- UNUSED(mask);
+ int psync_result;
/* If this event fired after the user turned the instance into a master
* with SLAVEOF NO ONE we must just return ASAP. */
if (server.repl_state == REPL_STATE_NONE) {
- close(fd);
+ connClose(conn);
return;
}
/* Check for errors in the socket: after a non blocking connect() we
* may find that the socket is in error state. */
- if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &sockerr, &errlen) == -1)
- sockerr = errno;
- if (sockerr) {
+ if (connGetState(conn) != CONN_STATE_CONNECTED) {
serverLog(LL_WARNING,"Error condition on socket for SYNC: %s",
- strerror(sockerr));
+ connGetLastError(conn));
goto error;
}
@@ -1604,18 +1963,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
serverLog(LL_NOTICE,"Non blocking connect for SYNC fired the event.");
/* Delete the writable event so that the readable event remains
* registered and we can wait for the PONG reply. */
- aeDeleteFileEvent(server.el,fd,AE_WRITABLE);
+ connSetReadHandler(conn, syncWithMaster);
+ connSetWriteHandler(conn, NULL);
server.repl_state = REPL_STATE_RECEIVE_PONG;
/* Send the PING, don't check for errors at all, we have the timeout
* that will take care about this. */
- err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"PING",NULL);
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"PING",NULL);
if (err) goto write_error;
return;
}
/* Receive the PONG command. */
if (server.repl_state == REPL_STATE_RECEIVE_PONG) {
- err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* We accept only two replies as valid, a positive +PONG reply
* (we just check for "+") or an authentication error.
@@ -1639,8 +1999,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* AUTH with the master if required. */
if (server.repl_state == REPL_STATE_SEND_AUTH) {
- if (server.masterauth) {
- err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"AUTH",server.masterauth,NULL);
+ if (server.masteruser && server.masterauth) {
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",
+ server.masteruser,server.masterauth,NULL);
+ if (err) goto write_error;
+ server.repl_state = REPL_STATE_RECEIVE_AUTH;
+ return;
+ } else if (server.masterauth) {
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"AUTH",server.masterauth,NULL);
if (err) goto write_error;
server.repl_state = REPL_STATE_RECEIVE_AUTH;
return;
@@ -1651,7 +2017,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive AUTH reply. */
if (server.repl_state == REPL_STATE_RECEIVE_AUTH) {
- err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
if (err[0] == '-') {
serverLog(LL_WARNING,"Unable to AUTH to MASTER: %s",err);
sdsfree(err);
@@ -1664,11 +2030,14 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave port, so that Master's INFO command can list the
* slave listening port correctly. */
if (server.repl_state == REPL_STATE_SEND_PORT) {
- sds port = sdsfromlonglong(server.slave_announce_port ?
- server.slave_announce_port : server.port);
- err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
- "listening-port",port, NULL);
- sdsfree(port);
+ int port;
+ if (server.slave_announce_port) port = server.slave_announce_port;
+ else if (server.tls_replication && server.tls_port) port = server.tls_port;
+ else port = server.port;
+ sds portstr = sdsfromlonglong(port);
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
+ "listening-port",portstr, NULL);
+ sdsfree(portstr);
if (err) goto write_error;
sdsfree(err);
server.repl_state = REPL_STATE_RECEIVE_PORT;
@@ -1677,7 +2046,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF listening-port reply. */
if (server.repl_state == REPL_STATE_RECEIVE_PORT) {
- err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */
if (err[0] == '-') {
@@ -1698,7 +2067,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Set the slave ip, so that Master's INFO command can list the
* slave IP address port correctly in case of port forwarding or NAT. */
if (server.repl_state == REPL_STATE_SEND_IP) {
- err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"ip-address",server.slave_announce_ip, NULL);
if (err) goto write_error;
sdsfree(err);
@@ -1708,7 +2077,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive REPLCONF ip-address reply. */
if (server.repl_state == REPL_STATE_RECEIVE_IP) {
- err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support
* REPLCONF listening-port. */
if (err[0] == '-') {
@@ -1726,7 +2095,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
*
* The master will ignore capabilities it does not understand. */
if (server.repl_state == REPL_STATE_SEND_CAPA) {
- err = sendSynchronousCommand(SYNC_CMD_WRITE,fd,"REPLCONF",
+ err = sendSynchronousCommand(SYNC_CMD_WRITE,conn,"REPLCONF",
"capa","eof","capa","psync2",NULL);
if (err) goto write_error;
sdsfree(err);
@@ -1736,7 +2105,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
/* Receive CAPA reply. */
if (server.repl_state == REPL_STATE_RECEIVE_CAPA) {
- err = sendSynchronousCommand(SYNC_CMD_READ,fd,NULL);
+ err = sendSynchronousCommand(SYNC_CMD_READ,conn,NULL);
/* Ignore the error if any, not all the Redis versions support
* REPLCONF capa. */
if (err[0] == '-') {
@@ -1753,7 +2122,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* and the global offset, to try a partial resync at the next
* reconnection attempt. */
if (server.repl_state == REPL_STATE_SEND_PSYNC) {
- if (slaveTryPartialResynchronization(fd,0) == PSYNC_WRITE_ERROR) {
+ if (slaveTryPartialResynchronization(conn,0) == PSYNC_WRITE_ERROR) {
err = sdsnew("Write error sending the PSYNC command.");
goto write_error;
}
@@ -1769,7 +2138,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
goto error;
}
- psync_result = slaveTryPartialResynchronization(fd,1);
+ psync_result = slaveTryPartialResynchronization(conn,1);
if (psync_result == PSYNC_WAIT_REPLY) return; /* Try again later... */
/* If the master is in an transient error, we should try to PSYNC
@@ -1782,7 +2151,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* uninstalling the read handler from the file descriptor. */
if (psync_result == PSYNC_CONTINUE) {
- serverLog(LL_NOTICE, "MASTER <-> SLAVE sync: Master accepted a Partial Resynchronization.");
+ serverLog(LL_NOTICE, "MASTER <-> REPLICA sync: Master accepted a Partial Resynchronization.");
return;
}
@@ -1798,7 +2167,7 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
* already populated. */
if (psync_result == PSYNC_NOT_SUPPORTED) {
serverLog(LL_NOTICE,"Retrying with SYNC...");
- if (syncWrite(fd,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
+ if (connSyncWrite(conn,"SYNC\r\n",6,server.repl_syncio_timeout*1000) == -1) {
serverLog(LL_WARNING,"I/O error writing to MASTER: %s",
strerror(errno));
goto error;
@@ -1806,25 +2175,30 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
}
/* Prepare a suitable temp file for bulk transfer */
- while(maxtries--) {
- snprintf(tmpfile,256,
- "temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
- dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
- if (dfd != -1) break;
- sleep(1);
- }
- if (dfd == -1) {
- serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> SLAVE synchronization: %s",strerror(errno));
- goto error;
+ if (!useDisklessLoad()) {
+ while(maxtries--) {
+ snprintf(tmpfile,256,
+ "temp-%d.%ld.rdb",(int)server.unixtime,(long int)getpid());
+ dfd = open(tmpfile,O_CREAT|O_WRONLY|O_EXCL,0644);
+ if (dfd != -1) break;
+ sleep(1);
+ }
+ if (dfd == -1) {
+ serverLog(LL_WARNING,"Opening the temp file needed for MASTER <-> REPLICA synchronization: %s",strerror(errno));
+ goto error;
+ }
+ server.repl_transfer_tmpfile = zstrdup(tmpfile);
+ server.repl_transfer_fd = dfd;
}
/* Setup the non blocking download of the bulk file. */
- if (aeCreateFileEvent(server.el,fd, AE_READABLE,readSyncBulkPayload,NULL)
- == AE_ERR)
+ if (connSetReadHandler(conn, readSyncBulkPayload)
+ == C_ERR)
{
+ char conninfo[CONN_INFO_LEN];
serverLog(LL_WARNING,
- "Can't create readable event for SYNC: %s (fd=%d)",
- strerror(errno),fd);
+ "Can't create readable event for SYNC: %s (%s)",
+ strerror(errno), connGetInfo(conn, conninfo, sizeof(conninfo)));
goto error;
}
@@ -1832,16 +2206,19 @@ void syncWithMaster(aeEventLoop *el, int fd, void *privdata, int mask) {
server.repl_transfer_size = -1;
server.repl_transfer_read = 0;
server.repl_transfer_last_fsync_off = 0;
- server.repl_transfer_fd = dfd;
server.repl_transfer_lastio = server.unixtime;
- server.repl_transfer_tmpfile = zstrdup(tmpfile);
return;
error:
- aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
if (dfd != -1) close(dfd);
- close(fd);
- server.repl_transfer_s = -1;
+ connClose(conn);
+ server.repl_transfer_s = NULL;
+ if (server.repl_transfer_fd != -1)
+ close(server.repl_transfer_fd);
+ if (server.repl_transfer_tmpfile)
+ zfree(server.repl_transfer_tmpfile);
+ server.repl_transfer_tmpfile = NULL;
+ server.repl_transfer_fd = -1;
server.repl_state = REPL_STATE_CONNECT;
return;
@@ -1852,26 +2229,18 @@ write_error: /* Handle sendSynchronousCommand(SYNC_CMD_WRITE) errors. */
}
int connectWithMaster(void) {
- int fd;
-
- fd = anetTcpNonBlockBestEffortBindConnect(NULL,
- server.masterhost,server.masterport,NET_FIRST_BIND_ADDR);
- if (fd == -1) {
+ server.repl_transfer_s = server.tls_replication ? connCreateTLS() : connCreateSocket();
+ if (connConnect(server.repl_transfer_s, server.masterhost, server.masterport,
+ NET_FIRST_BIND_ADDR, syncWithMaster) == C_ERR) {
serverLog(LL_WARNING,"Unable to connect to MASTER: %s",
- strerror(errno));
+ connGetLastError(server.repl_transfer_s));
+ connClose(server.repl_transfer_s);
+ server.repl_transfer_s = NULL;
return C_ERR;
}
- if (aeCreateFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE,syncWithMaster,NULL) ==
- AE_ERR)
- {
- close(fd);
- serverLog(LL_WARNING,"Can't create readable event for SYNC");
- return C_ERR;
- }
server.repl_transfer_lastio = server.unixtime;
- server.repl_transfer_s = fd;
server.repl_state = REPL_STATE_CONNECTING;
return C_OK;
}
@@ -1881,11 +2250,8 @@ int connectWithMaster(void) {
* Never call this function directly, use cancelReplicationHandshake() instead.
*/
void undoConnectWithMaster(void) {
- int fd = server.repl_transfer_s;
-
- aeDeleteFileEvent(server.el,fd,AE_READABLE|AE_WRITABLE);
- close(fd);
- server.repl_transfer_s = -1;
+ connClose(server.repl_transfer_s);
+ server.repl_transfer_s = NULL;
}
/* Abort the async download of the bulk dataset while SYNC-ing with master.
@@ -1894,9 +2260,13 @@ void undoConnectWithMaster(void) {
void replicationAbortSyncTransfer(void) {
serverAssert(server.repl_state == REPL_STATE_TRANSFER);
undoConnectWithMaster();
- close(server.repl_transfer_fd);
- unlink(server.repl_transfer_tmpfile);
- zfree(server.repl_transfer_tmpfile);
+ if (server.repl_transfer_fd!=-1) {
+ close(server.repl_transfer_fd);
+ unlink(server.repl_transfer_tmpfile);
+ zfree(server.repl_transfer_tmpfile);
+ server.repl_transfer_tmpfile = NULL;
+ server.repl_transfer_fd = -1;
+ }
}
/* This function aborts a non blocking replication attempt if there is one
@@ -1940,9 +2310,11 @@ void replicationSetMaster(char *ip, int port) {
cancelReplicationHandshake();
/* Before destroying our master state, create a cached master using
* our own parameters, to later PSYNC with the new master. */
- if (was_master) replicationCacheMasterUsingMyself();
+ if (was_master) {
+ replicationDiscardCachedMaster();
+ replicationCacheMasterUsingMyself();
+ }
server.repl_state = REPL_STATE_CONNECT;
- server.repl_down_since = 0;
}
/* Cancel replication, setting the instance as a master itself. */
@@ -1989,11 +2361,11 @@ void replicationHandleMasterDisconnection(void) {
* the slaves only if we'll have to do a full resync with our master. */
}
-void slaveofCommand(client *c) {
+void replicaofCommand(client *c) {
/* SLAVEOF is not allowed in cluster mode as replication is automatically
* configured using the current address of the master node. */
if (server.cluster_enabled) {
- addReplyError(c,"SLAVEOF not allowed in cluster mode.");
+ addReplyError(c,"REPLICAOF not allowed in cluster mode.");
return;
}
@@ -2007,25 +2379,40 @@ void slaveofCommand(client *c) {
serverLog(LL_NOTICE,"MASTER MODE enabled (user request from '%s')",
client);
sdsfree(client);
+ /* Restart the AOF subsystem in case we shut it down during a sync when
+ * we were still a slave. */
+ if (server.aof_enabled && server.aof_state == AOF_OFF) restartAOFAfterSYNC();
}
} else {
long port;
+ if (c->flags & CLIENT_SLAVE)
+ {
+ /* If a client is already a replica they cannot run this command,
+ * because it involves flushing all replicas (including this
+ * client) */
+ addReplyError(c, "Command is not valid when client is a replica.");
+ return;
+ }
+
if ((getLongFromObjectOrReply(c, c->argv[2], &port, NULL) != C_OK))
return;
/* Check if we are already attached to the specified slave */
if (server.masterhost && !strcasecmp(server.masterhost,c->argv[1]->ptr)
&& server.masterport == port) {
- serverLog(LL_NOTICE,"SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.");
- addReplySds(c,sdsnew("+OK Already connected to specified master\r\n"));
+ serverLog(LL_NOTICE,"REPLICAOF would result into synchronization "
+ "with the master we are already connected "
+ "with. No operation performed.");
+ addReplySds(c,sdsnew("+OK Already connected to specified "
+ "master\r\n"));
return;
}
/* There was no previous master or the user specified a different one,
* we can continue. */
replicationSetMaster(c->argv[1]->ptr, port);
sds client = catClientInfoString(sdsempty(),c);
- serverLog(LL_NOTICE,"SLAVE OF %s:%d enabled (user request from '%s')",
+ serverLog(LL_NOTICE,"REPLICAOF %s:%d enabled (user request from '%s')",
server.masterhost, server.masterport, client);
sdsfree(client);
}
@@ -2042,32 +2429,32 @@ void roleCommand(client *c) {
void *mbcount;
int slaves = 0;
- addReplyMultiBulkLen(c,3);
+ addReplyArrayLen(c,3);
addReplyBulkCBuffer(c,"master",6);
addReplyLongLong(c,server.master_repl_offset);
- mbcount = addDeferredMultiBulkLength(c);
+ mbcount = addReplyDeferredLen(c);
listRewind(server.slaves,&li);
while((ln = listNext(&li))) {
client *slave = ln->value;
char ip[NET_IP_STR_LEN], *slaveip = slave->slave_ip;
if (slaveip[0] == '\0') {
- if (anetPeerToString(slave->fd,ip,sizeof(ip),NULL) == -1)
+ if (connPeerToString(slave->conn,ip,sizeof(ip),NULL) == -1)
continue;
slaveip = ip;
}
if (slave->replstate != SLAVE_STATE_ONLINE) continue;
- addReplyMultiBulkLen(c,3);
+ addReplyArrayLen(c,3);
addReplyBulkCString(c,slaveip);
addReplyBulkLongLong(c,slave->slave_listening_port);
addReplyBulkLongLong(c,slave->repl_ack_off);
slaves++;
}
- setDeferredMultiBulkLength(c,mbcount,slaves);
+ setDeferredArrayLen(c,mbcount,slaves);
} else {
char *slavestate = NULL;
- addReplyMultiBulkLen(c,5);
+ addReplyArrayLen(c,5);
addReplyBulkCBuffer(c,"slave",5);
addReplyBulkCString(c,server.masterhost);
addReplyLongLong(c,server.masterport);
@@ -2096,7 +2483,7 @@ void replicationSendAck(void) {
if (c != NULL) {
c->flags |= CLIENT_MASTER_FORCE_REPLY;
- addReplyMultiBulkLen(c,3);
+ addReplyArrayLen(c,3);
addReplyBulkCString(c,"REPLCONF");
addReplyBulkCString(c,"ACK");
addReplyBulkLongLong(c,c->reploff);
@@ -2112,7 +2499,7 @@ void replicationSendAck(void) {
* functions. */
/* This function is called by freeClient() in order to cache the master
- * client structure instead of destryoing it. freeClient() will return
+ * client structure instead of destroying it. freeClient() will return
* ASAP after this function returns, so every action needed to avoid problems
* with a client that is really "suspended" has to be done by this function.
*
@@ -2140,6 +2527,8 @@ void replicationCacheMaster(client *c) {
server.master->read_reploff = server.master->reploff;
if (c->flags & CLIENT_MULTI) discardTransaction(c);
listEmpty(c->reply);
+ c->sentlen = 0;
+ c->reply_bytes = 0;
c->bufpos = 0;
resetClient(c);
@@ -2172,7 +2561,7 @@ void replicationCacheMasterUsingMyself(void) {
/* The master client we create can be set to any DBID, because
* the new master will start its replication stream with SELECT. */
server.master_initial_offset = server.master_repl_offset;
- replicationCreateMasterClient(-1,-1);
+ replicationCreateMasterClient(NULL,-1);
/* Use our own ID / offset. */
memcpy(server.master->replid, server.replid, sizeof(server.replid));
@@ -2181,7 +2570,7 @@ void replicationCacheMasterUsingMyself(void) {
unlinkClient(server.master);
server.cached_master = server.master;
server.master = NULL;
- serverLog(LL_NOTICE,"Before turning into a slave, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
+ serverLog(LL_NOTICE,"Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.");
}
/* Free a cached master, called when there are no longer the conditions for
@@ -2201,19 +2590,20 @@ void replicationDiscardCachedMaster(void) {
* This function is called when successfully setup a partial resynchronization
* so the stream of data that we'll receive will start from were this
* master left. */
-void replicationResurrectCachedMaster(int newfd) {
+void replicationResurrectCachedMaster(connection *conn) {
server.master = server.cached_master;
server.cached_master = NULL;
- server.master->fd = newfd;
+ server.master->conn = conn;
+ connSetPrivateData(server.master->conn, server.master);
server.master->flags &= ~(CLIENT_CLOSE_AFTER_REPLY|CLIENT_CLOSE_ASAP);
server.master->authenticated = 1;
server.master->lastinteraction = server.unixtime;
server.repl_state = REPL_STATE_CONNECTED;
+ server.repl_down_since = 0;
/* Re-add to the list of clients. */
linkClient(server.master);
- if (aeCreateFileEvent(server.el, newfd, AE_READABLE,
- readQueryFromClient, server.master)) {
+ if (connSetReadHandler(server.master->conn, readQueryFromClient)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the readable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */
}
@@ -2221,8 +2611,7 @@ void replicationResurrectCachedMaster(int newfd) {
/* We may also need to install the write handler as well if there is
* pending data in the write buffers. */
if (clientHasPendingReplies(server.master)) {
- if (aeCreateFileEvent(server.el, newfd, AE_WRITABLE,
- sendReplyToClient, server.master)) {
+ if (connSetWriteHandler(server.master->conn, sendReplyToClient)) {
serverLog(LL_WARNING,"Error resurrecting the cached master, impossible to add the writable handler: %s", strerror(errno));
freeClientAsync(server.master); /* Close ASAP. */
}
@@ -2396,7 +2785,7 @@ void waitCommand(client *c) {
long long offset = c->woff;
if (server.masterhost) {
- addReplyError(c,"WAIT cannot be used with slave instances. Please also note that since Redis 4.0 if a slave is configured to be writable (which is not the default) writes to slaves are just local and are not propagated.");
+ addReplyError(c,"WAIT cannot be used with replica instances. Please also note that since Redis 4.0 if a replica is configured to be writable (which is not the default) writes to replicas are just local and are not propagated.");
return;
}
@@ -2528,7 +2917,7 @@ void replicationCron(void) {
serverLog(LL_NOTICE,"Connecting to MASTER %s:%d",
server.masterhost, server.masterport);
if (connectWithMaster() == C_OK) {
- serverLog(LL_NOTICE,"MASTER <-> SLAVE sync started");
+ serverLog(LL_NOTICE,"MASTER <-> REPLICA sync started");
}
}
@@ -2551,10 +2940,21 @@ void replicationCron(void) {
if ((replication_cron_loops % server.repl_ping_slave_period) == 0 &&
listLength(server.slaves))
{
- ping_argv[0] = createStringObject("PING",4);
- replicationFeedSlaves(server.slaves, server.slaveseldb,
- ping_argv, 1);
- decrRefCount(ping_argv[0]);
+ /* Note that we don't send the PING if the clients are paused during
+ * a Redis Cluster manual failover: the PING we send will otherwise
+ * alter the replication offsets of master and slave, and will no longer
+ * match the one stored into 'mf_master_offset' state. */
+ int manual_failover_in_progress =
+ server.cluster_enabled &&
+ server.cluster->mf_end &&
+ clientsArePaused();
+
+ if (!manual_failover_in_progress) {
+ ping_argv[0] = createStringObject("PING",4);
+ replicationFeedSlaves(server.slaves, server.slaveseldb,
+ ping_argv, 1);
+ decrRefCount(ping_argv[0]);
+ }
}
/* Second, send a newline to all the slaves in pre-synchronization
@@ -2581,9 +2981,7 @@ void replicationCron(void) {
server.rdb_child_type != RDB_CHILD_TYPE_SOCKET));
if (is_presync) {
- if (write(slave->fd, "\n", 1) == -1) {
- /* Don't worry about socket errors, it's just a ping. */
- }
+ connWrite(slave->conn, "\n", 1);
}
}
@@ -2600,7 +2998,7 @@ void replicationCron(void) {
if (slave->flags & CLIENT_PRE_PSYNC) continue;
if ((server.unixtime - slave->repl_ack_time) > server.repl_timeout)
{
- serverLog(LL_WARNING, "Disconnecting timedout slave: %s",
+ serverLog(LL_WARNING, "Disconnecting timedout replica: %s",
replicationGetSlaveName(slave));
freeClient(slave);
}
@@ -2630,7 +3028,7 @@ void replicationCron(void) {
* be the same as our repl-id.
* 3. We, yet as master, receive some updates, that will not
* increment the master_repl_offset.
- * 4. Later we are turned into a slave, connecto to the new
+ * 4. Later we are turned into a slave, connect to the new
* master that will accept our PSYNC request by second
* replication ID, but there will be data inconsistency
* because we received writes. */
@@ -2639,7 +3037,7 @@ void replicationCron(void) {
freeReplicationBacklog();
serverLog(LL_NOTICE,
"Replication backlog freed after %d seconds "
- "without connected slaves.",
+ "without connected replicas.",
(int) server.repl_backlog_time_limit);
}
}
@@ -2660,7 +3058,7 @@ void replicationCron(void) {
* In case of diskless replication, we make sure to wait the specified
* number of seconds (according to configuration) so that other slaves
* have the time to arrive before we start streaming. */
- if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
+ if (!hasActiveChildProcess()) {
time_t idle, max_idle = 0;
int slaves_waiting = 0;
int mincapa = -1;
diff --git a/src/rio.c b/src/rio.c
index 23b907060..c8c924380 100644
--- a/src/rio.c
+++ b/src/rio.c
@@ -92,6 +92,7 @@ static const rio rioBufferIO = {
rioBufferFlush,
NULL, /* update_checksum */
0, /* current checksum */
+ 0, /* flags */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
@@ -116,7 +117,7 @@ static size_t rioFileWrite(rio *r, const void *buf, size_t len) {
r->io.file.buffered >= r->io.file.autosync)
{
fflush(r->io.file.fp);
- aof_fsync(fileno(r->io.file.fp));
+ redis_fsync(fileno(r->io.file.fp));
r->io.file.buffered = 0;
}
return retval;
@@ -145,6 +146,7 @@ static const rio rioFileIO = {
rioFileFlush,
NULL, /* update_checksum */
0, /* current checksum */
+ 0, /* flags */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
@@ -157,81 +159,180 @@ void rioInitWithFile(rio *r, FILE *fp) {
r->io.file.autosync = 0;
}
-/* ------------------- File descriptors set implementation ------------------- */
+/* ------------------- Connection implementation -------------------
+ * We use this RIO implemetnation when reading an RDB file directly from
+ * the connection to the memory via rdbLoadRio(), thus this implementation
+ * only implements reading from a connection that is, normally,
+ * just a socket. */
+
+static size_t rioConnWrite(rio *r, const void *buf, size_t len) {
+ UNUSED(r);
+ UNUSED(buf);
+ UNUSED(len);
+ return 0; /* Error, this target does not yet support writing. */
+}
+
+/* Returns 1 or 0 for success/failure. */
+static size_t rioConnRead(rio *r, void *buf, size_t len) {
+ size_t avail = sdslen(r->io.conn.buf)-r->io.conn.pos;
+
+ /* If the buffer is too small for the entire request: realloc. */
+ if (sdslen(r->io.conn.buf) + sdsavail(r->io.conn.buf) < len)
+ r->io.conn.buf = sdsMakeRoomFor(r->io.conn.buf, len - sdslen(r->io.conn.buf));
+
+ /* If the remaining unused buffer is not large enough: memmove so that we
+ * can read the rest. */
+ if (len > avail && sdsavail(r->io.conn.buf) < len - avail) {
+ sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
+ r->io.conn.pos = 0;
+ }
+
+ /* If we don't already have all the data in the sds, read more */
+ while (len > sdslen(r->io.conn.buf) - r->io.conn.pos) {
+ size_t buffered = sdslen(r->io.conn.buf) - r->io.conn.pos;
+ size_t toread = len - buffered;
+ /* Read either what's missing, or PROTO_IOBUF_LEN, the bigger of
+ * the two. */
+ if (toread < PROTO_IOBUF_LEN) toread = PROTO_IOBUF_LEN;
+ if (toread > sdsavail(r->io.conn.buf)) toread = sdsavail(r->io.conn.buf);
+ if (r->io.conn.read_limit != 0 &&
+ r->io.conn.read_so_far + buffered + toread > r->io.conn.read_limit)
+ {
+ if (r->io.conn.read_limit >= r->io.conn.read_so_far - buffered)
+ toread = r->io.conn.read_limit - r->io.conn.read_so_far - buffered;
+ else {
+ errno = EOVERFLOW;
+ return 0;
+ }
+ }
+ int retval = connRead(r->io.conn.conn,
+ (char*)r->io.conn.buf + sdslen(r->io.conn.buf),
+ toread);
+ if (retval <= 0) {
+ if (errno == EWOULDBLOCK) errno = ETIMEDOUT;
+ return 0;
+ }
+ sdsIncrLen(r->io.conn.buf, retval);
+ }
+
+ memcpy(buf, (char*)r->io.conn.buf + r->io.conn.pos, len);
+ r->io.conn.read_so_far += len;
+ r->io.conn.pos += len;
+ return len;
+}
+
+/* Returns read/write position in file. */
+static off_t rioConnTell(rio *r) {
+ return r->io.conn.read_so_far;
+}
+
+/* Flushes any buffer to target device if applicable. Returns 1 on success
+ * and 0 on failures. */
+static int rioConnFlush(rio *r) {
+ /* Our flush is implemented by the write method, that recognizes a
+ * buffer set to NULL with a count of zero as a flush request. */
+ return rioConnWrite(r,NULL,0);
+}
+
+static const rio rioConnIO = {
+ rioConnRead,
+ rioConnWrite,
+ rioConnTell,
+ rioConnFlush,
+ NULL, /* update_checksum */
+ 0, /* current checksum */
+ 0, /* flags */
+ 0, /* bytes read or written */
+ 0, /* read/write chunk size */
+ { { NULL, 0 } } /* union for io-specific vars */
+};
+
+/* Create an RIO that implements a buffered read from an fd
+ * read_limit argument stops buffering when the reaching the limit. */
+void rioInitWithConn(rio *r, connection *conn, size_t read_limit) {
+ *r = rioConnIO;
+ r->io.conn.conn = conn;
+ r->io.conn.pos = 0;
+ r->io.conn.read_limit = read_limit;
+ r->io.conn.read_so_far = 0;
+ r->io.conn.buf = sdsnewlen(NULL, PROTO_IOBUF_LEN);
+ sdsclear(r->io.conn.buf);
+}
+
+/* Release the RIO tream. Optionally returns the unread buffered data
+ * when the SDS pointer 'remaining' is passed. */
+void rioFreeConn(rio *r, sds *remaining) {
+ if (remaining && (size_t)r->io.conn.pos < sdslen(r->io.conn.buf)) {
+ if (r->io.conn.pos > 0) sdsrange(r->io.conn.buf, r->io.conn.pos, -1);
+ *remaining = r->io.conn.buf;
+ } else {
+ sdsfree(r->io.conn.buf);
+ if (remaining) *remaining = NULL;
+ }
+ r->io.conn.buf = NULL;
+}
+
+/* ------------------- File descriptor implementation ------------------
+ * This target is used to write the RDB file to pipe, when the master just
+ * streams the data to the replicas without creating an RDB on-disk image
+ * (diskless replication option).
+ * It only implements writes. */
/* Returns 1 or 0 for success/failure.
- * The function returns success as long as we are able to correctly write
- * to at least one file descriptor.
*
* When buf is NULL and len is 0, the function performs a flush operation
* if there is some pending buffer, so this function is also used in order
- * to implement rioFdsetFlush(). */
-static size_t rioFdsetWrite(rio *r, const void *buf, size_t len) {
+ * to implement rioFdFlush(). */
+static size_t rioFdWrite(rio *r, const void *buf, size_t len) {
ssize_t retval;
- int j;
unsigned char *p = (unsigned char*) buf;
int doflush = (buf == NULL && len == 0);
- /* To start we always append to our buffer. If it gets larger than
- * a given size, we actually write to the sockets. */
- if (len) {
- r->io.fdset.buf = sdscatlen(r->io.fdset.buf,buf,len);
- len = 0; /* Prevent entering the while below if we don't flush. */
- if (sdslen(r->io.fdset.buf) > PROTO_IOBUF_LEN) doflush = 1;
- }
-
- if (doflush) {
- p = (unsigned char*) r->io.fdset.buf;
- len = sdslen(r->io.fdset.buf);
+ /* For small writes, we rather keep the data in user-space buffer, and flush
+ * it only when it grows. however for larger writes, we prefer to flush
+ * any pre-existing buffer, and write the new one directly without reallocs
+ * and memory copying. */
+ if (len > PROTO_IOBUF_LEN) {
+ /* First, flush any pre-existing buffered data. */
+ if (sdslen(r->io.fd.buf)) {
+ if (rioFdWrite(r, NULL, 0) == 0)
+ return 0;
+ }
+ /* Write the new data, keeping 'p' and 'len' from the input. */
+ } else {
+ if (len) {
+ r->io.fd.buf = sdscatlen(r->io.fd.buf,buf,len);
+ if (sdslen(r->io.fd.buf) > PROTO_IOBUF_LEN)
+ doflush = 1;
+ if (!doflush)
+ return 1;
+ }
+ /* Flusing the buffered data. set 'p' and 'len' accordintly. */
+ p = (unsigned char*) r->io.fd.buf;
+ len = sdslen(r->io.fd.buf);
}
- /* Write in little chunchs so that when there are big writes we
- * parallelize while the kernel is sending data in background to
- * the TCP socket. */
- while(len) {
- size_t count = len < 1024 ? len : 1024;
- int broken = 0;
- for (j = 0; j < r->io.fdset.numfds; j++) {
- if (r->io.fdset.state[j] != 0) {
- /* Skip FDs alraedy in error. */
- broken++;
- continue;
- }
-
- /* Make sure to write 'count' bytes to the socket regardless
- * of short writes. */
- size_t nwritten = 0;
- while(nwritten != count) {
- retval = write(r->io.fdset.fds[j],p+nwritten,count-nwritten);
- if (retval <= 0) {
- /* With blocking sockets, which is the sole user of this
- * rio target, EWOULDBLOCK is returned only because of
- * the SO_SNDTIMEO socket option, so we translate the error
- * into one more recognizable by the user. */
- if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
- break;
- }
- nwritten += retval;
- }
-
- if (nwritten != count) {
- /* Mark this FD as broken. */
- r->io.fdset.state[j] = errno;
- if (r->io.fdset.state[j] == 0) r->io.fdset.state[j] = EIO;
- }
+ size_t nwritten = 0;
+ while(nwritten != len) {
+ retval = write(r->io.fd.fd,p+nwritten,len-nwritten);
+ if (retval <= 0) {
+ /* With blocking io, which is the sole user of this
+ * rio target, EWOULDBLOCK is returned only because of
+ * the SO_SNDTIMEO socket option, so we translate the error
+ * into one more recognizable by the user. */
+ if (retval == -1 && errno == EWOULDBLOCK) errno = ETIMEDOUT;
+ return 0; /* error. */
}
- if (broken == r->io.fdset.numfds) return 0; /* All the FDs in error. */
- p += count;
- len -= count;
- r->io.fdset.pos += count;
+ nwritten += retval;
}
- if (doflush) sdsclear(r->io.fdset.buf);
+ r->io.fd.pos += len;
+ sdsclear(r->io.fd.buf);
return 1;
}
/* Returns 1 or 0 for success/failure. */
-static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
+static size_t rioFdRead(rio *r, void *buf, size_t len) {
UNUSED(r);
UNUSED(buf);
UNUSED(len);
@@ -239,48 +340,41 @@ static size_t rioFdsetRead(rio *r, void *buf, size_t len) {
}
/* Returns read/write position in file. */
-static off_t rioFdsetTell(rio *r) {
- return r->io.fdset.pos;
+static off_t rioFdTell(rio *r) {
+ return r->io.fd.pos;
}
/* Flushes any buffer to target device if applicable. Returns 1 on success
* and 0 on failures. */
-static int rioFdsetFlush(rio *r) {
+static int rioFdFlush(rio *r) {
/* Our flush is implemented by the write method, that recognizes a
* buffer set to NULL with a count of zero as a flush request. */
- return rioFdsetWrite(r,NULL,0);
+ return rioFdWrite(r,NULL,0);
}
-static const rio rioFdsetIO = {
- rioFdsetRead,
- rioFdsetWrite,
- rioFdsetTell,
- rioFdsetFlush,
+static const rio rioFdIO = {
+ rioFdRead,
+ rioFdWrite,
+ rioFdTell,
+ rioFdFlush,
NULL, /* update_checksum */
0, /* current checksum */
+ 0, /* flags */
0, /* bytes read or written */
0, /* read/write chunk size */
{ { NULL, 0 } } /* union for io-specific vars */
};
-void rioInitWithFdset(rio *r, int *fds, int numfds) {
- int j;
-
- *r = rioFdsetIO;
- r->io.fdset.fds = zmalloc(sizeof(int)*numfds);
- r->io.fdset.state = zmalloc(sizeof(int)*numfds);
- memcpy(r->io.fdset.fds,fds,sizeof(int)*numfds);
- for (j = 0; j < numfds; j++) r->io.fdset.state[j] = 0;
- r->io.fdset.numfds = numfds;
- r->io.fdset.pos = 0;
- r->io.fdset.buf = sdsempty();
+void rioInitWithFd(rio *r, int fd) {
+ *r = rioFdIO;
+ r->io.fd.fd = fd;
+ r->io.fd.pos = 0;
+ r->io.fd.buf = sdsempty();
}
/* release the rio stream. */
-void rioFreeFdset(rio *r) {
- zfree(r->io.fdset.fds);
- zfree(r->io.fdset.state);
- sdsfree(r->io.fdset.buf);
+void rioFreeFd(rio *r) {
+ sdsfree(r->io.fd.buf);
}
/* ---------------------------- Generic functions ---------------------------- */
@@ -300,7 +394,7 @@ void rioGenericUpdateChecksum(rio *r, const void *buf, size_t len) {
* disk I/O concentrated in very little time. When we fsync in an explicit
* way instead the I/O pressure is more distributed across time. */
void rioSetAutoSync(rio *r, off_t bytes) {
- serverAssert(r->read == rioFileIO.read);
+ if(r->write != rioFileIO.write) return;
r->io.file.autosync = bytes;
}
diff --git a/src/rio.h b/src/rio.h
index c996c54f6..9576335e8 100644
--- a/src/rio.h
+++ b/src/rio.h
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2009-2012, Pieter Noordhuis <pcnoordhuis at gmail dot com>
- * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2009-2019, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,10 @@
#include <stdio.h>
#include <stdint.h>
#include "sds.h"
+#include "connection.h"
+
+#define RIO_FLAG_READ_ERROR (1<<0)
+#define RIO_FLAG_WRITE_ERROR (1<<1)
struct _rio {
/* Backend functions.
@@ -51,8 +55,8 @@ struct _rio {
* computation. */
void (*update_cksum)(struct _rio *, const void *buf, size_t len);
- /* The current checksum */
- uint64_t cksum;
+ /* The current checksum and flags (see RIO_FLAG_*) */
+ uint64_t cksum, flags;
/* number of bytes read or written */
size_t processed_bytes;
@@ -73,14 +77,20 @@ struct _rio {
off_t buffered; /* Bytes written since last fsync. */
off_t autosync; /* fsync after 'autosync' bytes written. */
} file;
- /* Multiple FDs target (used to write to N sockets). */
+ /* Connection object (used to read from socket) */
+ struct {
+ connection *conn; /* Connection */
+ off_t pos; /* pos in buf that was returned */
+ sds buf; /* buffered data */
+ size_t read_limit; /* don't allow to buffer/read more than that */
+ size_t read_so_far; /* amount of data read from the rio (not buffered) */
+ } conn;
+ /* FD target (used to write to pipe). */
struct {
- int *fds; /* File descriptors. */
- int *state; /* Error state of each fd. 0 (if ok) or errno. */
- int numfds;
+ int fd; /* File descriptor. */
off_t pos;
sds buf;
- } fdset;
+ } fd;
} io;
};
@@ -91,11 +101,14 @@ typedef struct _rio rio;
* if needed. */
static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
+ if (r->flags & RIO_FLAG_WRITE_ERROR) return 0;
while (len) {
size_t bytes_to_write = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_write);
- if (r->write(r,buf,bytes_to_write) == 0)
+ if (r->write(r,buf,bytes_to_write) == 0) {
+ r->flags |= RIO_FLAG_WRITE_ERROR;
return 0;
+ }
buf = (char*)buf + bytes_to_write;
len -= bytes_to_write;
r->processed_bytes += bytes_to_write;
@@ -104,10 +117,13 @@ static inline size_t rioWrite(rio *r, const void *buf, size_t len) {
}
static inline size_t rioRead(rio *r, void *buf, size_t len) {
+ if (r->flags & RIO_FLAG_READ_ERROR) return 0;
while (len) {
size_t bytes_to_read = (r->max_processing_chunk && r->max_processing_chunk < len) ? r->max_processing_chunk : len;
- if (r->read(r,buf,bytes_to_read) == 0)
+ if (r->read(r,buf,bytes_to_read) == 0) {
+ r->flags |= RIO_FLAG_READ_ERROR;
return 0;
+ }
if (r->update_cksum) r->update_cksum(r,buf,bytes_to_read);
buf = (char*)buf + bytes_to_read;
len -= bytes_to_read;
@@ -124,11 +140,29 @@ static inline int rioFlush(rio *r) {
return r->flush(r);
}
+/* This function allows to know if there was a read error in any past
+ * operation, since the rio stream was created or since the last call
+ * to rioClearError(). */
+static inline int rioGetReadError(rio *r) {
+ return (r->flags & RIO_FLAG_READ_ERROR) != 0;
+}
+
+/* Like rioGetReadError() but for write errors. */
+static inline int rioGetWriteError(rio *r) {
+ return (r->flags & RIO_FLAG_WRITE_ERROR) != 0;
+}
+
+static inline void rioClearErrors(rio *r) {
+ r->flags &= ~(RIO_FLAG_READ_ERROR|RIO_FLAG_WRITE_ERROR);
+}
+
void rioInitWithFile(rio *r, FILE *fp);
void rioInitWithBuffer(rio *r, sds s);
-void rioInitWithFdset(rio *r, int *fds, int numfds);
+void rioInitWithConn(rio *r, connection *conn, size_t read_limit);
+void rioInitWithFd(rio *r, int fd);
-void rioFreeFdset(rio *r);
+void rioFreeFd(rio *r);
+void rioFreeConn(rio *r, sds* out_remainingBufferedData);
size_t rioWriteBulkCount(rio *r, char prefix, long count);
size_t rioWriteBulkString(rio *r, const char *buf, size_t len);
diff --git a/src/scripting.c b/src/scripting.c
index 3c0597c7a..7cf21f408 100644
--- a/src/scripting.c
+++ b/src/scripting.c
@@ -42,7 +42,10 @@ char *redisProtocolToLuaType_Int(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Bulk(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Status(lua_State *lua, char *reply);
char *redisProtocolToLuaType_Error(lua_State *lua, char *reply);
-char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply);
+char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype);
+char *redisProtocolToLuaType_Null(lua_State *lua, char *reply);
+char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf);
+char *redisProtocolToLuaType_Double(lua_State *lua, char *reply);
int redis_math_random (lua_State *L);
int redis_math_randomseed (lua_State *L);
void ldbInit(void);
@@ -58,7 +61,7 @@ sds ldbCatStackValue(sds s, lua_State *lua, int idx);
#define LDB_BREAKPOINTS_MAX 64 /* Max number of breakpoints. */
#define LDB_MAX_LEN_DEFAULT 256 /* Default len limit for replies / var dumps. */
struct ldbState {
- int fd; /* Socket of the debugging client. */
+ connection *conn; /* Connection of the debugging client. */
int active; /* Are we debugging EVAL right now? */
int forked; /* Is this a fork()ed debugging session? */
list *logs; /* List of messages to send to the client. */
@@ -132,7 +135,12 @@ char *redisProtocolToLuaType(lua_State *lua, char* reply) {
case '$': p = redisProtocolToLuaType_Bulk(lua,reply); break;
case '+': p = redisProtocolToLuaType_Status(lua,reply); break;
case '-': p = redisProtocolToLuaType_Error(lua,reply); break;
- case '*': p = redisProtocolToLuaType_MultiBulk(lua,reply); break;
+ case '*': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '%': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '~': p = redisProtocolToLuaType_Aggregate(lua,reply,*p); break;
+ case '_': p = redisProtocolToLuaType_Null(lua,reply); break;
+ case '#': p = redisProtocolToLuaType_Bool(lua,reply,p[1]); break;
+ case ',': p = redisProtocolToLuaType_Double(lua,reply); break;
}
return p;
}
@@ -180,26 +188,80 @@ char *redisProtocolToLuaType_Error(lua_State *lua, char *reply) {
return p+2;
}
-char *redisProtocolToLuaType_MultiBulk(lua_State *lua, char *reply) {
+char *redisProtocolToLuaType_Aggregate(lua_State *lua, char *reply, int atype) {
char *p = strchr(reply+1,'\r');
long long mbulklen;
int j = 0;
string2ll(reply+1,p-reply-1,&mbulklen);
- p += 2;
- if (mbulklen == -1) {
- lua_pushboolean(lua,0);
- return p;
- }
- lua_newtable(lua);
- for (j = 0; j < mbulklen; j++) {
- lua_pushnumber(lua,j+1);
- p = redisProtocolToLuaType(lua,p);
+ if (server.lua_client->resp == 2 || atype == '*') {
+ p += 2;
+ if (mbulklen == -1) {
+ lua_pushboolean(lua,0);
+ return p;
+ }
+ lua_newtable(lua);
+ for (j = 0; j < mbulklen; j++) {
+ lua_pushnumber(lua,j+1);
+ p = redisProtocolToLuaType(lua,p);
+ lua_settable(lua,-3);
+ }
+ } else if (server.lua_client->resp == 3) {
+ /* Here we handle only Set and Map replies in RESP3 mode, since arrays
+ * follow the above RESP2 code path. Note that those are represented
+ * as a table with the "map" or "set" field populated with the actual
+ * table representing the set or the map type. */
+ p += 2;
+ lua_newtable(lua);
+ lua_pushstring(lua,atype == '%' ? "map" : "set");
+ lua_newtable(lua);
+ for (j = 0; j < mbulklen; j++) {
+ p = redisProtocolToLuaType(lua,p);
+ if (atype == '%') {
+ p = redisProtocolToLuaType(lua,p);
+ } else {
+ lua_pushboolean(lua,1);
+ }
+ lua_settable(lua,-3);
+ }
lua_settable(lua,-3);
}
return p;
}
+char *redisProtocolToLuaType_Null(lua_State *lua, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ lua_pushnil(lua);
+ return p+2;
+}
+
+char *redisProtocolToLuaType_Bool(lua_State *lua, char *reply, int tf) {
+ char *p = strchr(reply+1,'\r');
+ lua_pushboolean(lua,tf == 't');
+ return p+2;
+}
+
+char *redisProtocolToLuaType_Double(lua_State *lua, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ char buf[MAX_LONG_DOUBLE_CHARS+1];
+ size_t len = p-reply-1;
+ double d;
+
+ if (len <= MAX_LONG_DOUBLE_CHARS) {
+ memcpy(buf,reply+1,len);
+ buf[len] = '\0';
+ d = strtod(buf,NULL); /* We expect a valid representation. */
+ } else {
+ d = 0;
+ }
+
+ lua_newtable(lua);
+ lua_pushstring(lua,"double");
+ lua_pushnumber(lua,d);
+ lua_settable(lua,-3);
+ return p+2;
+}
+
/* This function is used in order to push an error on the Lua stack in the
* format used by redis.pcall to return errors, which is a lua table
* with a single "err" field set to the error string. Note that this
@@ -274,6 +336,8 @@ void luaSortArray(lua_State *lua) {
* Lua reply to Redis reply conversion functions.
* ------------------------------------------------------------------------- */
+/* Reply to client 'c' converting the top element in the Lua stack to a
+ * Redis reply. As a side effect the element is consumed from the stack. */
void luaReplyToRedisReply(client *c, lua_State *lua) {
int t = lua_type(lua,-1);
@@ -282,7 +346,11 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
addReplyBulkCBuffer(c,(char*)lua_tostring(lua,-1),lua_strlen(lua,-1));
break;
case LUA_TBOOLEAN:
- addReply(c,lua_toboolean(lua,-1) ? shared.cone : shared.nullbulk);
+ if (server.lua_client->resp == 2)
+ addReply(c,lua_toboolean(lua,-1) ? shared.cone :
+ shared.null[c->resp]);
+ else
+ addReplyBool(c,lua_toboolean(lua,-1));
break;
case LUA_TNUMBER:
addReplyLongLong(c,(long long)lua_tonumber(lua,-1));
@@ -292,6 +360,8 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
* Error are returned as a single element table with 'err' field.
* Status replies are returned as single element table with 'ok'
* field. */
+
+ /* Handle error reply. */
lua_pushstring(lua,"err");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
@@ -303,8 +373,9 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
lua_pop(lua,2);
return;
}
+ lua_pop(lua,1); /* Discard field name pushed before. */
- lua_pop(lua,1);
+ /* Handle status reply. */
lua_pushstring(lua,"ok");
lua_gettable(lua,-2);
t = lua_type(lua,-1);
@@ -313,28 +384,84 @@ void luaReplyToRedisReply(client *c, lua_State *lua) {
sdsmapchars(ok,"\r\n"," ",2);
addReplySds(c,sdscatprintf(sdsempty(),"+%s\r\n",ok));
sdsfree(ok);
- lua_pop(lua,1);
- } else {
- void *replylen = addDeferredMultiBulkLength(c);
- int j = 1, mbulklen = 0;
-
- lua_pop(lua,1); /* Discard the 'ok' field value we popped */
- while(1) {
- lua_pushnumber(lua,j++);
- lua_gettable(lua,-2);
- t = lua_type(lua,-1);
- if (t == LUA_TNIL) {
- lua_pop(lua,1);
- break;
- }
- luaReplyToRedisReply(c, lua);
- mbulklen++;
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle double reply. */
+ lua_pushstring(lua,"double");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TNUMBER) {
+ addReplyDouble(c,lua_tonumber(lua,-1));
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle map reply. */
+ lua_pushstring(lua,"map");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TTABLE) {
+ int maplen = 0;
+ void *replylen = addReplyDeferredLen(c);
+ lua_pushnil(lua); /* Use nil to start iteration. */
+ while (lua_next(lua,-2)) {
+ /* Stack now: table, key, value */
+ luaReplyToRedisReply(c, lua); /* Return value. */
+ lua_pushvalue(lua,-1); /* Dup key before consuming. */
+ luaReplyToRedisReply(c, lua); /* Return key. */
+ /* Stack now: table, key. */
+ maplen++;
}
- setDeferredMultiBulkLength(c,replylen,mbulklen);
+ setDeferredMapLen(c,replylen,maplen);
+ lua_pop(lua,2);
+ return;
}
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle set reply. */
+ lua_pushstring(lua,"set");
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TTABLE) {
+ int setlen = 0;
+ void *replylen = addReplyDeferredLen(c);
+ lua_pushnil(lua); /* Use nil to start iteration. */
+ while (lua_next(lua,-2)) {
+ /* Stack now: table, key, true */
+ lua_pop(lua,1); /* Discard the boolean value. */
+ lua_pushvalue(lua,-1); /* Dup key before consuming. */
+ luaReplyToRedisReply(c, lua); /* Return key. */
+ /* Stack now: table, key. */
+ setlen++;
+ }
+ setDeferredSetLen(c,replylen,setlen);
+ lua_pop(lua,2);
+ return;
+ }
+ lua_pop(lua,1); /* Discard field name pushed before. */
+
+ /* Handle the array reply. */
+ void *replylen = addReplyDeferredLen(c);
+ int j = 1, mbulklen = 0;
+ while(1) {
+ lua_pushnumber(lua,j++);
+ lua_gettable(lua,-2);
+ t = lua_type(lua,-1);
+ if (t == LUA_TNIL) {
+ lua_pop(lua,1);
+ break;
+ }
+ luaReplyToRedisReply(c, lua);
+ mbulklen++;
+ }
+ setDeferredArrayLen(c,replylen,mbulklen);
break;
default:
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
lua_pop(lua,1);
}
@@ -442,6 +569,12 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
/* Setup our fake client for command execution */
c->argv = argv;
c->argc = argc;
+ c->user = server.lua_caller->user;
+
+ /* Process module hooks */
+ moduleCallCommandFilters(c);
+ argv = c->argv;
+ argc = c->argc;
/* Log the command if debugging is active. */
if (ldb.active && ldb.step) {
@@ -479,10 +612,24 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
goto cleanup;
}
+ /* Check the ACLs. */
+ int acl_retval = ACLCheckCommandPerm(c);
+ if (acl_retval != ACL_OK) {
+ if (acl_retval == ACL_DENIED_CMD)
+ luaPushError(lua, "The user executing the script can't run this "
+ "command or subcommand");
+ else
+ luaPushError(lua, "The user executing the script can't access "
+ "at least one of the keys mentioned in the "
+ "command arguments");
+ goto cleanup;
+ }
+
/* Write commands are forbidden against read-only slaves, or if a
* command marked as non-deterministic was already called in the context
* of this script. */
if (cmd->flags & CMD_WRITE) {
+ int deny_write_type = writeCommandsDeniedByDiskError();
if (server.lua_random_dirty && !server.lua_replicate_commands) {
luaPushError(lua,
"Write commands not allowed after non deterministic commands. Call redis.replicate_commands() at the start of your script in order to switch to single commands replication mode.");
@@ -493,11 +640,16 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
{
luaPushError(lua, shared.roslaveerr->ptr);
goto cleanup;
- } else if (server.stop_writes_on_bgsave_err &&
- server.saveparamslen > 0 &&
- server.lastbgsave_status == C_ERR)
- {
- luaPushError(lua, shared.bgsaveerr->ptr);
+ } else if (deny_write_type != DISK_ERROR_TYPE_NONE) {
+ if (deny_write_type == DISK_ERROR_TYPE_RDB) {
+ luaPushError(lua, shared.bgsaveerr->ptr);
+ } else {
+ sds aof_write_err = sdscatfmt(sdsempty(),
+ "-MISCONF Errors writing to the AOF file: %s\r\n",
+ strerror(server.aof_last_write_errno));
+ luaPushError(lua, aof_write_err);
+ sdsfree(aof_write_err);
+ }
goto cleanup;
}
}
@@ -506,10 +658,13 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
* could enlarge the memory usage are not allowed, but only if this is the
* first write in the context of this script, otherwise we can't stop
* in the middle. */
- if (server.maxmemory && server.lua_write_dirty == 0 &&
+ if (server.maxmemory && /* Maxmemory is actually enabled. */
+ !server.loading && /* Don't care about mem if loading. */
+ !server.masterhost && /* Slave must execute the script. */
+ server.lua_write_dirty == 0 && /* Script had no side effects so far. */
(cmd->flags & CMD_DENYOOM))
{
- if (freeMemoryIfNeeded() == C_ERR) {
+ if (getMaxmemoryState(NULL,NULL,NULL,NULL) != C_OK) {
luaPushError(lua, shared.oomerr->ptr);
goto cleanup;
}
@@ -575,9 +730,9 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
reply = sdsnewlen(c->buf,c->bufpos);
c->bufpos = 0;
while(listLength(c->reply)) {
- sds o = listNodeValue(listFirst(c->reply));
+ clientReplyBlock *o = listNodeValue(listFirst(c->reply));
- reply = sdscatsds(reply,o);
+ reply = sdscatlen(reply,o->buf,o->used);
listDelNode(c->reply,listFirst(c->reply));
}
}
@@ -628,6 +783,8 @@ cleanup:
argv_size = 0;
}
+ c->user = NULL;
+
if (raise_error) {
/* If we are here we should have an error in the stack, in the
* form of a table with an "err" field. Extract the string to
@@ -768,7 +925,7 @@ int luaRedisSetReplCommand(lua_State *lua) {
flags = lua_tonumber(lua,-1);
if ((flags & ~(PROPAGATE_AOF|PROPAGATE_REPL)) != 0) {
- lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_SLAVE, REPL_ALL or REPL_NONE.");
+ lua_pushstring(lua, "Invalid replication flags. Use REPL_AOF, REPL_REPLICA, REPL_ALL or REPL_NONE.");
return lua_error(lua);
}
server.lua_repl = flags;
@@ -811,6 +968,25 @@ int luaLogCommand(lua_State *lua) {
return 0;
}
+/* redis.setresp() */
+int luaSetResp(lua_State *lua) {
+ int argc = lua_gettop(lua);
+
+ if (argc != 1) {
+ lua_pushstring(lua, "redis.setresp() requires one argument.");
+ return lua_error(lua);
+ }
+
+ int resp = lua_tonumber(lua,-argc);
+ if (resp != 2 && resp != 3) {
+ lua_pushstring(lua, "RESP version must be 2 or 3.");
+ return lua_error(lua);
+ }
+
+ server.lua_client->resp = resp;
+ return 0;
+}
+
/* ---------------------------------------------------------------------------
* Lua engine initialization and reset.
* ------------------------------------------------------------------------- */
@@ -907,8 +1083,8 @@ void scriptingInit(int setup) {
if (setup) {
server.lua_client = NULL;
server.lua_caller = NULL;
+ server.lua_cur_script = NULL;
server.lua_timedout = 0;
- server.lua_always_replicate_commands = 0; /* Only DEBUG can change it.*/
ldbInit();
}
@@ -919,6 +1095,7 @@ void scriptingInit(int setup) {
* This is useful for replication, as we need to replicate EVALSHA
* as EVAL, so we need to remember the associated script. */
server.lua_scripts = dictCreate(&shaScriptObjectDictType,NULL);
+ server.lua_scripts_mem = 0;
/* Register the redis commands table and fields */
lua_newtable(lua);
@@ -938,6 +1115,11 @@ void scriptingInit(int setup) {
lua_pushcfunction(lua,luaLogCommand);
lua_settable(lua,-3);
+ /* redis.setresp */
+ lua_pushstring(lua,"setresp");
+ lua_pushcfunction(lua,luaSetResp);
+ lua_settable(lua,-3);
+
lua_pushstring(lua,"LOG_DEBUG");
lua_pushnumber(lua,LL_DEBUG);
lua_settable(lua,-3);
@@ -989,6 +1171,10 @@ void scriptingInit(int setup) {
lua_pushnumber(lua,PROPAGATE_REPL);
lua_settable(lua,-3);
+ lua_pushstring(lua,"REPL_REPLICA");
+ lua_pushnumber(lua,PROPAGATE_REPL);
+ lua_settable(lua,-3);
+
lua_pushstring(lua,"REPL_ALL");
lua_pushnumber(lua,PROPAGATE_AOF|PROPAGATE_REPL);
lua_settable(lua,-3);
@@ -1057,7 +1243,7 @@ void scriptingInit(int setup) {
* Note: there is no need to create it again when this function is called
* by scriptingReset(). */
if (server.lua_client == NULL) {
- server.lua_client = createClient(-1);
+ server.lua_client = createClient(NULL);
server.lua_client->flags |= CLIENT_LUA;
}
@@ -1073,6 +1259,7 @@ void scriptingInit(int setup) {
* This function is used in order to reset the scripting environment. */
void scriptingRelease(void) {
dictRelease(server.lua_scripts);
+ server.lua_scripts_mem = 0;
lua_close(server.lua);
}
@@ -1207,26 +1394,32 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body) {
* EVALSHA commands as EVAL using the original script. */
int retval = dictAdd(server.lua_scripts,sha,body);
serverAssertWithInfo(c ? c : server.lua_client,NULL,retval == DICT_OK);
+ server.lua_scripts_mem += sdsZmallocSize(sha) + getStringObjectSdsUsedMemory(body);
incrRefCount(body);
return sha;
}
/* This is the Lua script "count" hook that we use to detect scripts timeout. */
void luaMaskCountHook(lua_State *lua, lua_Debug *ar) {
- long long elapsed;
+ long long elapsed = mstime() - server.lua_time_start;
UNUSED(ar);
UNUSED(lua);
- elapsed = mstime() - server.lua_time_start;
+ /* Set the timeout condition if not already set and the maximum
+ * execution time was reached. */
if (elapsed >= server.lua_time_limit && server.lua_timedout == 0) {
- serverLog(LL_WARNING,"Lua slow script detected: still in execution after %lld milliseconds. You can try killing the script using the SCRIPT KILL command.",elapsed);
+ serverLog(LL_WARNING,
+ "Lua slow script detected: still in execution after %lld milliseconds. "
+ "You can try killing the script using the SCRIPT KILL command. "
+ "Script SHA1 is: %s",
+ elapsed, server.lua_cur_script);
server.lua_timedout = 1;
/* Once the script timeouts we reenter the event loop to permit others
* to call SCRIPT KILL or SHUTDOWN NOSAVE if needed. For this reason
* we need to mask the client executing the script from the event loop.
* If we don't do that the client may disconnect and could no longer be
* here when the EVAL command will return. */
- aeDeleteFileEvent(server.el, server.lua_caller->fd, AE_READABLE);
+ protectClient(server.lua_caller);
}
if (server.lua_timedout) processEventsWhileBlocked();
if (server.lua_kill) {
@@ -1240,6 +1433,7 @@ void evalGenericCommand(client *c, int evalsha) {
lua_State *lua = server.lua;
char funcname[43];
long long numkeys;
+ long long initial_server_dirty = server.dirty;
int delhook = 0, err;
/* When we replicate whole scripts, we want the same PRNG sequence at
@@ -1323,8 +1517,9 @@ void evalGenericCommand(client *c, int evalsha) {
luaSetGlobalArray(lua,"KEYS",c->argv+3,numkeys);
luaSetGlobalArray(lua,"ARGV",c->argv+3+numkeys,c->argc-3-numkeys);
- /* Select the right DB in the context of the Lua client */
+ /* Set the Lua client database and protocol. */
selectDb(server.lua_client,c->db->id);
+ server.lua_client->resp = 2; /* Default is RESP2, scripts can change it. */
/* Set a hook in order to be able to stop the script execution if it
* is running for too much time.
@@ -1334,11 +1529,10 @@ void evalGenericCommand(client *c, int evalsha) {
* If we are debugging, we set instead a "line" hook so that the
* debugger is call-back at every line executed by the script. */
server.lua_caller = c;
+ server.lua_cur_script = funcname + 2;
server.lua_time_start = mstime();
server.lua_kill = 0;
- if (server.lua_time_limit > 0 && server.masterhost == NULL &&
- ldb.active == 0)
- {
+ if (server.lua_time_limit > 0 && ldb.active == 0) {
lua_sethook(lua,luaMaskCountHook,LUA_MASKCOUNT,100000);
delhook = 1;
} else if (ldb.active) {
@@ -1355,12 +1549,14 @@ void evalGenericCommand(client *c, int evalsha) {
if (delhook) lua_sethook(lua,NULL,0,0); /* Disable hook */
if (server.lua_timedout) {
server.lua_timedout = 0;
- /* Restore the readable handler that was unregistered when the
- * script timeout was detected. */
- aeCreateFileEvent(server.el,c->fd,AE_READABLE,
- readQueryFromClient,c);
+ /* Restore the client that was protected when the script timeout
+ * was detected. */
+ unprotectClient(c);
+ if (server.masterhost && server.master)
+ queueClientForReprocessing(server.master);
}
server.lua_caller = NULL;
+ server.lua_cur_script = NULL;
/* Call the Lua garbage collector from time to time to avoid a
* full cycle performed by Lua, which adds too latency.
@@ -1422,9 +1618,21 @@ void evalGenericCommand(client *c, int evalsha) {
replicationScriptCacheAdd(c->argv[1]->ptr);
serverAssertWithInfo(c,NULL,script != NULL);
- rewriteClientCommandArgument(c,0,
- resetRefCount(createStringObject("EVAL",4)));
- rewriteClientCommandArgument(c,1,script);
+
+ /* If the script did not produce any changes in the dataset we want
+ * just to replicate it as SCRIPT LOAD, otherwise we risk running
+ * an aborted script on slaves (that may then produce results there)
+ * or just running a CPU costly read-only script on the slaves. */
+ if (server.dirty == initial_server_dirty) {
+ rewriteClientCommandVector(c,3,
+ resetRefCount(createStringObject("SCRIPT",6)),
+ resetRefCount(createStringObject("LOAD",4)),
+ script);
+ } else {
+ rewriteClientCommandArgument(c,0,
+ resetRefCount(createStringObject("EVAL",4)));
+ rewriteClientCommandArgument(c,1,script);
+ }
forceCommandPropagation(c,PROPAGATE_REPL|PROPAGATE_AOF);
}
}
@@ -1457,11 +1665,11 @@ void evalShaCommand(client *c) {
void scriptCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"debug (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
-"exists <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
-"flush -- Flush the Lua scripts cache. Very dangerous on slaves.",
-"kill -- Kill the currently executing Lua script.",
-"load <script> -- Load a script into the scripts cache, without executing it.",
+"DEBUG (yes|sync|no) -- Set the debug mode for subsequent scripts executed.",
+"EXISTS <sha1> [<sha1> ...] -- Return information about the existence of the scripts in the script cache.",
+"FLUSH -- Flush the Lua scripts cache. Very dangerous on replicas.",
+"KILL -- Kill the currently executing Lua script.",
+"LOAD <script> -- Load a script into the scripts cache, without executing it.",
NULL
};
addReplyHelp(c, help);
@@ -1473,7 +1681,7 @@ NULL
} else if (c->argc >= 2 && !strcasecmp(c->argv[1]->ptr,"exists")) {
int j;
- addReplyMultiBulkLen(c, c->argc-2);
+ addReplyArrayLen(c, c->argc-2);
for (j = 2; j < c->argc; j++) {
if (dictFind(server.lua_scripts,c->argv[j]->ptr))
addReply(c,shared.cone);
@@ -1488,6 +1696,8 @@ NULL
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"kill")) {
if (server.lua_caller == NULL) {
addReplySds(c,sdsnew("-NOTBUSY No scripts in execution right now.\r\n"));
+ } else if (server.lua_caller->flags & CLIENT_MASTER) {
+ addReplySds(c,sdsnew("-UNKILLABLE The busy script was sent by a master instance in the context of replication and cannot be killed.\r\n"));
} else if (server.lua_write_dirty) {
addReplySds(c,sdsnew("-UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.\r\n"));
} else {
@@ -1514,7 +1724,7 @@ NULL
return;
}
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SCRIPT HELP", (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
}
}
@@ -1524,7 +1734,7 @@ NULL
/* Initialize Lua debugger data structures. */
void ldbInit(void) {
- ldb.fd = -1;
+ ldb.conn = NULL;
ldb.active = 0;
ldb.logs = listCreate();
listSetFreeMethod(ldb.logs,(void (*)(void*))sdsfree);
@@ -1546,7 +1756,7 @@ void ldbFlushLog(list *log) {
void ldbEnable(client *c) {
c->flags |= CLIENT_LUA_DEBUG;
ldbFlushLog(ldb.logs);
- ldb.fd = c->fd;
+ ldb.conn = c->conn;
ldb.step = 1;
ldb.bpcount = 0;
ldb.luabp = 0;
@@ -1601,7 +1811,7 @@ void ldbSendLogs(void) {
proto = sdscatlen(proto,"\r\n",2);
listDelNode(ldb.logs,ln);
}
- if (write(ldb.fd,proto,sdslen(proto)) == -1) {
+ if (connWrite(ldb.conn,proto,sdslen(proto)) == -1) {
/* Avoid warning. We don't check the return value of write()
* since the next read() will catch the I/O error and will
* close the debugging session. */
@@ -1624,7 +1834,7 @@ void ldbSendLogs(void) {
int ldbStartSession(client *c) {
ldb.forked = (c->flags & CLIENT_LUA_DEBUG_SYNC) == 0;
if (ldb.forked) {
- pid_t cp = fork();
+ pid_t cp = redisFork();
if (cp == -1) {
addReplyError(c,"Fork() failed: can't run EVAL in debugging mode.");
return 0;
@@ -1641,7 +1851,6 @@ int ldbStartSession(client *c) {
* socket to make sure if the parent crashes a reset is sent
* to the clients. */
serverLog(LL_WARNING,"Redis forked for debugging eval");
- closeListeningSockets(0);
} else {
/* Parent */
listAddNodeTail(ldb.children,(void*)(unsigned long)cp);
@@ -1654,8 +1863,8 @@ int ldbStartSession(client *c) {
}
/* Setup our debugging session. */
- anetBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,5000);
+ connBlock(ldb.conn);
+ connSendTimeout(ldb.conn,5000);
ldb.active = 1;
/* First argument of EVAL is the script itself. We split it into different
@@ -1682,7 +1891,7 @@ void ldbEndSession(client *c) {
/* If it's a fork()ed session, we just exit. */
if (ldb.forked) {
- writeToClient(c->fd, c, 0);
+ writeToClient(c,0);
serverLog(LL_WARNING,"Lua debugging session child exiting");
exitFromChild(0);
} else {
@@ -1691,8 +1900,8 @@ void ldbEndSession(client *c) {
}
/* Otherwise let's restore client's state. */
- anetNonBlock(NULL,ldb.fd);
- anetSendTimeout(NULL,ldb.fd,0);
+ connNonBlock(ldb.conn);
+ connSendTimeout(ldb.conn,0);
/* Close the client connectin after sending the final EVAL reply
* in order to signal the end of the debugging session. */
@@ -1716,7 +1925,7 @@ int ldbRemoveChild(pid_t pid) {
return 0;
}
-/* Return the number of children we still did not received termination
+/* Return the number of children we still did not receive termination
* acknowledge via wait() in the parent process. */
int ldbPendingChildren(void) {
return listLength(ldb.children);
@@ -1984,6 +2193,11 @@ char *ldbRedisProtocolToHuman_Int(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Bulk(sds *o, char *reply);
char *ldbRedisProtocolToHuman_Status(sds *o, char *reply);
char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Set(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Map(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Null(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply);
+char *ldbRedisProtocolToHuman_Double(sds *o, char *reply);
/* Get Redis protocol from 'reply' and appends it in human readable form to
* the passed SDS string 'o'.
@@ -1998,6 +2212,11 @@ char *ldbRedisProtocolToHuman(sds *o, char *reply) {
case '+': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '-': p = ldbRedisProtocolToHuman_Status(o,reply); break;
case '*': p = ldbRedisProtocolToHuman_MultiBulk(o,reply); break;
+ case '~': p = ldbRedisProtocolToHuman_Set(o,reply); break;
+ case '%': p = ldbRedisProtocolToHuman_Map(o,reply); break;
+ case '_': p = ldbRedisProtocolToHuman_Null(o,reply); break;
+ case '#': p = ldbRedisProtocolToHuman_Bool(o,reply); break;
+ case ',': p = ldbRedisProtocolToHuman_Double(o,reply); break;
}
return p;
}
@@ -2052,6 +2271,62 @@ char *ldbRedisProtocolToHuman_MultiBulk(sds *o, char *reply) {
return p;
}
+char *ldbRedisProtocolToHuman_Set(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ long long mbulklen;
+ int j = 0;
+
+ string2ll(reply+1,p-reply-1,&mbulklen);
+ p += 2;
+ *o = sdscatlen(*o,"~(",2);
+ for (j = 0; j < mbulklen; j++) {
+ p = ldbRedisProtocolToHuman(o,p);
+ if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
+ }
+ *o = sdscatlen(*o,")",1);
+ return p;
+}
+
+char *ldbRedisProtocolToHuman_Map(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ long long mbulklen;
+ int j = 0;
+
+ string2ll(reply+1,p-reply-1,&mbulklen);
+ p += 2;
+ *o = sdscatlen(*o,"{",1);
+ for (j = 0; j < mbulklen; j++) {
+ p = ldbRedisProtocolToHuman(o,p);
+ *o = sdscatlen(*o," => ",4);
+ p = ldbRedisProtocolToHuman(o,p);
+ if (j != mbulklen-1) *o = sdscatlen(*o,",",1);
+ }
+ *o = sdscatlen(*o,"}",1);
+ return p;
+}
+
+char *ldbRedisProtocolToHuman_Null(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ *o = sdscatlen(*o,"(null)",6);
+ return p+2;
+}
+
+char *ldbRedisProtocolToHuman_Bool(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ if (reply[1] == 't')
+ *o = sdscatlen(*o,"#true",5);
+ else
+ *o = sdscatlen(*o,"#false",6);
+ return p+2;
+}
+
+char *ldbRedisProtocolToHuman_Double(sds *o, char *reply) {
+ char *p = strchr(reply+1,'\r');
+ *o = sdscatlen(*o,"(double) ",9);
+ *o = sdscatlen(*o,reply+1,p-reply-1);
+ return p+2;
+}
+
/* Log a Redis reply as debugger output, in an human readable format.
* If the resulting string is longer than 'len' plus a few more chars
* used as prefix, it gets truncated. */
@@ -2263,7 +2538,7 @@ int ldbRepl(lua_State *lua) {
while(1) {
while((argv = ldbReplParseCommand(&argc)) == NULL) {
char buf[1024];
- int nread = read(ldb.fd,buf,sizeof(buf));
+ int nread = connRead(ldb.conn,buf,sizeof(buf));
if (nread <= 0) {
/* Make sure the script runs without user input since the
* client is no longer connected. */
diff --git a/src/sds.c b/src/sds.c
index 1c977e450..98bd2e77f 100644
--- a/src/sds.c
+++ b/src/sds.c
@@ -67,8 +67,10 @@ static inline char sdsReqType(size_t string_size) {
#if (LONG_MAX == LLONG_MAX)
if (string_size < 1ll<<32)
return SDS_TYPE_32;
-#endif
return SDS_TYPE_64;
+#else
+ return SDS_TYPE_32;
+#endif
}
/* Create a new sds string with the content specified by the 'init' pointer
@@ -255,8 +257,12 @@ sds sdsRemoveFreeSpace(sds s) {
char type, oldtype = s[-1] & SDS_TYPE_MASK;
int hdrlen, oldhdrlen = sdsHdrSize(oldtype);
size_t len = sdslen(s);
+ size_t avail = sdsavail(s);
sh = (char*)s-oldhdrlen;
+ /* Return ASAP if there is no space left. */
+ if (avail == 0) return s;
+
/* Check what would be the minimum SDS header that is just good enough to
* fit this string. */
type = sdsReqType(len);
@@ -283,7 +289,7 @@ sds sdsRemoveFreeSpace(sds s) {
return s;
}
-/* Return the total size of the allocation of the specifed sds string,
+/* Return the total size of the allocation of the specified sds string,
* including:
* 1) The sds header before the pointer.
* 2) The string.
@@ -597,6 +603,10 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
long i;
va_list ap;
+ /* To avoid continuous reallocations, let's start with a buffer that
+ * can hold at least two times the format string itself. It's not the
+ * best heuristic but seems to work in practice. */
+ s = sdsMakeRoomFor(s, initlen + strlen(fmt)*2);
va_start(ap,fmt);
f = fmt; /* Next format specifier byte to process. */
i = initlen; /* Position of the next byte to write to dest str. */
@@ -693,7 +703,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
* s = sdstrim(s,"Aa. :");
* printf("%s\n", s);
*
- * Output will be just "Hello World".
+ * Output will be just "HelloWorld".
*/
sds sdstrim(sds s, const char *cset) {
char *start, *end, *sp, *ep;
diff --git a/src/sentinel.c b/src/sentinel.c
index 6c6a3a0cd..0490db4e9 100644
--- a/src/sentinel.c
+++ b/src/sentinel.c
@@ -30,6 +30,10 @@
#include "server.h"
#include "hiredis.h"
+#ifdef USE_OPENSSL
+#include "openssl/ssl.h"
+#include "hiredis_ssl.h"
+#endif
#include "async.h"
#include <ctype.h>
@@ -40,6 +44,10 @@
extern char **environ;
+#ifdef USE_OPENSSL
+extern SSL_CTX *redis_tls_ctx;
+#endif
+
#define REDIS_SENTINEL_PORT 26379
/* ======================== Sentinel global state =========================== */
@@ -84,6 +92,7 @@ typedef struct sentinelAddr {
#define SENTINEL_MAX_PENDING_COMMANDS 100
#define SENTINEL_ELECTION_TIMEOUT 10000
#define SENTINEL_MAX_DESYNC 1000
+#define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 1
/* Failover machine different states. */
#define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */
@@ -177,6 +186,10 @@ typedef struct sentinelRedisInstance {
mstime_t o_down_since_time; /* Objectively down since time. */
mstime_t down_after_period; /* Consider it down after that period. */
mstime_t info_refresh; /* Time at which we received INFO output from it. */
+ dict *renamed_commands; /* Commands renamed in this instance:
+ Sentinel will use the alternative commands
+ mapped on this table to send things like
+ SLAVEOF, CONFING, INFO, ... */
/* Role and the first time we observed it.
* This is useful in order to delay replacing what the instance reports
@@ -241,6 +254,8 @@ struct sentinelState {
int announce_port; /* Port that is gossiped to other sentinels if
non zero. */
unsigned long simfailure_flags; /* Failures simulation. */
+ int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
+ paths at runtime? */
} sentinel;
/* A script execution job. */
@@ -380,7 +395,9 @@ void sentinelSimFailureCrash(void);
/* ========================= Dictionary types =============================== */
uint64_t dictSdsHash(const void *key);
+uint64_t dictSdsCaseHash(const void *key);
int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
+int dictSdsKeyCaseCompare(void *privdata, const void *key1, const void *key2);
void releaseSentinelRedisInstance(sentinelRedisInstance *ri);
void dictInstancesValDestructor (void *privdata, void *obj) {
@@ -414,6 +431,16 @@ dictType leaderVotesDictType = {
NULL /* val destructor */
};
+/* Instance renamed commands table. */
+dictType renamedCommandsDictType = {
+ dictSdsCaseHash, /* hash function */
+ NULL, /* key dup */
+ NULL, /* val dup */
+ dictSdsKeyCaseCompare, /* key compare */
+ dictSdsDestructor, /* key destructor */
+ dictSdsDestructor /* val destructor */
+};
+
/* =========================== Initialization =============================== */
void sentinelCommand(client *c);
@@ -431,15 +458,18 @@ struct redisCommand sentinelcmds[] = {
{"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
{"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
{"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
- {"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
- {"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
- {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
+ {"role",sentinelRoleCommand,1,"ok-loading",0,NULL,0,0,0,0,0},
+ {"client",clientCommand,-2,"read-only no-script",0,NULL,0,0,0,0,0},
+ {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
+ {"auth",authCommand,2,"no-script ok-loading ok-stale fast",0,NULL,0,0,0,0,0},
+ {"hello",helloCommand,-2,"no-script fast",0,NULL,0,0,0,0,0}
};
/* This function overwrites a few normal Redis config default with Sentinel
* specific defaults. */
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT;
+ server.protected_mode = 0; /* Sentinel must be exposed. */
}
/* Perform the Sentinel mode initialization. */
@@ -455,6 +485,11 @@ void initSentinel(void) {
retval = dictAdd(server.commands, sdsnew(cmd->name), cmd);
serverAssert(retval == DICT_OK);
+
+ /* Translate the command string flags description into an actual
+ * set of flags. */
+ if (populateCommandTableParseFlags(cmd,cmd->sflags) == C_ERR)
+ serverPanic("Unsupported command flag");
}
/* Initialize various data structures. */
@@ -468,6 +503,7 @@ void initSentinel(void) {
sentinel.announce_ip = NULL;
sentinel.announce_port = 0;
sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
+ sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
memset(sentinel.myid,0,sizeof(sentinel.myid));
}
@@ -494,7 +530,7 @@ void sentinelIsRunning(void) {
if (sentinel.myid[j] != 0) break;
if (j == CONFIG_RUN_ID_SIZE) {
- /* Pick ID and presist the config. */
+ /* Pick ID and persist the config. */
getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
sentinelFlushConfig();
}
@@ -863,17 +899,17 @@ void sentinelPendingScriptsCommand(client *c) {
listNode *ln;
listIter li;
- addReplyMultiBulkLen(c,listLength(sentinel.scripts_queue));
+ addReplyArrayLen(c,listLength(sentinel.scripts_queue));
listRewind(sentinel.scripts_queue,&li);
while ((ln = listNext(&li)) != NULL) {
sentinelScriptJob *sj = ln->value;
int j = 0;
- addReplyMultiBulkLen(c,10);
+ addReplyMapLen(c,5);
addReplyBulkCString(c,"argv");
while (sj->argv[j]) j++;
- addReplyMultiBulkLen(c,j);
+ addReplyArrayLen(c,j);
j = 0;
while (sj->argv[j]) addReplyBulkCString(c,sj->argv[j++]);
@@ -1207,6 +1243,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *
ri->master = master;
ri->slaves = dictCreate(&instancesDictType,NULL);
ri->info_refresh = 0;
+ ri->renamed_commands = dictCreate(&renamedCommandsDictType,NULL);
/* Failover state. */
ri->leader = NULL;
@@ -1254,6 +1291,7 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {
sdsfree(ri->auth_pass);
sdsfree(ri->info);
releaseSentinelAddr(ri->addr);
+ dictRelease(ri->renamed_commands);
/* Clear state into the master if needed. */
if ((ri->flags & SRI_SLAVE) && (ri->flags & SRI_PROMOTED) && ri->master)
@@ -1568,6 +1606,21 @@ char *sentinelGetInstanceTypeString(sentinelRedisInstance *ri) {
else return "unknown";
}
+/* This function is used in order to send commands to Redis instances: the
+ * commands we send from Sentinel may be renamed, a common case is a master
+ * with CONFIG and SLAVEOF commands renamed for security concerns. In that
+ * case we check the ri->renamed_command table (or if the instance is a slave,
+ * we check the one of the master), and map the command that we should send
+ * to the set of renamed commads. However, if the command was not renamed,
+ * we just return "command" itself. */
+char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {
+ sds sc = sdsnew(command);
+ if (ri->master) ri = ri->master;
+ char *retval = dictFetchValue(ri->renamed_commands, sc);
+ sdsfree(sc);
+ return retval ? retval : command;
+}
+
/* ============================ Config handling ============================= */
char *sentinelHandleConfiguration(char **argv, int argc) {
sentinelRedisInstance *ri;
@@ -1650,16 +1703,18 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
ri->leader_epoch = strtoull(argv[2],NULL,10);
- } else if (!strcasecmp(argv[0],"known-slave") && argc == 4) {
+ } else if ((!strcasecmp(argv[0],"known-slave") ||
+ !strcasecmp(argv[0],"known-replica")) && argc == 4)
+ {
sentinelRedisInstance *slave;
- /* known-slave <name> <ip> <port> */
+ /* known-replica <name> <ip> <port> */
ri = sentinelGetMasterByName(argv[1]);
if (!ri) return "No such master with specified name.";
if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,argv[2],
atoi(argv[3]), ri->quorum, ri)) == NULL)
{
- return "Wrong hostname or port for slave.";
+ return "Wrong hostname or port for replica.";
}
} else if (!strcasecmp(argv[0],"known-sentinel") &&
(argc == 4 || argc == 5)) {
@@ -1677,6 +1732,17 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
si->runid = sdsnew(argv[4]);
sentinelTryConnectionSharing(si);
}
+ } else if (!strcasecmp(argv[0],"rename-command") && argc == 4) {
+ /* rename-command <name> <command> <renamed-command> */
+ ri = sentinelGetMasterByName(argv[1]);
+ if (!ri) return "No such master with specified name.";
+ sds oldcmd = sdsnew(argv[2]);
+ sds newcmd = sdsnew(argv[3]);
+ if (dictAdd(ri->renamed_commands,oldcmd,newcmd) != DICT_OK) {
+ sdsfree(oldcmd);
+ sdsfree(newcmd);
+ return "Same command renamed multiple times with rename-command.";
+ }
} else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
/* announce-ip <ip-address> */
if (strlen(argv[1]))
@@ -1684,6 +1750,12 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
} else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {
/* announce-port <port> */
sentinel.announce_port = atoi(argv[1]);
+ } else if (!strcasecmp(argv[0],"deny-scripts-reconfig") && argc == 2) {
+ /* deny-scripts-reconfig <yes|no> */
+ if ((sentinel.deny_scripts_reconfig = yesnotoi(argv[1])) == -1) {
+ return "Please specify yes or no for the "
+ "deny-scripts-reconfig options.";
+ }
} else {
return "Unrecognized sentinel configuration statement.";
}
@@ -1704,6 +1776,12 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
line = sdscatprintf(sdsempty(), "sentinel myid %s", sentinel.myid);
rewriteConfigRewriteLine(state,"sentinel",line,1);
+ /* sentinel deny-scripts-reconfig. */
+ line = sdscatprintf(sdsempty(), "sentinel deny-scripts-reconfig %s",
+ sentinel.deny_scripts_reconfig ? "yes" : "no");
+ rewriteConfigRewriteLine(state,"sentinel",line,
+ sentinel.deny_scripts_reconfig != SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG);
+
/* For every master emit a "sentinel monitor" config entry. */
di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) {
@@ -1794,7 +1872,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
if (sentinelAddrIsEqual(slave_addr,master_addr))
slave_addr = master->addr;
line = sdscatprintf(sdsempty(),
- "sentinel known-slave %s %s %d",
+ "sentinel known-replica %s %s %d",
master->name, slave_addr->ip, slave_addr->port);
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
@@ -1811,6 +1889,18 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,"sentinel",line,1);
}
dictReleaseIterator(di2);
+
+ /* sentinel rename-command */
+ di2 = dictGetIterator(master->renamed_commands);
+ while((de = dictNext(di2)) != NULL) {
+ sds oldname = dictGetKey(de);
+ sds newname = dictGetVal(de);
+ line = sdscatprintf(sdsempty(),
+ "sentinel rename-command %s %s %s",
+ master->name, oldname, newname);
+ rewriteConfigRewriteLine(state,"sentinel",line,1);
+ }
+ dictReleaseIterator(di2);
}
/* sentinel current-epoch is a global state valid for all the masters. */
@@ -1867,15 +1957,29 @@ werr:
/* Send the AUTH command with the specified master password if needed.
* Note that for slaves the password set for the master is used.
*
+ * In case this Sentinel requires a password as well, via the "requirepass"
+ * configuration directive, we assume we should use the local password in
+ * order to authenticate when connecting with the other Sentinels as well.
+ * So basically all the Sentinels share the same password and use it to
+ * authenticate reciprocally.
+ *
* We don't check at all if the command was successfully transmitted
* to the instance as if it fails Sentinel will detect the instance down,
* will disconnect and reconnect the link and so forth. */
void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) {
- char *auth_pass = (ri->flags & SRI_MASTER) ? ri->auth_pass :
- ri->master->auth_pass;
+ char *auth_pass = NULL;
+
+ if (ri->flags & SRI_MASTER) {
+ auth_pass = ri->auth_pass;
+ } else if (ri->flags & SRI_SLAVE) {
+ auth_pass = ri->master->auth_pass;
+ } else if (ri->flags & SRI_SENTINEL) {
+ auth_pass = ACLDefaultUserFirstPassword();
+ }
if (auth_pass) {
- if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "AUTH %s",
+ if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri, "%s %s",
+ sentinelInstanceMapCommand(ri,"AUTH"),
auth_pass) == C_OK) ri->link->pending_commands++;
}
}
@@ -1891,12 +1995,27 @@ void sentinelSetClientName(sentinelRedisInstance *ri, redisAsyncContext *c, char
snprintf(name,sizeof(name),"sentinel-%.8s-%s",sentinel.myid,type);
if (redisAsyncCommand(c, sentinelDiscardReplyCallback, ri,
- "CLIENT SETNAME %s", name) == C_OK)
+ "%s SETNAME %s",
+ sentinelInstanceMapCommand(ri,"CLIENT"),
+ name) == C_OK)
{
ri->link->pending_commands++;
}
}
+static int instanceLinkNegotiateTLS(redisAsyncContext *context) {
+#ifndef USE_OPENSSL
+ (void) context;
+#else
+ if (!redis_tls_ctx) return C_ERR;
+ SSL *ssl = SSL_new(redis_tls_ctx);
+ if (!ssl) return C_ERR;
+
+ if (redisInitiateSSL(&context->c, ssl) == REDIS_ERR) return C_ERR;
+#endif
+ return C_OK;
+}
+
/* Create the async connections for the instance link if the link
* is disconnected. Note that link->disconnected is true even if just
* one of the two links (commands and pub/sub) is missing. */
@@ -1912,7 +2031,11 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Commands connection. */
if (link->cc == NULL) {
link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
- if (link->cc->err) {
+ if (!link->cc->err && server.tls_replication &&
+ (instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
+ sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
+ instanceLinkCloseConnection(link,link->cc);
+ } else if (link->cc->err) {
sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
link->cc->errstr);
instanceLinkCloseConnection(link,link->cc);
@@ -1935,7 +2058,10 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
/* Pub / Sub */
if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
- if (link->pc->err) {
+ if (!link->pc->err && server.tls_replication &&
+ (instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
+ sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
+ } else if (link->pc->err) {
sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
link->pc->errstr);
instanceLinkCloseConnection(link,link->pc);
@@ -1953,8 +2079,9 @@ void sentinelReconnectInstance(sentinelRedisInstance *ri) {
sentinelSetClientName(ri,link->pc,"pubsub");
/* Now we subscribe to the Sentinels "Hello" channel. */
retval = redisAsyncCommand(link->pc,
- sentinelReceiveHelloMessages, ri, "SUBSCRIBE %s",
- SENTINEL_HELLO_CHANNEL);
+ sentinelReceiveHelloMessages, ri, "%s %s",
+ sentinelInstanceMapCommand(ri,"SUBSCRIBE"),
+ SENTINEL_HELLO_CHANNEL);
if (retval != C_OK) {
/* If we can't subscribe, the Pub/Sub connection is useless
* and we can simply disconnect it and try again. */
@@ -2288,8 +2415,11 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
{
if (redisAsyncCommand(ri->link->cc,
sentinelDiscardReplyCallback, ri,
- "SCRIPT KILL") == C_OK)
+ "%s KILL",
+ sentinelInstanceMapCommand(ri,"SCRIPT")) == C_OK)
+ {
ri->link->pending_commands++;
+ }
ri->flags |= SRI_SCRIPT_KILL_SENT;
}
}
@@ -2452,7 +2582,7 @@ void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privd
}
/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
- * instance in order to broadcast the current configuraiton for this
+ * instance in order to broadcast the current configuration for this
* master, and to advertise the existence of this Sentinel at the same time.
*
* The message has the following format:
@@ -2482,8 +2612,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
return C_ERR;
announce_ip = ip;
}
- announce_port = sentinel.announce_port ?
- sentinel.announce_port : server.port;
+ if (sentinel.announce_port) announce_port = sentinel.announce_port;
+ else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
+ else announce_port = server.port;
/* Format and send the Hello message. */
snprintf(payload,sizeof(payload),
@@ -2495,8 +2626,9 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
master->name,master_addr->ip,master_addr->port,
(unsigned long long) master->config_epoch);
retval = redisAsyncCommand(ri->link->cc,
- sentinelPublishReplyCallback, ri, "PUBLISH %s %s",
- SENTINEL_HELLO_CHANNEL,payload);
+ sentinelPublishReplyCallback, ri, "%s %s %s",
+ sentinelInstanceMapCommand(ri,"PUBLISH"),
+ SENTINEL_HELLO_CHANNEL,payload);
if (retval != C_OK) return C_ERR;
ri->link->pending_commands++;
return C_OK;
@@ -2541,13 +2673,14 @@ int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master) {
* queued in the connection. */
int sentinelSendPing(sentinelRedisInstance *ri) {
int retval = redisAsyncCommand(ri->link->cc,
- sentinelPingReplyCallback, ri, "PING");
+ sentinelPingReplyCallback, ri, "%s",
+ sentinelInstanceMapCommand(ri,"PING"));
if (retval == C_OK) {
ri->link->pending_commands++;
ri->link->last_ping_time = mstime();
/* We update the active ping time only if we received the pong for
* the previous ping, otherwise we are technically waiting since the
- * first ping that did not received a reply. */
+ * first ping that did not receive a reply. */
if (ri->link->act_ping_time == 0)
ri->link->act_ping_time = ri->link->last_ping_time;
return 1;
@@ -2599,20 +2732,25 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
ping_period = ri->down_after_period;
if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;
+ /* Send INFO to masters and slaves, not sentinels. */
if ((ri->flags & SRI_SENTINEL) == 0 &&
(ri->info_refresh == 0 ||
(now - ri->info_refresh) > info_period))
{
- /* Send INFO to masters and slaves, not sentinels. */
retval = redisAsyncCommand(ri->link->cc,
- sentinelInfoReplyCallback, ri, "INFO");
+ sentinelInfoReplyCallback, ri, "%s",
+ sentinelInstanceMapCommand(ri,"INFO"));
if (retval == C_OK) ri->link->pending_commands++;
- } else if ((now - ri->link->last_pong_time) > ping_period &&
+ }
+
+ /* Send PING to all the three kinds of instances. */
+ if ((now - ri->link->last_pong_time) > ping_period &&
(now - ri->link->last_ping_time) > ping_period/2) {
- /* Send PING to all the three kinds of instances. */
sentinelSendPing(ri);
- } else if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
- /* PUBLISH hello messages to all the three kinds of instances. */
+ }
+
+ /* PUBLISH hello messages to all the three kinds of instances. */
+ if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
sentinelSendHello(ri);
}
}
@@ -2638,7 +2776,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
void *mbl;
int fields = 0;
- mbl = addDeferredMultiBulkLength(c);
+ mbl = addReplyDeferredLen(c);
addReplyBulkCString(c,"name");
addReplyBulkCString(c,ri->name);
@@ -2819,7 +2957,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
fields++;
}
- setDeferredMultiBulkLength(c,mbl,fields*2);
+ setDeferredMapLen(c,mbl,fields);
}
/* Output a number of instances contained inside a dictionary as
@@ -2829,7 +2967,7 @@ void addReplyDictOfRedisInstances(client *c, dict *instances) {
dictEntry *de;
di = dictGetIterator(instances);
- addReplyMultiBulkLen(c,dictSize(instances));
+ addReplyArrayLen(c,dictSize(instances));
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
@@ -2892,8 +3030,10 @@ void sentinelCommand(client *c) {
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
== NULL) return;
addReplySentinelRedisInstance(c,ri);
- } else if (!strcasecmp(c->argv[1]->ptr,"slaves")) {
- /* SENTINEL SLAVES <master-name> */
+ } else if (!strcasecmp(c->argv[1]->ptr,"slaves") ||
+ !strcasecmp(c->argv[1]->ptr,"replicas"))
+ {
+ /* SENTINEL REPLICAS <master-name> */
sentinelRedisInstance *ri;
if (c->argc != 3) goto numargserr;
@@ -2957,7 +3097,7 @@ void sentinelCommand(client *c) {
/* Reply with a three-elements multi-bulk reply:
* down state, leader, vote epoch. */
- addReplyMultiBulkLen(c,3);
+ addReplyArrayLen(c,3);
addReply(c, isdown ? shared.cone : shared.czero);
addReplyBulkCString(c, leader ? leader : "*");
addReplyLongLong(c, (long long)leader_epoch);
@@ -2973,11 +3113,11 @@ void sentinelCommand(client *c) {
if (c->argc != 3) goto numargserr;
ri = sentinelGetMasterByName(c->argv[2]->ptr);
if (ri == NULL) {
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
} else {
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyBulkCString(c,addr->ip);
addReplyBulkLongLong(c,addr->port);
}
@@ -2993,7 +3133,7 @@ void sentinelCommand(client *c) {
return;
}
if (sentinelSelectSlave(ri) == NULL) {
- addReplySds(c,sdsnew("-NOGOODSLAVE No suitable slave to promote\r\n"));
+ addReplySds(c,sdsnew("-NOGOODSLAVE No suitable replica to promote\r\n"));
return;
}
serverLog(LL_WARNING,"Executing user requested FAILOVER of '%s'",
@@ -3095,7 +3235,7 @@ void sentinelCommand(client *c) {
addReplySds(c,e);
}
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
- if (c->argc < 3 || c->argc % 2 == 0) goto numargserr;
+ if (c->argc < 3) goto numargserr;
sentinelSetCommand(c);
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
/* SENTINEL INFO-CACHE <name> */
@@ -3127,7 +3267,7 @@ void sentinelCommand(client *c) {
* 3.) other master name
* ...
*/
- addReplyMultiBulkLen(c,dictSize(masters_local) * 2);
+ addReplyArrayLen(c,dictSize(masters_local) * 2);
dictIterator *di;
dictEntry *de;
@@ -3135,25 +3275,25 @@ void sentinelCommand(client *c) {
while ((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
addReplyBulkCBuffer(c,ri->name,strlen(ri->name));
- addReplyMultiBulkLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,dictSize(ri->slaves) + 1); /* +1 for self */
+ addReplyArrayLen(c,2);
addReplyLongLong(c, now - ri->info_refresh);
if (ri->info)
addReplyBulkCBuffer(c,ri->info,sdslen(ri->info));
else
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
dictIterator *sdi;
dictEntry *sde;
sdi = dictGetIterator(ri->slaves);
while ((sde = dictNext(sdi)) != NULL) {
sentinelRedisInstance *sri = dictGetVal(sde);
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyLongLong(c, now - sri->info_refresh);
if (sri->info)
addReplyBulkCBuffer(c,sri->info,sdslen(sri->info));
else
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
dictReleaseIterator(sdi);
}
@@ -3175,9 +3315,9 @@ void sentinelCommand(client *c) {
sentinel.simfailure_flags |=
SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION;
serverLog(LL_WARNING,"Failure simulation: this Sentinel "
- "will crash after promoting the selected slave to master");
+ "will crash after promoting the selected replica to master");
} else if (!strcasecmp(c->argv[j]->ptr,"help")) {
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyBulkCString(c,"crash-after-election");
addReplyBulkCString(c,"crash-after-promotion");
} else {
@@ -3271,15 +3411,15 @@ void sentinelInfoCommand(client *c) {
addReplyBulkSds(c, info);
}
-/* Implements Sentinel verison of the ROLE command. The output is
+/* Implements Sentinel version of the ROLE command. The output is
* "sentinel" and the list of currently monitored master names. */
void sentinelRoleCommand(client *c) {
dictIterator *di;
dictEntry *de;
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyBulkCBuffer(c,"sentinel",8);
- addReplyMultiBulkLen(c,dictSize(sentinel.masters));
+ addReplyArrayLen(c,dictSize(sentinel.masters));
di = dictGetIterator(sentinel.masters);
while((de = dictNext(di)) != NULL) {
@@ -3294,39 +3434,58 @@ void sentinelRoleCommand(client *c) {
void sentinelSetCommand(client *c) {
sentinelRedisInstance *ri;
int j, changes = 0;
- char *option, *value;
+ int badarg = 0; /* Bad argument position for error reporting. */
+ char *option;
if ((ri = sentinelGetMasterByNameOrReplyError(c,c->argv[2]))
== NULL) return;
/* Process option - value pairs. */
- for (j = 3; j < c->argc; j += 2) {
+ for (j = 3; j < c->argc; j++) {
+ int moreargs = (c->argc-1) - j;
option = c->argv[j]->ptr;
- value = c->argv[j+1]->ptr;
- robj *o = c->argv[j+1];
long long ll;
+ int old_j = j; /* Used to know what to log as an event. */
- if (!strcasecmp(option,"down-after-milliseconds")) {
+ if (!strcasecmp(option,"down-after-milliseconds") && moreargs > 0) {
/* down-after-millisecodns <milliseconds> */
- if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
+ robj *o = c->argv[++j];
+ if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
+ badarg = j;
goto badfmt;
+ }
ri->down_after_period = ll;
sentinelPropagateDownAfterPeriod(ri);
changes++;
- } else if (!strcasecmp(option,"failover-timeout")) {
+ } else if (!strcasecmp(option,"failover-timeout") && moreargs > 0) {
/* failover-timeout <milliseconds> */
- if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
+ robj *o = c->argv[++j];
+ if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
+ badarg = j;
goto badfmt;
+ }
ri->failover_timeout = ll;
changes++;
- } else if (!strcasecmp(option,"parallel-syncs")) {
+ } else if (!strcasecmp(option,"parallel-syncs") && moreargs > 0) {
/* parallel-syncs <milliseconds> */
- if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
+ robj *o = c->argv[++j];
+ if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
+ badarg = j;
goto badfmt;
+ }
ri->parallel_syncs = ll;
changes++;
- } else if (!strcasecmp(option,"notification-script")) {
+ } else if (!strcasecmp(option,"notification-script") && moreargs > 0) {
/* notification-script <path> */
+ char *value = c->argv[++j]->ptr;
+ if (sentinel.deny_scripts_reconfig) {
+ addReplyError(c,
+ "Reconfiguration of scripts path is denied for "
+ "security reasons. Check the deny-scripts-reconfig "
+ "configuration directive in your Sentinel configuration");
+ return;
+ }
+
if (strlen(value) && access(value,X_OK) == -1) {
addReplyError(c,
"Notification script seems non existing or non executable");
@@ -3336,8 +3495,17 @@ void sentinelSetCommand(client *c) {
sdsfree(ri->notification_script);
ri->notification_script = strlen(value) ? sdsnew(value) : NULL;
changes++;
- } else if (!strcasecmp(option,"client-reconfig-script")) {
+ } else if (!strcasecmp(option,"client-reconfig-script") && moreargs > 0) {
/* client-reconfig-script <path> */
+ char *value = c->argv[++j]->ptr;
+ if (sentinel.deny_scripts_reconfig) {
+ addReplyError(c,
+ "Reconfiguration of scripts path is denied for "
+ "security reasons. Check the deny-scripts-reconfig "
+ "configuration directive in your Sentinel configuration");
+ return;
+ }
+
if (strlen(value) && access(value,X_OK) == -1) {
addReplyError(c,
"Client reconfiguration script seems non existing or "
@@ -3348,24 +3516,65 @@ void sentinelSetCommand(client *c) {
sdsfree(ri->client_reconfig_script);
ri->client_reconfig_script = strlen(value) ? sdsnew(value) : NULL;
changes++;
- } else if (!strcasecmp(option,"auth-pass")) {
+ } else if (!strcasecmp(option,"auth-pass") && moreargs > 0) {
/* auth-pass <password> */
+ char *value = c->argv[++j]->ptr;
sdsfree(ri->auth_pass);
ri->auth_pass = strlen(value) ? sdsnew(value) : NULL;
changes++;
- } else if (!strcasecmp(option,"quorum")) {
+ } else if (!strcasecmp(option,"quorum") && moreargs > 0) {
/* quorum <count> */
- if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0)
+ robj *o = c->argv[++j];
+ if (getLongLongFromObject(o,&ll) == C_ERR || ll <= 0) {
+ badarg = j;
goto badfmt;
+ }
ri->quorum = ll;
changes++;
+ } else if (!strcasecmp(option,"rename-command") && moreargs > 1) {
+ /* rename-command <oldname> <newname> */
+ sds oldname = c->argv[++j]->ptr;
+ sds newname = c->argv[++j]->ptr;
+
+ if ((sdslen(oldname) == 0) || (sdslen(newname) == 0)) {
+ badarg = sdslen(newname) ? j-1 : j;
+ goto badfmt;
+ }
+
+ /* Remove any older renaming for this command. */
+ dictDelete(ri->renamed_commands,oldname);
+
+ /* If the target name is the same as the source name there
+ * is no need to add an entry mapping to itself. */
+ if (!dictSdsKeyCaseCompare(NULL,oldname,newname)) {
+ oldname = sdsdup(oldname);
+ newname = sdsdup(newname);
+ dictAdd(ri->renamed_commands,oldname,newname);
+ }
+ changes++;
} else {
- addReplyErrorFormat(c,"Unknown option '%s' for SENTINEL SET",
- option);
+ addReplyErrorFormat(c,"Unknown option or number of arguments for "
+ "SENTINEL SET '%s'", option);
if (changes) sentinelFlushConfig();
return;
}
- sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",option,value);
+
+ /* Log the event. */
+ int numargs = j-old_j+1;
+ switch(numargs) {
+ case 2:
+ sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s",c->argv[old_j]->ptr,
+ c->argv[old_j+1]->ptr);
+ break;
+ case 3:
+ sentinelEvent(LL_WARNING,"+set",ri,"%@ %s %s %s",c->argv[old_j]->ptr,
+ c->argv[old_j+1]->ptr,
+ c->argv[old_j+2]->ptr);
+ break;
+ default:
+ sentinelEvent(LL_WARNING,"+set",ri,"%@ %s",c->argv[old_j]->ptr);
+ break;
+ }
}
if (changes) sentinelFlushConfig();
@@ -3375,7 +3584,7 @@ void sentinelSetCommand(client *c) {
badfmt: /* Bad format errors */
if (changes) sentinelFlushConfig();
addReplyErrorFormat(c,"Invalid argument '%s' for SENTINEL SET '%s'",
- value, option);
+ (char*)c->argv[badarg]->ptr,option);
}
/* Our fake PUBLISH command: it is actually useful only to receive hello messages
@@ -3413,8 +3622,8 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
if (ri->link->cc &&
(mstime() - ri->link->cc_conn_time) >
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
- ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
- /* The pending ping is delayed, and we did not received
+ ri->link->act_ping_time != 0 && /* There is a pending ping... */
+ /* The pending ping is delayed, and we did not receive
* error replies as well. */
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
@@ -3570,7 +3779,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
*
* 1) We believe it is down, or there is a failover in progress.
* 2) Sentinel is connected.
- * 3) We did not received the info within SENTINEL_ASK_PERIOD ms. */
+ * 3) We did not receive the info within SENTINEL_ASK_PERIOD ms. */
if ((master->flags & SRI_S_DOWN) == 0) continue;
if (ri->link->disconnected) continue;
if (!(flags & SENTINEL_ASK_FORCED) &&
@@ -3581,7 +3790,8 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
ll2string(port,sizeof(port),master->addr->port);
retval = redisAsyncCommand(ri->link->cc,
sentinelReceiveIsMasterDownReply, ri,
- "SENTINEL is-master-down-by-addr %s %s %llu %s",
+ "%s is-master-down-by-addr %s %s %llu %s",
+ sentinelInstanceMapCommand(ri,"SENTINEL"),
master->addr->ip, port,
sentinel.current_epoch,
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
@@ -3601,7 +3811,7 @@ void sentinelSimFailureCrash(void) {
}
/* Vote for the sentinel with 'req_runid' or return the old vote if already
- * voted for the specifed 'req_epoch' or one greater.
+ * voted for the specified 'req_epoch' or one greater.
*
* If a vote is not available returns NULL, otherwise return the Sentinel
* runid and populate the leader_epoch with the epoch of the vote. */
@@ -3752,7 +3962,7 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
/* In order to send SLAVEOF in a safe way, we send a transaction performing
* the following tasks:
* 1) Reconfigure the instance according to the specified host/port params.
- * 2) Rewrite the configuraiton.
+ * 2) Rewrite the configuration.
* 3) Disconnect all clients (but this one sending the commnad) in order
* to trigger the ask-master-on-reconnection protocol for connected
* clients.
@@ -3760,17 +3970,21 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
* Note that we don't check the replies returned by commands, since we
* will observe instead the effects in the next INFO output. */
retval = redisAsyncCommand(ri->link->cc,
- sentinelDiscardReplyCallback, ri, "MULTI");
+ sentinelDiscardReplyCallback, ri, "%s",
+ sentinelInstanceMapCommand(ri,"MULTI"));
if (retval == C_ERR) return retval;
ri->link->pending_commands++;
retval = redisAsyncCommand(ri->link->cc,
- sentinelDiscardReplyCallback, ri, "SLAVEOF %s %s", host, portstr);
+ sentinelDiscardReplyCallback, ri, "%s %s %s",
+ sentinelInstanceMapCommand(ri,"SLAVEOF"),
+ host, portstr);
if (retval == C_ERR) return retval;
ri->link->pending_commands++;
retval = redisAsyncCommand(ri->link->cc,
- sentinelDiscardReplyCallback, ri, "CONFIG REWRITE");
+ sentinelDiscardReplyCallback, ri, "%s REWRITE",
+ sentinelInstanceMapCommand(ri,"CONFIG"));
if (retval == C_ERR) return retval;
ri->link->pending_commands++;
@@ -3780,12 +3994,14 @@ int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
* recognized as a syntax error, and the transaction will not fail (but
* only the unsupported command will fail). */
retval = redisAsyncCommand(ri->link->cc,
- sentinelDiscardReplyCallback, ri, "CLIENT KILL TYPE normal");
+ sentinelDiscardReplyCallback, ri, "%s KILL TYPE normal",
+ sentinelInstanceMapCommand(ri,"CLIENT"));
if (retval == C_ERR) return retval;
ri->link->pending_commands++;
retval = redisAsyncCommand(ri->link->cc,
- sentinelDiscardReplyCallback, ri, "EXEC");
+ sentinelDiscardReplyCallback, ri, "%s",
+ sentinelInstanceMapCommand(ri,"EXEC"));
if (retval == C_ERR) return retval;
ri->link->pending_commands++;
diff --git a/src/server.c b/src/server.c
index 94850e2ea..3bbc25844 100644
--- a/src/server.c
+++ b/src/server.c
@@ -76,244 +76,939 @@ volatile unsigned long lru_clock; /* Server global current LRU time. */
*
* Every entry is composed of the following fields:
*
- * name: a string representing the command name.
- * function: pointer to the C function implementing the command.
- * arity: number of arguments, it is possible to use -N to say >= N
- * sflags: command flags as string. See below for a table of flags.
- * flags: flags as bitmask. Computed by Redis using the 'sflags' field.
- * get_keys_proc: an optional function to get key arguments from a command.
+ * name: A string representing the command name.
+ *
+ * function: Pointer to the C function implementing the command.
+ *
+ * arity: Number of arguments, it is possible to use -N to say >= N
+ *
+ * sflags: Command flags as string. See below for a table of flags.
+ *
+ * flags: Flags as bitmask. Computed by Redis using the 'sflags' field.
+ *
+ * get_keys_proc: An optional function to get key arguments from a command.
* This is only used when the following three fields are not
* enough to specify what arguments are keys.
- * first_key_index: first argument that is a key
- * last_key_index: last argument that is a key
- * key_step: step to get all the keys from first to last argument. For instance
- * in MSET the step is two since arguments are key,val,key,val,...
- * microseconds: microseconds of total execution time for this command.
- * calls: total number of calls of this command.
+ *
+ * first_key_index: First argument that is a key
+ *
+ * last_key_index: Last argument that is a key
+ *
+ * key_step: Step to get all the keys from first to last argument.
+ * For instance in MSET the step is two since arguments
+ * are key,val,key,val,...
+ *
+ * microseconds: Microseconds of total execution time for this command.
+ *
+ * calls: Total number of calls of this command.
+ *
+ * id: Command bit identifier for ACLs or other goals.
*
* The flags, microseconds and calls fields are computed by Redis and should
* always be set to zero.
*
- * Command flags are expressed using strings where every character represents
- * a flag. Later the populateCommandTable() function will take care of
- * populating the real 'flags' field using this characters.
+ * Command flags are expressed using space separated strings, that are turned
+ * into actual flags by the populateCommandTable() function.
*
* This is the meaning of the flags:
*
- * w: write command (may modify the key space).
- * r: read command (will never modify the key space).
- * m: may increase memory usage once called. Don't allow if out of memory.
- * a: admin command, like SAVE or SHUTDOWN.
- * p: Pub/Sub related command.
- * f: force replication of this command, regardless of server.dirty.
- * s: command not allowed in scripts.
- * R: random command. Command is not deterministic, that is, the same command
- * with the same arguments, with the same key space, may have different
- * results. For instance SPOP and RANDOMKEY are two random commands.
- * S: Sort command output array if called from script, so that the output
- * is deterministic.
- * l: Allow command while loading the database.
- * t: Allow command while a slave has stale data but is not allowed to
- * server this data. Normally no command is accepted in this condition
- * but just a few.
- * M: Do not automatically propagate the command on MONITOR.
- * k: Perform an implicit ASKING for this command, so the command will be
- * accepted in cluster mode if the slot is marked as 'importing'.
- * F: Fast command: O(1) or O(log(N)) command that should never delay
- * its execution as long as the kernel scheduler is giving us time.
- * Note that commands that may trigger a DEL as a side effect (like SET)
- * are not fast commands.
+ * write: Write command (may modify the key space).
+ *
+ * read-only: All the non special commands just reading from keys without
+ * changing the content, or returning other informations like
+ * the TIME command. Special commands such administrative commands
+ * or transaction related commands (multi, exec, discard, ...)
+ * are not flagged as read-only commands, since they affect the
+ * server or the connection in other ways.
+ *
+ * use-memory: May increase memory usage once called. Don't allow if out
+ * of memory.
+ *
+ * admin: Administrative command, like SAVE or SHUTDOWN.
+ *
+ * pub-sub: Pub/Sub related command.
+ *
+ * no-script: Command not allowed in scripts.
+ *
+ * random: Random command. Command is not deterministic, that is, the same
+ * command with the same arguments, with the same key space, may
+ * have different results. For instance SPOP and RANDOMKEY are
+ * two random commands.
+ *
+ * to-sort: Sort command output array if called from script, so that the
+ * output is deterministic. When this flag is used (not always
+ * possible), then the "random" flag is not needed.
+ *
+ * ok-loading: Allow the command while loading the database.
+ *
+ * ok-stale: Allow the command while a slave has stale data but is not
+ * allowed to serve this data. Normally no command is accepted
+ * in this condition but just a few.
+ *
+ * no-monitor: Do not automatically propagate the command on MONITOR.
+ *
+ * no-slowlog: Do not automatically propagate the command to the slowlog.
+ *
+ * cluster-asking: Perform an implicit ASKING for this command, so the
+ * command will be accepted in cluster mode if the slot is marked
+ * as 'importing'.
+ *
+ * fast: Fast command: O(1) or O(log(N)) command that should never
+ * delay its execution as long as the kernel scheduler is giving
+ * us time. Note that commands that may trigger a DEL as a side
+ * effect (like SET) are not fast commands.
+ *
+ * The following additional flags are only used in order to put commands
+ * in a specific ACL category. Commands can have multiple ACL categories.
+ *
+ * @keyspace, @read, @write, @set, @sortedset, @list, @hash, @string, @bitmap,
+ * @hyperloglog, @stream, @admin, @fast, @slow, @pubsub, @blocking, @dangerous,
+ * @connection, @transaction, @scripting, @geo.
+ *
+ * Note that:
+ *
+ * 1) The read-only flag implies the @read ACL category.
+ * 2) The write flag implies the @write ACL category.
+ * 3) The fast flag implies the @fast ACL category.
+ * 4) The admin flag implies the @admin and @dangerous ACL category.
+ * 5) The pub-sub flag implies the @pubsub ACL category.
+ * 6) The lack of fast flag implies the @slow ACL category.
+ * 7) The non obvious "keyspace" category includes the commands
+ * that interact with keys without having anything to do with
+ * specific data structures, such as: DEL, RENAME, MOVE, SELECT,
+ * TYPE, EXPIRE*, PEXPIRE*, TTL, PTTL, ...
*/
+
struct redisCommand redisCommandTable[] = {
- {"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
- {"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
- {"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
- {"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
- {"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
- {"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
- {"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
- {"unlink",unlinkCommand,-2,"wF",0,NULL,1,-1,1,0,0},
- {"exists",existsCommand,-2,"rF",0,NULL,1,-1,1,0,0},
- {"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
- {"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"bitfield",bitfieldCommand,-2,"wm",0,NULL,1,1,1,0,0},
- {"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
- {"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
- {"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
- {"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0},
- {"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0},
- {"mget",mgetCommand,-2,"rF",0,NULL,1,-1,1,0,0},
- {"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
- {"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
- {"rpushx",rpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0},
- {"lpushx",lpushxCommand,-3,"wmF",0,NULL,1,1,1,0,0},
- {"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
- {"rpop",rpopCommand,2,"wF",0,NULL,1,1,1,0,0},
- {"lpop",lpopCommand,2,"wF",0,NULL,1,1,1,0,0},
- {"brpop",brpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
- {"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
- {"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
- {"llen",llenCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0},
- {"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0},
- {"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0},
- {"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0},
- {"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0},
- {"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0},
- {"sadd",saddCommand,-3,"wmF",0,NULL,1,1,1,0,0},
- {"srem",sremCommand,-3,"wF",0,NULL,1,1,1,0,0},
- {"smove",smoveCommand,4,"wF",0,NULL,1,2,1,0,0},
- {"sismember",sismemberCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"scard",scardCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"spop",spopCommand,-2,"wRF",0,NULL,1,1,1,0,0},
- {"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0},
- {"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0},
- {"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
- {"sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0},
- {"sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
- {"sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0},
- {"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
- {"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0},
- {"sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
- {"zadd",zaddCommand,-4,"wmF",0,NULL,1,1,1,0,0},
- {"zincrby",zincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
- {"zrem",zremCommand,-3,"wF",0,NULL,1,1,1,0,0},
- {"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
- {"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
- {"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
- {"zunionstore",zunionstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
- {"zinterstore",zinterstoreCommand,-4,"wm",0,zunionInterGetKeys,0,0,0,0,0},
- {"zrange",zrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zrangebyscore",zrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zcount",zcountCommand,4,"rF",0,NULL,1,1,1,0,0},
- {"zlexcount",zlexcountCommand,4,"rF",0,NULL,1,1,1,0,0},
- {"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"zcard",zcardCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"zscore",zscoreCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
- {"hset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
- {"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
- {"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"hmset",hsetCommand,-4,"wmF",0,NULL,1,1,1,0,0},
- {"hmget",hmgetCommand,-3,"rF",0,NULL,1,1,1,0,0},
- {"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
- {"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
- {"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
- {"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"hstrlen",hstrlenCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
- {"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
- {"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
- {"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
- {"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
- {"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
- {"decrby",decrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
- {"incrbyfloat",incrbyfloatCommand,3,"wmF",0,NULL,1,1,1,0,0},
- {"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
- {"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
- {"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
- {"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
- {"select",selectCommand,2,"lF",0,NULL,0,0,0,0,0},
- {"swapdb",swapdbCommand,3,"wF",0,NULL,0,0,0,0,0},
- {"move",moveCommand,3,"wF",0,NULL,1,1,1,0,0},
- {"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
- {"renamenx",renamenxCommand,3,"wF",0,NULL,1,2,1,0,0},
- {"expire",expireCommand,3,"wF",0,NULL,1,1,1,0,0},
- {"expireat",expireatCommand,3,"wF",0,NULL,1,1,1,0,0},
- {"pexpire",pexpireCommand,3,"wF",0,NULL,1,1,1,0,0},
- {"pexpireat",pexpireatCommand,3,"wF",0,NULL,1,1,1,0,0},
- {"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
- {"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
- {"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
- {"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0},
- {"ping",pingCommand,-1,"tF",0,NULL,0,0,0,0,0},
- {"echo",echoCommand,2,"F",0,NULL,0,0,0,0,0},
- {"save",saveCommand,1,"as",0,NULL,0,0,0,0,0},
- {"bgsave",bgsaveCommand,-1,"a",0,NULL,0,0,0,0,0},
- {"bgrewriteaof",bgrewriteaofCommand,1,"a",0,NULL,0,0,0,0,0},
- {"shutdown",shutdownCommand,-1,"alt",0,NULL,0,0,0,0,0},
- {"lastsave",lastsaveCommand,1,"RF",0,NULL,0,0,0,0,0},
- {"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"multi",multiCommand,1,"sF",0,NULL,0,0,0,0,0},
- {"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
- {"discard",discardCommand,1,"sF",0,NULL,0,0,0,0,0},
- {"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
- {"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},
- {"replconf",replconfCommand,-1,"aslt",0,NULL,0,0,0,0,0},
- {"flushdb",flushdbCommand,-1,"w",0,NULL,0,0,0,0,0},
- {"flushall",flushallCommand,-1,"w",0,NULL,0,0,0,0,0},
- {"sort",sortCommand,-2,"wm",0,sortGetKeys,1,1,1,0,0},
- {"info",infoCommand,-1,"lt",0,NULL,0,0,0,0,0},
- {"monitor",monitorCommand,1,"as",0,NULL,0,0,0,0,0},
- {"ttl",ttlCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"touch",touchCommand,-2,"rF",0,NULL,1,1,1,0,0},
- {"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
- {"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
- {"role",roleCommand,1,"lst",0,NULL,0,0,0,0,0},
- {"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
- {"config",configCommand,-2,"lat",0,NULL,0,0,0,0,0},
- {"subscribe",subscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
- {"unsubscribe",unsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
- {"psubscribe",psubscribeCommand,-2,"pslt",0,NULL,0,0,0,0,0},
- {"punsubscribe",punsubscribeCommand,-1,"pslt",0,NULL,0,0,0,0,0},
- {"publish",publishCommand,3,"pltF",0,NULL,0,0,0,0,0},
- {"pubsub",pubsubCommand,-2,"pltR",0,NULL,0,0,0,0,0},
- {"watch",watchCommand,-2,"sF",0,NULL,1,-1,1,0,0},
- {"unwatch",unwatchCommand,1,"sF",0,NULL,0,0,0,0,0},
- {"cluster",clusterCommand,-2,"a",0,NULL,0,0,0,0,0},
- {"restore",restoreCommand,-4,"wm",0,NULL,1,1,1,0,0},
- {"restore-asking",restoreCommand,-4,"wmk",0,NULL,1,1,1,0,0},
- {"migrate",migrateCommand,-6,"w",0,migrateGetKeys,0,0,0,0,0},
- {"asking",askingCommand,1,"F",0,NULL,0,0,0,0,0},
- {"readonly",readonlyCommand,1,"F",0,NULL,0,0,0,0,0},
- {"readwrite",readwriteCommand,1,"F",0,NULL,0,0,0,0,0},
- {"dump",dumpCommand,2,"r",0,NULL,1,1,1,0,0},
- {"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
- {"memory",memoryCommand,-2,"r",0,NULL,0,0,0,0,0},
- {"client",clientCommand,-2,"as",0,NULL,0,0,0,0,0},
- {"eval",evalCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
- {"evalsha",evalShaCommand,-3,"s",0,evalGetKeys,0,0,0,0,0},
- {"slowlog",slowlogCommand,-2,"a",0,NULL,0,0,0,0,0},
- {"script",scriptCommand,-2,"s",0,NULL,0,0,0,0,0},
- {"time",timeCommand,1,"RF",0,NULL,0,0,0,0,0},
- {"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
- {"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
- {"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
- {"wait",waitCommand,3,"s",0,NULL,0,0,0,0,0},
- {"command",commandCommand,0,"lt",0,NULL,0,0,0,0,0},
- {"geoadd",geoaddCommand,-5,"wm",0,NULL,1,1,1,0,0},
- {"georadius",georadiusCommand,-6,"w",0,georadiusGetKeys,1,1,1,0,0},
- {"georadius_ro",georadiusroCommand,-6,"r",0,georadiusGetKeys,1,1,1,0,0},
- {"georadiusbymember",georadiusbymemberCommand,-5,"w",0,georadiusGetKeys,1,1,1,0,0},
- {"georadiusbymember_ro",georadiusbymemberroCommand,-5,"r",0,georadiusGetKeys,1,1,1,0,0},
- {"geohash",geohashCommand,-2,"r",0,NULL,1,1,1,0,0},
- {"geopos",geoposCommand,-2,"r",0,NULL,1,1,1,0,0},
- {"geodist",geodistCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"pfselftest",pfselftestCommand,1,"a",0,NULL,0,0,0,0,0},
- {"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
- {"pfcount",pfcountCommand,-2,"r",0,NULL,1,-1,1,0,0},
- {"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
- {"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
- {"xadd",xaddCommand,-5,"wmF",0,NULL,1,1,1,0,0},
- {"xrange",xrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"xrevrange",xrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
- {"xlen",xlenCommand,2,"rF",0,NULL,1,1,1,0,0},
- {"xread",xreadCommand,-3,"rs",0,xreadGetKeys,1,1,1,0,0},
- {"post",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
- {"host:",securityWarningCommand,-1,"lt",0,NULL,0,0,0,0,0},
- {"latency",latencyCommand,-2,"aslt",0,NULL,0,0,0,0,0}
+ {"module",moduleCommand,-2,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"get",getCommand,2,
+ "read-only fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ /* Note that we can't flag set as fast, since it may perform an
+ * implicit DEL of a large key. */
+ {"set",setCommand,-3,
+ "write use-memory @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"setnx",setnxCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"setex",setexCommand,4,
+ "write use-memory @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"psetex",psetexCommand,4,
+ "write use-memory @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"append",appendCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"strlen",strlenCommand,2,
+ "read-only fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"del",delCommand,-2,
+ "write @keyspace",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"unlink",unlinkCommand,-2,
+ "write fast @keyspace",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"exists",existsCommand,-2,
+ "read-only fast @keyspace",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"setbit",setbitCommand,4,
+ "write use-memory @bitmap",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"getbit",getbitCommand,3,
+ "read-only fast @bitmap",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"bitfield",bitfieldCommand,-2,
+ "write use-memory @bitmap",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"setrange",setrangeCommand,4,
+ "write use-memory @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"getrange",getrangeCommand,4,
+ "read-only @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"substr",getrangeCommand,4,
+ "read-only @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"incr",incrCommand,2,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"decr",decrCommand,2,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"mget",mgetCommand,-2,
+ "read-only fast @string",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"rpush",rpushCommand,-3,
+ "write use-memory fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lpush",lpushCommand,-3,
+ "write use-memory fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"rpushx",rpushxCommand,-3,
+ "write use-memory fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lpushx",lpushxCommand,-3,
+ "write use-memory fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"linsert",linsertCommand,5,
+ "write use-memory @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"rpop",rpopCommand,2,
+ "write fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lpop",lpopCommand,2,
+ "write fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"brpop",brpopCommand,-3,
+ "write no-script @list @blocking",
+ 0,NULL,1,-2,1,0,0,0},
+
+ {"brpoplpush",brpoplpushCommand,4,
+ "write use-memory no-script @list @blocking",
+ 0,NULL,1,2,1,0,0,0},
+
+ {"blpop",blpopCommand,-3,
+ "write no-script @list @blocking",
+ 0,NULL,1,-2,1,0,0,0},
+
+ {"llen",llenCommand,2,
+ "read-only fast @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lindex",lindexCommand,3,
+ "read-only @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lset",lsetCommand,4,
+ "write use-memory @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lrange",lrangeCommand,4,
+ "read-only @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"ltrim",ltrimCommand,4,
+ "write @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"lrem",lremCommand,4,
+ "write @list",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"rpoplpush",rpoplpushCommand,3,
+ "write use-memory @list",
+ 0,NULL,1,2,1,0,0,0},
+
+ {"sadd",saddCommand,-3,
+ "write use-memory fast @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"srem",sremCommand,-3,
+ "write fast @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"smove",smoveCommand,4,
+ "write fast @set",
+ 0,NULL,1,2,1,0,0,0},
+
+ {"sismember",sismemberCommand,3,
+ "read-only fast @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"scard",scardCommand,2,
+ "read-only fast @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"spop",spopCommand,-2,
+ "write random fast @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"srandmember",srandmemberCommand,-2,
+ "read-only random @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"sinter",sinterCommand,-2,
+ "read-only to-sort @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"sinterstore",sinterstoreCommand,-3,
+ "write use-memory @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"sunion",sunionCommand,-2,
+ "read-only to-sort @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"sunionstore",sunionstoreCommand,-3,
+ "write use-memory @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"sdiff",sdiffCommand,-2,
+ "read-only to-sort @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"sdiffstore",sdiffstoreCommand,-3,
+ "write use-memory @set",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"smembers",sinterCommand,2,
+ "read-only to-sort @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"sscan",sscanCommand,-3,
+ "read-only random @set",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zadd",zaddCommand,-4,
+ "write use-memory fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zincrby",zincrbyCommand,4,
+ "write use-memory fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrem",zremCommand,-3,
+ "write fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zremrangebyscore",zremrangebyscoreCommand,4,
+ "write @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zremrangebyrank",zremrangebyrankCommand,4,
+ "write @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zremrangebylex",zremrangebylexCommand,4,
+ "write @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zunionstore",zunionstoreCommand,-4,
+ "write use-memory @sortedset",
+ 0,zunionInterGetKeys,0,0,0,0,0,0},
+
+ {"zinterstore",zinterstoreCommand,-4,
+ "write use-memory @sortedset",
+ 0,zunionInterGetKeys,0,0,0,0,0,0},
+
+ {"zrange",zrangeCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrangebyscore",zrangebyscoreCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrevrangebyscore",zrevrangebyscoreCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrangebylex",zrangebylexCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrevrangebylex",zrevrangebylexCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zcount",zcountCommand,4,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zlexcount",zlexcountCommand,4,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrevrange",zrevrangeCommand,-4,
+ "read-only @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zcard",zcardCommand,2,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zscore",zscoreCommand,3,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrank",zrankCommand,3,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zrevrank",zrevrankCommand,3,
+ "read-only fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zscan",zscanCommand,-3,
+ "read-only random @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zpopmin",zpopminCommand,-2,
+ "write fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"zpopmax",zpopmaxCommand,-2,
+ "write fast @sortedset",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"bzpopmin",bzpopminCommand,-3,
+ "write no-script fast @sortedset @blocking",
+ 0,NULL,1,-2,1,0,0,0},
+
+ {"bzpopmax",bzpopmaxCommand,-3,
+ "write no-script fast @sortedset @blocking",
+ 0,NULL,1,-2,1,0,0,0},
+
+ {"hset",hsetCommand,-4,
+ "write use-memory fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hsetnx",hsetnxCommand,4,
+ "write use-memory fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hget",hgetCommand,3,
+ "read-only fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hmset",hsetCommand,-4,
+ "write use-memory fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hmget",hmgetCommand,-3,
+ "read-only fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hincrby",hincrbyCommand,4,
+ "write use-memory fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hincrbyfloat",hincrbyfloatCommand,4,
+ "write use-memory fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hdel",hdelCommand,-3,
+ "write fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hlen",hlenCommand,2,
+ "read-only fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hstrlen",hstrlenCommand,3,
+ "read-only fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hkeys",hkeysCommand,2,
+ "read-only to-sort @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hvals",hvalsCommand,2,
+ "read-only to-sort @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hgetall",hgetallCommand,2,
+ "read-only random @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hexists",hexistsCommand,3,
+ "read-only fast @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"hscan",hscanCommand,-3,
+ "read-only random @hash",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"incrby",incrbyCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"decrby",decrbyCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"incrbyfloat",incrbyfloatCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"getset",getsetCommand,3,
+ "write use-memory fast @string",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"mset",msetCommand,-3,
+ "write use-memory @string",
+ 0,NULL,1,-1,2,0,0,0},
+
+ {"msetnx",msetnxCommand,-3,
+ "write use-memory @string",
+ 0,NULL,1,-1,2,0,0,0},
+
+ {"randomkey",randomkeyCommand,1,
+ "read-only random @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"select",selectCommand,2,
+ "ok-loading fast @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"swapdb",swapdbCommand,3,
+ "write fast @keyspace @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"move",moveCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ /* Like for SET, we can't mark rename as a fast command because
+ * overwriting the target key may result in an implicit slow DEL. */
+ {"rename",renameCommand,3,
+ "write @keyspace",
+ 0,NULL,1,2,1,0,0,0},
+
+ {"renamenx",renamenxCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,2,1,0,0,0},
+
+ {"expire",expireCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"expireat",expireatCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"pexpire",pexpireCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"pexpireat",pexpireatCommand,3,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"keys",keysCommand,2,
+ "read-only to-sort @keyspace @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"scan",scanCommand,-2,
+ "read-only random @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"dbsize",dbsizeCommand,1,
+ "read-only fast @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"auth",authCommand,-2,
+ "no-script ok-loading ok-stale fast no-monitor no-slowlog @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ /* We don't allow PING during loading since in Redis PING is used as
+ * failure detection, and a loading server is considered to be
+ * not available. */
+ {"ping",pingCommand,-1,
+ "ok-stale fast @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"echo",echoCommand,2,
+ "read-only fast @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"save",saveCommand,1,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"bgsave",bgsaveCommand,-1,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"bgrewriteaof",bgrewriteaofCommand,1,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"shutdown",shutdownCommand,-1,
+ "admin no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"lastsave",lastsaveCommand,1,
+ "read-only random fast @admin @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"type",typeCommand,2,
+ "read-only fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"multi",multiCommand,1,
+ "no-script fast @transaction",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"exec",execCommand,1,
+ "no-script no-monitor no-slowlog @transaction",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"discard",discardCommand,1,
+ "no-script fast @transaction",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"sync",syncCommand,1,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"psync",syncCommand,3,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"replconf",replconfCommand,-1,
+ "admin no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"flushdb",flushdbCommand,-1,
+ "write @keyspace @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"flushall",flushallCommand,-1,
+ "write @keyspace @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"sort",sortCommand,-2,
+ "write use-memory @list @set @sortedset @dangerous",
+ 0,sortGetKeys,1,1,1,0,0,0},
+
+ {"info",infoCommand,-1,
+ "ok-loading ok-stale random @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"monitor",monitorCommand,1,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"ttl",ttlCommand,2,
+ "read-only fast random @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"touch",touchCommand,-2,
+ "read-only fast @keyspace",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"pttl",pttlCommand,2,
+ "read-only fast random @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"persist",persistCommand,2,
+ "write fast @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"slaveof",replicaofCommand,3,
+ "admin no-script ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"replicaof",replicaofCommand,3,
+ "admin no-script ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"role",roleCommand,1,
+ "ok-loading ok-stale no-script fast read-only @dangerous",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"debug",debugCommand,-2,
+ "admin no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"config",configCommand,-2,
+ "admin ok-loading ok-stale no-script",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"subscribe",subscribeCommand,-2,
+ "pub-sub no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"unsubscribe",unsubscribeCommand,-1,
+ "pub-sub no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"psubscribe",psubscribeCommand,-2,
+ "pub-sub no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"punsubscribe",punsubscribeCommand,-1,
+ "pub-sub no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"publish",publishCommand,3,
+ "pub-sub ok-loading ok-stale fast",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"pubsub",pubsubCommand,-2,
+ "pub-sub ok-loading ok-stale random",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"watch",watchCommand,-2,
+ "no-script fast @transaction",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"unwatch",unwatchCommand,1,
+ "no-script fast @transaction",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"cluster",clusterCommand,-2,
+ "admin ok-stale random",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"restore",restoreCommand,-4,
+ "write use-memory @keyspace @dangerous",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"restore-asking",restoreCommand,-4,
+ "write use-memory cluster-asking @keyspace @dangerous",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"migrate",migrateCommand,-6,
+ "write random @keyspace @dangerous",
+ 0,migrateGetKeys,0,0,0,0,0,0},
+
+ {"asking",askingCommand,1,
+ "fast @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"readonly",readonlyCommand,1,
+ "fast @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"readwrite",readwriteCommand,1,
+ "fast @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"dump",dumpCommand,2,
+ "read-only random @keyspace",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"object",objectCommand,-2,
+ "read-only random @keyspace",
+ 0,NULL,2,2,1,0,0,0},
+
+ {"memory",memoryCommand,-2,
+ "random read-only",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"client",clientCommand,-2,
+ "admin no-script random @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"hello",helloCommand,-2,
+ "no-script fast no-monitor no-slowlog @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ /* EVAL can modify the dataset, however it is not flagged as a write
+ * command since we do the check while running commands from Lua. */
+ {"eval",evalCommand,-3,
+ "no-script @scripting",
+ 0,evalGetKeys,0,0,0,0,0,0},
+
+ {"evalsha",evalShaCommand,-3,
+ "no-script @scripting",
+ 0,evalGetKeys,0,0,0,0,0,0},
+
+ {"slowlog",slowlogCommand,-2,
+ "admin random",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"script",scriptCommand,-2,
+ "no-script @scripting",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"time",timeCommand,1,
+ "read-only random fast",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"bitop",bitopCommand,-4,
+ "write use-memory @bitmap",
+ 0,NULL,2,-1,1,0,0,0},
+
+ {"bitcount",bitcountCommand,-2,
+ "read-only @bitmap",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"bitpos",bitposCommand,-3,
+ "read-only @bitmap",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"wait",waitCommand,3,
+ "no-script @keyspace",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"command",commandCommand,-1,
+ "ok-loading ok-stale random @connection",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"geoadd",geoaddCommand,-5,
+ "write use-memory @geo",
+ 0,NULL,1,1,1,0,0,0},
+
+ /* GEORADIUS has store options that may write. */
+ {"georadius",georadiusCommand,-6,
+ "write @geo",
+ 0,georadiusGetKeys,1,1,1,0,0,0},
+
+ {"georadius_ro",georadiusroCommand,-6,
+ "read-only @geo",
+ 0,georadiusGetKeys,1,1,1,0,0,0},
+
+ {"georadiusbymember",georadiusbymemberCommand,-5,
+ "write @geo",
+ 0,georadiusGetKeys,1,1,1,0,0,0},
+
+ {"georadiusbymember_ro",georadiusbymemberroCommand,-5,
+ "read-only @geo",
+ 0,georadiusGetKeys,1,1,1,0,0,0},
+
+ {"geohash",geohashCommand,-2,
+ "read-only @geo",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"geopos",geoposCommand,-2,
+ "read-only @geo",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"geodist",geodistCommand,-4,
+ "read-only @geo",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"pfselftest",pfselftestCommand,1,
+ "admin @hyperloglog",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"pfadd",pfaddCommand,-2,
+ "write use-memory fast @hyperloglog",
+ 0,NULL,1,1,1,0,0,0},
+
+ /* Technically speaking PFCOUNT may change the key since it changes the
+ * final bytes in the HyperLogLog representation. However in this case
+ * we claim that the representation, even if accessible, is an internal
+ * affair, and the command is semantically read only. */
+ {"pfcount",pfcountCommand,-2,
+ "read-only @hyperloglog",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"pfmerge",pfmergeCommand,-2,
+ "write use-memory @hyperloglog",
+ 0,NULL,1,-1,1,0,0,0},
+
+ {"pfdebug",pfdebugCommand,-3,
+ "admin write",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"xadd",xaddCommand,-5,
+ "write use-memory fast random @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xrange",xrangeCommand,-4,
+ "read-only @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xrevrange",xrevrangeCommand,-4,
+ "read-only @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xlen",xlenCommand,2,
+ "read-only fast @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xread",xreadCommand,-4,
+ "read-only no-script @stream @blocking",
+ 0,xreadGetKeys,1,1,1,0,0,0},
+
+ {"xreadgroup",xreadCommand,-7,
+ "write no-script @stream @blocking",
+ 0,xreadGetKeys,1,1,1,0,0,0},
+
+ {"xgroup",xgroupCommand,-2,
+ "write use-memory @stream",
+ 0,NULL,2,2,1,0,0,0},
+
+ {"xsetid",xsetidCommand,3,
+ "write use-memory fast @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xack",xackCommand,-4,
+ "write fast random @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xpending",xpendingCommand,-3,
+ "read-only random @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xclaim",xclaimCommand,-6,
+ "write random fast @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xinfo",xinfoCommand,-2,
+ "read-only random @stream",
+ 0,NULL,2,2,1,0,0,0},
+
+ {"xdel",xdelCommand,-3,
+ "write fast @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"xtrim",xtrimCommand,-2,
+ "write random @stream",
+ 0,NULL,1,1,1,0,0,0},
+
+ {"post",securityWarningCommand,-1,
+ "ok-loading ok-stale read-only",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"host:",securityWarningCommand,-1,
+ "ok-loading ok-stale read-only",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"latency",latencyCommand,-2,
+ "admin no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"lolwut",lolwutCommand,-1,
+ "read-only fast",
+ 0,NULL,0,0,0,0,0,0},
+
+ {"acl",aclCommand,-2,
+ "admin no-script ok-loading ok-stale",
+ 0,NULL,0,0,0,0,0,0}
};
/*============================ Utility functions ============================ */
+/* We use a private localtime implementation which is fork-safe. The logging
+ * function of Redis may be called from other threads. */
+void nolocks_localtime(struct tm *tmp, time_t t, time_t tz, int dst);
+
/* Low level logging. To use only for very big messages, otherwise
* serverLog() is to prefer. */
void serverLogRaw(int level, const char *msg) {
@@ -339,7 +1034,9 @@ void serverLogRaw(int level, const char *msg) {
pid_t pid = getpid();
gettimeofday(&tv,NULL);
- off = strftime(buf,sizeof(buf),"%d %b %H:%M:%S.",localtime(&tv.tv_sec));
+ struct tm tm;
+ nolocks_localtime(&tm,tv.tv_sec,server.timezone,server.daylight_active);
+ off = strftime(buf,sizeof(buf),"%d %b %Y %H:%M:%S.",&tm);
snprintf(buf+off,sizeof(buf)-off,"%03d",(int)tv.tv_usec/1000);
if (server.sentinel_mode) {
role_char = 'X'; /* Sentinel. */
@@ -752,12 +1449,18 @@ int incrementallyRehash(int dbid) {
* for dict.c to resize the hash tables accordingly to the fact we have o not
* running childs. */
void updateDictResizePolicy(void) {
- if (server.rdb_child_pid == -1 && server.aof_child_pid == -1)
+ if (!hasActiveChildProcess())
dictEnableResize();
else
dictDisableResize();
}
+int hasActiveChildProcess() {
+ return server.rdb_child_pid != -1 ||
+ server.aof_child_pid != -1 ||
+ server.module_child_pid != -1;
+}
+
/* ======================= Cron: called every 100 ms ======================== */
/* Add a sample to the operations per second array of samples. */
@@ -833,28 +1536,116 @@ int clientsCronResizeQueryBuffer(client *c) {
/* There are two conditions to resize the query buffer:
* 1) Query buffer is > BIG_ARG and too big for latest peak.
- * 2) Client is inactive and the buffer is bigger than 1k. */
- if (((querybuf_size > PROTO_MBULK_BIG_ARG) &&
- (querybuf_size/(c->querybuf_peak+1)) > 2) ||
- (querybuf_size > 1024 && idletime > 2))
+ * 2) Query buffer is > BIG_ARG and client is idle. */
+ if (querybuf_size > PROTO_MBULK_BIG_ARG &&
+ ((querybuf_size/(c->querybuf_peak+1)) > 2 ||
+ idletime > 2))
{
- /* Only resize the query buffer if it is actually wasting space. */
- if (sdsavail(c->querybuf) > 1024) {
+ /* Only resize the query buffer if it is actually wasting
+ * at least a few kbytes. */
+ if (sdsavail(c->querybuf) > 1024*4) {
c->querybuf = sdsRemoveFreeSpace(c->querybuf);
}
}
/* Reset the peak again to capture the peak memory usage in the next
* cycle. */
c->querybuf_peak = 0;
+
+ /* Clients representing masters also use a "pending query buffer" that
+ * is the yet not applied part of the stream we are reading. Such buffer
+ * also needs resizing from time to time, otherwise after a very large
+ * transfer (a huge value or a big MIGRATE operation) it will keep using
+ * a lot of memory. */
+ if (c->flags & CLIENT_MASTER) {
+ /* There are two conditions to resize the pending query buffer:
+ * 1) Pending Query buffer is > LIMIT_PENDING_QUERYBUF.
+ * 2) Used length is smaller than pending_querybuf_size/2 */
+ size_t pending_querybuf_size = sdsAllocSize(c->pending_querybuf);
+ if(pending_querybuf_size > LIMIT_PENDING_QUERYBUF &&
+ sdslen(c->pending_querybuf) < (pending_querybuf_size/2))
+ {
+ c->pending_querybuf = sdsRemoveFreeSpace(c->pending_querybuf);
+ }
+ }
return 0;
}
+/* This function is used in order to track clients using the biggest amount
+ * of memory in the latest few seconds. This way we can provide such information
+ * in the INFO output (clients section), without having to do an O(N) scan for
+ * all the clients.
+ *
+ * This is how it works. We have an array of CLIENTS_PEAK_MEM_USAGE_SLOTS slots
+ * where we track, for each, the biggest client output and input buffers we
+ * saw in that slot. Every slot correspond to one of the latest seconds, since
+ * the array is indexed by doing UNIXTIME % CLIENTS_PEAK_MEM_USAGE_SLOTS.
+ *
+ * When we want to know what was recently the peak memory usage, we just scan
+ * such few slots searching for the maximum value. */
+#define CLIENTS_PEAK_MEM_USAGE_SLOTS 8
+size_t ClientsPeakMemInput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
+size_t ClientsPeakMemOutput[CLIENTS_PEAK_MEM_USAGE_SLOTS];
+
+int clientsCronTrackExpansiveClients(client *c) {
+ size_t in_usage = sdsAllocSize(c->querybuf);
+ size_t out_usage = getClientOutputBufferMemoryUsage(c);
+ int i = server.unixtime % CLIENTS_PEAK_MEM_USAGE_SLOTS;
+ int zeroidx = (i+1) % CLIENTS_PEAK_MEM_USAGE_SLOTS;
+
+ /* Always zero the next sample, so that when we switch to that second, we'll
+ * only register samples that are greater in that second without considering
+ * the history of such slot.
+ *
+ * Note: our index may jump to any random position if serverCron() is not
+ * called for some reason with the normal frequency, for instance because
+ * some slow command is called taking multiple seconds to execute. In that
+ * case our array may end containing data which is potentially older
+ * than CLIENTS_PEAK_MEM_USAGE_SLOTS seconds: however this is not a problem
+ * since here we want just to track if "recently" there were very expansive
+ * clients from the POV of memory usage. */
+ ClientsPeakMemInput[zeroidx] = 0;
+ ClientsPeakMemOutput[zeroidx] = 0;
+
+ /* Track the biggest values observed so far in this slot. */
+ if (in_usage > ClientsPeakMemInput[i]) ClientsPeakMemInput[i] = in_usage;
+ if (out_usage > ClientsPeakMemOutput[i]) ClientsPeakMemOutput[i] = out_usage;
+
+ return 0; /* This function never terminates the client. */
+}
+
+/* Return the max samples in the memory usage of clients tracked by
+ * the function clientsCronTrackExpansiveClients(). */
+void getExpansiveClientsInfo(size_t *in_usage, size_t *out_usage) {
+ size_t i = 0, o = 0;
+ for (int j = 0; j < CLIENTS_PEAK_MEM_USAGE_SLOTS; j++) {
+ if (ClientsPeakMemInput[j] > i) i = ClientsPeakMemInput[j];
+ if (ClientsPeakMemOutput[j] > o) o = ClientsPeakMemOutput[j];
+ }
+ *in_usage = i;
+ *out_usage = o;
+}
+
+/* This function is called by serverCron() and is used in order to perform
+ * operations on clients that are important to perform constantly. For instance
+ * we use this function in order to disconnect clients after a timeout, including
+ * clients blocked in some blocking command with a non-zero timeout.
+ *
+ * The function makes some effort to process all the clients every second, even
+ * if this cannot be strictly guaranteed, since serverCron() may be called with
+ * an actual frequency lower than server.hz in case of latency events like slow
+ * commands.
+ *
+ * It is very important for this function, and the functions it calls, to be
+ * very fast: sometimes Redis has tens of hundreds of connected clients, and the
+ * default server.hz value is 10, so sometimes here we need to process thousands
+ * of clients per second, turning this function into a source of latency.
+ */
#define CLIENTS_CRON_MIN_ITERATIONS 5
void clientsCron(void) {
- /* Make sure to process at least numclients/server.hz of clients
- * per call. Since this function is called server.hz times per second
- * we are sure that in the worst case we process all the clients in 1
- * second. */
+ /* Try to process at least numclients/server.hz of clients
+ * per call. Since normally (if there are no big latency events) this
+ * function is called server.hz times per second, in the average case we
+ * process all the clients in 1 second. */
int numclients = listLength(server.clients);
int iterations = numclients/server.hz;
mstime_t now = mstime();
@@ -881,6 +1672,7 @@ void clientsCron(void) {
* terminated. */
if (clientsCronHandleTimeout(c,now)) continue;
if (clientsCronResizeQueryBuffer(c)) continue;
+ if (clientsCronTrackExpansiveClients(c)) continue;
}
}
@@ -890,10 +1682,12 @@ void clientsCron(void) {
void databasesCron(void) {
/* Expire keys by random sampling. Not required for slaves
* as master will synthesize DELs for us. */
- if (server.active_expire_enabled && server.masterhost == NULL) {
- activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
- } else if (server.masterhost != NULL) {
- expireSlaveKeys();
+ if (server.active_expire_enabled) {
+ if (server.masterhost == NULL) {
+ activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);
+ } else {
+ expireSlaveKeys();
+ }
}
/* Defrag keys gradually. */
@@ -903,7 +1697,7 @@ void databasesCron(void) {
/* Perform hash tables rehashing if needed, but only if there are no
* other processes saving the DB on disk. Otherwise rehashing is bad
* as will cause a lot of copy-on-write of memory pages. */
- if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) {
+ if (!hasActiveChildProcess()) {
/* We use global counters so if we stop the computation at a given
* DB we'll be able to start from the successive in the next
* cron loop iteration. */
@@ -944,9 +1738,74 @@ void databasesCron(void) {
* every object access, and accuracy is not needed. To access a global var is
* a lot faster than calling time(NULL) */
void updateCachedTime(void) {
- time_t unixtime = time(NULL);
- atomicSet(server.unixtime,unixtime);
+ server.unixtime = time(NULL);
server.mstime = mstime();
+
+ /* To get information about daylight saving time, we need to call
+ * localtime_r and cache the result. However calling localtime_r in this
+ * context is safe since we will never fork() while here, in the main
+ * thread. The logging function will call a thread safe version of
+ * localtime that has no locks. */
+ struct tm tm;
+ time_t ut = server.unixtime;
+ localtime_r(&ut,&tm);
+ server.daylight_active = tm.tm_isdst;
+}
+
+void checkChildrenDone(void) {
+ int statloc;
+ pid_t pid;
+
+ /* If we have a diskless rdb child (note that we support only one concurrent
+ * child), we want to avoid collecting it's exit status and acting on it
+ * as long as we didn't finish to drain the pipe, since then we're at risk
+ * of starting a new fork and a new pipe before we're done with the previous
+ * one. */
+ if (server.rdb_child_pid != -1 && server.rdb_pipe_conns)
+ return;
+
+ if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
+ int exitcode = WEXITSTATUS(statloc);
+ int bysignal = 0;
+
+ if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
+
+ /* sigKillChildHandler catches the signal and calls exit(), but we
+ * must make sure not to flag lastbgsave_status, etc incorrectly.
+ * We could directly terminate the child process via SIGUSR1
+ * without handling it, but in this case Valgrind will log an
+ * annoying error. */
+ if (exitcode == SERVER_CHILD_NOERROR_RETVAL) {
+ bysignal = SIGUSR1;
+ exitcode = 1;
+ }
+
+ if (pid == -1) {
+ serverLog(LL_WARNING,"wait3() returned an error: %s. "
+ "rdb_child_pid = %d, aof_child_pid = %d, module_child_pid = %d",
+ strerror(errno),
+ (int) server.rdb_child_pid,
+ (int) server.aof_child_pid,
+ (int) server.module_child_pid);
+ } else if (pid == server.rdb_child_pid) {
+ backgroundSaveDoneHandler(exitcode,bysignal);
+ if (!bysignal && exitcode == 0) receiveChildInfo();
+ } else if (pid == server.aof_child_pid) {
+ backgroundRewriteDoneHandler(exitcode,bysignal);
+ if (!bysignal && exitcode == 0) receiveChildInfo();
+ } else if (pid == server.module_child_pid) {
+ ModuleForkDoneHandler(exitcode,bysignal);
+ if (!bysignal && exitcode == 0) receiveChildInfo();
+ } else {
+ if (!ldbRemoveChild(pid)) {
+ serverLog(LL_WARNING,
+ "Warning, detected child with unmatched pid: %ld",
+ (long)pid);
+ }
+ }
+ updateDictResizePolicy();
+ closeChildInfoPipe();
+ }
}
/* This is our timer interrupt, called server.hz times per second.
@@ -981,6 +1840,21 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Update the time cache. */
updateCachedTime();
+ server.hz = server.config_hz;
+ /* Adapt the server.hz value to the number of configured clients. If we have
+ * many clients, we want to call serverCron() with an higher frequency. */
+ if (server.dynamic_hz) {
+ while (listLength(server.clients) / server.hz >
+ MAX_CLIENTS_PER_CLOCK_TICK)
+ {
+ server.hz *= 2;
+ if (server.hz > CONFIG_MAX_HZ) {
+ server.hz = CONFIG_MAX_HZ;
+ break;
+ }
+ }
+ }
+
run_with_period(100) {
trackInstantaneousMetric(STATS_METRIC_COMMAND,server.stat_numcommands);
trackInstantaneousMetric(STATS_METRIC_NET_INPUT,
@@ -1000,15 +1874,39 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
*
* Note that you can change the resolution altering the
* LRU_CLOCK_RESOLUTION define. */
- unsigned long lruclock = getLRUClock();
- atomicSet(server.lruclock,lruclock);
+ server.lruclock = getLRUClock();
/* Record the max memory used since the server was started. */
if (zmalloc_used_memory() > server.stat_peak_memory)
server.stat_peak_memory = zmalloc_used_memory();
- /* Sample the RSS here since this is a relatively slow call. */
- server.resident_set_size = zmalloc_get_rss();
+ run_with_period(100) {
+ /* Sample the RSS and other metrics here since this is a relatively slow call.
+ * We must sample the zmalloc_used at the same time we take the rss, otherwise
+ * the frag ratio calculate may be off (ratio of two samples at different times) */
+ server.cron_malloc_stats.process_rss = zmalloc_get_rss();
+ server.cron_malloc_stats.zmalloc_used = zmalloc_used_memory();
+ /* Sampling the allcator info can be slow too.
+ * The fragmentation ratio it'll show is potentically more accurate
+ * it excludes other RSS pages such as: shared libraries, LUA and other non-zmalloc
+ * allocations, and allocator reserved pages that can be pursed (all not actual frag) */
+ zmalloc_get_allocator_info(&server.cron_malloc_stats.allocator_allocated,
+ &server.cron_malloc_stats.allocator_active,
+ &server.cron_malloc_stats.allocator_resident);
+ /* in case the allocator isn't providing these stats, fake them so that
+ * fragmention info still shows some (inaccurate metrics) */
+ if (!server.cron_malloc_stats.allocator_resident) {
+ /* LUA memory isn't part of zmalloc_used, but it is part of the process RSS,
+ * so we must desuct it in order to be able to calculate correct
+ * "allocator fragmentation" ratio */
+ size_t lua_memory = lua_gc(server.lua,LUA_GCCOUNT,0)*1024LL;
+ server.cron_malloc_stats.allocator_resident = server.cron_malloc_stats.process_rss - lua_memory;
+ }
+ if (!server.cron_malloc_stats.allocator_active)
+ server.cron_malloc_stats.allocator_active = server.cron_malloc_stats.allocator_resident;
+ if (!server.cron_malloc_stats.allocator_allocated)
+ server.cron_malloc_stats.allocator_allocated = server.cron_malloc_stats.zmalloc_used;
+ }
/* We received a SIGTERM, shutting down here in a safe way, as it is
* not ok doing so inside the signal handler. */
@@ -1037,7 +1935,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
if (!server.sentinel_mode) {
run_with_period(5000) {
serverLog(LL_VERBOSE,
- "%lu clients connected (%lu slaves), %zu bytes in use",
+ "%lu clients connected (%lu replicas), %zu bytes in use",
listLength(server.clients)-listLength(server.slaves),
listLength(server.slaves),
zmalloc_used_memory());
@@ -1052,51 +1950,20 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
/* Start a scheduled AOF rewrite if this was requested by the user while
* a BGSAVE was in progress. */
- if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
+ if (!hasActiveChildProcess() &&
server.aof_rewrite_scheduled)
{
rewriteAppendOnlyFileBackground();
}
/* Check if a background saving or AOF rewrite in progress terminated. */
- if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||
- ldbPendingChildren())
+ if (hasActiveChildProcess() || ldbPendingChildren())
{
- int statloc;
- pid_t pid;
-
- if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
- int exitcode = WEXITSTATUS(statloc);
- int bysignal = 0;
-
- if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
-
- if (pid == -1) {
- serverLog(LL_WARNING,"wait3() returned an error: %s. "
- "rdb_child_pid = %d, aof_child_pid = %d",
- strerror(errno),
- (int) server.rdb_child_pid,
- (int) server.aof_child_pid);
- } else if (pid == server.rdb_child_pid) {
- backgroundSaveDoneHandler(exitcode,bysignal);
- if (!bysignal && exitcode == 0) receiveChildInfo();
- } else if (pid == server.aof_child_pid) {
- backgroundRewriteDoneHandler(exitcode,bysignal);
- if (!bysignal && exitcode == 0) receiveChildInfo();
- } else {
- if (!ldbRemoveChild(pid)) {
- serverLog(LL_WARNING,
- "Warning, detected child with unmatched pid: %ld",
- (long)pid);
- }
- }
- updateDictResizePolicy();
- closeChildInfoPipe();
- }
+ checkChildrenDone();
} else {
/* If there is not a background saving/rewrite in progress check if
* we have to save/rewrite now. */
- for (j = 0; j < server.saveparamslen; j++) {
+ for (j = 0; j < server.saveparamslen; j++) {
struct saveparam *sp = server.saveparams+j;
/* Save if we reached the given amount of changes,
@@ -1116,23 +1983,22 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
rdbSaveBackground(server.rdb_filename,rsiptr);
break;
}
- }
-
- /* Trigger an AOF rewrite if needed. */
- if (server.aof_state == AOF_ON &&
- server.rdb_child_pid == -1 &&
- server.aof_child_pid == -1 &&
- server.aof_rewrite_perc &&
- server.aof_current_size > server.aof_rewrite_min_size)
- {
+ }
+
+ /* Trigger an AOF rewrite if needed. */
+ if (server.aof_state == AOF_ON &&
+ !hasActiveChildProcess() &&
+ server.aof_rewrite_perc &&
+ server.aof_current_size > server.aof_rewrite_min_size)
+ {
long long base = server.aof_rewrite_base_size ?
- server.aof_rewrite_base_size : 1;
+ server.aof_rewrite_base_size : 1;
long long growth = (server.aof_current_size*100/base) - 100;
if (growth >= server.aof_rewrite_perc) {
serverLog(LL_NOTICE,"Starting automatic rewriting of AOF on %lld%% growth",growth);
rewriteAppendOnlyFileBackground();
}
- }
+ }
}
@@ -1149,9 +2015,6 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
flushAppendOnlyFile(0);
}
- /* Close clients that need to be closed asynchronous */
- freeClientsInAsyncFreeQueue();
-
/* Clear the paused clients flag if needed. */
clientsArePaused(); /* Don't check return value, just use the side effect.*/
@@ -1165,15 +2028,16 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
}
/* Run the Sentinel timer if we are in sentinel mode. */
- run_with_period(100) {
- if (server.sentinel_mode) sentinelTimer();
- }
+ if (server.sentinel_mode) sentinelTimer();
/* Cleanup expired MIGRATE cached sockets. */
run_with_period(1000) {
migrateCloseTimedoutSockets();
}
+ /* Stop the I/O threads if we don't have enough pending work. */
+ stopThreadedIOIfNeeded();
+
/* Start a scheduled BGSAVE if the corresponding flag is set. This is
* useful when we are forced to postpone a BGSAVE because an AOF
* rewrite is in progress.
@@ -1181,7 +2045,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
* Note: this code must be after the replicationCron() call above so
* make sure when refactoring this file to keep this order. This is useful
* because we want to give priority to RDB savings for replication. */
- if (server.rdb_child_pid == -1 && server.aof_child_pid == -1 &&
+ if (!hasActiveChildProcess() &&
server.rdb_bgsave_scheduled &&
(server.unixtime-server.lastbgsave_try > CONFIG_BGSAVE_RETRY_DELAY ||
server.lastbgsave_status == C_OK))
@@ -1202,6 +2066,11 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
void beforeSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
+ /* Handle TLS pending data. (must be done before flushAppendOnlyFile) */
+ tlsProcessPendingData();
+ /* If tls still has pending unread data don't sleep at all. */
+ aeSetDontWait(server.el, tlsHasPendingData());
+
/* Call the Redis Cluster before sleep function. Note that this function
* may change the state of Redis Cluster (from ok to fail or vice versa),
* so it's a good idea to call it before serving the unblocked clients
@@ -1245,7 +2114,10 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
flushAppendOnlyFile(0);
/* Handle writes with pending output buffers. */
- handleClientsWithPendingWrites();
+ handleClientsWithPendingWritesUsingThreads();
+
+ /* Close clients that need to be closed asynchronous */
+ freeClientsInAsyncFreeQueue();
/* Before we are going to sleep, let the threads access the dataset by
* releasing the GIL. Redis main thread will not touch anything at this
@@ -1259,6 +2131,7 @@ void beforeSleep(struct aeEventLoop *eventLoop) {
void afterSleep(struct aeEventLoop *eventLoop) {
UNUSED(eventLoop);
if (moduleCount()) moduleAcquireGIL();
+ handleClientsWithPendingReadsUsingThreads();
}
/* =========================== Server initialization ======================== */
@@ -1272,10 +2145,7 @@ void createSharedObjects(void) {
shared.emptybulk = createObject(OBJ_STRING,sdsnew("$0\r\n\r\n"));
shared.czero = createObject(OBJ_STRING,sdsnew(":0\r\n"));
shared.cone = createObject(OBJ_STRING,sdsnew(":1\r\n"));
- shared.cnegone = createObject(OBJ_STRING,sdsnew(":-1\r\n"));
- shared.nullbulk = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
- shared.nullmultibulk = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
- shared.emptymultibulk = createObject(OBJ_STRING,sdsnew("*0\r\n"));
+ shared.emptyarray = createObject(OBJ_STRING,sdsnew("*0\r\n"));
shared.pong = createObject(OBJ_STRING,sdsnew("+PONG\r\n"));
shared.queued = createObject(OBJ_STRING,sdsnew("+QUEUED\r\n"));
shared.emptyscan = createObject(OBJ_STRING,sdsnew("*2\r\n$1\r\n0\r\n*0\r\n"));
@@ -1296,11 +2166,11 @@ void createSharedObjects(void) {
shared.slowscripterr = createObject(OBJ_STRING,sdsnew(
"-BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.\r\n"));
shared.masterdownerr = createObject(OBJ_STRING,sdsnew(
- "-MASTERDOWN Link with MASTER is down and slave-serve-stale-data is set to 'no'.\r\n"));
+ "-MASTERDOWN Link with MASTER is down and replica-serve-stale-data is set to 'no'.\r\n"));
shared.bgsaveerr = createObject(OBJ_STRING,sdsnew(
"-MISCONF Redis is configured to save RDB snapshots, but it is currently not able to persist on disk. Commands that may modify the data set are disabled, because this instance is configured to report errors during writes if RDB snapshotting fails (stop-writes-on-bgsave-error option). Please check the Redis logs for details about the RDB error.\r\n"));
shared.roslaveerr = createObject(OBJ_STRING,sdsnew(
- "-READONLY You can't write against a read only slave.\r\n"));
+ "-READONLY You can't write against a read only replica.\r\n"));
shared.noautherr = createObject(OBJ_STRING,sdsnew(
"-NOAUTH Authentication required.\r\n"));
shared.oomerr = createObject(OBJ_STRING,sdsnew(
@@ -1308,13 +2178,34 @@ void createSharedObjects(void) {
shared.execaborterr = createObject(OBJ_STRING,sdsnew(
"-EXECABORT Transaction discarded because of previous errors.\r\n"));
shared.noreplicaserr = createObject(OBJ_STRING,sdsnew(
- "-NOREPLICAS Not enough good slaves to write.\r\n"));
+ "-NOREPLICAS Not enough good replicas to write.\r\n"));
shared.busykeyerr = createObject(OBJ_STRING,sdsnew(
"-BUSYKEY Target key name already exists.\r\n"));
shared.space = createObject(OBJ_STRING,sdsnew(" "));
shared.colon = createObject(OBJ_STRING,sdsnew(":"));
shared.plus = createObject(OBJ_STRING,sdsnew("+"));
+ /* The shared NULL depends on the protocol version. */
+ shared.null[0] = NULL;
+ shared.null[1] = NULL;
+ shared.null[2] = createObject(OBJ_STRING,sdsnew("$-1\r\n"));
+ shared.null[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
+
+ shared.nullarray[0] = NULL;
+ shared.nullarray[1] = NULL;
+ shared.nullarray[2] = createObject(OBJ_STRING,sdsnew("*-1\r\n"));
+ shared.nullarray[3] = createObject(OBJ_STRING,sdsnew("_\r\n"));
+
+ shared.emptymap[0] = NULL;
+ shared.emptymap[1] = NULL;
+ shared.emptymap[2] = createObject(OBJ_STRING,sdsnew("*0\r\n"));
+ shared.emptymap[3] = createObject(OBJ_STRING,sdsnew("%0\r\n"));
+
+ shared.emptyset[0] = NULL;
+ shared.emptyset[1] = NULL;
+ shared.emptyset[2] = createObject(OBJ_STRING,sdsnew("*0\r\n"));
+ shared.emptyset[3] = createObject(OBJ_STRING,sdsnew("~0\r\n"));
+
for (j = 0; j < PROTO_SHARED_SELECT_CMDS; j++) {
char dictid_str[64];
int dictid_len;
@@ -1336,6 +2227,9 @@ void createSharedObjects(void) {
shared.rpop = createStringObject("RPOP",4);
shared.lpop = createStringObject("LPOP",4);
shared.lpush = createStringObject("LPUSH",5);
+ shared.rpoplpush = createStringObject("RPOPLPUSH",9);
+ shared.zpopmin = createStringObject("ZPOPMIN",7);
+ shared.zpopmax = createStringObject("ZPOPMAX",7);
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
@@ -1358,37 +2252,41 @@ void createSharedObjects(void) {
void initServerConfig(void) {
int j;
- pthread_mutex_init(&server.next_client_id_mutex,NULL);
- pthread_mutex_init(&server.lruclock_mutex,NULL);
- pthread_mutex_init(&server.unixtime_mutex,NULL);
-
+ updateCachedTime();
getRandomHexChars(server.runid,CONFIG_RUN_ID_SIZE);
server.runid[CONFIG_RUN_ID_SIZE] = '\0';
changeReplicationId();
clearReplicationId2();
+ server.timezone = getTimeZone(); /* Initialized by tzset(). */
server.configfile = NULL;
server.executable = NULL;
- server.hz = CONFIG_DEFAULT_HZ;
+ server.hz = server.config_hz = CONFIG_DEFAULT_HZ;
+ server.dynamic_hz = CONFIG_DEFAULT_DYNAMIC_HZ;
server.arch_bits = (sizeof(long) == 8) ? 64 : 32;
server.port = CONFIG_DEFAULT_SERVER_PORT;
+ server.tls_port = CONFIG_DEFAULT_SERVER_TLS_PORT;
server.tcp_backlog = CONFIG_DEFAULT_TCP_BACKLOG;
server.bindaddr_count = 0;
server.unixsocket = NULL;
server.unixsocketperm = CONFIG_DEFAULT_UNIX_SOCKET_PERM;
server.ipfd_count = 0;
+ server.tlsfd_count = 0;
server.sofd = -1;
server.protected_mode = CONFIG_DEFAULT_PROTECTED_MODE;
+ server.gopher_enabled = CONFIG_DEFAULT_GOPHER_ENABLED;
server.dbnum = CONFIG_DEFAULT_DBNUM;
server.verbosity = CONFIG_DEFAULT_VERBOSITY;
server.maxidletime = CONFIG_DEFAULT_CLIENT_TIMEOUT;
server.tcpkeepalive = CONFIG_DEFAULT_TCP_KEEPALIVE;
server.active_expire_enabled = 1;
+ server.jemalloc_bg_thread = 1;
server.active_defrag_enabled = CONFIG_DEFAULT_ACTIVE_DEFRAG;
server.active_defrag_ignore_bytes = CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES;
server.active_defrag_threshold_lower = CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER;
server.active_defrag_threshold_upper = CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER;
server.active_defrag_cycle_min = CONFIG_DEFAULT_DEFRAG_CYCLE_MIN;
server.active_defrag_cycle_max = CONFIG_DEFAULT_DEFRAG_CYCLE_MAX;
+ server.active_defrag_max_scan_fields = CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS;
server.proto_max_bulk_len = CONFIG_DEFAULT_PROTO_MAX_BULK_LEN;
server.client_max_querybuf_len = PROTO_MAX_QUERYBUF_LEN;
server.saveparams = NULL;
@@ -1407,6 +2305,7 @@ void initServerConfig(void) {
server.aof_rewrite_min_size = AOF_REWRITE_MIN_SIZE;
server.aof_rewrite_base_size = 0;
server.aof_rewrite_scheduled = 0;
+ server.aof_flush_sleep = 0;
server.aof_last_fsync = time(NULL);
server.aof_rewrite_time_last = -1;
server.aof_rewrite_time_start = -1;
@@ -1416,12 +2315,15 @@ void initServerConfig(void) {
server.aof_selected_db = -1; /* Make sure the first time will not match */
server.aof_flush_postponed_start = 0;
server.aof_rewrite_incremental_fsync = CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
+ server.rdb_save_incremental_fsync = CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC;
+ server.rdb_key_save_delay = CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY;
+ server.key_load_delay = CONFIG_DEFAULT_KEY_LOAD_DELAY;
server.aof_load_truncated = CONFIG_DEFAULT_AOF_LOAD_TRUNCATED;
server.aof_use_rdb_preamble = CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE;
server.pidfile = NULL;
server.rdb_filename = zstrdup(CONFIG_DEFAULT_RDB_FILENAME);
server.aof_filename = zstrdup(CONFIG_DEFAULT_AOF_FILENAME);
- server.requirepass = NULL;
+ server.acl_filename = zstrdup(CONFIG_DEFAULT_ACL_FILENAME);
server.rdb_compression = CONFIG_DEFAULT_RDB_COMPRESSION;
server.rdb_checksum = CONFIG_DEFAULT_RDB_CHECKSUM;
server.stop_writes_on_bgsave_err = CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR;
@@ -1445,6 +2347,8 @@ void initServerConfig(void) {
server.zset_max_ziplist_entries = OBJ_ZSET_MAX_ZIPLIST_ENTRIES;
server.zset_max_ziplist_value = OBJ_ZSET_MAX_ZIPLIST_VALUE;
server.hll_sparse_max_bytes = CONFIG_DEFAULT_HLL_SPARSE_MAX_BYTES;
+ server.stream_node_max_bytes = OBJ_STREAM_NODE_MAX_BYTES;
+ server.stream_node_max_entries = OBJ_STREAM_NODE_MAX_ENTRIES;
server.shutdown_asap = 0;
server.cluster_enabled = 0;
server.cluster_node_timeout = CLUSTER_DEFAULT_NODE_TIMEOUT;
@@ -1456,6 +2360,7 @@ void initServerConfig(void) {
server.cluster_announce_ip = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_IP;
server.cluster_announce_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_PORT;
server.cluster_announce_bus_port = CONFIG_DEFAULT_CLUSTER_ANNOUNCE_BUS_PORT;
+ server.cluster_module_flags = CLUSTER_MODULE_FLAG_NONE;
server.migrate_cached_sockets = dictCreate(&migrateCacheDictType,NULL);
server.next_client_id = 1; /* Client IDs, start from 1 .*/
server.loading_process_events_interval_bytes = (1024*1024*2);
@@ -1464,9 +2369,10 @@ void initServerConfig(void) {
server.lazyfree_lazy_server_del = CONFIG_DEFAULT_LAZYFREE_LAZY_SERVER_DEL;
server.always_show_logo = CONFIG_DEFAULT_ALWAYS_SHOW_LOGO;
server.lua_time_limit = LUA_SCRIPT_TIME_LIMIT;
+ server.io_threads_num = CONFIG_DEFAULT_IO_THREADS_NUM;
+ server.io_threads_do_reads = CONFIG_DEFAULT_IO_THREADS_DO_READS;
- unsigned int lruclock = getLRUClock();
- atomicSet(server.lruclock,lruclock);
+ server.lruclock = getLRUClock();
resetServerSaveParams();
appendServerSaveParams(60*60,1); /* save after 1 hour and 1 change */
@@ -1481,13 +2387,18 @@ void initServerConfig(void) {
server.cached_master = NULL;
server.master_initial_offset = -1;
server.repl_state = REPL_STATE_NONE;
+ server.repl_transfer_tmpfile = NULL;
+ server.repl_transfer_fd = -1;
+ server.repl_transfer_s = NULL;
server.repl_syncio_timeout = CONFIG_REPL_SYNCIO_TIMEOUT;
server.repl_serve_stale_data = CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA;
server.repl_slave_ro = CONFIG_DEFAULT_SLAVE_READ_ONLY;
+ server.repl_slave_ignore_maxmemory = CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY;
server.repl_slave_lazy_flush = CONFIG_DEFAULT_SLAVE_LAZY_FLUSH;
server.repl_down_since = 0; /* Never connected, repl is down since EVER. */
server.repl_disable_tcp_nodelay = CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY;
server.repl_diskless_sync = CONFIG_DEFAULT_REPL_DISKLESS_SYNC;
+ server.repl_diskless_load = CONFIG_DEFAULT_REPL_DISKLESS_LOAD;
server.repl_diskless_sync_delay = CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY;
server.repl_ping_slave_period = CONFIG_DEFAULT_REPL_PING_SLAVE_PERIOD;
server.repl_timeout = CONFIG_DEFAULT_REPL_TIMEOUT;
@@ -1528,10 +2439,14 @@ void initServerConfig(void) {
server.lpushCommand = lookupCommandByCString("lpush");
server.lpopCommand = lookupCommandByCString("lpop");
server.rpopCommand = lookupCommandByCString("rpop");
+ server.zpopminCommand = lookupCommandByCString("zpopmin");
+ server.zpopmaxCommand = lookupCommandByCString("zpopmax");
server.sremCommand = lookupCommandByCString("srem");
server.execCommand = lookupCommandByCString("exec");
server.expireCommand = lookupCommandByCString("expire");
server.pexpireCommand = lookupCommandByCString("pexpire");
+ server.xclaimCommand = lookupCommandByCString("xclaim");
+ server.xgroupCommand = lookupCommandByCString("xgroup");
/* Slow log */
server.slowlog_log_slower_than = CONFIG_DEFAULT_SLOWLOG_LOG_SLOWER_THAN;
@@ -1540,12 +2455,21 @@ void initServerConfig(void) {
/* Latency monitor */
server.latency_monitor_threshold = CONFIG_DEFAULT_LATENCY_MONITOR_THRESHOLD;
+ /* Tracking. */
+ server.tracking_table_max_fill = CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL;
+
/* Debugging */
server.assert_failed = "<no assertion failed>";
server.assert_file = "<no file>";
server.assert_line = 0;
server.bug_report_start = 0;
server.watchdog_period = 0;
+
+ /* By default we want scripts to be always replicated by effects
+ * (single commands executed by the script), and not by sending the
+ * script to the slave / AOF. This is the new way starting from
+ * Redis 5. However it is possible to revert it via redis.conf. */
+ server.lua_always_replicate_commands = 1;
}
extern char **environ;
@@ -1750,7 +2674,7 @@ int listenToPort(int port, int *fds, int *count) {
(*count)++;
} else if (errno == EAFNOSUPPORT) {
unsupported++;
- serverLog(LL_WARNING,"Not listening to IPv6: unsupproted");
+ serverLog(LL_WARNING,"Not listening to IPv6: unsupported");
}
if (*count == 1 || unsupported) {
@@ -1762,7 +2686,7 @@ int listenToPort(int port, int *fds, int *count) {
(*count)++;
} else if (errno == EAFNOSUPPORT) {
unsupported++;
- serverLog(LL_WARNING,"Not listening to IPv4: unsupproted");
+ serverLog(LL_WARNING,"Not listening to IPv4: unsupported");
}
}
/* Exit the loop if we were able to bind * on IPv4 and IPv6,
@@ -1780,9 +2704,13 @@ int listenToPort(int port, int *fds, int *count) {
}
if (fds[*count] == ANET_ERR) {
serverLog(LL_WARNING,
- "Creating Server TCP listening socket %s:%d: %s",
+ "Could not create server TCP listening socket %s:%d: %s",
server.bindaddr[j] ? server.bindaddr[j] : "*",
port, server.neterr);
+ if (errno == ENOPROTOOPT || errno == EPROTONOSUPPORT ||
+ errno == ESOCKTNOSUPPORT || errno == EPFNOSUPPORT ||
+ errno == EAFNOSUPPORT || errno == EADDRNOTAVAIL)
+ continue;
return C_ERR;
}
anetNonBlock(NULL,fds[*count]);
@@ -1809,6 +2737,7 @@ void resetServerStats(void) {
server.stat_active_defrag_misses = 0;
server.stat_active_defrag_key_hits = 0;
server.stat_active_defrag_key_misses = 0;
+ server.stat_active_defrag_scanned = 0;
server.stat_fork_time = 0;
server.stat_fork_rate = 0;
server.stat_rejected_conn = 0;
@@ -1839,13 +2768,16 @@ void initServer(void) {
server.syslog_facility);
}
+ server.hz = server.config_hz;
server.pid = getpid();
server.current_client = NULL;
server.clients = listCreate();
+ server.clients_index = raxNew();
server.clients_to_close = listCreate();
server.slaves = listCreate();
server.monitors = listCreate();
server.clients_pending_write = listCreate();
+ server.clients_pending_read = listCreate();
server.slaveseldb = -1; /* Force to emit the first SELECT command. */
server.unblocked_clients = listCreate();
server.ready_keys = listCreate();
@@ -1854,6 +2786,11 @@ void initServer(void) {
server.clients_paused = 0;
server.system_memory_size = zmalloc_get_memory_size();
+ if (server.tls_port && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
+ serverLog(LL_WARNING, "Failed to configure TLS. Check logs for more info.");
+ exit(1);
+ }
+
createSharedObjects();
adjustOpenFilesLimit();
server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
@@ -1869,6 +2806,9 @@ void initServer(void) {
if (server.port != 0 &&
listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
exit(1);
+ if (server.tls_port != 0 &&
+ listenToPort(server.tls_port,server.tlsfd,&server.tlsfd_count) == C_ERR)
+ exit(1);
/* Open the listening Unix domain socket. */
if (server.unixsocket != NULL) {
@@ -1883,7 +2823,7 @@ void initServer(void) {
}
/* Abort if there are no listening sockets at all. */
- if (server.ipfd_count == 0 && server.sofd < 0) {
+ if (server.ipfd_count == 0 && server.tlsfd_count == 0 && server.sofd < 0) {
serverLog(LL_WARNING, "Configured to not listen anywhere, exiting.");
exit(1);
}
@@ -1897,6 +2837,7 @@ void initServer(void) {
server.db[j].watched_keys = dictCreate(&keylistDictType,NULL);
server.db[j].id = j;
server.db[j].avg_ttl = 0;
+ server.db[j].defrag_later = listCreate();
}
evictionPoolAlloc(); /* Initialize the LRU keys pool. */
server.pubsub_channels = dictCreate(&keylistDictType,NULL);
@@ -1906,7 +2847,13 @@ void initServer(void) {
server.cronloops = 0;
server.rdb_child_pid = -1;
server.aof_child_pid = -1;
+ server.module_child_pid = -1;
server.rdb_child_type = RDB_CHILD_TYPE_NONE;
+ server.rdb_pipe_conns = NULL;
+ server.rdb_pipe_numconns = 0;
+ server.rdb_pipe_numconns_writing = 0;
+ server.rdb_pipe_buff = NULL;
+ server.rdb_pipe_bufflen = 0;
server.rdb_bgsave_scheduled = 0;
server.child_info_pipe[0] = -1;
server.child_info_pipe[1] = -1;
@@ -1924,12 +2871,16 @@ void initServer(void) {
server.stat_peak_memory = 0;
server.stat_rdb_cow_bytes = 0;
server.stat_aof_cow_bytes = 0;
- server.resident_set_size = 0;
+ server.stat_module_cow_bytes = 0;
+ server.cron_malloc_stats.zmalloc_used = 0;
+ server.cron_malloc_stats.process_rss = 0;
+ server.cron_malloc_stats.allocator_allocated = 0;
+ server.cron_malloc_stats.allocator_active = 0;
+ server.cron_malloc_stats.allocator_resident = 0;
server.lastbgsave_status = C_OK;
server.aof_last_write_status = C_OK;
server.aof_last_write_errno = 0;
server.repl_good_slaves_count = 0;
- updateCachedTime();
/* Create the timer callback, this is our way to process many background
* operations incrementally, like clients timeout, eviction of unaccessed
@@ -1949,6 +2900,14 @@ void initServer(void) {
"Unrecoverable error creating server.ipfd file event.");
}
}
+ for (j = 0; j < server.tlsfd_count; j++) {
+ if (aeCreateFileEvent(server.el, server.tlsfd[j], AE_READABLE,
+ acceptTLSHandler,NULL) == AE_ERR)
+ {
+ serverPanic(
+ "Unrecoverable error creating server.tlsfd file event.");
+ }
+ }
if (server.sofd > 0 && aeCreateFileEvent(server.el,server.sofd,AE_READABLE,
acceptUnixHandler,NULL) == AE_ERR) serverPanic("Unrecoverable error creating server.sofd file event.");
@@ -1988,10 +2947,81 @@ void initServer(void) {
scriptingInit(1);
slowlogInit();
latencyMonitorInit();
+}
+
+/* Some steps in server initialization need to be done last (after modules
+ * are loaded).
+ * Specifically, creation of threads due to a race bug in ld.so, in which
+ * Thread Local Storage initialization collides with dlopen call.
+ * see: https://sourceware.org/bugzilla/show_bug.cgi?id=19329 */
+void InitServerLast() {
bioInit();
+ initThreadedIO();
+ set_jemalloc_bg_thread(server.jemalloc_bg_thread);
server.initial_memory_usage = zmalloc_used_memory();
}
+/* Parse the flags string description 'strflags' and set them to the
+ * command 'c'. If the flags are all valid C_OK is returned, otherwise
+ * C_ERR is returned (yet the recognized flags are set in the command). */
+int populateCommandTableParseFlags(struct redisCommand *c, char *strflags) {
+ int argc;
+ sds *argv;
+
+ /* Split the line into arguments for processing. */
+ argv = sdssplitargs(strflags,&argc);
+ if (argv == NULL) return C_ERR;
+
+ for (int j = 0; j < argc; j++) {
+ char *flag = argv[j];
+ if (!strcasecmp(flag,"write")) {
+ c->flags |= CMD_WRITE|CMD_CATEGORY_WRITE;
+ } else if (!strcasecmp(flag,"read-only")) {
+ c->flags |= CMD_READONLY|CMD_CATEGORY_READ;
+ } else if (!strcasecmp(flag,"use-memory")) {
+ c->flags |= CMD_DENYOOM;
+ } else if (!strcasecmp(flag,"admin")) {
+ c->flags |= CMD_ADMIN|CMD_CATEGORY_ADMIN|CMD_CATEGORY_DANGEROUS;
+ } else if (!strcasecmp(flag,"pub-sub")) {
+ c->flags |= CMD_PUBSUB|CMD_CATEGORY_PUBSUB;
+ } else if (!strcasecmp(flag,"no-script")) {
+ c->flags |= CMD_NOSCRIPT;
+ } else if (!strcasecmp(flag,"random")) {
+ c->flags |= CMD_RANDOM;
+ } else if (!strcasecmp(flag,"to-sort")) {
+ c->flags |= CMD_SORT_FOR_SCRIPT;
+ } else if (!strcasecmp(flag,"ok-loading")) {
+ c->flags |= CMD_LOADING;
+ } else if (!strcasecmp(flag,"ok-stale")) {
+ c->flags |= CMD_STALE;
+ } else if (!strcasecmp(flag,"no-monitor")) {
+ c->flags |= CMD_SKIP_MONITOR;
+ } else if (!strcasecmp(flag,"no-slowlog")) {
+ c->flags |= CMD_SKIP_SLOWLOG;
+ } else if (!strcasecmp(flag,"cluster-asking")) {
+ c->flags |= CMD_ASKING;
+ } else if (!strcasecmp(flag,"fast")) {
+ c->flags |= CMD_FAST | CMD_CATEGORY_FAST;
+ } else {
+ /* Parse ACL categories here if the flag name starts with @. */
+ uint64_t catflag;
+ if (flag[0] == '@' &&
+ (catflag = ACLGetCommandCategoryFlagByName(flag+1)) != 0)
+ {
+ c->flags |= catflag;
+ } else {
+ sdsfreesplitres(argv,argc);
+ return C_ERR;
+ }
+ }
+ }
+ /* If it's not @fast is @slow in this binary world. */
+ if (!(c->flags & CMD_CATEGORY_FAST)) c->flags |= CMD_CATEGORY_SLOW;
+
+ sdsfreesplitres(argv,argc);
+ return C_OK;
+}
+
/* Populates the Redis Command Table starting from the hard coded list
* we have on top of redis.c file. */
void populateCommandTable(void) {
@@ -2000,29 +3030,14 @@ void populateCommandTable(void) {
for (j = 0; j < numcommands; j++) {
struct redisCommand *c = redisCommandTable+j;
- char *f = c->sflags;
int retval1, retval2;
- while(*f != '\0') {
- switch(*f) {
- case 'w': c->flags |= CMD_WRITE; break;
- case 'r': c->flags |= CMD_READONLY; break;
- case 'm': c->flags |= CMD_DENYOOM; break;
- case 'a': c->flags |= CMD_ADMIN; break;
- case 'p': c->flags |= CMD_PUBSUB; break;
- case 's': c->flags |= CMD_NOSCRIPT; break;
- case 'R': c->flags |= CMD_RANDOM; break;
- case 'S': c->flags |= CMD_SORT_FOR_SCRIPT; break;
- case 'l': c->flags |= CMD_LOADING; break;
- case 't': c->flags |= CMD_STALE; break;
- case 'M': c->flags |= CMD_SKIP_MONITOR; break;
- case 'k': c->flags |= CMD_ASKING; break;
- case 'F': c->flags |= CMD_FAST; break;
- default: serverPanic("Unsupported command flag"); break;
- }
- f++;
- }
+ /* Translate the command string flags description into an actual
+ * set of flags. */
+ if (populateCommandTableParseFlags(c,c->sflags) == C_ERR)
+ serverPanic("Unsupported command flag");
+ c->id = ACLGetCommandID(c->name); /* Assign the ID used for ACL. */
retval1 = dictAdd(server.commands, sdsnew(c->name), c);
/* Populate an additional dictionary that will be unaffected
* by rename-command statements in redis.conf. */
@@ -2225,6 +3240,7 @@ void preventCommandReplication(client *c) {
void call(client *c, int flags) {
long long dirty, start, duration;
int client_old_flags = c->flags;
+ struct redisCommand *real_cmd = c->cmd;
/* Sent the command to clients in MONITOR mode, only if the commands are
* not generated from reading an AOF. */
@@ -2266,15 +3282,19 @@ void call(client *c, int flags) {
/* Log the command into the Slow log if needed, and populate the
* per-command statistics that we show in INFO commandstats. */
- if (flags & CMD_CALL_SLOWLOG && c->cmd->proc != execCommand) {
+ if (flags & CMD_CALL_SLOWLOG && !(c->cmd->flags & CMD_SKIP_SLOWLOG)) {
char *latency_event = (c->cmd->flags & CMD_FAST) ?
"fast-command" : "command";
latencyAddSampleIfNeeded(latency_event,duration/1000);
slowlogPushEntryIfNeeded(c,c->argv,c->argc,duration);
}
+
if (flags & CMD_CALL_STATS) {
- c->lastcmd->microseconds += duration;
- c->lastcmd->calls++;
+ /* use the real command that was executed (cmd and lastamc) may be
+ * different, in case of MULTI-EXEC or re-written commands such as
+ * EXPIRE, GEOADD, etc. */
+ real_cmd->microseconds += duration;
+ real_cmd->calls++;
}
/* Propagate the command into the AOF and replication link */
@@ -2293,7 +3313,7 @@ void call(client *c, int flags) {
if (c->flags & CLIENT_FORCE_AOF) propagate_flags |= PROPAGATE_AOF;
/* However prevent AOF / replication propagation if the command
- * implementatino called preventCommandPropagation() or similar,
+ * implementations called preventCommandPropagation() or similar,
* or if we don't have the call() flags to do so. */
if (c->flags & CLIENT_PREVENT_REPL_PROP ||
!(flags & CMD_CALL_PROPAGATE_REPL))
@@ -2336,6 +3356,16 @@ void call(client *c, int flags) {
redisOpArrayFree(&server.also_propagate);
}
server.also_propagate = prev_also_propagate;
+
+ /* If the client has keys tracking enabled for client side caching,
+ * make sure to remember the keys it fetched via this command. */
+ if (c->cmd->flags & CMD_READONLY) {
+ client *caller = (c->flags & CLIENT_LUA && server.lua_caller) ?
+ server.lua_caller : c;
+ if (caller->flags & CLIENT_TRACKING)
+ trackingRememberKeys(caller);
+ }
+
server.stat_numcommands++;
}
@@ -2348,6 +3378,8 @@ void call(client *c, int flags) {
* other operations can be performed by the caller. Otherwise
* if C_ERR is returned the client was destroyed (i.e. after QUIT). */
int processCommand(client *c) {
+ moduleCallCommandFilters(c);
+
/* The QUIT command is handled separately. Normal command procs will
* go through checking for replication and QUIT will cause trouble
* when FORCE_REPLICATION is enabled and would be implemented in
@@ -2363,8 +3395,13 @@ int processCommand(client *c) {
c->cmd = c->lastcmd = lookupCommand(c->argv[0]->ptr);
if (!c->cmd) {
flagTransaction(c);
- addReplyErrorFormat(c,"unknown command '%s'",
- (char*)c->argv[0]->ptr);
+ sds args = sdsempty();
+ int i;
+ for (i=1; i < c->argc && sdslen(args) < 128; i++)
+ args = sdscatprintf(args, "`%.*s`, ", 128-(int)sdslen(args), (char*)c->argv[i]->ptr);
+ addReplyErrorFormat(c,"unknown command `%s`, with args beginning with: %s",
+ (char*)c->argv[0]->ptr, args);
+ sdsfree(args);
return C_OK;
} else if ((c->cmd->arity > 0 && c->cmd->arity != c->argc) ||
(c->argc < -c->cmd->arity)) {
@@ -2374,11 +3411,33 @@ int processCommand(client *c) {
return C_OK;
}
- /* Check if the user is authenticated */
- if (server.requirepass && !c->authenticated && c->cmd->proc != authCommand)
- {
+ /* Check if the user is authenticated. This check is skipped in case
+ * the default user is flagged as "nopass" and is active. */
+ int auth_required = (!(DefaultUser->flags & USER_FLAG_NOPASS) ||
+ DefaultUser->flags & USER_FLAG_DISABLED) &&
+ !c->authenticated;
+ if (auth_required) {
+ /* AUTH and HELLO are valid even in non authenticated state. */
+ if (c->cmd->proc != authCommand && c->cmd->proc != helloCommand) {
+ flagTransaction(c);
+ addReply(c,shared.noautherr);
+ return C_OK;
+ }
+ }
+
+ /* Check if the user can run this command according to the current
+ * ACLs. */
+ int acl_retval = ACLCheckCommandPerm(c);
+ if (acl_retval != ACL_OK) {
flagTransaction(c);
- addReply(c,shared.noautherr);
+ if (acl_retval == ACL_DENIED_CMD)
+ addReplyErrorFormat(c,
+ "-NOPERM this user has no permissions to run "
+ "the '%s' command or its subcommand", c->cmd->name);
+ else
+ addReplyErrorFormat(c,
+ "-NOPERM this user has no permissions to access "
+ "one of the keys used as arguments");
return C_OK;
}
@@ -2410,36 +3469,45 @@ int processCommand(client *c) {
/* Handle the maxmemory directive.
*
- * First we try to free some memory if possible (if there are volatile
- * keys in the dataset). If there are not the only thing we can do
- * is returning an error. */
- if (server.maxmemory) {
- int retval = freeMemoryIfNeeded();
+ * Note that we do not want to reclaim memory if we are here re-entering
+ * the event loop since there is a busy Lua script running in timeout
+ * condition, to avoid mixing the propagation of scripts with the
+ * propagation of DELs due to eviction. */
+ if (server.maxmemory && !server.lua_timedout) {
+ int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;
/* freeMemoryIfNeeded may flush slave output buffers. This may result
* into a slave, that may be the active client, to be freed. */
if (server.current_client == NULL) return C_ERR;
/* It was impossible to free enough memory, and the command the client
- * is trying to execute is denied during OOM conditions? Error. */
- if ((c->cmd->flags & CMD_DENYOOM) && retval == C_ERR) {
+ * is trying to execute is denied during OOM conditions or the client
+ * is in MULTI/EXEC context? Error. */
+ if (out_of_memory &&
+ (c->cmd->flags & CMD_DENYOOM ||
+ (c->flags & CLIENT_MULTI &&
+ c->cmd->proc != execCommand &&
+ c->cmd->proc != discardCommand)))
+ {
flagTransaction(c);
addReply(c, shared.oomerr);
return C_OK;
}
}
+ /* Make sure to use a reasonable amount of memory for client side
+ * caching metadata. */
+ if (server.tracking_clients) trackingLimitUsedSlots();
+
/* Don't accept write commands if there are problems persisting on disk
* and if this is a master instance. */
- if (((server.stop_writes_on_bgsave_err &&
- server.saveparamslen > 0 &&
- server.lastbgsave_status == C_ERR) ||
- server.aof_last_write_status == C_ERR) &&
+ int deny_write_type = writeCommandsDeniedByDiskError();
+ if (deny_write_type != DISK_ERROR_TYPE_NONE &&
server.masterhost == NULL &&
(c->cmd->flags & CMD_WRITE ||
c->cmd->proc == pingCommand))
{
flagTransaction(c);
- if (server.aof_last_write_status == C_OK)
+ if (deny_write_type == DISK_ERROR_TYPE_RDB)
addReply(c, shared.bgsaveerr);
else
addReplySds(c,
@@ -2472,8 +3540,9 @@ int processCommand(client *c) {
return C_OK;
}
- /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
- if (c->flags & CLIENT_PUBSUB &&
+ /* Only allow a subset of commands in the context of Pub/Sub if the
+ * connection is in RESP2 mode. With RESP3 there are no limits. */
+ if ((c->flags & CLIENT_PUBSUB && c->resp == 2) &&
c->cmd->proc != pingCommand &&
c->cmd->proc != subscribeCommand &&
c->cmd->proc != unsubscribeCommand &&
@@ -2505,6 +3574,7 @@ int processCommand(client *c) {
/* Lua script too slow? Only allow a limited number of commands. */
if (server.lua_timedout &&
c->cmd->proc != authCommand &&
+ c->cmd->proc != helloCommand &&
c->cmd->proc != replconfCommand &&
!(c->cmd->proc == shutdownCommand &&
c->argc == 2 &&
@@ -2542,6 +3612,7 @@ void closeListeningSockets(int unlink_unix_socket) {
int j;
for (j = 0; j < server.ipfd_count; j++) close(server.ipfd[j]);
+ for (j = 0; j < server.tlsfd_count; j++) close(server.tlsfd[j]);
if (server.sofd != -1) close(server.sofd);
if (server.cluster_enabled)
for (j = 0; j < server.cfd_count; j++) close(server.cfd[j]);
@@ -2565,8 +3636,13 @@ int prepareForShutdown(int flags) {
overwrite the synchronous saving did by SHUTDOWN. */
if (server.rdb_child_pid != -1) {
serverLog(LL_WARNING,"There is a child saving an .rdb. Killing it!");
- kill(server.rdb_child_pid,SIGUSR1);
- rdbRemoveTempFile(server.rdb_child_pid);
+ killRDBChild();
+ }
+
+ /* Kill module child if there is one. */
+ if (server.module_child_pid != -1) {
+ serverLog(LL_WARNING,"There is a module fork child. Killing it!");
+ TerminateModuleForkChild(server.module_child_pid,0);
}
if (server.aof_state != AOF_OFF) {
@@ -2581,12 +3657,12 @@ int prepareForShutdown(int flags) {
}
serverLog(LL_WARNING,
"There is a child rewriting the AOF. Killing it!");
- kill(server.aof_child_pid,SIGUSR1);
+ killAppendOnlyChild();
}
/* Append only file: flush buffers and fsync() the AOF at exit */
serverLog(LL_NOTICE,"Calling fsync() on the AOF file.");
flushAppendOnlyFile(1);
- aof_fsync(server.aof_fd);
+ redis_fsync(server.aof_fd);
}
/* Create a new RDB file before exiting. */
@@ -2625,57 +3701,29 @@ int prepareForShutdown(int flags) {
/*================================== Commands =============================== */
-/* Return zero if strings are the same, non-zero if they are not.
- * The comparison is performed in a way that prevents an attacker to obtain
- * information about the nature of the strings just monitoring the execution
- * time of the function.
+/* Sometimes Redis cannot accept write commands because there is a perstence
+ * error with the RDB or AOF file, and Redis is configured in order to stop
+ * accepting writes in such situation. This function returns if such a
+ * condition is active, and the type of the condition.
*
- * Note that limiting the comparison length to strings up to 512 bytes we
- * can avoid leaking any information about the password length and any
- * possible branch misprediction related leak.
+ * Function return values:
+ *
+ * DISK_ERROR_TYPE_NONE: No problems, we can accept writes.
+ * DISK_ERROR_TYPE_AOF: Don't accept writes: AOF errors.
+ * DISK_ERROR_TYPE_RDB: Don't accept writes: RDB errors.
*/
-int time_independent_strcmp(char *a, char *b) {
- char bufa[CONFIG_AUTHPASS_MAX_LEN], bufb[CONFIG_AUTHPASS_MAX_LEN];
- /* The above two strlen perform len(a) + len(b) operations where either
- * a or b are fixed (our password) length, and the difference is only
- * relative to the length of the user provided string, so no information
- * leak is possible in the following two lines of code. */
- unsigned int alen = strlen(a);
- unsigned int blen = strlen(b);
- unsigned int j;
- int diff = 0;
-
- /* We can't compare strings longer than our static buffers.
- * Note that this will never pass the first test in practical circumstances
- * so there is no info leak. */
- if (alen > sizeof(bufa) || blen > sizeof(bufb)) return 1;
-
- memset(bufa,0,sizeof(bufa)); /* Constant time. */
- memset(bufb,0,sizeof(bufb)); /* Constant time. */
- /* Again the time of the following two copies is proportional to
- * len(a) + len(b) so no info is leaked. */
- memcpy(bufa,a,alen);
- memcpy(bufb,b,blen);
-
- /* Always compare all the chars in the two buffers without
- * conditional expressions. */
- for (j = 0; j < sizeof(bufa); j++) {
- diff |= (bufa[j] ^ bufb[j]);
- }
- /* Length must be equal as well. */
- diff |= alen ^ blen;
- return diff; /* If zero strings are the same. */
-}
-
-void authCommand(client *c) {
- if (!server.requirepass) {
- addReplyError(c,"Client sent AUTH, but no password is set");
- } else if (!time_independent_strcmp(c->argv[1]->ptr, server.requirepass)) {
- c->authenticated = 1;
- addReply(c,shared.ok);
+int writeCommandsDeniedByDiskError(void) {
+ if (server.stop_writes_on_bgsave_err &&
+ server.saveparamslen > 0 &&
+ server.lastbgsave_status == C_ERR)
+ {
+ return DISK_ERROR_TYPE_RDB;
+ } else if (server.aof_state != AOF_OFF &&
+ server.aof_last_write_status == C_ERR)
+ {
+ return DISK_ERROR_TYPE_AOF;
} else {
- c->authenticated = 0;
- addReplyError(c,"invalid password");
+ return DISK_ERROR_TYPE_NONE;
}
}
@@ -2689,7 +3737,7 @@ void pingCommand(client *c) {
return;
}
- if (c->flags & CLIENT_PUBSUB) {
+ if (c->flags & CLIENT_PUBSUB && c->resp == 2) {
addReply(c,shared.mbulkhdr[2]);
addReplyBulkCBuffer(c,"pong",4);
if (c->argc == 1)
@@ -2714,7 +3762,7 @@ void timeCommand(client *c) {
/* gettimeofday() can only fail if &tv is a bad address so we
* don't check for errors. */
gettimeofday(&tv,NULL);
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyBulkLongLong(c,tv.tv_sec);
addReplyBulkLongLong(c,tv.tv_usec);
}
@@ -2731,15 +3779,15 @@ int addReplyCommandFlag(client *c, struct redisCommand *cmd, int f, char *reply)
/* Output the representation of a Redis command. Used by the COMMAND command. */
void addReplyCommand(client *c, struct redisCommand *cmd) {
if (!cmd) {
- addReply(c, shared.nullbulk);
+ addReplyNull(c);
} else {
- /* We are adding: command name, arg count, flags, first, last, offset */
- addReplyMultiBulkLen(c, 6);
+ /* We are adding: command name, arg count, flags, first, last, offset, categories */
+ addReplyArrayLen(c, 7);
addReplyBulkCString(c, cmd->name);
addReplyLongLong(c, cmd->arity);
int flagcount = 0;
- void *flaglen = addDeferredMultiBulkLength(c);
+ void *flaglen = addReplyDeferredLen(c);
flagcount += addReplyCommandFlag(c,cmd,CMD_WRITE, "write");
flagcount += addReplyCommandFlag(c,cmd,CMD_READONLY, "readonly");
flagcount += addReplyCommandFlag(c,cmd,CMD_DENYOOM, "denyoom");
@@ -2751,6 +3799,7 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
flagcount += addReplyCommandFlag(c,cmd,CMD_LOADING, "loading");
flagcount += addReplyCommandFlag(c,cmd,CMD_STALE, "stale");
flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_MONITOR, "skip_monitor");
+ flagcount += addReplyCommandFlag(c,cmd,CMD_SKIP_SLOWLOG, "skip_slowlog");
flagcount += addReplyCommandFlag(c,cmd,CMD_ASKING, "asking");
flagcount += addReplyCommandFlag(c,cmd,CMD_FAST, "fast");
if ((cmd->getkeys_proc && !(cmd->flags & CMD_MODULE)) ||
@@ -2759,11 +3808,13 @@ void addReplyCommand(client *c, struct redisCommand *cmd) {
addReplyStatus(c, "movablekeys");
flagcount += 1;
}
- setDeferredMultiBulkLength(c, flaglen, flagcount);
+ setDeferredSetLen(c, flaglen, flagcount);
addReplyLongLong(c, cmd->firstkey);
addReplyLongLong(c, cmd->lastkey);
addReplyLongLong(c, cmd->keystep);
+
+ addReplyCommandCategories(c,cmd);
}
}
@@ -2775,14 +3826,14 @@ void commandCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
"(no subcommand) -- Return details about all Redis commands.",
-"count -- Return the total number of commands in this Redis server.",
-"getkeys <full-command> -- Return the keys from a full Redis command.",
-"info [command-name ...] -- Return details about multiple Redis commands.",
+"COUNT -- Return the total number of commands in this Redis server.",
+"GETKEYS <full-command> -- Return the keys from a full Redis command.",
+"INFO [command-name ...] -- Return details about multiple Redis commands.",
NULL
};
addReplyHelp(c, help);
} else if (c->argc == 1) {
- addReplyMultiBulkLen(c, dictSize(server.commands));
+ addReplyArrayLen(c, dictSize(server.commands));
di = dictGetIterator(server.commands);
while ((de = dictNext(di)) != NULL) {
addReplyCommand(c, dictGetVal(de));
@@ -2790,7 +3841,7 @@ NULL
dictReleaseIterator(di);
} else if (!strcasecmp(c->argv[1]->ptr, "info")) {
int i;
- addReplyMultiBulkLen(c, c->argc-2);
+ addReplyArrayLen(c, c->argc-2);
for (i = 2; i < c->argc; i++) {
addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr));
}
@@ -2801,7 +3852,10 @@ NULL
int *keys, numkeys, j;
if (!cmd) {
- addReplyErrorFormat(c,"Invalid command specified");
+ addReplyError(c,"Invalid command specified");
+ return;
+ } else if (cmd->getkeys_proc == NULL && cmd->firstkey == 0) {
+ addReplyError(c,"The command has no key arguments");
return;
} else if ((cmd->arity > 0 && cmd->arity != c->argc-2) ||
((c->argc-2) < -cmd->arity))
@@ -2811,11 +3865,15 @@ NULL
}
keys = getKeysFromCommand(cmd,c->argv+2,c->argc-2,&numkeys);
- addReplyMultiBulkLen(c,numkeys);
- for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
- getKeysFreeResult(keys);
+ if (!keys) {
+ addReplyError(c,"Invalid arguments specified for command");
+ } else {
+ addReplyArrayLen(c,numkeys);
+ for (j = 0; j < numkeys; j++) addReplyBulk(c,c->argv[keys[j]+2]);
+ getKeysFreeResult(keys);
+ }
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try COMMAND HELP", (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
}
}
@@ -2827,7 +3885,6 @@ void bytesToHuman(char *s, unsigned long long n) {
if (n < 1024) {
/* Bytes */
sprintf(s,"%lluB",n);
- return;
} else if (n < (1024*1024)) {
d = (double)n/(1024);
sprintf(s,"%.2fK",d);
@@ -2857,17 +3914,18 @@ sds genRedisInfoString(char *section) {
time_t uptime = server.unixtime-server.stat_starttime;
int j;
struct rusage self_ru, c_ru;
- unsigned long lol, bib;
- int allsections = 0, defsections = 0;
+ int allsections = 0, defsections = 0, everything = 0, modules = 0;
int sections = 0;
if (section == NULL) section = "default";
allsections = strcasecmp(section,"all") == 0;
defsections = strcasecmp(section,"default") == 0;
+ everything = strcasecmp(section,"everything") == 0;
+ modules = strcasecmp(section,"modules") == 0;
+ if (everything) allsections = 1;
getrusage(RUSAGE_SELF, &self_ru);
getrusage(RUSAGE_CHILDREN, &c_ru);
- getClientsMaxBuffers(&lol,&bib);
/* Server */
if (allsections || defsections || !strcasecmp(section,"server")) {
@@ -2887,33 +3945,32 @@ sds genRedisInfoString(char *section) {
call_uname = 0;
}
- unsigned int lruclock;
- atomicGet(server.lruclock,lruclock);
- info = sdscatprintf(info,
+ info = sdscatfmt(info,
"# Server\r\n"
"redis_version:%s\r\n"
"redis_git_sha1:%s\r\n"
- "redis_git_dirty:%d\r\n"
- "redis_build_id:%llx\r\n"
+ "redis_git_dirty:%i\r\n"
+ "redis_build_id:%s\r\n"
"redis_mode:%s\r\n"
"os:%s %s %s\r\n"
- "arch_bits:%d\r\n"
+ "arch_bits:%i\r\n"
"multiplexing_api:%s\r\n"
"atomicvar_api:%s\r\n"
- "gcc_version:%d.%d.%d\r\n"
- "process_id:%ld\r\n"
+ "gcc_version:%i.%i.%i\r\n"
+ "process_id:%I\r\n"
"run_id:%s\r\n"
- "tcp_port:%d\r\n"
- "uptime_in_seconds:%jd\r\n"
- "uptime_in_days:%jd\r\n"
- "hz:%d\r\n"
- "lru_clock:%ld\r\n"
+ "tcp_port:%i\r\n"
+ "uptime_in_seconds:%I\r\n"
+ "uptime_in_days:%I\r\n"
+ "hz:%i\r\n"
+ "configured_hz:%i\r\n"
+ "lru_clock:%u\r\n"
"executable:%s\r\n"
"config_file:%s\r\n",
REDIS_VERSION,
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
- (unsigned long long) redisBuildId(),
+ redisBuildIdString(),
mode,
name.sysname, name.release, name.machine,
server.arch_bits,
@@ -2924,29 +3981,34 @@ sds genRedisInfoString(char *section) {
#else
0,0,0,
#endif
- (long) getpid(),
+ (int64_t) getpid(),
server.runid,
- server.port,
- (intmax_t)uptime,
- (intmax_t)(uptime/(3600*24)),
+ server.port ? server.port : server.tls_port,
+ (int64_t)uptime,
+ (int64_t)(uptime/(3600*24)),
server.hz,
- (unsigned long) lruclock,
+ server.config_hz,
+ server.lruclock,
server.executable ? server.executable : "",
server.configfile ? server.configfile : "");
}
/* Clients */
if (allsections || defsections || !strcasecmp(section,"clients")) {
+ size_t maxin, maxout;
+ getExpansiveClientsInfo(&maxin,&maxout);
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,
"# Clients\r\n"
"connected_clients:%lu\r\n"
- "client_longest_output_list:%lu\r\n"
- "client_biggest_input_buf:%lu\r\n"
- "blocked_clients:%d\r\n",
+ "client_recent_max_input_buffer:%zu\r\n"
+ "client_recent_max_output_buffer:%zu\r\n"
+ "blocked_clients:%d\r\n"
+ "tracking_clients:%d\r\n",
listLength(server.clients)-listLength(server.slaves),
- lol, bib,
- server.blocked_clients);
+ maxin, maxout,
+ server.blocked_clients,
+ server.tracking_clients);
}
/* Memory */
@@ -2955,6 +4017,7 @@ sds genRedisInfoString(char *section) {
char peak_hmem[64];
char total_system_hmem[64];
char used_memory_lua_hmem[64];
+ char used_memory_scripts_hmem[64];
char used_memory_rss_hmem[64];
char maxmemory_hmem[64];
size_t zmalloc_used = zmalloc_used_memory();
@@ -2974,7 +4037,8 @@ sds genRedisInfoString(char *section) {
bytesToHuman(peak_hmem,server.stat_peak_memory);
bytesToHuman(total_system_hmem,total_system_mem);
bytesToHuman(used_memory_lua_hmem,memory_lua);
- bytesToHuman(used_memory_rss_hmem,server.resident_set_size);
+ bytesToHuman(used_memory_scripts_hmem,mh->lua_caches);
+ bytesToHuman(used_memory_rss_hmem,server.cron_malloc_stats.process_rss);
bytesToHuman(maxmemory_hmem,server.maxmemory);
if (sections++) info = sdscat(info,"\r\n");
@@ -2991,20 +4055,38 @@ sds genRedisInfoString(char *section) {
"used_memory_startup:%zu\r\n"
"used_memory_dataset:%zu\r\n"
"used_memory_dataset_perc:%.2f%%\r\n"
+ "allocator_allocated:%zu\r\n"
+ "allocator_active:%zu\r\n"
+ "allocator_resident:%zu\r\n"
"total_system_memory:%lu\r\n"
"total_system_memory_human:%s\r\n"
"used_memory_lua:%lld\r\n"
"used_memory_lua_human:%s\r\n"
+ "used_memory_scripts:%lld\r\n"
+ "used_memory_scripts_human:%s\r\n"
+ "number_of_cached_scripts:%lu\r\n"
"maxmemory:%lld\r\n"
"maxmemory_human:%s\r\n"
"maxmemory_policy:%s\r\n"
+ "allocator_frag_ratio:%.2f\r\n"
+ "allocator_frag_bytes:%zu\r\n"
+ "allocator_rss_ratio:%.2f\r\n"
+ "allocator_rss_bytes:%zd\r\n"
+ "rss_overhead_ratio:%.2f\r\n"
+ "rss_overhead_bytes:%zd\r\n"
"mem_fragmentation_ratio:%.2f\r\n"
+ "mem_fragmentation_bytes:%zd\r\n"
+ "mem_not_counted_for_evict:%zu\r\n"
+ "mem_replication_backlog:%zu\r\n"
+ "mem_clients_slaves:%zu\r\n"
+ "mem_clients_normal:%zu\r\n"
+ "mem_aof_buffer:%zu\r\n"
"mem_allocator:%s\r\n"
"active_defrag_running:%d\r\n"
"lazyfree_pending_objects:%zu\r\n",
zmalloc_used,
hmem,
- server.resident_set_size,
+ server.cron_malloc_stats.process_rss,
used_memory_rss_hmem,
server.stat_peak_memory,
peak_hmem,
@@ -3013,14 +4095,35 @@ sds genRedisInfoString(char *section) {
mh->startup_allocated,
mh->dataset,
mh->dataset_perc,
+ server.cron_malloc_stats.allocator_allocated,
+ server.cron_malloc_stats.allocator_active,
+ server.cron_malloc_stats.allocator_resident,
(unsigned long)total_system_mem,
total_system_hmem,
memory_lua,
used_memory_lua_hmem,
+ (long long) mh->lua_caches,
+ used_memory_scripts_hmem,
+ dictSize(server.lua_scripts),
server.maxmemory,
maxmemory_hmem,
evict_policy,
- mh->fragmentation,
+ mh->allocator_frag,
+ mh->allocator_frag_bytes,
+ mh->allocator_rss,
+ mh->allocator_rss_bytes,
+ mh->rss_extra,
+ mh->rss_extra_bytes,
+ mh->total_frag, /* This is the total RSS overhead, including
+ fragmentation, but not just it. This field
+ (and the next one) is named like that just
+ for backward compatibility. */
+ mh->total_frag_bytes,
+ freeMemoryGetNotCountedMemory(),
+ mh->repl_backlog,
+ mh->clients_slaves,
+ mh->clients_normal,
+ mh->aof_buffer,
ZMALLOC_LIB,
server.active_defrag_running,
lazyfreeGetPendingObjectsCount()
@@ -3048,7 +4151,9 @@ sds genRedisInfoString(char *section) {
"aof_current_rewrite_time_sec:%jd\r\n"
"aof_last_bgrewrite_status:%s\r\n"
"aof_last_write_status:%s\r\n"
- "aof_last_cow_size:%zu\r\n",
+ "aof_last_cow_size:%zu\r\n"
+ "module_fork_in_progress:%d\r\n"
+ "module_fork_last_cow_size:%zu\r\n",
server.loading,
server.dirty,
server.rdb_child_pid != -1,
@@ -3066,9 +4171,11 @@ sds genRedisInfoString(char *section) {
-1 : time(NULL)-server.aof_rewrite_time_start),
(server.aof_lastbgrewrite_status == C_OK) ? "ok" : "err",
(server.aof_last_write_status == C_OK) ? "ok" : "err",
- server.stat_aof_cow_bytes);
+ server.stat_aof_cow_bytes,
+ server.module_child_pid != -1,
+ server.stat_module_cow_bytes);
- if (server.aof_state != AOF_OFF) {
+ if (server.aof_enabled) {
info = sdscatprintf(info,
"aof_current_size:%lld\r\n"
"aof_base_size:%lld\r\n"
@@ -3148,7 +4255,8 @@ sds genRedisInfoString(char *section) {
"active_defrag_hits:%lld\r\n"
"active_defrag_misses:%lld\r\n"
"active_defrag_key_hits:%lld\r\n"
- "active_defrag_key_misses:%lld\r\n",
+ "active_defrag_key_misses:%lld\r\n"
+ "tracking_used_slots:%lld\r\n",
server.stat_numconnections,
server.stat_numcommands,
getInstantaneousMetric(STATS_METRIC_COMMAND),
@@ -3174,7 +4282,8 @@ sds genRedisInfoString(char *section) {
server.stat_active_defrag_hits,
server.stat_active_defrag_misses,
server.stat_active_defrag_key_hits,
- server.stat_active_defrag_key_misses);
+ server.stat_active_defrag_key_misses,
+ trackingGetUsedSlots());
}
/* Replication */
@@ -3222,7 +4331,7 @@ sds genRedisInfoString(char *section) {
if (server.repl_state != REPL_STATE_CONNECTED) {
info = sdscatprintf(info,
"master_link_down_since_seconds:%jd\r\n",
- (intmax_t)server.unixtime-server.repl_down_since);
+ (intmax_t)(server.unixtime-server.repl_down_since));
}
info = sdscatprintf(info,
"slave_priority:%d\r\n"
@@ -3258,7 +4367,7 @@ sds genRedisInfoString(char *section) {
long lag = 0;
if (slaveip[0] == '\0') {
- if (anetPeerToString(slave->fd,ip,sizeof(ip),&port) == -1)
+ if (connPeerToString(slave->conn,ip,sizeof(ip),&port) == -1)
continue;
slaveip = ip;
}
@@ -3310,14 +4419,21 @@ sds genRedisInfoString(char *section) {
if (sections++) info = sdscat(info,"\r\n");
info = sdscatprintf(info,
"# CPU\r\n"
- "used_cpu_sys:%.2f\r\n"
- "used_cpu_user:%.2f\r\n"
- "used_cpu_sys_children:%.2f\r\n"
- "used_cpu_user_children:%.2f\r\n",
- (float)self_ru.ru_stime.tv_sec+(float)self_ru.ru_stime.tv_usec/1000000,
- (float)self_ru.ru_utime.tv_sec+(float)self_ru.ru_utime.tv_usec/1000000,
- (float)c_ru.ru_stime.tv_sec+(float)c_ru.ru_stime.tv_usec/1000000,
- (float)c_ru.ru_utime.tv_sec+(float)c_ru.ru_utime.tv_usec/1000000);
+ "used_cpu_sys:%ld.%06ld\r\n"
+ "used_cpu_user:%ld.%06ld\r\n"
+ "used_cpu_sys_children:%ld.%06ld\r\n"
+ "used_cpu_user_children:%ld.%06ld\r\n",
+ (long)self_ru.ru_stime.tv_sec, (long)self_ru.ru_stime.tv_usec,
+ (long)self_ru.ru_utime.tv_sec, (long)self_ru.ru_utime.tv_usec,
+ (long)c_ru.ru_stime.tv_sec, (long)c_ru.ru_stime.tv_usec,
+ (long)c_ru.ru_utime.tv_sec, (long)c_ru.ru_utime.tv_usec);
+ }
+
+ /* Modules */
+ if (allsections || defsections || !strcasecmp(section,"modules")) {
+ if (sections++) info = sdscat(info,"\r\n");
+ info = sdscatprintf(info,"# Modules\r\n");
+ info = genModulesInfoString(info);
}
/* Command statistics */
@@ -3365,6 +4481,17 @@ sds genRedisInfoString(char *section) {
}
}
}
+
+ /* Get info from modules.
+ * if user asked for "everything" or "modules", or a specific section
+ * that's not found yet. */
+ if (everything || modules ||
+ (!allsections && !defsections && sections==0)) {
+ info = modulesCollectInfo(info,
+ everything || modules ? NULL: section,
+ 0, /* not a crash report */
+ sections);
+ }
return info;
}
@@ -3375,7 +4502,9 @@ void infoCommand(client *c) {
addReply(c,shared.syntaxerr);
return;
}
- addReplyBulkSds(c, genRedisInfoString(section));
+ sds info = genRedisInfoString(section);
+ addReplyVerbatim(c,info,sdslen(info),"txt");
+ sdsfree(info);
}
void monitorCommand(client *c) {
@@ -3465,7 +4594,7 @@ void usage(void) {
fprintf(stderr," ./redis-server (run the server with default conf)\n");
fprintf(stderr," ./redis-server /etc/redis/6379.conf\n");
fprintf(stderr," ./redis-server --port 7777\n");
- fprintf(stderr," ./redis-server --port 7777 --slaveof 127.0.0.1 8888\n");
+ fprintf(stderr," ./redis-server --port 7777 --replicaof 127.0.0.1 8888\n");
fprintf(stderr," ./redis-server /etc/myredis.conf --loglevel verbose\n\n");
fprintf(stderr,"Sentinel mode:\n");
fprintf(stderr," ./redis-server /etc/sentinel.conf --sentinel\n");
@@ -3492,7 +4621,7 @@ void redisAsciiArt(void) {
if (!show_logo) {
serverLog(LL_NOTICE,
"Running mode=%s, port=%d.",
- mode, server.port
+ mode, server.port ? server.port : server.tls_port
);
} else {
snprintf(buf,1024*16,ascii_logo,
@@ -3500,7 +4629,7 @@ void redisAsciiArt(void) {
redisGitSHA1(),
strtol(redisGitDirty(),NULL,10) > 0,
(sizeof(long) == 8) ? "64" : "32",
- mode, server.port,
+ mode, server.port ? server.port : server.tls_port,
(long) getpid()
);
serverLogRaw(LL_NOTICE|LL_RAW,buf);
@@ -3531,6 +4660,7 @@ static void sigShutdownHandler(int sig) {
rdbRemoveTempFile(getpid());
exit(1); /* Exit with an error since this was not a clean shutdown. */
} else if (server.loading) {
+ serverLogFromHandler(LL_WARNING, "Received shutdown signal during loading, exiting now.");
exit(0);
}
@@ -3561,6 +4691,61 @@ void setupSignalHandlers(void) {
return;
}
+/* This is the signal handler for children process. It is currently useful
+ * in order to track the SIGUSR1, that we send to a child in order to terminate
+ * it in a clean way, without the parent detecting an error and stop
+ * accepting writes because of a write error condition. */
+static void sigKillChildHandler(int sig) {
+ UNUSED(sig);
+ serverLogFromHandler(LL_WARNING, "Received SIGUSR1 in child, exiting now.");
+ exitFromChild(SERVER_CHILD_NOERROR_RETVAL);
+}
+
+void setupChildSignalHandlers(void) {
+ struct sigaction act;
+
+ /* When the SA_SIGINFO flag is set in sa_flags then sa_sigaction is used.
+ * Otherwise, sa_handler is used. */
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sigKillChildHandler;
+ sigaction(SIGUSR1, &act, NULL);
+ return;
+}
+
+int redisFork() {
+ int childpid;
+ long long start = ustime();
+ if ((childpid = fork()) == 0) {
+ /* Child */
+ closeListeningSockets(0);
+ setupChildSignalHandlers();
+ } else {
+ /* Parent */
+ server.stat_fork_time = ustime()-start;
+ server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
+ latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
+ if (childpid == -1) {
+ return -1;
+ }
+ updateDictResizePolicy();
+ }
+ return childpid;
+}
+
+void sendChildCOWInfo(int ptype, char *pname) {
+ size_t private_dirty = zmalloc_get_private_dirty(-1);
+
+ if (private_dirty) {
+ serverLog(LL_NOTICE,
+ "%s: %zu MB of memory used by copy-on-write",
+ pname, private_dirty/(1024*1024));
+ }
+
+ server.child_info_data.cow_size = private_dirty;
+ sendChildInfo(ptype);
+}
+
void memtest(size_t megabytes, int passes);
/* Returns 1 if there is --sentinel among the arguments or if
@@ -3587,12 +4772,14 @@ void loadDataFromDisk(void) {
(float)(ustime()-start)/1000000);
/* Restore the replication ID / offset from the RDB file. */
- if (server.masterhost &&
+ if ((server.masterhost ||
+ (server.cluster_enabled &&
+ nodeIsSlave(server.cluster->myself))) &&
rsi.repl_id_is_set &&
rsi.repl_offset != -1 &&
/* Note that older implementations may save a repl_stream_db
- * of -1 inside the RDB file in a wrong way, see more information
- * in function rdbPopulateSaveInfo. */
+ * of -1 inside the RDB file in a wrong way, see more
+ * information in function rdbPopulateSaveInfo. */
rsi.repl_stream_db != -1)
{
memcpy(server.replid,rsi.repl_id,sizeof(server.replid));
@@ -3625,7 +4812,7 @@ void redisSetProcTitle(char *title) {
setproctitle("%s %s:%d%s",
title,
server.bindaddr_count ? server.bindaddr[0] : "*",
- server.port,
+ server.port ? server.port : server.tls_port,
server_mode);
#else
UNUSED(title);
@@ -3746,12 +4933,12 @@ int main(int argc, char **argv) {
return sha1Test(argc, argv);
} else if (!strcasecmp(argv[2], "util")) {
return utilTest(argc, argv);
- } else if (!strcasecmp(argv[2], "sds")) {
- return sdsTest(argc, argv);
} else if (!strcasecmp(argv[2], "endianconv")) {
return endianconvTest(argc, argv);
} else if (!strcasecmp(argv[2], "crc64")) {
return crc64Test(argc, argv);
+ } else if (!strcasecmp(argv[2], "zmalloc")) {
+ return zmalloc_test(argc, argv);
}
return -1; /* test not found */
@@ -3763,15 +4950,20 @@ int main(int argc, char **argv) {
spt_init(argc, argv);
#endif
setlocale(LC_COLLATE,"");
+ tzset(); /* Populates 'timezone' global. */
zmalloc_set_oom_handler(redisOutOfMemoryHandler);
srand(time(NULL)^getpid());
gettimeofday(&tv,NULL);
- char hashseed[16];
- getRandomHexChars(hashseed,sizeof(hashseed));
- dictSetHashFunctionSeed((uint8_t*)hashseed);
+
+ uint8_t hashseed[16];
+ getRandomBytes(hashseed,sizeof(hashseed));
+ dictSetHashFunctionSeed(hashseed);
server.sentinel_mode = checkForSentinelMode(argc,argv);
initServerConfig();
+ ACLInit(); /* The ACL subsystem must be initialized ASAP because the
+ basic networking code and client creation depends on it. */
moduleInitModulesSystem();
+ tlsInit();
/* Store the executable path and arguments in a safe place in order
* to be able to restart the server later. */
@@ -3822,7 +5014,7 @@ int main(int argc, char **argv) {
configfile = argv[j];
server.configfile = getAbsolutePath(configfile);
/* Replace the config file in server.exec_argv with
- * its absoulte path. */
+ * its absolute path. */
zfree(server.exec_argv[j]);
server.exec_argv[j] = zstrdup(server.configfile);
j++;
@@ -3894,6 +5086,8 @@ int main(int argc, char **argv) {
linuxMemoryWarnings();
#endif
moduleLoadFromQueue();
+ ACLLoadUsersAtStartup();
+ InitServerLast();
loadDataFromDisk();
if (server.cluster_enabled) {
if (verifyClusterConfigWithData() == C_ERR) {
@@ -3903,11 +5097,12 @@ int main(int argc, char **argv) {
exit(1);
}
}
- if (server.ipfd_count > 0)
+ if (server.ipfd_count > 0 || server.tlsfd_count > 0)
serverLog(LL_NOTICE,"Ready to accept connections");
if (server.sofd > 0)
serverLog(LL_NOTICE,"The server is now ready to accept connections at %s", server.unixsocket);
} else {
+ InitServerLast();
sentinelIsRunning();
}
diff --git a/src/server.h b/src/server.h
index 0668c3752..97672d727 100644
--- a/src/server.h
+++ b/src/server.h
@@ -59,7 +59,6 @@ typedef long long mstime_t; /* millisecond time type. */
#include "anet.h" /* Networking the easy way */
#include "ziplist.h" /* Compact list data structure */
#include "intset.h" /* Compact integer set structure */
-#include "stream.h" /* Stream data type header file. */
#include "version.h" /* Version macro */
#include "util.h" /* Misc functions useful in many places */
#include "latency.h" /* Latency monitor API */
@@ -67,6 +66,10 @@ typedef long long mstime_t; /* millisecond time type. */
#include "quicklist.h" /* Lists are encoded as linked lists of
N-elements flat arrays */
#include "rax.h" /* Radix tree */
+#include "connection.h" /* Connection abstraction */
+
+#define REDISMODULE_CORE 1
+#include "redismodule.h" /* Redis modules API defines. */
/* Following includes allow test functions to be called from Redis main() */
#include "zipmap.h"
@@ -79,20 +82,25 @@ typedef long long mstime_t; /* millisecond time type. */
#define C_ERR -1
/* Static server configuration */
-#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
+#define CONFIG_DEFAULT_DYNAMIC_HZ 1 /* Adapt hz to # of clients.*/
+#define CONFIG_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
#define CONFIG_MIN_HZ 1
#define CONFIG_MAX_HZ 500
-#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port */
-#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog */
-#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* default client timeout: infinite */
+#define MAX_CLIENTS_PER_CLOCK_TICK 200 /* HZ is adapted based on that. */
+#define CONFIG_DEFAULT_SERVER_PORT 6379 /* TCP port. */
+#define CONFIG_DEFAULT_SERVER_TLS_PORT 0 /* TCP port. */
+#define CONFIG_DEFAULT_TCP_BACKLOG 511 /* TCP listen backlog. */
+#define CONFIG_DEFAULT_CLIENT_TIMEOUT 0 /* Default client timeout: infinite */
#define CONFIG_DEFAULT_DBNUM 16
+#define CONFIG_DEFAULT_IO_THREADS_NUM 1 /* Single threaded by default */
+#define CONFIG_DEFAULT_IO_THREADS_DO_READS 0 /* Read + parse from threads? */
#define CONFIG_MAX_LINE 1024
#define CRON_DBS_PER_CALL 16
#define NET_MAX_WRITES_PER_EVENT (1024*64)
#define PROTO_SHARED_SELECT_CMDS 10
#define OBJ_SHARED_INTEGERS 10000
#define OBJ_SHARED_BULKHDR_LEN 32
-#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages */
+#define LOG_MAX_LEN 1024 /* Default maximum length of syslog messages.*/
#define AOF_REWRITE_PERC 100
#define AOF_REWRITE_MIN_SIZE (64*1024*1024)
#define AOF_REWRITE_ITEMS_PER_CMD 64
@@ -120,6 +128,7 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_UNIX_SOCKET_PERM 0
#define CONFIG_DEFAULT_TCP_KEEPALIVE 300
#define CONFIG_DEFAULT_PROTECTED_MODE 1
+#define CONFIG_DEFAULT_GOPHER_ENABLED 0
#define CONFIG_DEFAULT_LOGFILE ""
#define CONFIG_DEFAULT_SYSLOG_ENABLED 0
#define CONFIG_DEFAULT_STOP_WRITES_ON_BGSAVE_ERROR 1
@@ -128,8 +137,11 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC 0
#define CONFIG_DEFAULT_REPL_DISKLESS_SYNC_DELAY 5
+#define CONFIG_DEFAULT_RDB_KEY_SAVE_DELAY 0
+#define CONFIG_DEFAULT_KEY_LOAD_DELAY 0
#define CONFIG_DEFAULT_SLAVE_SERVE_STALE_DATA 1
#define CONFIG_DEFAULT_SLAVE_READ_ONLY 1
+#define CONFIG_DEFAULT_SLAVE_IGNORE_MAXMEMORY 1
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_IP NULL
#define CONFIG_DEFAULT_SLAVE_ANNOUNCE_PORT 0
#define CONFIG_DEFAULT_REPL_DISABLE_TCP_NODELAY 0
@@ -140,11 +152,13 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_AOF_FILENAME "appendonly.aof"
#define CONFIG_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
#define CONFIG_DEFAULT_AOF_LOAD_TRUNCATED 1
-#define CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE 0
+#define CONFIG_DEFAULT_AOF_USE_RDB_PREAMBLE 1
#define CONFIG_DEFAULT_ACTIVE_REHASHING 1
#define CONFIG_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
+#define CONFIG_DEFAULT_RDB_SAVE_INCREMENTAL_FSYNC 1
#define CONFIG_DEFAULT_MIN_SLAVES_TO_WRITE 0
#define CONFIG_DEFAULT_MIN_SLAVES_MAX_LAG 10
+#define CONFIG_DEFAULT_ACL_FILENAME ""
#define NET_IP_STR_LEN 46 /* INET6_ADDRSTRLEN is 46, but we need to be sure */
#define NET_PEER_ID_LEN (NET_IP_STR_LEN+32) /* Must be enough for ip:port */
#define CONFIG_BINDADDR_MAX 16
@@ -159,9 +173,11 @@ typedef long long mstime_t; /* millisecond time type. */
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_LOWER 10 /* don't defrag when fragmentation is below 10% */
#define CONFIG_DEFAULT_DEFRAG_THRESHOLD_UPPER 100 /* maximum defrag force at 100% fragmentation */
#define CONFIG_DEFAULT_DEFRAG_IGNORE_BYTES (100<<20) /* don't defrag if frag overhead is below 100mb */
-#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 25 /* 25% CPU min (at lower threshold) */
+#define CONFIG_DEFAULT_DEFRAG_CYCLE_MIN 5 /* 5% CPU min (at lower threshold) */
#define CONFIG_DEFAULT_DEFRAG_CYCLE_MAX 75 /* 75% CPU max (at upper threshold) */
+#define CONFIG_DEFAULT_DEFRAG_MAX_SCAN_FIELDS 1000 /* keys with more than 1000 fields will be processed separately */
#define CONFIG_DEFAULT_PROTO_MAX_BULK_LEN (512ll*1024*1024) /* Bulk request max size */
+#define CONFIG_DEFAULT_TRACKING_TABLE_MAX_FILL 10 /* 10% tracking table max fill. */
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
@@ -169,6 +185,14 @@ typedef long long mstime_t; /* millisecond time type. */
#define ACTIVE_EXPIRE_CYCLE_SLOW 0
#define ACTIVE_EXPIRE_CYCLE_FAST 1
+/* Children process will exit with this status code to signal that the
+ * process terminated without an error: this is useful in order to kill
+ * a saving child (RDB or AOF one), without triggering in the parent the
+ * write protection that is normally turned on on write errors.
+ * Usually children that are terminated with SIGUSR1 will exit with this
+ * special code. */
+#define SERVER_CHILD_NOERROR_RETVAL 255
+
/* Instantaneous metrics tracking. */
#define STATS_METRIC_SAMPLES 16 /* Number of samples per metric. */
#define STATS_METRIC_COMMAND 0 /* Number of commands executed. */
@@ -183,7 +207,9 @@ typedef long long mstime_t; /* millisecond time type. */
#define PROTO_INLINE_MAX_SIZE (1024*64) /* Max size of inline reads */
#define PROTO_MBULK_BIG_ARG (1024*32)
#define LONG_STR_SIZE 21 /* Bytes needed for long -> str + '\0' */
-#define AOF_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
+#define REDIS_AUTOSYNC_BYTES (1024*1024*32) /* fdatasync every 32MB */
+
+#define LIMIT_PENDING_QUERYBUF (4*1024*1024) /* 4mb */
/* When configuring the server eventloop, we setup it so that the total number
* of file descriptors we can handle are server.maxclients + RESERVED_FDS +
@@ -196,22 +222,48 @@ typedef long long mstime_t; /* millisecond time type. */
/* Command flags. Please check the command table defined in the redis.c file
* for more information about the meaning of every flag. */
-#define CMD_WRITE (1<<0) /* "w" flag */
-#define CMD_READONLY (1<<1) /* "r" flag */
-#define CMD_DENYOOM (1<<2) /* "m" flag */
-#define CMD_MODULE (1<<3) /* Command exported by module. */
-#define CMD_ADMIN (1<<4) /* "a" flag */
-#define CMD_PUBSUB (1<<5) /* "p" flag */
-#define CMD_NOSCRIPT (1<<6) /* "s" flag */
-#define CMD_RANDOM (1<<7) /* "R" flag */
-#define CMD_SORT_FOR_SCRIPT (1<<8) /* "S" flag */
-#define CMD_LOADING (1<<9) /* "l" flag */
-#define CMD_STALE (1<<10) /* "t" flag */
-#define CMD_SKIP_MONITOR (1<<11) /* "M" flag */
-#define CMD_ASKING (1<<12) /* "k" flag */
-#define CMD_FAST (1<<13) /* "F" flag */
-#define CMD_MODULE_GETKEYS (1<<14) /* Use the modules getkeys interface. */
-#define CMD_MODULE_NO_CLUSTER (1<<15) /* Deny on Redis Cluster. */
+#define CMD_WRITE (1ULL<<0) /* "write" flag */
+#define CMD_READONLY (1ULL<<1) /* "read-only" flag */
+#define CMD_DENYOOM (1ULL<<2) /* "use-memory" flag */
+#define CMD_MODULE (1ULL<<3) /* Command exported by module. */
+#define CMD_ADMIN (1ULL<<4) /* "admin" flag */
+#define CMD_PUBSUB (1ULL<<5) /* "pub-sub" flag */
+#define CMD_NOSCRIPT (1ULL<<6) /* "no-script" flag */
+#define CMD_RANDOM (1ULL<<7) /* "random" flag */
+#define CMD_SORT_FOR_SCRIPT (1ULL<<8) /* "to-sort" flag */
+#define CMD_LOADING (1ULL<<9) /* "ok-loading" flag */
+#define CMD_STALE (1ULL<<10) /* "ok-stale" flag */
+#define CMD_SKIP_MONITOR (1ULL<<11) /* "no-monitor" flag */
+#define CMD_SKIP_SLOWLOG (1ULL<<12) /* "no-slowlog" flag */
+#define CMD_ASKING (1ULL<<13) /* "cluster-asking" flag */
+#define CMD_FAST (1ULL<<14) /* "fast" flag */
+
+/* Command flags used by the module system. */
+#define CMD_MODULE_GETKEYS (1ULL<<15) /* Use the modules getkeys interface. */
+#define CMD_MODULE_NO_CLUSTER (1ULL<<16) /* Deny on Redis Cluster. */
+
+/* Command flags that describe ACLs categories. */
+#define CMD_CATEGORY_KEYSPACE (1ULL<<17)
+#define CMD_CATEGORY_READ (1ULL<<18)
+#define CMD_CATEGORY_WRITE (1ULL<<19)
+#define CMD_CATEGORY_SET (1ULL<<20)
+#define CMD_CATEGORY_SORTEDSET (1ULL<<21)
+#define CMD_CATEGORY_LIST (1ULL<<22)
+#define CMD_CATEGORY_HASH (1ULL<<23)
+#define CMD_CATEGORY_STRING (1ULL<<24)
+#define CMD_CATEGORY_BITMAP (1ULL<<25)
+#define CMD_CATEGORY_HYPERLOGLOG (1ULL<<26)
+#define CMD_CATEGORY_GEO (1ULL<<27)
+#define CMD_CATEGORY_STREAM (1ULL<<28)
+#define CMD_CATEGORY_PUBSUB (1ULL<<29)
+#define CMD_CATEGORY_ADMIN (1ULL<<30)
+#define CMD_CATEGORY_FAST (1ULL<<31)
+#define CMD_CATEGORY_SLOW (1ULL<<32)
+#define CMD_CATEGORY_BLOCKING (1ULL<<33)
+#define CMD_CATEGORY_DANGEROUS (1ULL<<34)
+#define CMD_CATEGORY_CONNECTION (1ULL<<35)
+#define CMD_CATEGORY_TRANSACTION (1ULL<<36)
+#define CMD_CATEGORY_SCRIPTING (1ULL<<37)
/* AOF states */
#define AOF_OFF 0 /* AOF is off */
@@ -219,8 +271,8 @@ typedef long long mstime_t; /* millisecond time type. */
#define AOF_WAIT_REWRITE 2 /* AOF waits rewrite to start appending */
/* Client flags */
-#define CLIENT_SLAVE (1<<0) /* This client is a slave server */
-#define CLIENT_MASTER (1<<1) /* This client is a master server */
+#define CLIENT_SLAVE (1<<0) /* This client is a repliaca */
+#define CLIENT_MASTER (1<<1) /* This client is a master */
#define CLIENT_MONITOR (1<<2) /* This client is a slave monitor, see MONITOR */
#define CLIENT_MULTI (1<<3) /* This client is in a MULTI context */
#define CLIENT_BLOCKED (1<<4) /* The client is waiting in a blocking operation */
@@ -250,6 +302,17 @@ typedef long long mstime_t; /* millisecond time type. */
#define CLIENT_LUA_DEBUG (1<<25) /* Run EVAL in debug mode. */
#define CLIENT_LUA_DEBUG_SYNC (1<<26) /* EVAL debugging without fork() */
#define CLIENT_MODULE (1<<27) /* Non connected client used by some module. */
+#define CLIENT_PROTECTED (1<<28) /* Client should not be freed for now. */
+#define CLIENT_PENDING_READ (1<<29) /* The client has pending reads and was put
+ in the list of clients we can read
+ from. */
+#define CLIENT_PENDING_COMMAND (1<<30) /* Used in threaded I/O to signal after
+ we return single threaded that the
+ client has already pending commands
+ to be executed. */
+#define CLIENT_TRACKING (1<<31) /* Client enabled keys tracking in order to
+ perform client side caching. */
+#define CLIENT_TRACKING_BROKEN_REDIR (1ULL<<32) /* Target client is invalid. */
/* Client block type (btype field in client structure)
* if CLIENT_BLOCKED flag is set. */
@@ -258,7 +321,8 @@ typedef long long mstime_t; /* millisecond time type. */
#define BLOCKED_WAIT 2 /* WAIT for synchronous replication. */
#define BLOCKED_MODULE 3 /* Blocked by a loadable module. */
#define BLOCKED_STREAM 4 /* XREAD. */
-#define BLOCKED_NUM 5 /* Number of blocked states. */
+#define BLOCKED_ZSET 5 /* BZPOP et al. */
+#define BLOCKED_NUM 6 /* Number of blocked states. */
/* Client request types */
#define PROTO_REQ_INLINE 1
@@ -315,6 +379,8 @@ typedef long long mstime_t; /* millisecond time type. */
/* List related stuff */
#define LIST_HEAD 0
#define LIST_TAIL 1
+#define ZSET_MIN 0
+#define ZSET_MAX 1
/* Sort operations */
#define SORT_OP_GET 0
@@ -336,7 +402,7 @@ typedef long long mstime_t; /* millisecond time type. */
/* Anti-warning macro... */
#define UNUSED(V) ((void) V)
-#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */
+#define ZSKIPLIST_MAXLEVEL 64 /* Should be enough for 2^64 elements */
#define ZSKIPLIST_P 0.25 /* Skiplist P = 1/4 */
/* Append only defines */
@@ -345,12 +411,20 @@ typedef long long mstime_t; /* millisecond time type. */
#define AOF_FSYNC_EVERYSEC 2
#define CONFIG_DEFAULT_AOF_FSYNC AOF_FSYNC_EVERYSEC
-/* Zip structure related defaults */
+/* Replication diskless load defines */
+#define REPL_DISKLESS_LOAD_DISABLED 0
+#define REPL_DISKLESS_LOAD_WHEN_DB_EMPTY 1
+#define REPL_DISKLESS_LOAD_SWAPDB 2
+#define CONFIG_DEFAULT_REPL_DISKLESS_LOAD REPL_DISKLESS_LOAD_DISABLED
+
+/* Zipped structures related defaults */
#define OBJ_HASH_MAX_ZIPLIST_ENTRIES 512
#define OBJ_HASH_MAX_ZIPLIST_VALUE 64
#define OBJ_SET_MAX_INTSET_ENTRIES 512
#define OBJ_ZSET_MAX_ZIPLIST_ENTRIES 128
#define OBJ_ZSET_MAX_ZIPLIST_VALUE 64
+#define OBJ_STREAM_NODE_MAX_BYTES 4096
+#define OBJ_STREAM_NODE_MAX_ENTRIES 100
/* List defaults */
#define OBJ_LIST_MAX_ZIPLIST_SIZE -2
@@ -429,7 +503,8 @@ typedef long long mstime_t; /* millisecond time type. */
#define NOTIFY_EXPIRED (1<<8) /* x */
#define NOTIFY_EVICTED (1<<9) /* e */
#define NOTIFY_STREAM (1<<10) /* t */
-#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM) /* A flag */
+#define NOTIFY_KEY_MISS (1<<11) /* m */
+#define NOTIFY_ALL (NOTIFY_GENERIC | NOTIFY_STRING | NOTIFY_LIST | NOTIFY_SET | NOTIFY_HASH | NOTIFY_ZSET | NOTIFY_EXPIRED | NOTIFY_EVICTED | NOTIFY_STREAM | NOTIFY_KEY_MISS) /* A flag */
/* Get the first bind addr or NULL */
#define NET_FIRST_BIND_ADDR (server.bindaddr_count ? server.bindaddr[0] : NULL)
@@ -477,6 +552,10 @@ typedef long long mstime_t; /* millisecond time type. */
#define REDISMODULE_TYPE_ENCVER(id) (id & REDISMODULE_TYPE_ENCVER_MASK)
#define REDISMODULE_TYPE_SIGN(id) ((id & ~((uint64_t)REDISMODULE_TYPE_ENCVER_MASK)) >>REDISMODULE_TYPE_ENCVER_BITS)
+/* Bit flags for moduleTypeAuxSaveFunc */
+#define REDISMODULE_AUX_BEFORE_RDB (1<<0)
+#define REDISMODULE_AUX_AFTER_RDB (1<<1)
+
struct RedisModule;
struct RedisModuleIO;
struct RedisModuleDigest;
@@ -489,6 +568,8 @@ struct redisObject;
* is deleted. */
typedef void *(*moduleTypeLoadFunc)(struct RedisModuleIO *io, int encver);
typedef void (*moduleTypeSaveFunc)(struct RedisModuleIO *io, void *value);
+typedef int (*moduleTypeAuxLoadFunc)(struct RedisModuleIO *rdb, int encver, int when);
+typedef void (*moduleTypeAuxSaveFunc)(struct RedisModuleIO *rdb, int when);
typedef void (*moduleTypeRewriteFunc)(struct RedisModuleIO *io, struct redisObject *key, void *value);
typedef void (*moduleTypeDigestFunc)(struct RedisModuleDigest *digest, void *value);
typedef size_t (*moduleTypeMemUsageFunc)(const void *value);
@@ -505,6 +586,9 @@ typedef struct RedisModuleType {
moduleTypeMemUsageFunc mem_usage;
moduleTypeDigestFunc digest;
moduleTypeFreeFunc free;
+ moduleTypeAuxLoadFunc aux_load;
+ moduleTypeAuxSaveFunc aux_save;
+ int aux_save_triggers;
char name[10]; /* 9 bytes name + null term. Charset: A-Z a-z 0-9 _- */
} moduleType;
@@ -539,16 +623,18 @@ typedef struct RedisModuleIO {
int ver; /* Module serialization version: 1 (old),
* 2 (current version with opcodes annotation). */
struct RedisModuleCtx *ctx; /* Optional context, see RM_GetContextFromIO()*/
+ struct redisObject *key; /* Optional name of key processed */
} RedisModuleIO;
/* Macro to initialize an IO context. Note that the 'ver' field is populated
* inside rdb.c according to the version of the value to load. */
-#define moduleInitIOContext(iovar,mtype,rioptr) do { \
+#define moduleInitIOContext(iovar,mtype,rioptr,keyptr) do { \
iovar.rio = rioptr; \
iovar.type = mtype; \
iovar.bytes = 0; \
iovar.error = 0; \
iovar.ver = 0; \
+ iovar.key = keyptr; \
iovar.ctx = NULL; \
} while(0);
@@ -598,6 +684,11 @@ typedef struct redisObject {
void *ptr;
} robj;
+/* The a string name for an object's type as listed above
+ * Native types are checked against the OBJ_STRING, OBJ_LIST, OBJ_* defines,
+ * and Module types have their registered name returned. */
+char *getObjectTypeName(robj*);
+
/* Macro used to initialize a Redis object allocated on the stack.
* Note that this macro is taken near the structure definition to make sure
* we'll update it when the structure is changed, to avoid bugs like
@@ -611,6 +702,13 @@ typedef struct redisObject {
struct evictionPoolEntry; /* Defined in evict.c */
+/* This structure is used in order to represent the output buffer of a client,
+ * which is actually a linked list of blocks like that, that is: client->reply. */
+typedef struct clientReplyBlock {
+ size_t size, used;
+ char buf[];
+} clientReplyBlock;
+
/* Redis database representation. There are multiple databases identified
* by integers from 0 (the default database) up to the max configured
* database. The database number is the 'id' field in the structure. */
@@ -622,6 +720,7 @@ typedef struct redisDb {
dict *watched_keys; /* WATCHED keys for MULTI/EXEC CAS */
int id; /* Database ID */
long long avg_ttl; /* Average TTL, just for stats */
+ list *defrag_later; /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;
/* Client MULTI/EXEC state */
@@ -634,6 +733,9 @@ typedef struct multiCmd {
typedef struct multiState {
multiCmd *commands; /* Array of MULTI commands */
int count; /* Total number of MULTI commands */
+ int cmd_flags; /* The accumulated command flags OR-ed together.
+ So if at least a command has a given flag, it
+ will be set in this field. */
int minreplicas; /* MINREPLICAS for synchronous replication */
time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
} multiState;
@@ -645,7 +747,7 @@ typedef struct blockingState {
mstime_t timeout; /* Blocking operation timeout. If UNIX current time
* is > timeout then the operation timed out. */
- /* BLOCKED_LIST and BLOCKED_STREAM */
+ /* BLOCKED_LIST, BLOCKED_ZSET and BLOCKED_STREAM */
dict *keys; /* The keys we are waiting to terminate a blocking
* operation such as BLPOP or XREAD. Or NULL. */
robj *target; /* The key that should receive the element,
@@ -653,8 +755,10 @@ typedef struct blockingState {
/* BLOCK_STREAM */
size_t xread_count; /* XREAD COUNT option. */
- robj *xread_group; /* XREAD group name. */
+ robj *xread_group; /* XREADGROUP group name. */
+ robj *xread_consumer; /* XREADGROUP consumer name. */
mstime_t xread_retry_time, xread_retry_ttl;
+ int xread_group_noack;
/* BLOCKED_WAIT */
int numreplicas; /* Number of replicas we are waiting for ACK. */
@@ -682,21 +786,74 @@ typedef struct readyList {
robj *key;
} readyList;
+/* This structure represents a Redis user. This is useful for ACLs, the
+ * user is associated to the connection after the connection is authenticated.
+ * If there is no associated user, the connection uses the default user. */
+#define USER_COMMAND_BITS_COUNT 1024 /* The total number of command bits
+ in the user structure. The last valid
+ command ID we can set in the user
+ is USER_COMMAND_BITS_COUNT-1. */
+#define USER_FLAG_ENABLED (1<<0) /* The user is active. */
+#define USER_FLAG_DISABLED (1<<1) /* The user is disabled. */
+#define USER_FLAG_ALLKEYS (1<<2) /* The user can mention any key. */
+#define USER_FLAG_ALLCOMMANDS (1<<3) /* The user can run all commands. */
+#define USER_FLAG_NOPASS (1<<4) /* The user requires no password, any
+ provided password will work. For the
+ default user, this also means that
+ no AUTH is needed, and every
+ connection is immediately
+ authenticated. */
+typedef struct user {
+ sds name; /* The username as an SDS string. */
+ uint64_t flags; /* See USER_FLAG_* */
+
+ /* The bit in allowed_commands is set if this user has the right to
+ * execute this command. In commands having subcommands, if this bit is
+ * set, then all the subcommands are also available.
+ *
+ * If the bit for a given command is NOT set and the command has
+ * subcommands, Redis will also check allowed_subcommands in order to
+ * understand if the command can be executed. */
+ uint64_t allowed_commands[USER_COMMAND_BITS_COUNT/64];
+
+ /* This array points, for each command ID (corresponding to the command
+ * bit set in allowed_commands), to an array of SDS strings, terminated by
+ * a NULL pointer, with all the sub commands that can be executed for
+ * this command. When no subcommands matching is used, the field is just
+ * set to NULL to avoid allocating USER_COMMAND_BITS_COUNT pointers. */
+ sds **allowed_subcommands;
+ list *passwords; /* A list of SDS valid passwords for this user. */
+ list *patterns; /* A list of allowed key patterns. If this field is NULL
+ the user cannot mention any key in a command, unless
+ the flag ALLKEYS is set in the user. */
+} user;
+
/* With multiplexing we need to take per-client state.
* Clients are taken in a linked list. */
+
+#define CLIENT_ID_AOF (UINT64_MAX) /* Reserved ID for the AOF client. If you
+ need more reserved IDs use UINT64_MAX-1,
+ -2, ... and so forth. */
+
typedef struct client {
uint64_t id; /* Client incremental unique ID. */
- int fd; /* Client socket. */
+ connection *conn;
+ int resp; /* RESP protocol version. Can be 2 or 3. */
redisDb *db; /* Pointer to currently SELECTed DB. */
robj *name; /* As set by CLIENT SETNAME. */
sds querybuf; /* Buffer we use to accumulate client queries. */
- sds pending_querybuf; /* If this is a master, this buffer represents the
- yet not applied replication stream that we
- are receiving from the master. */
+ size_t qb_pos; /* The position we have read in querybuf. */
+ sds pending_querybuf; /* If this client is flagged as master, this buffer
+ represents the yet not applied portion of the
+ replication stream that we are receiving from
+ the master. */
size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */
int argc; /* Num of arguments of current command. */
robj **argv; /* Arguments of current command. */
struct redisCommand *cmd, *lastcmd; /* Last command executed. */
+ user *user; /* User associated with this connection. If the
+ user is set to NULL the connection can do
+ anything (admin). */
int reqtype; /* Request protocol type: PROTO_REQ_* */
int multibulklen; /* Number of multi bulk arguments left to read. */
long bulklen; /* Length of bulk argument in multi bulk request. */
@@ -707,10 +864,10 @@ typedef struct client {
time_t ctime; /* Client creation time. */
time_t lastinteraction; /* Time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time;
- int flags; /* Client flags: CLIENT_* macros. */
- int authenticated; /* When requirepass is non-NULL. */
+ uint64_t flags; /* Client flags: CLIENT_* macros. */
+ int authenticated; /* Needed when the default user requires auth. */
int replstate; /* Replication state if this is a slave. */
- int repl_put_online_on_ack; /* Install slave write handler on ACK. */
+ int repl_put_online_on_ack; /* Install slave write handler on first ACK. */
int repldbfd; /* Replication DB file descriptor. */
off_t repldboff; /* Replication DB file offset. */
off_t repldbsize; /* Replication DB file size. */
@@ -736,6 +893,11 @@ typedef struct client {
sds peerid; /* Cached peer ID. */
listNode *client_list_node; /* list node in client list */
+ /* If this client is in tracking mode and this field is non zero,
+ * invalidation messages for keys fetched by this client will be send to
+ * the specified client ID. */
+ uint64_t client_tracking_redirection;
+
/* Response buffer */
int bufpos;
char buf[PROTO_REPLY_CHUNK_BYTES];
@@ -753,14 +915,14 @@ struct moduleLoadQueueEntry {
};
struct sharedObjectsStruct {
- robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *cnegone, *pong, *space,
- *colon, *nullbulk, *nullmultibulk, *queued,
- *emptymultibulk, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
+ robj *crlf, *ok, *err, *emptybulk, *czero, *cone, *pong, *space,
+ *colon, *queued, *null[4], *nullarray[4], *emptymap[4], *emptyset[4],
+ *emptyarray, *wrongtypeerr, *nokeyerr, *syntaxerr, *sameobjecterr,
*outofrangeerr, *noscripterr, *loadingerr, *slowscripterr, *bgsaveerr,
*masterdownerr, *roslaveerr, *execaborterr, *noautherr, *noreplicaserr,
*busykeyerr, *oomerr, *plus, *messagebulk, *pmessagebulk, *subscribebulk,
*unsubscribebulk, *psubscribebulk, *punsubscribebulk, *del, *unlink,
- *rpop, *lpop, *lpush, *emptyscan,
+ *rpop, *lpop, *lpush, *rpoplpush, *zpopmin, *zpopmax, *emptyscan,
*select[PROTO_SHARED_SELECT_CMDS],
*integers[OBJ_SHARED_INTEGERS],
*mbulkhdr[OBJ_SHARED_BULKHDR_LEN], /* "*<value>\r\n" */
@@ -775,7 +937,7 @@ typedef struct zskiplistNode {
struct zskiplistNode *backward;
struct zskiplistLevel {
struct zskiplistNode *forward;
- unsigned int span;
+ unsigned long span;
} level[];
} zskiplistNode;
@@ -832,13 +994,21 @@ struct redisMemOverhead {
size_t clients_slaves;
size_t clients_normal;
size_t aof_buffer;
+ size_t lua_caches;
size_t overhead_total;
size_t dataset;
size_t total_keys;
size_t bytes_per_key;
float dataset_perc;
float peak_perc;
- float fragmentation;
+ float total_frag;
+ ssize_t total_frag_bytes;
+ float allocator_frag;
+ ssize_t allocator_frag_bytes;
+ float allocator_rss;
+ ssize_t allocator_rss_bytes;
+ float rss_extra;
+ size_t rss_extra_bytes;
size_t num_dbs;
struct {
size_t dbid;
@@ -867,6 +1037,30 @@ typedef struct rdbSaveInfo {
#define RDB_SAVE_INFO_INIT {-1,0,"000000000000000000000000000000",-1}
+struct malloc_stats {
+ size_t zmalloc_used;
+ size_t process_rss;
+ size_t allocator_allocated;
+ size_t allocator_active;
+ size_t allocator_resident;
+};
+
+/*-----------------------------------------------------------------------------
+ * TLS Context Configuration
+ *----------------------------------------------------------------------------*/
+
+typedef struct redisTLSContextConfig {
+ char *cert_file;
+ char *key_file;
+ char *dh_params_file;
+ char *ca_cert_file;
+ char *ca_cert_dir;
+ char *protocols;
+ char *ciphers;
+ char *ciphersuites;
+ int prefer_server_ciphers;
+} redisTLSContextConfig;
+
/*-----------------------------------------------------------------------------
* Global server state
*----------------------------------------------------------------------------*/
@@ -882,6 +1076,7 @@ struct clusterState;
#define CHILD_INFO_MAGIC 0xC17DDA7A12345678LL
#define CHILD_INFO_TYPE_RDB 0
#define CHILD_INFO_TYPE_AOF 1
+#define CHILD_INFO_TYPE_MODULE 3
struct redisServer {
/* General */
@@ -889,16 +1084,19 @@ struct redisServer {
char *configfile; /* Absolute config file path, or NULL */
char *executable; /* Absolute executable file path. */
char **exec_argv; /* Executable argv vector (copy). */
+ int dynamic_hz; /* Change hz value depending on # of clients. */
+ int config_hz; /* Configured HZ value. May be different than
+ the actual 'hz' field value if dynamic-hz
+ is enabled. */
int hz; /* serverCron() calls frequency in hertz */
redisDb *db;
dict *commands; /* Command table */
dict *orig_commands; /* Command table before command renaming. */
aeEventLoop *el;
- unsigned int lruclock; /* Clock for LRU eviction */
+ _Atomic unsigned int lruclock; /* Clock for LRU eviction */
int shutdown_asap; /* SHUTDOWN needed ASAP */
int activerehashing; /* Incremental rehash in serverCron() */
int active_defrag_running; /* Active defragmentation running (holds current scan aggressiveness) */
- char *requirepass; /* Pass for AUTH command, or NULL */
char *pidfile; /* PID file path */
int arch_bits; /* 32 or 64 depending on sizeof(long) */
int cronloops; /* Number of times the cron function run */
@@ -907,13 +1105,17 @@ struct redisServer {
size_t initial_memory_usage; /* Bytes used after initialization. */
int always_show_logo; /* Show logo even for non-stdout logging. */
/* Modules */
- dict *moduleapi; /* Exported APIs dictionary for modules. */
+ dict *moduleapi; /* Exported core APIs dictionary for modules. */
+ dict *sharedapi; /* Like moduleapi but containing the APIs that
+ modules share with each other. */
list *loadmodule_queue; /* List of modules to load at startup. */
int module_blocked_pipe[2]; /* Pipe used to awake the event loop if a
client blocked on a module command needs
to be processed. */
+ pid_t module_child_pid; /* PID of module child */
/* Networking */
int port; /* TCP listening port */
+ int tls_port; /* TLS listening port */
int tcp_backlog; /* TCP listen() backlog */
char *bindaddr[CONFIG_BINDADDR_MAX]; /* Addresses we should bind to */
int bindaddr_count; /* Number of addresses in server.bindaddr[] */
@@ -921,20 +1123,29 @@ struct redisServer {
mode_t unixsocketperm; /* UNIX socket permission */
int ipfd[CONFIG_BINDADDR_MAX]; /* TCP socket file descriptors */
int ipfd_count; /* Used slots in ipfd[] */
+ int tlsfd[CONFIG_BINDADDR_MAX]; /* TLS socket file descriptors */
+ int tlsfd_count; /* Used slots in tlsfd[] */
int sofd; /* Unix socket file descriptor */
int cfd[CONFIG_BINDADDR_MAX];/* Cluster bus listening socket */
int cfd_count; /* Used slots in cfd[] */
list *clients; /* List of active clients */
list *clients_to_close; /* Clients to close asynchronously */
list *clients_pending_write; /* There is to write or install handler. */
+ list *clients_pending_read; /* Client has pending read socket buffers. */
list *slaves, *monitors; /* List of slaves and MONITORs */
client *current_client; /* Current client, only used on crash report */
+ rax *clients_index; /* Active clients dictionary by client ID. */
int clients_paused; /* True if clients are currently paused */
mstime_t clients_pause_end_time; /* Time when we undo clients_paused */
char neterr[ANET_ERR_LEN]; /* Error buffer for anet.c */
dict *migrate_cached_sockets;/* MIGRATE cached sockets */
- uint64_t next_client_id; /* Next client unique ID. Incremental. */
+ _Atomic uint64_t next_client_id; /* Next client unique ID. Incremental. */
int protected_mode; /* Don't accept external connections. */
+ int gopher_enabled; /* If true the server will reply to gopher
+ queries. Will still serve RESP2 queries. */
+ int io_threads_num; /* Number of IO threads to use. */
+ int io_threads_do_reads; /* Read and parse from IO threads? */
+
/* RDB / AOF loading information */
int loading; /* We are loading data from disk if true */
off_t loading_total_bytes;
@@ -942,9 +1153,11 @@ struct redisServer {
time_t loading_start_time;
off_t loading_process_events_interval_bytes;
/* Fast pointers to often looked up command */
- struct redisCommand *delCommand, *multiCommand, *lpushCommand, *lpopCommand,
- *rpopCommand, *sremCommand, *execCommand, *expireCommand,
- *pexpireCommand;
+ struct redisCommand *delCommand, *multiCommand, *lpushCommand,
+ *lpopCommand, *rpopCommand, *zpopminCommand,
+ *zpopmaxCommand, *sremCommand, *execCommand,
+ *expireCommand, *pexpireCommand, *xclaimCommand,
+ *xgroupCommand;
/* Fields used only for stats */
time_t stat_starttime; /* Server start time */
long long stat_numcommands; /* Number of processed commands */
@@ -959,6 +1172,7 @@ struct redisServer {
long long stat_active_defrag_misses; /* number of allocations scanned but not moved */
long long stat_active_defrag_key_hits; /* number of keys with moved allocations */
long long stat_active_defrag_key_misses;/* number of keys scanned and not moved */
+ long long stat_active_defrag_scanned; /* number of dictEntries scanned */
size_t stat_peak_memory; /* Max used memory record */
long long stat_fork_time; /* Time needed to perform latest fork() */
double stat_fork_rate; /* Fork rate in GB/sec. */
@@ -970,11 +1184,12 @@ struct redisServer {
long long slowlog_entry_id; /* SLOWLOG current entry ID */
long long slowlog_log_slower_than; /* SLOWLOG time limit (to get logged) */
unsigned long slowlog_max_len; /* SLOWLOG max number of items logged */
- size_t resident_set_size; /* RSS sampled in serverCron(). */
- long long stat_net_input_bytes; /* Bytes read from network. */
- long long stat_net_output_bytes; /* Bytes written to network. */
+ struct malloc_stats cron_malloc_stats; /* sampled in serverCron(). */
+ _Atomic long long stat_net_input_bytes; /* Bytes read from network. */
+ _Atomic long long stat_net_output_bytes; /* Bytes written to network. */
size_t stat_rdb_cow_bytes; /* Copy on write bytes during RDB saving. */
size_t stat_aof_cow_bytes; /* Copy on write bytes during AOF rewrite. */
+ size_t stat_module_cow_bytes; /* Copy on write bytes during module fork. */
/* The following two are used to track instantaneous metrics, like
* number of operations per second, network traffic. */
struct {
@@ -989,18 +1204,21 @@ struct redisServer {
int tcpkeepalive; /* Set SO_KEEPALIVE if non-zero. */
int active_expire_enabled; /* Can be disabled for testing purposes. */
int active_defrag_enabled;
+ int jemalloc_bg_thread; /* Enable jemalloc background thread */
size_t active_defrag_ignore_bytes; /* minimum amount of fragmentation waste to start active defrag */
int active_defrag_threshold_lower; /* minimum percentage of fragmentation to start active defrag */
int active_defrag_threshold_upper; /* maximum percentage of fragmentation at which we use maximum effort */
int active_defrag_cycle_min; /* minimal effort for defrag in CPU percentage */
int active_defrag_cycle_max; /* maximal effort for defrag in CPU percentage */
- size_t client_max_querybuf_len; /* Limit for client query buffer length */
+ unsigned long active_defrag_max_scan_fields; /* maximum number of fields of set/hash/zset/list to process from within the main dict scan */
+ _Atomic size_t client_max_querybuf_len; /* Limit for client query buffer length */
int dbnum; /* Total number of configured DBs */
int supervised; /* 1 if supervised, 0 otherwise. */
int supervised_mode; /* See SUPERVISED_* */
int daemonize; /* True if running as a daemon */
clientBufferLimitsConfig client_obuf_limits[CLIENT_TYPE_OBUF_COUNT];
/* AOF persistence */
+ int aof_enabled; /* AOF configuration */
int aof_state; /* AOF_(ON|OFF|WAIT_REWRITE) */
int aof_fsync; /* Kind of fsync() policy */
char *aof_filename; /* Name of the AOF file */
@@ -1009,6 +1227,8 @@ struct redisServer {
off_t aof_rewrite_min_size; /* the AOF file is at least N bytes. */
off_t aof_rewrite_base_size; /* AOF size on latest startup or rewrite. */
off_t aof_current_size; /* AOF current size. */
+ off_t aof_fsync_offset; /* AOF offset which is already synced to disk. */
+ int aof_flush_sleep; /* Micros to sleep before flush. (used by tests) */
int aof_rewrite_scheduled; /* Rewrite once BGSAVE terminates. */
pid_t aof_child_pid; /* PID if rewriting process */
list *aof_rewrite_buf_blocks; /* Hold changes during an AOF rewrite. */
@@ -1021,7 +1241,8 @@ struct redisServer {
time_t aof_rewrite_time_start; /* Current AOF rewrite start time. */
int aof_lastbgrewrite_status; /* C_OK or C_ERR */
unsigned long aof_delayed_fsync; /* delayed AOF fsync() counter */
- int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
+ int aof_rewrite_incremental_fsync;/* fsync incrementally while aof rewriting? */
+ int rdb_save_incremental_fsync; /* fsync incrementally while rdb saving? */
int aof_last_write_status; /* C_OK or C_ERR */
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
@@ -1053,8 +1274,17 @@ struct redisServer {
int rdb_child_type; /* Type of save by active child. */
int lastbgsave_status; /* C_OK or C_ERR */
int stop_writes_on_bgsave_err; /* Don't allow writes if can't BGSAVE */
- int rdb_pipe_write_result_to_parent; /* RDB pipes used to return the state */
- int rdb_pipe_read_result_from_child; /* of each slave in diskless SYNC. */
+ int rdb_pipe_write; /* RDB pipes used to transfer the rdb */
+ int rdb_pipe_read; /* data to the parent process in diskless repl. */
+ connection **rdb_pipe_conns; /* Connections which are currently the */
+ int rdb_pipe_numconns; /* target of diskless rdb fork child. */
+ int rdb_pipe_numconns_writing; /* Number of rdb conns with pending writes. */
+ char *rdb_pipe_buff; /* In diskless replication, this buffer holds data */
+ int rdb_pipe_bufflen; /* that was read from the the rdb pipe. */
+ int rdb_key_save_delay; /* Delay in microseconds between keys while
+ * writing the RDB. (for testings) */
+ int key_load_delay; /* Delay in microseconds between keys while
+ * loading aof or rdb. (for testings) */
/* Pipe and data structures for child -> parent info sharing. */
int child_info_pipe[2]; /* Pipe used to write the child_info_data. */
struct {
@@ -1090,9 +1320,12 @@ struct redisServer {
int repl_min_slaves_to_write; /* Min number of slaves to write. */
int repl_min_slaves_max_lag; /* Max lag of <count> slaves to write. */
int repl_good_slaves_count; /* Number of slaves with lag <= max_lag. */
- int repl_diskless_sync; /* Send RDB to slaves sockets directly. */
+ int repl_diskless_sync; /* Master send RDB to slaves sockets directly. */
+ int repl_diskless_load; /* Slave parse RDB directly from the socket.
+ * see REPL_DISKLESS_LOAD_* enum */
int repl_diskless_sync_delay; /* Delay to start a diskless repl BGSAVE. */
/* Replication (slave) */
+ char *masteruser; /* AUTH with this user and masterauth with master */
char *masterauth; /* AUTH with this password with master */
char *masterhost; /* Hostname of master */
int masterport; /* Port of master */
@@ -1104,12 +1337,13 @@ struct redisServer {
off_t repl_transfer_size; /* Size of RDB to read from master during sync. */
off_t repl_transfer_read; /* Amount of RDB read from master during sync. */
off_t repl_transfer_last_fsync_off; /* Offset when we fsync-ed last time. */
- int repl_transfer_s; /* Slave -> Master SYNC socket */
+ connection *repl_transfer_s; /* Slave -> Master SYNC connection */
int repl_transfer_fd; /* Slave -> Master SYNC temp file descriptor */
char *repl_transfer_tmpfile; /* Slave-> master SYNC temp file name */
time_t repl_transfer_lastio; /* Unix time of the latest read, for timeout */
int repl_serve_stale_data; /* Serve stale data when link is down? */
int repl_slave_ro; /* Slave is read only? */
+ int repl_slave_ignore_maxmemory; /* If true slaves do not evict. */
time_t repl_down_since; /* Unix time at which link with master went down */
int repl_disable_tcp_nodelay; /* Disable TCP_NODELAY after SYNC? */
int slave_priority; /* Reported in INFO and used by Sentinel. */
@@ -1141,6 +1375,9 @@ struct redisServer {
unsigned int blocked_clients_by_type[BLOCKED_NUM];
list *unblocked_clients; /* list of clients to unblock before next loop */
list *ready_keys; /* List of readyList structures for BLPOP & co */
+ /* Client side caching. */
+ unsigned int tracking_clients; /* # of clients with tracking enabled.*/
+ int tracking_table_max_fill; /* Max fill percentage. */
/* Sort parameters - qsort_r() is only available under BSD so we
* have to take this state global, in order to pass it to sortCompare() */
int sort_desc;
@@ -1154,12 +1391,16 @@ struct redisServer {
size_t zset_max_ziplist_entries;
size_t zset_max_ziplist_value;
size_t hll_sparse_max_bytes;
+ size_t stream_node_max_bytes;
+ int64_t stream_node_max_entries;
/* List parameters */
int list_max_ziplist_size;
int list_compress_depth;
/* time cache */
- time_t unixtime; /* Unix time sampled every cron cycle. */
- long long mstime; /* Like 'unixtime' but with milliseconds resolution. */
+ _Atomic time_t unixtime; /* Unix time sampled every cron cycle. */
+ time_t timezone; /* Cached timezone. As set by tzset(). */
+ int daylight_active; /* Currently in daylight saving time. */
+ long long mstime; /* 'unixtime' with milliseconds resolution. */
/* Pubsub */
dict *pubsub_channels; /* Map channels to list of subscribed clients */
list *pubsub_patterns; /* A list of pubsub_patterns */
@@ -1179,11 +1420,17 @@ struct redisServer {
char *cluster_announce_ip; /* IP address to announce on cluster bus. */
int cluster_announce_port; /* base port to announce on cluster bus. */
int cluster_announce_bus_port; /* bus port to announce on cluster bus. */
+ int cluster_module_flags; /* Set of flags that Redis modules are able
+ to set in order to suppress certain
+ native Redis Cluster features. Check the
+ REDISMODULE_CLUSTER_FLAG_*. */
/* Scripting */
lua_State *lua; /* The Lua interpreter. We use just one for all clients */
client *lua_client; /* The "fake client" to query Redis from Lua */
client *lua_caller; /* The client running EVAL right now, or NULL */
+ char* lua_cur_script; /* SHA1 of the script currently running, or NULL */
dict *lua_scripts; /* A dictionary of SHA1 -> Lua scripts */
+ unsigned long long lua_scripts_mem; /* Cached scripts' memory + oh */
mstime_t lua_time_limit; /* Script timeout in milliseconds */
mstime_t lua_time_start; /* Start time of script, milliseconds time */
int lua_write_dirty; /* True if a write command was called during the
@@ -1204,6 +1451,8 @@ struct redisServer {
/* Latency monitor */
long long latency_monitor_threshold;
dict *latency_events;
+ /* ACLs */
+ char *acl_filename; /* ACL Users file. NULL if not configured. */
/* Assert & bug reporting */
const char *assert_failed;
const char *assert_file;
@@ -1212,12 +1461,11 @@ struct redisServer {
int watchdog_period; /* Software watchdog period in ms. 0 = off */
/* System hardware info */
size_t system_memory_size; /* Total memory in system as reported by OS */
-
- /* Mutexes used to protect atomic variables when atomic builtins are
- * not available. */
- pthread_mutex_t lruclock_mutex;
- pthread_mutex_t next_client_id_mutex;
- pthread_mutex_t unixtime_mutex;
+ /* TLS Configuration */
+ int tls_cluster;
+ int tls_replication;
+ int tls_auth_clients;
+ redisTLSContextConfig tls_ctx_config;
};
typedef struct pubsubPattern {
@@ -1231,8 +1479,8 @@ struct redisCommand {
char *name;
redisCommandProc *proc;
int arity;
- char *sflags; /* Flags as string representation, one char per flag. */
- int flags; /* The actual flags, obtained from the 'sflags' field. */
+ char *sflags; /* Flags as string representation, one char per flag. */
+ uint64_t flags; /* The actual flags, obtained from the 'sflags' field. */
/* Use a function to determine keys arguments in a command line.
* Used for Redis Cluster redirect. */
redisGetKeysProc *getkeys_proc;
@@ -1241,6 +1489,11 @@ struct redisCommand {
int lastkey; /* The last argument that's a key */
int keystep; /* The step between first and last key */
long long microseconds, calls;
+ int id; /* Command ID. This is a progressive ID starting from 0 that
+ is assigned at runtime, and is used in order to check
+ ACLs. A connection is able to execute a given command if
+ the user associated to the connection has this command
+ bit set in the bitmap of allowed commands. */
};
struct redisFunctionSym {
@@ -1297,6 +1550,8 @@ typedef struct {
dictEntry *de;
} hashTypeIterator;
+#include "stream.h" /* Stream data type header file. */
+
#define OBJ_HASH_KEY 1
#define OBJ_HASH_VALUE 2
@@ -1340,32 +1595,51 @@ size_t moduleCount(void);
void moduleAcquireGIL(void);
void moduleReleaseGIL(void);
void moduleNotifyKeyspaceEvent(int type, const char *event, robj *key, int dbid);
-
+void moduleCallCommandFilters(client *c);
+void ModuleForkDoneHandler(int exitcode, int bysignal);
+int TerminateModuleForkChild(int child_pid, int wait);
+ssize_t rdbSaveModulesAux(rio *rdb, int when);
+int moduleAllDatatypesHandleErrors();
+sds modulesCollectInfo(sds info, sds section, int for_crash_report, int sections);
+void moduleFireServerEvent(uint64_t eid, int subid, void *data);
/* Utils */
long long ustime(void);
long long mstime(void);
-void getRandomHexChars(char *p, unsigned int len);
+void getRandomHexChars(char *p, size_t len);
+void getRandomBytes(unsigned char *p, size_t len);
uint64_t crc64(uint64_t crc, const unsigned char *s, uint64_t l);
void exitFromChild(int retcode);
size_t redisPopcount(void *s, long count);
void redisSetProcTitle(char *title);
/* networking.c -- Networking and Client related operations */
-client *createClient(int fd);
+client *createClient(connection *conn);
void closeTimedoutClients(void);
void freeClient(client *c);
void freeClientAsync(client *c);
void resetClient(client *c);
-void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
-void *addDeferredMultiBulkLength(client *c);
-void setDeferredMultiBulkLength(client *c, void *node, long length);
+void sendReplyToClient(connection *conn);
+void *addReplyDeferredLen(client *c);
+void setDeferredArrayLen(client *c, void *node, long length);
+void setDeferredMapLen(client *c, void *node, long length);
+void setDeferredSetLen(client *c, void *node, long length);
+void setDeferredAttributeLen(client *c, void *node, long length);
+void setDeferredPushLen(client *c, void *node, long length);
void processInputBuffer(client *c);
+void processInputBufferAndReplicate(client *c);
+void processGopherRequest(client *c);
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
+void acceptTLSHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
-void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
-void addReplyString(client *c, const char *s, size_t len);
+void readQueryFromClient(connection *conn);
+void addReplyNull(client *c);
+void addReplyNullArray(client *c);
+void addReplyBool(client *c, int b);
+void addReplyVerbatim(client *c, const char *s, size_t len, const char *ext);
+void addReplyProto(client *c, const char *s, size_t len);
+void AddReplyFromClient(client *c, client *src);
void addReplyBulk(client *c, robj *obj);
void addReplyBulkCString(client *c, const char *s);
void addReplyBulkCBuffer(client *c, const void *p, size_t len);
@@ -1378,17 +1652,24 @@ void addReplyStatus(client *c, const char *status);
void addReplyDouble(client *c, double d);
void addReplyHumanLongDouble(client *c, long double d);
void addReplyLongLong(client *c, long long ll);
-void addReplyMultiBulkLen(client *c, long length);
+void addReplyArrayLen(client *c, long length);
+void addReplyMapLen(client *c, long length);
+void addReplySetLen(client *c, long length);
+void addReplyAttributeLen(client *c, long length);
+void addReplyPushLen(client *c, long length);
void addReplyHelp(client *c, const char **help);
+void addReplySubcommandSyntaxError(client *c);
+void addReplyLoadedModules(client *c);
void copyClientOutputBuffer(client *dst, client *src);
size_t sdsZmallocSize(sds s);
size_t getStringObjectSdsUsedMemory(robj *o);
+void freeClientReplyValue(void *o);
void *dupClientReplyValue(void *o);
void getClientsMaxBuffers(unsigned long *longest_output_list,
unsigned long *biggest_input_buffer);
char *getClientPeerId(client *client);
sds catClientInfoString(sds s, client *client);
-sds getAllClientsInfoString(void);
+sds getAllClientsInfoString(int type);
void rewriteClientCommandVector(client *c, int argc, ...);
void rewriteClientCommandArgument(client *c, int i, robj *newval);
void replaceClientCommandVector(client *c, int argc, robj **argv);
@@ -1405,10 +1686,17 @@ void pauseClients(mstime_t duration);
int clientsArePaused(void);
int processEventsWhileBlocked(void);
int handleClientsWithPendingWrites(void);
+int handleClientsWithPendingWritesUsingThreads(void);
+int handleClientsWithPendingReadsUsingThreads(void);
+int stopThreadedIOIfNeeded(void);
int clientHasPendingReplies(client *c);
void unlinkClient(client *c);
-int writeToClient(int fd, client *c, int handler_installed);
+int writeToClient(client *c, int handler_installed);
void linkClient(client *c);
+void protectClient(client *c);
+void unprotectClient(client *c);
+void initThreadedIO(void);
+client *lookupClientByID(uint64_t id);
#ifdef __GNUC__
void addReplyErrorFormat(client *c, const char *fmt, ...)
@@ -1420,6 +1708,15 @@ void addReplyErrorFormat(client *c, const char *fmt, ...);
void addReplyStatusFormat(client *c, const char *fmt, ...);
#endif
+/* Client side caching (tracking mode) */
+void enableTracking(client *c, uint64_t redirect_to);
+void disableTracking(client *c);
+void trackingRememberKeys(client *c);
+void trackingInvalidateKey(robj *keyobj);
+void trackingInvalidateKeysOnFlush(int dbid);
+void trackingLimitUsedSlots(void);
+unsigned long long trackingGetUsedSlots(void);
+
/* List data type */
void listTypeTryConversion(robj *subject, robj *value);
void listTypePush(robj *subject, robj *value, int where);
@@ -1469,6 +1766,7 @@ robj *tryObjectEncoding(robj *o);
robj *getDecodedObject(robj *o);
size_t stringObjectLen(robj *o);
robj *createStringObjectFromLongLong(long long value);
+robj *createStringObjectFromLongLongForValue(long long value);
robj *createStringObjectFromLongDouble(long double value, int humanfriendly);
robj *createQuicklistObject(void);
robj *createZiplistObject(void);
@@ -1492,6 +1790,7 @@ int compareStringObjects(robj *a, robj *b);
int collateStringObjects(robj *a, robj *b);
int equalStringObjects(robj *a, robj *b);
unsigned long long estimateObjectIdleTime(robj *o);
+void trimStringObjectIfNeeded(robj *o);
#define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)
/* Synchronous I/O with timeout */
@@ -1528,15 +1827,24 @@ void clearReplicationId2(void);
void chopReplicationBacklog(void);
void replicationCacheMasterUsingMyself(void);
void feedReplicationBacklog(void *ptr, size_t len);
+void rdbPipeReadHandler(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
+void rdbPipeWriteHandlerConnRemoved(struct connection *conn);
/* Generic persistence functions */
-void startLoading(FILE *fp);
+void startLoadingFile(FILE* fp, char* filename);
+void startLoading(size_t size);
void loadingProgress(off_t pos);
void stopLoading(void);
+#define DISK_ERROR_TYPE_AOF 1 /* Don't accept writes: AOF errors. */
+#define DISK_ERROR_TYPE_RDB 2 /* Don't accept writes: RDB errors. */
+#define DISK_ERROR_TYPE_NONE 0 /* No problems, we can accept writes. */
+int writeCommandsDeniedByDiskError(void);
+
/* RDB persistence */
#include "rdb.h"
int rdbSaveRio(rio *rdb, int *error, int flags, rdbSaveInfo *rsi);
+void killRDBChild(void);
/* AOF persistence */
void flushAppendOnlyFile(int force);
@@ -1550,6 +1858,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal);
void aofRewriteBufferReset(void);
unsigned long aofRewriteBufferSize(void);
ssize_t aofReadDiffFromParent(void);
+void killAppendOnlyChild(void);
/* Child info */
void openChildInfoPipe(void);
@@ -1557,17 +1866,45 @@ void closeChildInfoPipe(void);
void sendChildInfo(int process_type);
void receiveChildInfo(void);
+/* Fork helpers */
+int redisFork();
+int hasActiveChildProcess();
+void sendChildCOWInfo(int ptype, char *pname);
+
+/* acl.c -- Authentication related prototypes. */
+extern rax *Users;
+extern user *DefaultUser;
+void ACLInit(void);
+/* Return values for ACLCheckUserCredentials(). */
+#define ACL_OK 0
+#define ACL_DENIED_CMD 1
+#define ACL_DENIED_KEY 2
+int ACLCheckUserCredentials(robj *username, robj *password);
+int ACLAuthenticateUser(client *c, robj *username, robj *password);
+unsigned long ACLGetCommandID(const char *cmdname);
+user *ACLGetUserByName(const char *name, size_t namelen);
+int ACLCheckCommandPerm(client *c);
+int ACLSetUser(user *u, const char *op, ssize_t oplen);
+sds ACLDefaultUserFirstPassword(void);
+uint64_t ACLGetCommandCategoryFlagByName(const char *name);
+int ACLAppendUserForLoading(sds *argv, int argc, int *argc_err);
+char *ACLSetUserStringError(void);
+int ACLLoadConfiguredUsers(void);
+sds ACLDescribeUser(user *u);
+void ACLLoadUsersAtStartup(void);
+void addReplyCommandCategories(client *c, struct redisCommand *cmd);
+
/* Sorted sets data type */
/* Input flags. */
#define ZADD_NONE 0
#define ZADD_INCR (1<<0) /* Increment the score instead of setting it. */
#define ZADD_NX (1<<1) /* Don't touch elements not already existing. */
-#define ZADD_XX (1<<2) /* Only touch elements already exisitng. */
+#define ZADD_XX (1<<2) /* Only touch elements already existing. */
/* Output flags. */
#define ZADD_NOP (1<<3) /* Operation not performed because of conditionals.*/
-#define ZADD_NAN (1<<4) /* Only touch elements already exisitng. */
+#define ZADD_NAN (1<<4) /* Only touch elements already existing. */
#define ZADD_ADDED (1<<5) /* The element was new and was added. */
#define ZADD_UPDATED (1<<6) /* The element already existed, score updated. */
@@ -1598,7 +1935,7 @@ void zzlNext(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
void zzlPrev(unsigned char *zl, unsigned char **eptr, unsigned char **sptr);
unsigned char *zzlFirstInRange(unsigned char *zl, zrangespec *range);
unsigned char *zzlLastInRange(unsigned char *zl, zrangespec *range);
-unsigned int zsetLength(const robj *zobj);
+unsigned long zsetLength(const robj *zobj);
void zsetConvert(robj *zobj, int encoding);
void zsetConvertToZiplistIfNeeded(robj *zobj, size_t maxelelen);
int zsetScore(robj *zobj, sds member, double *score);
@@ -1606,6 +1943,7 @@ unsigned long zslGetRank(zskiplist *zsl, double score, sds o);
int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore);
long zsetRank(robj *zobj, sds ele, int reverse);
int zsetDel(robj *zobj, sds ele);
+void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey, robj *countarg);
sds ziplistGetObject(unsigned char *sptr);
int zslValueGteMin(double value, zrangespec *spec);
int zslValueLteMax(double value, zrangespec *spec);
@@ -1621,7 +1959,10 @@ int zslLexValueGteMin(sds value, zlexrangespec *spec);
int zslLexValueLteMax(sds value, zlexrangespec *spec);
/* Core functions */
+int getMaxmemoryState(size_t *total, size_t *logical, size_t *tofree, float *level);
+size_t freeMemoryGetNotCountedMemory();
int freeMemoryIfNeeded(void);
+int freeMemoryIfNeededAndSafe(void);
int processCommand(client *c);
void setupSignalHandlers(void);
struct redisCommand *lookupCommand(sds name);
@@ -1630,6 +1971,8 @@ struct redisCommand *lookupCommandOrOriginal(sds name);
void call(client *c, int flags);
void propagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int flags);
void alsoPropagate(struct redisCommand *cmd, int dbid, robj **argv, int argc, int target);
+void redisOpArrayInit(redisOpArray *oa);
+void redisOpArrayFree(redisOpArray *oa);
void forceCommandPropagation(client *c, int flags);
void preventCommandPropagation(client *c);
void preventCommandAOF(client *c);
@@ -1658,6 +2001,7 @@ unsigned int LRU_CLOCK(void);
const char *evictPolicyToString(void);
struct redisMemOverhead *getMemoryOverheadData(void);
void freeMemoryOverheadData(struct redisMemOverhead *mh);
+void checkChildrenDone(void);
#define RESTART_SERVER_NONE 0
#define RESTART_SERVER_GRACEFULLY (1<<0) /* Do proper shutdown. */
@@ -1685,7 +2029,6 @@ void setTypeConvert(robj *subject, int enc);
void hashTypeConvert(robj *o, int enc);
void hashTypeTryConversion(robj *subject, robj **argv, int start, int end);
-void hashTypeTryObjectEncoding(robj *subject, robj **o1, robj **o2);
int hashTypeExists(robj *o, sds key);
int hashTypeDelete(robj *o, sds key);
unsigned long hashTypeLength(const robj *o);
@@ -1709,6 +2052,7 @@ int pubsubUnsubscribeAllPatterns(client *c, int notify);
void freePubsubPattern(void *p);
int listMatchPubsubPattern(void *a, void *b);
int pubsubPublishMessage(robj *channel, robj *message);
+void addReplyPubsubMessage(client *c, robj *channel, robj *msg);
/* Keyspace events notification */
void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid);
@@ -1737,6 +2081,8 @@ robj *lookupKeyWriteOrReply(client *c, robj *key, robj *reply);
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags);
robj *objectCommandLookup(client *c, robj *key);
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply);
+void objectSetLRUOrLFU(robj *val, long long lfu_freq, long long lru_idle,
+ long long lru_clock);
#define LOOKUP_NONE 0
#define LOOKUP_NOTOUCH (1<<0)
void dbAdd(redisDb *db, robj *key, robj *val);
@@ -1751,6 +2097,8 @@ robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o);
#define EMPTYDB_NO_FLAGS 0 /* No flags. */
#define EMPTYDB_ASYNC (1<<0) /* Reclaim memory in another thread. */
long long emptyDb(int dbnum, int flags, void(callback)(void*));
+long long emptyDbGeneric(redisDb *dbarray, int dbnum, int flags, void(callback)(void*));
+long long dbTotalServerKeyCount();
int selectDb(client *c, int id);
void signalModifiedKey(redisDb *db, robj *key);
@@ -1768,6 +2116,7 @@ int dbAsyncDelete(redisDb *db, robj *key);
void emptyDbAsync(redisDb *db);
void slotToKeyFlushAsync(void);
size_t lazyfreeGetPendingObjectsCount(void);
+void freeObjAsync(robj *o);
/* API to get key arguments from commands */
int *getKeysFromCommand(struct redisCommand *cmd, robj **argv, int argc, int *numkeys);
@@ -1787,6 +2136,7 @@ void clusterCron(void);
void clusterPropagatePublish(robj *channel, robj *message);
void migrateCloseTimedoutSockets(void);
void clusterBeforeSleep(void);
+int clusterSendModuleMessageToTarget(const char *target, uint64_t module_id, uint8_t type, unsigned char *payload, uint32_t len);
/* Sentinel */
void initSentinelConfig(void);
@@ -1811,6 +2161,7 @@ sds luaCreateFunction(client *c, lua_State *lua, robj *body);
void processUnblockedClients(void);
void blockClient(client *c, int btype);
void unblockClient(client *c);
+void queueClientForReprocessing(client *c);
void replyToBlockedClientTimedOut(client *c);
int getTimeoutFromObjectOrReply(client *c, robj *object, mstime_t *timeout, int unit);
void disconnectAllBlockedClients(void);
@@ -1841,6 +2192,7 @@ void dictSdsDestructor(void *privdata, void *val);
char *redisGitSHA1(void);
char *redisGitDirty(void);
uint64_t redisBuildId(void);
+char *redisBuildIdString(void);
/* Commands prototypes */
void authCommand(client *c);
@@ -1924,7 +2276,7 @@ void ttlCommand(client *c);
void touchCommand(client *c);
void pttlCommand(client *c);
void persistCommand(client *c);
-void slaveofCommand(client *c);
+void replicaofCommand(client *c);
void roleCommand(client *c);
void debugCommand(client *c);
void msetCommand(client *c);
@@ -1944,6 +2296,10 @@ void zremCommand(client *c);
void zscoreCommand(client *c);
void zremrangebyscoreCommand(client *c);
void zremrangebylexCommand(client *c);
+void zpopminCommand(client *c);
+void zpopmaxCommand(client *c);
+void bzpopminCommand(client *c);
+void bzpopmaxCommand(client *c);
void multiCommand(client *c);
void execCommand(client *c);
void discardCommand(client *c);
@@ -1992,6 +2348,7 @@ void dumpCommand(client *c);
void objectCommand(client *c);
void memoryCommand(client *c);
void clientCommand(client *c);
+void helloCommand(client *c);
void evalCommand(client *c);
void evalShaCommand(client *c);
void scriptCommand(client *c);
@@ -2024,6 +2381,16 @@ void xrangeCommand(client *c);
void xrevrangeCommand(client *c);
void xlenCommand(client *c);
void xreadCommand(client *c);
+void xgroupCommand(client *c);
+void xsetidCommand(client *c);
+void xackCommand(client *c);
+void xpendingCommand(client *c);
+void xclaimCommand(client *c);
+void xinfoCommand(client *c);
+void xdelCommand(client *c);
+void xtrimCommand(client *c);
+void lolwutCommand(client *c);
+void aclCommand(client *c);
#if defined(__GNUC__)
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
@@ -2040,6 +2407,7 @@ void bugReportStart(void);
void serverLogObjectDebugInfo(const robj *o);
void sigsegvHandler(int sig, siginfo_t *info, void *secret);
sds genRedisInfoString(char *section);
+sds genModulesInfoString(sds info);
void enableWatchdog(int period);
void disableWatchdog(void);
void watchdogScheduleSignal(int period);
@@ -2047,6 +2415,11 @@ void serverLogHexDump(int level, char *descr, void *value, size_t len);
int memtest_preserving_test(unsigned long *m, size_t bytes, int passes);
void mixDigest(unsigned char *digest, void *ptr, size_t len);
void xorDigest(unsigned char *digest, void *ptr, size_t len);
+int populateCommandTableParseFlags(struct redisCommand *c, char *strflags);
+
+/* TLS stuff */
+void tlsInit(void);
+int tlsConfigure(redisTLSContextConfig *ctx_config);
#define redisDebug(fmt, ...) \
printf("DEBUG %s:%d > " fmt "\n", __FILE__, __LINE__, __VA_ARGS__)
diff --git a/src/setproctitle.c b/src/setproctitle.c
index 6563242de..5f91d7bfe 100644
--- a/src/setproctitle.c
+++ b/src/setproctitle.c
@@ -39,7 +39,7 @@
#include <errno.h> /* errno program_invocation_name program_invocation_short_name */
#if !defined(HAVE_SETPROCTITLE)
-#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__)
+#if (defined __NetBSD__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __DragonFly__)
#define HAVE_SETPROCTITLE 1
#else
#define HAVE_SETPROCTITLE 0
diff --git a/src/sha256.c b/src/sha256.c
new file mode 100644
index 000000000..d644d2d4e
--- /dev/null
+++ b/src/sha256.c
@@ -0,0 +1,158 @@
+/*********************************************************************
+* Filename: sha256.c
+* Author: Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details: Implementation of the SHA-256 hashing algorithm.
+ SHA-256 is one of the three algorithms in the SHA2
+ specification. The others, SHA-384 and SHA-512, are not
+ offered in this implementation.
+ Algorithm specification can be found here:
+ * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
+ This implementation uses little endian byte order.
+*********************************************************************/
+
+/*************************** HEADER FILES ***************************/
+#include <stdlib.h>
+#include <string.h>
+#include "sha256.h"
+
+/****************************** MACROS ******************************/
+#define ROTLEFT(a,b) (((a) << (b)) | ((a) >> (32-(b))))
+#define ROTRIGHT(a,b) (((a) >> (b)) | ((a) << (32-(b))))
+
+#define CH(x,y,z) (((x) & (y)) ^ (~(x) & (z)))
+#define MAJ(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
+#define EP0(x) (ROTRIGHT(x,2) ^ ROTRIGHT(x,13) ^ ROTRIGHT(x,22))
+#define EP1(x) (ROTRIGHT(x,6) ^ ROTRIGHT(x,11) ^ ROTRIGHT(x,25))
+#define SIG0(x) (ROTRIGHT(x,7) ^ ROTRIGHT(x,18) ^ ((x) >> 3))
+#define SIG1(x) (ROTRIGHT(x,17) ^ ROTRIGHT(x,19) ^ ((x) >> 10))
+
+/**************************** VARIABLES *****************************/
+static const WORD k[64] = {
+ 0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
+ 0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
+ 0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
+ 0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
+ 0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
+ 0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
+ 0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
+ 0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
+};
+
+/*********************** FUNCTION DEFINITIONS ***********************/
+void sha256_transform(SHA256_CTX *ctx, const BYTE data[])
+{
+ WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64];
+
+ for (i = 0, j = 0; i < 16; ++i, j += 4)
+ m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]);
+ for ( ; i < 64; ++i)
+ m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16];
+
+ a = ctx->state[0];
+ b = ctx->state[1];
+ c = ctx->state[2];
+ d = ctx->state[3];
+ e = ctx->state[4];
+ f = ctx->state[5];
+ g = ctx->state[6];
+ h = ctx->state[7];
+
+ for (i = 0; i < 64; ++i) {
+ t1 = h + EP1(e) + CH(e,f,g) + k[i] + m[i];
+ t2 = EP0(a) + MAJ(a,b,c);
+ h = g;
+ g = f;
+ f = e;
+ e = d + t1;
+ d = c;
+ c = b;
+ b = a;
+ a = t1 + t2;
+ }
+
+ ctx->state[0] += a;
+ ctx->state[1] += b;
+ ctx->state[2] += c;
+ ctx->state[3] += d;
+ ctx->state[4] += e;
+ ctx->state[5] += f;
+ ctx->state[6] += g;
+ ctx->state[7] += h;
+}
+
+void sha256_init(SHA256_CTX *ctx)
+{
+ ctx->datalen = 0;
+ ctx->bitlen = 0;
+ ctx->state[0] = 0x6a09e667;
+ ctx->state[1] = 0xbb67ae85;
+ ctx->state[2] = 0x3c6ef372;
+ ctx->state[3] = 0xa54ff53a;
+ ctx->state[4] = 0x510e527f;
+ ctx->state[5] = 0x9b05688c;
+ ctx->state[6] = 0x1f83d9ab;
+ ctx->state[7] = 0x5be0cd19;
+}
+
+void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len)
+{
+ WORD i;
+
+ for (i = 0; i < len; ++i) {
+ ctx->data[ctx->datalen] = data[i];
+ ctx->datalen++;
+ if (ctx->datalen == 64) {
+ sha256_transform(ctx, ctx->data);
+ ctx->bitlen += 512;
+ ctx->datalen = 0;
+ }
+ }
+}
+
+void sha256_final(SHA256_CTX *ctx, BYTE hash[])
+{
+ WORD i;
+
+ i = ctx->datalen;
+
+ // Pad whatever data is left in the buffer.
+ if (ctx->datalen < 56) {
+ ctx->data[i++] = 0x80;
+ while (i < 56)
+ ctx->data[i++] = 0x00;
+ }
+ else {
+ ctx->data[i++] = 0x80;
+ while (i < 64)
+ ctx->data[i++] = 0x00;
+ sha256_transform(ctx, ctx->data);
+ memset(ctx->data, 0, 56);
+ }
+
+ // Append to the padding the total message's length in bits and transform.
+ ctx->bitlen += ctx->datalen * 8;
+ ctx->data[63] = ctx->bitlen;
+ ctx->data[62] = ctx->bitlen >> 8;
+ ctx->data[61] = ctx->bitlen >> 16;
+ ctx->data[60] = ctx->bitlen >> 24;
+ ctx->data[59] = ctx->bitlen >> 32;
+ ctx->data[58] = ctx->bitlen >> 40;
+ ctx->data[57] = ctx->bitlen >> 48;
+ ctx->data[56] = ctx->bitlen >> 56;
+ sha256_transform(ctx, ctx->data);
+
+ // Since this implementation uses little endian byte ordering and SHA uses big endian,
+ // reverse all the bytes when copying the final state to the output hash.
+ for (i = 0; i < 4; ++i) {
+ hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff;
+ hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff;
+ }
+}
diff --git a/src/sha256.h b/src/sha256.h
new file mode 100644
index 000000000..dc53ead2b
--- /dev/null
+++ b/src/sha256.h
@@ -0,0 +1,35 @@
+/*********************************************************************
+* Filename: sha256.h
+* Author: Brad Conte (brad AT bradconte.com)
+* Copyright:
+* Disclaimer: This code is presented "as is" without any guarantees.
+* Details: Defines the API for the corresponding SHA1 implementation.
+*********************************************************************/
+
+#ifndef SHA256_H
+#define SHA256_H
+
+/*************************** HEADER FILES ***************************/
+#include <stddef.h>
+#include <stdint.h>
+
+/****************************** MACROS ******************************/
+#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest
+
+/**************************** DATA TYPES ****************************/
+typedef uint8_t BYTE; // 8-bit byte
+typedef uint32_t WORD; // 32-bit word
+
+typedef struct {
+ BYTE data[64];
+ WORD datalen;
+ unsigned long long bitlen;
+ WORD state[8];
+} SHA256_CTX;
+
+/*********************** FUNCTION DECLARATIONS **********************/
+void sha256_init(SHA256_CTX *ctx);
+void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len);
+void sha256_final(SHA256_CTX *ctx, BYTE hash[]);
+
+#endif // SHA256_H
diff --git a/src/siphash.c b/src/siphash.c
index 6c41fe6b6..357741132 100644
--- a/src/siphash.c
+++ b/src/siphash.c
@@ -58,7 +58,8 @@ int siptlw(int c) {
/* Test of the CPU is Little Endian and supports not aligned accesses.
* Two interesting conditions to speedup the function that happen to be
* in most of x86 servers. */
-#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__)
+#if defined(__X86_64__) || defined(__x86_64__) || defined (__i386__) \
+ || defined (__aarch64__) || defined (__arm64__)
#define UNALIGNED_LE_CPU
#endif
@@ -142,12 +143,12 @@ uint64_t siphash(const uint8_t *in, const size_t inlen, const uint8_t *k) {
}
switch (left) {
- case 7: b |= ((uint64_t)in[6]) << 48;
- case 6: b |= ((uint64_t)in[5]) << 40;
- case 5: b |= ((uint64_t)in[4]) << 32;
- case 4: b |= ((uint64_t)in[3]) << 24;
- case 3: b |= ((uint64_t)in[2]) << 16;
- case 2: b |= ((uint64_t)in[1]) << 8;
+ case 7: b |= ((uint64_t)in[6]) << 48; /* fall-thru */
+ case 6: b |= ((uint64_t)in[5]) << 40; /* fall-thru */
+ case 5: b |= ((uint64_t)in[4]) << 32; /* fall-thru */
+ case 4: b |= ((uint64_t)in[3]) << 24; /* fall-thru */
+ case 3: b |= ((uint64_t)in[2]) << 16; /* fall-thru */
+ case 2: b |= ((uint64_t)in[1]) << 8; /* fall-thru */
case 1: b |= ((uint64_t)in[0]); break;
case 0: break;
}
@@ -202,12 +203,12 @@ uint64_t siphash_nocase(const uint8_t *in, const size_t inlen, const uint8_t *k)
}
switch (left) {
- case 7: b |= ((uint64_t)siptlw(in[6])) << 48;
- case 6: b |= ((uint64_t)siptlw(in[5])) << 40;
- case 5: b |= ((uint64_t)siptlw(in[4])) << 32;
- case 4: b |= ((uint64_t)siptlw(in[3])) << 24;
- case 3: b |= ((uint64_t)siptlw(in[2])) << 16;
- case 2: b |= ((uint64_t)siptlw(in[1])) << 8;
+ case 7: b |= ((uint64_t)siptlw(in[6])) << 48; /* fall-thru */
+ case 6: b |= ((uint64_t)siptlw(in[5])) << 40; /* fall-thru */
+ case 5: b |= ((uint64_t)siptlw(in[4])) << 32; /* fall-thru */
+ case 4: b |= ((uint64_t)siptlw(in[3])) << 24; /* fall-thru */
+ case 3: b |= ((uint64_t)siptlw(in[2])) << 16; /* fall-thru */
+ case 2: b |= ((uint64_t)siptlw(in[1])) << 8; /* fall-thru */
case 1: b |= ((uint64_t)siptlw(in[0])); break;
case 0: break;
}
diff --git a/src/slowlog.c b/src/slowlog.c
index 2613435af..1d715e39b 100644
--- a/src/slowlog.c
+++ b/src/slowlog.c
@@ -142,11 +142,11 @@ void slowlogReset(void) {
void slowlogCommand(client *c) {
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
const char *help[] = {
-"get [count] -- Return top entries from the slowlog (default: 10)."
+"GET [count] -- Return top entries from the slowlog (default: 10)."
" Entries are made of:",
" id, timestamp, time in microseconds, arguments array, client IP and port, client name",
-"len -- Return the length of the slowlog.",
-"reset -- Reset the slowlog.",
+"LEN -- Return the length of the slowlog.",
+"RESET -- Reset the slowlog.",
NULL
};
addReplyHelp(c, help);
@@ -169,24 +169,24 @@ NULL
return;
listRewind(server.slowlog,&li);
- totentries = addDeferredMultiBulkLength(c);
+ totentries = addReplyDeferredLen(c);
while(count-- && (ln = listNext(&li))) {
int j;
se = ln->value;
- addReplyMultiBulkLen(c,6);
+ addReplyArrayLen(c,6);
addReplyLongLong(c,se->id);
addReplyLongLong(c,se->time);
addReplyLongLong(c,se->duration);
- addReplyMultiBulkLen(c,se->argc);
+ addReplyArrayLen(c,se->argc);
for (j = 0; j < se->argc; j++)
addReplyBulk(c,se->argv[j]);
addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
sent++;
}
- setDeferredMultiBulkLength(c,totentries,sent);
+ setDeferredArrayLen(c,totentries,sent);
} else {
- addReplyErrorFormat(c, "Unknown subcommand or wrong number of arguments for '%s'. Try SLOWLOG HELP", (char*)c->argv[1]->ptr);
+ addReplySubcommandSyntaxError(c);
}
}
diff --git a/src/sort.c b/src/sort.c
index 7ddd37d95..db26da158 100644
--- a/src/sort.c
+++ b/src/sort.c
@@ -58,7 +58,7 @@ redisSortOperation *createSortOperation(int type, robj *pattern) {
*
* The returned object will always have its refcount increased by 1
* when it is non-NULL. */
-robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
+robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst, int writeflag) {
char *p, *f, *k;
sds spat, ssub;
robj *keyobj, *fieldobj = NULL, *o;
@@ -106,7 +106,10 @@ robj *lookupKeyByPattern(redisDb *db, robj *pattern, robj *subst) {
decrRefCount(subst); /* Incremented by decodeObject() */
/* Lookup substituted key */
- o = lookupKeyRead(db,keyobj);
+ if (!writeflag)
+ o = lookupKeyRead(db,keyobj);
+ else
+ o = lookupKeyWrite(db,keyobj);
if (o == NULL) goto noobj;
if (fieldobj) {
@@ -193,35 +196,17 @@ void sortCommand(client *c) {
long limit_start = 0, limit_count = -1, start, end;
int j, dontsort = 0, vectorlen;
int getop = 0; /* GET operation counter */
- int int_convertion_error = 0;
+ int int_conversion_error = 0;
int syntax_error = 0;
robj *sortval, *sortby = NULL, *storekey = NULL;
redisSortObject *vector; /* Resulting vector to sort */
- /* Lookup the key to sort. It must be of the right types */
- sortval = lookupKeyRead(c->db,c->argv[1]);
- if (sortval && sortval->type != OBJ_SET &&
- sortval->type != OBJ_LIST &&
- sortval->type != OBJ_ZSET)
- {
- addReply(c,shared.wrongtypeerr);
- return;
- }
-
/* Create a list of operations to perform for every sorted element.
* Operations can be GET */
operations = listCreate();
listSetFreeMethod(operations,zfree);
j = 2; /* options start at argv[2] */
- /* Now we need to protect sortval incrementing its count, in the future
- * SORT may have options able to overwrite/delete keys during the sorting
- * and the sorted key itself may get destroyed */
- if (sortval)
- incrRefCount(sortval);
- else
- sortval = createQuicklistObject();
-
/* The SORT command has an SQL-alike syntax, parse it */
while(j < c->argc) {
int leftargs = c->argc-j-1;
@@ -280,11 +265,33 @@ void sortCommand(client *c) {
/* Handle syntax errors set during options parsing. */
if (syntax_error) {
- decrRefCount(sortval);
listRelease(operations);
return;
}
+ /* Lookup the key to sort. It must be of the right types */
+ if (storekey)
+ sortval = lookupKeyRead(c->db,c->argv[1]);
+ else
+ sortval = lookupKeyWrite(c->db,c->argv[1]);
+ if (sortval && sortval->type != OBJ_SET &&
+ sortval->type != OBJ_LIST &&
+ sortval->type != OBJ_ZSET)
+ {
+ listRelease(operations);
+ addReply(c,shared.wrongtypeerr);
+ return;
+ }
+
+ /* Now we need to protect sortval incrementing its count, in the future
+ * SORT may have options able to overwrite/delete keys during the sorting
+ * and the sorted key itself may get destroyed */
+ if (sortval)
+ incrRefCount(sortval);
+ else
+ sortval = createQuicklistObject();
+
+
/* When sorting a set with no sort specified, we must sort the output
* so the result is consistent across scripting and replication.
*
@@ -447,12 +454,12 @@ void sortCommand(client *c) {
serverAssertWithInfo(c,sortval,j == vectorlen);
/* Now it's time to load the right scores in the sorting vector */
- if (dontsort == 0) {
+ if (!dontsort) {
for (j = 0; j < vectorlen; j++) {
robj *byval;
if (sortby) {
/* lookup value to sort by */
- byval = lookupKeyByPattern(c->db,sortby,vector[j].obj);
+ byval = lookupKeyByPattern(c->db,sortby,vector[j].obj,storekey!=NULL);
if (!byval) continue;
} else {
/* use object itself to sort by */
@@ -469,7 +476,7 @@ void sortCommand(client *c) {
if (eptr[0] != '\0' || errno == ERANGE ||
isnan(vector[j].u.score))
{
- int_convertion_error = 1;
+ int_conversion_error = 1;
}
} else if (byval->encoding == OBJ_ENCODING_INT) {
/* Don't need to decode the object if it's
@@ -487,9 +494,7 @@ void sortCommand(client *c) {
decrRefCount(byval);
}
}
- }
- if (dontsort == 0) {
server.sort_desc = desc;
server.sort_alpha = alpha;
server.sort_bypattern = sortby ? 1 : 0;
@@ -503,11 +508,11 @@ void sortCommand(client *c) {
/* Send command output to the output buffer, performing the specified
* GET/DEL/INCR/DECR operations if any. */
outputlen = getop ? getop*(end-start+1) : end-start+1;
- if (int_convertion_error) {
+ if (int_conversion_error) {
addReplyError(c,"One or more scores can't be converted into double");
} else if (storekey == NULL) {
/* STORE option not specified, sent the sorting result to client */
- addReplyMultiBulkLen(c,outputlen);
+ addReplyArrayLen(c,outputlen);
for (j = start; j <= end; j++) {
listNode *ln;
listIter li;
@@ -517,11 +522,11 @@ void sortCommand(client *c) {
while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->db,sop->pattern,
- vector[j].obj);
+ vector[j].obj,storekey!=NULL);
if (sop->type == SORT_OP_GET) {
if (!val) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
addReplyBulk(c,val);
decrRefCount(val);
@@ -547,7 +552,7 @@ void sortCommand(client *c) {
while((ln = listNext(&li))) {
redisSortOperation *sop = ln->value;
robj *val = lookupKeyByPattern(c->db,sop->pattern,
- vector[j].obj);
+ vector[j].obj,storekey!=NULL);
if (sop->type == SORT_OP_GET) {
if (!val) val = createStringObject("",0);
diff --git a/src/stream.h b/src/stream.h
index 214b6d9a5..1163b3527 100644
--- a/src/stream.h
+++ b/src/stream.h
@@ -17,6 +17,7 @@ typedef struct stream {
rax *rax; /* The radix tree holding the stream. */
uint64_t length; /* Number of elements inside this stream. */
streamID last_id; /* Zero if there are yet no items. */
+ rax *cgroups; /* Consumer groups dictionary: name -> streamCG */
} stream;
/* We define an iterator to iterate stream items in an abstract way, without
@@ -26,6 +27,7 @@ typedef struct stream {
* rewriting code that also needs to iterate the stream to emit the XADD
* commands. */
typedef struct streamIterator {
+ stream *stream; /* The stream we are iterating. */
streamID master_id; /* ID of the master entry at listpack head. */
uint64_t master_fields_count; /* Master entries # of fields. */
unsigned char *master_fields_start; /* Master entries start in listpack. */
@@ -37,6 +39,7 @@ typedef struct streamIterator {
raxIterator ri; /* Rax iterator. */
unsigned char *lp; /* Current listpack. */
unsigned char *lp_ele; /* Current listpack cursor. */
+ unsigned char *lp_flags; /* Current entry flags pointer. */
/* Buffers used to hold the string of lpGet() when the element is
* integer encoded, so that there is no string representation of the
* element inside the listpack itself. */
@@ -44,16 +47,68 @@ typedef struct streamIterator {
unsigned char value_buf[LP_INTBUF_SIZE];
} streamIterator;
-/* Prototypes of exported APIs. */
+/* Consumer group. */
+typedef struct streamCG {
+ streamID last_id; /* Last delivered (not acknowledged) ID for this
+ group. Consumers that will just ask for more
+ messages will served with IDs > than this. */
+ rax *pel; /* Pending entries list. This is a radix tree that
+ has every message delivered to consumers (without
+ the NOACK option) that was yet not acknowledged
+ as processed. The key of the radix tree is the
+ ID as a 64 bit big endian number, while the
+ associated value is a streamNACK structure.*/
+ rax *consumers; /* A radix tree representing the consumers by name
+ and their associated representation in the form
+ of streamConsumer structures. */
+} streamCG;
+
+/* A specific consumer in a consumer group. */
+typedef struct streamConsumer {
+ mstime_t seen_time; /* Last time this consumer was active. */
+ sds name; /* Consumer name. This is how the consumer
+ will be identified in the consumer group
+ protocol. Case sensitive. */
+ rax *pel; /* Consumer specific pending entries list: all
+ the pending messages delivered to this
+ consumer not yet acknowledged. Keys are
+ big endian message IDs, while values are
+ the same streamNACK structure referenced
+ in the "pel" of the conumser group structure
+ itself, so the value is shared. */
+} streamConsumer;
+
+/* Pending (yet not acknowledged) message in a consumer group. */
+typedef struct streamNACK {
+ mstime_t delivery_time; /* Last time this message was delivered. */
+ uint64_t delivery_count; /* Number of times this message was delivered.*/
+ streamConsumer *consumer; /* The consumer this message was delivered to
+ in the last delivery. */
+} streamNACK;
+/* Stream propagation informations, passed to functions in order to propagate
+ * XCLAIM commands to AOF and slaves. */
+typedef struct streamPropInfo {
+ robj *keyname;
+ robj *groupname;
+} streamPropInfo;
+
+/* Prototypes of exported APIs. */
struct client;
stream *streamNew(void);
void freeStream(stream *s);
-size_t streamReplyWithRange(struct client *c, stream *s, streamID *start, streamID *end, size_t count, int rev);
+size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi);
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev);
int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields);
void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsigned char **valueptr, int64_t *fieldlen, int64_t *valuelen);
void streamIteratorStop(streamIterator *si);
+streamCG *streamLookupCG(stream *s, sds groupname);
+streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create);
+streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id);
+streamNACK *streamCreateNACK(streamConsumer *consumer);
+void streamDecodeID(void *buf, streamID *id);
+int streamCompareID(streamID *a, streamID *b);
+void streamFreeNACK(streamNACK *na);
#endif
diff --git a/src/t_hash.c b/src/t_hash.c
index fa3a893a6..e6ed33819 100644
--- a/src/t_hash.c
+++ b/src/t_hash.c
@@ -615,6 +615,10 @@ void hincrbyfloatCommand(client *c) {
}
value += incr;
+ if (isnan(value) || isinf(value)) {
+ addReplyError(c,"increment would produce NaN or Infinity");
+ return;
+ }
char buf[MAX_LONG_DOUBLE_CHARS];
int len = ld2string(buf,sizeof(buf),value,1);
@@ -641,7 +645,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
int ret;
if (o == NULL) {
- addReply(c, shared.nullbulk);
+ addReplyNull(c);
return;
}
@@ -652,7 +656,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
ret = hashTypeGetFromZiplist(o, field, &vstr, &vlen, &vll);
if (ret < 0) {
- addReply(c, shared.nullbulk);
+ addReplyNull(c);
} else {
if (vstr) {
addReplyBulkCBuffer(c, vstr, vlen);
@@ -664,7 +668,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
} else if (o->encoding == OBJ_ENCODING_HT) {
sds value = hashTypeGetFromHashTable(o, field);
if (value == NULL)
- addReply(c, shared.nullbulk);
+ addReplyNull(c);
else
addReplyBulkCBuffer(c, value, sdslen(value));
} else {
@@ -675,7 +679,7 @@ static void addHashFieldToReply(client *c, robj *o, sds field) {
void hgetCommand(client *c) {
robj *o;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL ||
checkType(c,o,OBJ_HASH)) return;
addHashFieldToReply(c, o, c->argv[2]->ptr);
@@ -693,7 +697,7 @@ void hmgetCommand(client *c) {
return;
}
- addReplyMultiBulkLen(c, c->argc-2);
+ addReplyArrayLen(c, c->argc-2);
for (i = 2; i < c->argc; i++) {
addHashFieldToReply(c, o, c->argv[i]->ptr);
}
@@ -766,17 +770,19 @@ static void addHashIteratorCursorToReply(client *c, hashTypeIterator *hi, int wh
void genericHgetallCommand(client *c, int flags) {
robj *o;
hashTypeIterator *hi;
- int multiplier = 0;
int length, count = 0;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
- || checkType(c,o,OBJ_HASH)) return;
-
- if (flags & OBJ_HASH_KEY) multiplier++;
- if (flags & OBJ_HASH_VALUE) multiplier++;
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymap[c->resp]))
+ == NULL || checkType(c,o,OBJ_HASH)) return;
- length = hashTypeLength(o) * multiplier;
- addReplyMultiBulkLen(c, length);
+ /* We return a map if the user requested keys and values, like in the
+ * HGETALL case. Otherwise to use a flat array makes more sense. */
+ length = hashTypeLength(o);
+ if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) {
+ addReplyMapLen(c, length);
+ } else {
+ addReplyArrayLen(c, length);
+ }
hi = hashTypeInitIterator(o);
while (hashTypeNext(hi) != C_ERR) {
@@ -791,6 +797,9 @@ void genericHgetallCommand(client *c, int flags) {
}
hashTypeReleaseIterator(hi);
+
+ /* Make sure we returned the right number of elements. */
+ if (flags & OBJ_HASH_KEY && flags & OBJ_HASH_VALUE) count /= 2;
serverAssert(count == length);
}
diff --git a/src/t_list.c b/src/t_list.c
index c7e6aac00..9bbd61de3 100644
--- a/src/t_list.c
+++ b/src/t_list.c
@@ -298,7 +298,7 @@ void linsertCommand(client *c) {
server.dirty++;
} else {
/* Notify client of a failed insert */
- addReply(c,shared.cnegone);
+ addReplyLongLong(c,-1);
return;
}
@@ -312,7 +312,7 @@ void llenCommand(client *c) {
}
void lindexCommand(client *c) {
- robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk);
+ robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]);
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
long index;
robj *value = NULL;
@@ -331,7 +331,7 @@ void lindexCommand(client *c) {
addReplyBulk(c,value);
decrRefCount(value);
} else {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
} else {
serverPanic("Unknown list encoding");
@@ -365,12 +365,12 @@ void lsetCommand(client *c) {
}
void popGenericCommand(client *c, int where) {
- robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk);
+ robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]);
if (o == NULL || checkType(c,o,OBJ_LIST)) return;
robj *value = listTypePop(o,where);
if (value == NULL) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
@@ -402,7 +402,7 @@ void lrangeCommand(client *c) {
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL
|| checkType(c,o,OBJ_LIST)) return;
llen = listTypeLength(o);
@@ -414,14 +414,14 @@ void lrangeCommand(client *c) {
/* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
- addReply(c,shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
return;
}
if (end >= llen) end = llen-1;
rangelen = (end-start)+1;
/* Return the result in form of a multi-bulk reply */
- addReplyMultiBulkLen(c,rangelen);
+ addReplyArrayLen(c,rangelen);
if (o->encoding == OBJ_ENCODING_QUICKLIST) {
listTypeIterator *iter = listTypeInitIterator(o, start, LIST_TAIL);
@@ -520,7 +520,7 @@ void lremCommand(client *c) {
if (removed) {
signalModifiedKey(c->db,c->argv[1]);
- notifyKeyspaceEvent(NOTIFY_GENERIC,"lrem",c->argv[1],c->db->id);
+ notifyKeyspaceEvent(NOTIFY_LIST,"lrem",c->argv[1],c->db->id);
}
if (listTypeLength(subject) == 0) {
@@ -564,13 +564,13 @@ void rpoplpushHandlePush(client *c, robj *dstkey, robj *dstobj, robj *value) {
void rpoplpushCommand(client *c) {
robj *sobj, *value;
- if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
- checkType(c,sobj,OBJ_LIST)) return;
+ if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
+ == NULL || checkType(c,sobj,OBJ_LIST)) return;
if (listTypeLength(sobj) == 0) {
/* This may only happen after loading very old RDB files. Recent
* versions of Redis delete keys of empty lists. */
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
robj *dobj = lookupKeyWrite(c->db,c->argv[2]);
robj *touchedkey = c->argv[1];
@@ -596,6 +596,9 @@ void rpoplpushCommand(client *c) {
signalModifiedKey(c->db,touchedkey);
decrRefCount(touchedkey);
server.dirty++;
+ if (c->cmd->proc == brpoplpushCommand) {
+ rewriteClientCommandVector(c,3,shared.rpoplpush,c->argv[1],c->argv[2]);
+ }
}
}
@@ -603,7 +606,7 @@ void rpoplpushCommand(client *c) {
* Blocking POP operations
*----------------------------------------------------------------------------*/
-/* This is a helper function for handleClientsBlockedOnLists(). It's work
+/* This is a helper function for handleClientsBlockedOnKeys(). It's work
* is to serve a specific client (receiver) that is blocked on 'key'
* in the context of the specified 'db', doing the following:
*
@@ -614,7 +617,7 @@ void rpoplpushCommand(client *c) {
* the AOF and replication channel.
*
* The argument 'where' is LIST_TAIL or LIST_HEAD, and indicates if the
- * 'value' element was popped fron the head (BLPOP) or tail (BRPOP) so that
+ * 'value' element was popped from the head (BLPOP) or tail (BRPOP) so that
* we can propagate the command properly.
*
* The function returns C_OK if we are able to serve the client, otherwise
@@ -636,9 +639,13 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
db->id,argv,2,PROPAGATE_AOF|PROPAGATE_REPL);
/* BRPOP/BLPOP */
- addReplyMultiBulkLen(receiver,2);
+ addReplyArrayLen(receiver,2);
addReplyBulk(receiver,key);
addReplyBulk(receiver,value);
+
+ /* Notify event. */
+ char *event = (where == LIST_HEAD) ? "lpop" : "rpop";
+ notifyKeyspaceEvent(NOTIFY_LIST,event,key,receiver->db->id);
} else {
/* BRPOPLPUSH */
robj *dstobj =
@@ -663,6 +670,9 @@ int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb
db->id,argv,3,
PROPAGATE_AOF|
PROPAGATE_REPL);
+
+ /* Notify event ("lpush" was notified by rpoplpushHandlePush). */
+ notifyKeyspaceEvent(NOTIFY_LIST,"rpop",key,receiver->db->id);
} else {
/* BRPOPLPUSH failed because of wrong
* destination type. */
@@ -694,7 +704,7 @@ void blockingPopGenericCommand(client *c, int where) {
robj *value = listTypePop(o,where);
serverAssert(value != NULL);
- addReplyMultiBulkLen(c,2);
+ addReplyArrayLen(c,2);
addReplyBulk(c,c->argv[j]);
addReplyBulk(c,value);
decrRefCount(value);
@@ -721,7 +731,7 @@ void blockingPopGenericCommand(client *c, int where) {
/* If we are inside a MULTI/EXEC and the list is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
if (c->flags & CLIENT_MULTI) {
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
return;
}
@@ -749,7 +759,7 @@ void brpoplpushCommand(client *c) {
if (c->flags & CLIENT_MULTI) {
/* Blocking against an empty list in a multi state
* returns immediately. */
- addReply(c, shared.nullbulk);
+ addReplyNull(c);
} else {
/* The list is empty and the client blocks. */
blockForKeys(c,BLOCKED_LIST,c->argv + 1,1,timeout,c->argv[2],NULL);
diff --git a/src/t_set.c b/src/t_set.c
index 8f21f71b1..abbf82fde 100644
--- a/src/t_set.c
+++ b/src/t_set.c
@@ -207,7 +207,7 @@ sds setTypeNextObject(setTypeIterator *si) {
* used field with values which are easy to trap if misused. */
int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) {
if (setobj->encoding == OBJ_ENCODING_HT) {
- dictEntry *de = dictGetRandomKey(setobj->ptr);
+ dictEntry *de = dictGetFairRandomKey(setobj->ptr);
*sdsele = dictGetKey(de);
*llele = -123456789; /* Not needed. Defensive. */
} else if (setobj->encoding == OBJ_ENCODING_INTSET) {
@@ -415,13 +415,13 @@ void spopWithCountCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is
* indeed a set. Otherwise, return nil */
- if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
+ if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return;
- /* If count is zero, serve an empty multibulk ASAP to avoid special
+ /* If count is zero, serve an empty set ASAP to avoid special
* cases later. */
if (count == 0) {
- addReply(c,shared.emptymultibulk);
+ addReply(c,shared.emptyset[c->resp]);
return;
}
@@ -455,7 +455,7 @@ void spopWithCountCommand(client *c) {
robj *propargv[3];
propargv[0] = createStringObject("SREM",4);
propargv[1] = c->argv[1];
- addReplyMultiBulkLen(c,count);
+ addReplySetLen(c,count);
/* Common iteration vars. */
sds sdsele;
@@ -516,11 +516,7 @@ void spopWithCountCommand(client *c) {
sdsfree(sdsele);
}
- /* Assign the new set as the key value. */
- incrRefCount(set); /* Protect the old set value. */
- dbOverwrite(c->db,c->argv[1],newset);
-
- /* Tranfer the old set to the client and release it. */
+ /* Transfer the old set to the client. */
setTypeIterator *si;
si = setTypeInitIterator(set);
while((encoding = setTypeNext(si,&sdsele,&llele)) != -1) {
@@ -539,7 +535,9 @@ void spopWithCountCommand(client *c) {
decrRefCount(objele);
}
setTypeReleaseIterator(si);
- decrRefCount(set);
+
+ /* Assign the new set as the key value. */
+ dbOverwrite(c->db,c->argv[1],newset);
}
/* Don't propagate the command itself even if we incremented the
@@ -568,8 +566,8 @@ void spopCommand(client *c) {
/* Make sure a key with the name inputted exists, and that it's type is
* indeed a set */
- if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
- checkType(c,set,OBJ_SET)) return;
+ if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp]))
+ == NULL || checkType(c,set,OBJ_SET)) return;
/* Get a random element from the set */
encoding = setTypeRandomElement(set,&sdsele,&llele);
@@ -634,13 +632,13 @@ void srandmemberWithCountCommand(client *c) {
uniq = 0;
}
- if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk))
+ if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyset[c->resp]))
== NULL || checkType(c,set,OBJ_SET)) return;
size = setTypeSize(set);
/* If count is zero, serve it ASAP to avoid special cases later. */
if (count == 0) {
- addReply(c,shared.emptymultibulk);
+ addReply(c,shared.emptyset[c->resp]);
return;
}
@@ -649,7 +647,7 @@ void srandmemberWithCountCommand(client *c) {
* This case is trivial and can be served without auxiliary data
* structures. */
if (!uniq) {
- addReplyMultiBulkLen(c,count);
+ addReplySetLen(c,count);
while(count--) {
encoding = setTypeRandomElement(set,&ele,&llele);
if (encoding == OBJ_ENCODING_INTSET) {
@@ -739,7 +737,7 @@ void srandmemberWithCountCommand(client *c) {
dictIterator *di;
dictEntry *de;
- addReplyMultiBulkLen(c,count);
+ addReplySetLen(c,count);
di = dictGetIterator(d);
while((de = dictNext(di)) != NULL)
addReplyBulk(c,dictGetKey(de));
@@ -762,8 +760,8 @@ void srandmemberCommand(client *c) {
return;
}
- if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
- checkType(c,set,OBJ_SET)) return;
+ if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp]))
+ == NULL || checkType(c,set,OBJ_SET)) return;
encoding = setTypeRandomElement(set,&ele,&llele);
if (encoding == OBJ_ENCODING_INTSET) {
@@ -815,7 +813,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
}
addReply(c,shared.czero);
} else {
- addReply(c,shared.emptymultibulk);
+ addReply(c,shared.emptyset[c->resp]);
}
return;
}
@@ -835,7 +833,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
* to the output list and save the pointer to later modify it with the
* right length */
if (!dstkey) {
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
} else {
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
@@ -913,7 +911,7 @@ void sinterGenericCommand(client *c, robj **setkeys,
signalModifiedKey(c->db,dstkey);
server.dirty++;
} else {
- setDeferredMultiBulkLength(c,replylen,cardinality);
+ setDeferredSetLen(c,replylen,cardinality);
}
zfree(sets);
}
@@ -1059,14 +1057,15 @@ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum,
/* Output the content of the resulting set, if not in STORE mode */
if (!dstkey) {
- addReplyMultiBulkLen(c,cardinality);
+ addReplySetLen(c,cardinality);
si = setTypeInitIterator(dstset);
while((ele = setTypeNextObject(si)) != NULL) {
addReplyBulkCBuffer(c,ele,sdslen(ele));
sdsfree(ele);
}
setTypeReleaseIterator(si);
- decrRefCount(dstset);
+ server.lazyfree_lazy_server_del ? freeObjAsync(dstset) :
+ decrRefCount(dstset);
} else {
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
diff --git a/src/t_stream.c b/src/t_stream.c
index 1f2e2094d..ea9a620f1 100644
--- a/src/t_stream.c
+++ b/src/t_stream.c
@@ -37,9 +37,13 @@
* mark the entry as deleted, or having the same field as the "master"
* entry at the start of the listpack> */
#define STREAM_ITEM_FLAG_NONE 0 /* No special flags. */
-#define STREAM_ITEM_FLAG_DELETED (1<<0) /* Entry is delted. Skip it. */
+#define STREAM_ITEM_FLAG_DELETED (1<<0) /* Entry is deleted. Skip it. */
#define STREAM_ITEM_FLAG_SAMEFIELDS (1<<1) /* Same fields as master entry. */
+void streamFreeCG(streamCG *cg);
+void streamFreeNACK(streamNACK *na);
+size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer);
+
/* -----------------------------------------------------------------------
* Low level stream encoding: a radix tree of listpacks.
* ----------------------------------------------------------------------- */
@@ -51,12 +55,15 @@ stream *streamNew(void) {
s->length = 0;
s->last_id.ms = 0;
s->last_id.seq = 0;
+ s->cgroups = NULL; /* Created on demand to save memory when not used. */
return s;
}
/* Free a stream, including the listpacks stored inside the radix tree. */
void freeStream(stream *s) {
raxFreeWithCallback(s->rax,(void(*)(void*))lpFree);
+ if (s->cgroups)
+ raxFreeWithCallback(s->cgroups,(void(*)(void*))streamFreeCG);
zfree(s);
}
@@ -142,24 +149,33 @@ void streamDecodeID(void *buf, streamID *id) {
id->seq = ntohu64(e[1]);
}
+/* Compare two stream IDs. Return -1 if a < b, 0 if a == b, 1 if a > b. */
+int streamCompareID(streamID *a, streamID *b) {
+ if (a->ms > b->ms) return 1;
+ else if (a->ms < b->ms) return -1;
+ /* The ms part is the same. Check the sequence part. */
+ else if (a->seq > b->seq) return 1;
+ else if (a->seq < b->seq) return -1;
+ /* Everything is the same: IDs are equal. */
+ return 0;
+}
+
/* Adds a new item into the stream 's' having the specified number of
* field-value pairs as specified in 'numfields' and stored into 'argv'.
* Returns the new entry ID populating the 'added_id' structure.
*
* If 'use_id' is not NULL, the ID is not auto-generated by the function,
- * but instead the passed ID is uesd to add the new entry. In this case
+ * but instead the passed ID is used to add the new entry. In this case
* adding the entry may fail as specified later in this comment.
*
* The function returns C_OK if the item was added, this is always true
* if the ID was generated by the function. However the function may return
* C_ERR if an ID was given via 'use_id', but adding it failed since the
* current top ID is greater or equal. */
-int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id, streamID *use_id) {
+int streamAppendItem(stream *s, robj **argv, int64_t numfields, streamID *added_id, streamID *use_id) {
/* If an ID was given, check that it's greater than the last entry ID
* or return an error. */
- if (use_id && (use_id->ms < s->last_id.ms ||
- (use_id->ms == s->last_id.ms &&
- use_id->seq <= s->last_id.seq))) return C_ERR;
+ if (use_id && streamCompareID(use_id,&s->last_id) <= 0) return C_ERR;
/* Add the new entry. */
raxIterator ri;
@@ -191,7 +207,7 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
/* Create a new listpack and radix tree node if needed. Note that when
* a new listpack is created, we populate it with a "master entry". This
- * is just a set of fields that is taken as refernce in order to compress
+ * is just a set of fields that is taken as references in order to compress
* the stream entries that we'll add inside the listpack.
*
* Note that while we use the first added entry fields to create
@@ -206,23 +222,37 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
* +-------+---------+------------+---------+--/--+---------+---------+-+
*
* count and deleted just represent respectively the total number of
- * entires inside the listpack that are valid, and marked as deleted
- * (delted flag in the entry flags set). So the total number of items
+ * entries inside the listpack that are valid, and marked as deleted
+ * (deleted flag in the entry flags set). So the total number of items
* actually inside the listpack (both deleted and not) is count+deleted.
*
* The real entries will be encoded with an ID that is just the
* millisecond and sequence difference compared to the key stored at
* the radix tree node containing the listpack (delta encoding), and
- * if the fields of the entry are the same as the master enty fields, the
+ * if the fields of the entry are the same as the master entry fields, the
* entry flags will specify this fact and the entry fields and number
* of fields will be omitted (see later in the code of this function).
*
* The "0" entry at the end is the same as the 'lp-count' entry in the
* regular stream entries (see below), and marks the fact that there are
- * no more entires, when we scan the stream from right to left. */
+ * no more entries, when we scan the stream from right to left. */
+
+ /* First of all, check if we can append to the current macro node or
+ * if we need to switch to the next one. 'lp' will be set to NULL if
+ * the current node is full. */
+ if (lp != NULL) {
+ if (server.stream_node_max_bytes &&
+ lp_bytes >= server.stream_node_max_bytes)
+ {
+ lp = NULL;
+ } else if (server.stream_node_max_entries) {
+ int64_t count = lpGetInteger(lpFirst(lp));
+ if (count >= server.stream_node_max_entries) lp = NULL;
+ }
+ }
int flags = STREAM_ITEM_FLAG_NONE;
- if (lp == NULL || lp_bytes > STREAM_BYTES_PER_LISTPACK) {
+ if (lp == NULL || lp_bytes >= server.stream_node_max_bytes) {
master_id = id;
streamEncodeID(rax_key,&id);
/* Create the listpack having the master entry ID and fields. */
@@ -230,7 +260,7 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
lp = lpAppendInteger(lp,1); /* One item, the one we are adding. */
lp = lpAppendInteger(lp,0); /* Zero deleted so far. */
lp = lpAppendInteger(lp,numfields);
- for (int i = 0; i < numfields; i++) {
+ for (int64_t i = 0; i < numfields; i++) {
sds field = argv[i*2]->ptr;
lp = lpAppend(lp,(unsigned char*)field,sdslen(field));
}
@@ -250,15 +280,15 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
/* Update count and skip the deleted fields. */
int64_t count = lpGetInteger(lp_ele);
lp = lpReplaceInteger(lp,&lp_ele,count+1);
- lp_ele = lpNext(lp,lp_ele); /* seek delted. */
+ lp_ele = lpNext(lp,lp_ele); /* seek deleted. */
lp_ele = lpNext(lp,lp_ele); /* seek master entry num fields. */
/* Check if the entry we are adding, have the same fields
* as the master entry. */
- int master_fields_count = lpGetInteger(lp_ele);
+ int64_t master_fields_count = lpGetInteger(lp_ele);
lp_ele = lpNext(lp,lp_ele);
if (numfields == master_fields_count) {
- int i;
+ int64_t i;
for (i = 0; i < master_fields_count; i++) {
sds field = argv[i*2]->ptr;
int64_t e_len;
@@ -302,14 +332,14 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
lp = lpAppendInteger(lp,id.seq - master_id.seq);
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))
lp = lpAppendInteger(lp,numfields);
- for (int i = 0; i < numfields; i++) {
+ for (int64_t i = 0; i < numfields; i++) {
sds field = argv[i*2]->ptr, value = argv[i*2+1]->ptr;
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS))
lp = lpAppend(lp,(unsigned char*)field,sdslen(field));
lp = lpAppend(lp,(unsigned char*)value,sdslen(value));
}
/* Compute and store the lp-count field. */
- int lp_count = numfields;
+ int64_t lp_count = numfields;
lp_count += 3; /* Add the 3 fixed fields flags + ms-diff + seq-diff. */
if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) {
/* If the item is not compressed, it also has the fields other than
@@ -319,7 +349,8 @@ int streamAppendItem(stream *s, robj **argv, int numfields, streamID *added_id,
lp = lpAppendInteger(lp,lp_count);
/* Insert back into the tree in order to update the listpack pointer. */
- raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);
+ if (ri.data != lp)
+ raxInsert(s->rax,(unsigned char*)&rax_key,sizeof(rax_key),lp,NULL);
s->length++;
s->last_id = id;
if (added_id) *added_id = id;
@@ -439,12 +470,12 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
* iteration is from the start to the end element (inclusive), otherwise
* if rev is non-zero, the iteration is reversed.
*
- * Once the iterator is initalized, we iterate like this:
+ * Once the iterator is initialized, we iterate like this:
*
* streamIterator myiterator;
* streamIteratorStart(&myiterator,...);
* int64_t numfields;
- * while(streamIteratorGetID(&myitereator,&ID,&numfields)) {
+ * while(streamIteratorGetID(&myiterator,&ID,&numfields)) {
* while(numfields--) {
* unsigned char *key, *value;
* size_t key_len, value_len;
@@ -455,20 +486,20 @@ int64_t streamTrimByLength(stream *s, size_t maxlen, int approx) {
* }
* streamIteratorStop(&myiterator); */
void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamID *end, int rev) {
- /* Intialize the iterator and translates the iteration start/stop
+ /* Initialize the iterator and translates the iteration start/stop
* elements into a 128 big big-endian number. */
if (start) {
streamEncodeID(si->start_key,start);
} else {
si->start_key[0] = 0;
- si->start_key[0] = 0;
+ si->start_key[1] = 0;
}
if (end) {
streamEncodeID(si->end_key,end);
} else {
si->end_key[0] = UINT64_MAX;
- si->end_key[0] = UINT64_MAX;
+ si->end_key[1] = UINT64_MAX;
}
/* Seek the correct node in the radix tree. */
@@ -490,6 +521,7 @@ void streamIteratorStart(streamIterator *si, stream *s, streamID *start, streamI
raxSeek(&si->ri,"$",NULL,0);
}
}
+ si->stream = s;
si->lp = NULL; /* There is no current listpack right now. */
si->lp_ele = NULL; /* Current listpack cursor. */
si->rev = rev; /* Direction, if non-zero reversed, from end to start. */
@@ -517,15 +549,22 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
si->master_fields_count = lpGetInteger(si->lp_ele);
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek first field. */
si->master_fields_start = si->lp_ele;
- /* Skip master fileds to seek the first entry. */
- for (uint64_t i = 0; i < si->master_fields_count; i++)
- si->lp_ele = lpNext(si->lp,si->lp_ele);
- /* We are now pointing the zero term of the master entry. If
- * we are iterating in reverse order, we need to seek the
- * end of the listpack. */
- if (si->rev) si->lp_ele = lpLast(si->lp);
+ /* We are now pointing to the first field of the master entry.
+ * We need to seek either the first or the last entry depending
+ * on the direction of the iteration. */
+ if (!si->rev) {
+ /* If we are iterating in normal order, skip the master fields
+ * to seek the first actual entry. */
+ for (uint64_t i = 0; i < si->master_fields_count; i++)
+ si->lp_ele = lpNext(si->lp,si->lp_ele);
+ } else {
+ /* If we are iterating in reverse direction, just seek the
+ * last part of the last entry in the listpack (that is, the
+ * fields count). */
+ si->lp_ele = lpLast(si->lp);
+ }
} else if (si->rev) {
- /* If we are itereating in the reverse order, and this is not
+ /* If we are iterating in the reverse order, and this is not
* the first entry emitted for this listpack, then we already
* emitted the current entry, and have to go back to the previous
* one. */
@@ -548,7 +587,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
/* If we are going backward, read the number of elements this
* entry is composed of, and jump backward N times to seek
* its start. */
- int lp_count = lpGetInteger(si->lp_ele);
+ int64_t lp_count = lpGetInteger(si->lp_ele);
if (lp_count == 0) { /* We reached the master entry. */
si->lp = NULL;
si->lp_ele = NULL;
@@ -558,6 +597,7 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
}
/* Get the flags entry. */
+ si->lp_flags = si->lp_ele;
int flags = lpGetInteger(si->lp_ele);
si->lp_ele = lpNext(si->lp,si->lp_ele); /* Seek ID. */
@@ -610,12 +650,17 @@ int streamIteratorGetID(streamIterator *si, streamID *id, int64_t *numfields) {
* forward, or seek the previous entry if we are going
* backward. */
if (!si->rev) {
- int to_discard = (flags & STREAM_ITEM_FLAG_SAMEFIELDS) ?
- *numfields : *numfields*2;
+ int64_t to_discard = (flags & STREAM_ITEM_FLAG_SAMEFIELDS) ?
+ *numfields : *numfields*2;
for (int64_t i = 0; i < to_discard; i++)
si->lp_ele = lpNext(si->lp,si->lp_ele);
} else {
- int prev_times = 4; /* flag + id ms/seq diff + numfields. */
+ int64_t prev_times = 4; /* flag + id ms + id seq + one more to
+ go back to the previous entry "count"
+ field. */
+ /* If the entry was not flagged SAMEFIELD we also read the
+ * number of fields, so go back one more. */
+ if (!(flags & STREAM_ITEM_FLAG_SAMEFIELDS)) prev_times++;
while(prev_times--) si->lp_ele = lpPrev(si->lp,si->lp_ele);
}
}
@@ -642,33 +687,248 @@ void streamIteratorGetField(streamIterator *si, unsigned char **fieldptr, unsign
si->lp_ele = lpNext(si->lp,si->lp_ele);
}
+/* Remove the current entry from the stream: can be called after the
+ * GetID() API or after any GetField() call, however we need to iterate
+ * a valid entry while calling this function. Moreover the function
+ * requires the entry ID we are currently iterating, that was previously
+ * returned by GetID().
+ *
+ * Note that after calling this function, next calls to GetField() can't
+ * be performed: the entry is now deleted. Instead the iterator will
+ * automatically re-seek to the next entry, so the caller should continue
+ * with GetID(). */
+void streamIteratorRemoveEntry(streamIterator *si, streamID *current) {
+ unsigned char *lp = si->lp;
+ int64_t aux;
+
+ /* We do not really delete the entry here. Instead we mark it as
+ * deleted flagging it, and also incrementing the count of the
+ * deleted entries in the listpack header.
+ *
+ * We start flagging: */
+ int flags = lpGetInteger(si->lp_flags);
+ flags |= STREAM_ITEM_FLAG_DELETED;
+ lp = lpReplaceInteger(lp,&si->lp_flags,flags);
+
+ /* Change the valid/deleted entries count in the master entry. */
+ unsigned char *p = lpFirst(lp);
+ aux = lpGetInteger(p);
+
+ if (aux == 1) {
+ /* If this is the last element in the listpack, we can remove the whole
+ * node. */
+ lpFree(lp);
+ raxRemove(si->stream->rax,si->ri.key,si->ri.key_len,NULL);
+ } else {
+ /* In the base case we alter the counters of valid/deleted entries. */
+ lp = lpReplaceInteger(lp,&p,aux-1);
+ p = lpNext(lp,p); /* Seek deleted field. */
+ aux = lpGetInteger(p);
+ lp = lpReplaceInteger(lp,&p,aux+1);
+
+ /* Update the listpack with the new pointer. */
+ if (si->lp != lp)
+ raxInsert(si->stream->rax,si->ri.key,si->ri.key_len,lp,NULL);
+ }
+
+ /* Update the number of entries counter. */
+ si->stream->length--;
+
+ /* Re-seek the iterator to fix the now messed up state. */
+ streamID start, end;
+ if (si->rev) {
+ streamDecodeID(si->start_key,&start);
+ end = *current;
+ } else {
+ start = *current;
+ streamDecodeID(si->end_key,&end);
+ }
+ streamIteratorStop(si);
+ streamIteratorStart(si,si->stream,&start,&end,si->rev);
+
+ /* TODO: perform a garbage collection here if the ration between
+ * deleted and valid goes over a certain limit. */
+}
+
/* Stop the stream iterator. The only cleanup we need is to free the rax
- * itereator, since the stream iterator itself is supposed to be stack
+ * iterator, since the stream iterator itself is supposed to be stack
* allocated. */
void streamIteratorStop(streamIterator *si) {
raxStop(&si->ri);
}
-/* Send the specified range to the client 'c'. The range the client will
- * receive is between start and end inclusive, if 'count' is non zero, no more
- * than 'count' elemnets are sent. The 'end' pointer can be NULL to mean that
- * we want all the elements from 'start' till the end of the stream. If 'rev'
- * is non zero, elements are produced in reversed order from end to start. */
-size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev) {
- void *arraylen_ptr = addDeferredMultiBulkLength(c);
+/* Delete the specified item ID from the stream, returning 1 if the item
+ * was deleted 0 otherwise (if it does not exist). */
+int streamDeleteItem(stream *s, streamID *id) {
+ int deleted = 0;
+ streamIterator si;
+ streamIteratorStart(&si,s,id,id,0);
+ streamID myid;
+ int64_t numfields;
+ if (streamIteratorGetID(&si,&myid,&numfields)) {
+ streamIteratorRemoveEntry(&si,&myid);
+ deleted = 1;
+ }
+ streamIteratorStop(&si);
+ return deleted;
+}
+
+/* Emit a reply in the client output buffer by formatting a Stream ID
+ * in the standard <ms>-<seq> format, using the simple string protocol
+ * of REPL. */
+void addReplyStreamID(client *c, streamID *id) {
+ sds replyid = sdscatfmt(sdsempty(),"%U-%U",id->ms,id->seq);
+ addReplyBulkSds(c,replyid);
+}
+
+/* Similar to the above function, but just creates an object, usually useful
+ * for replication purposes to create arguments. */
+robj *createObjectFromStreamID(streamID *id) {
+ return createObject(OBJ_STRING, sdscatfmt(sdsempty(),"%U-%U",
+ id->ms,id->seq));
+}
+
+/* As a result of an explicit XCLAIM or XREADGROUP command, new entries
+ * are created in the pending list of the stream and consumers. We need
+ * to propagate this changes in the form of XCLAIM commands. */
+void streamPropagateXCLAIM(client *c, robj *key, streamCG *group, robj *groupname, robj *id, streamNACK *nack) {
+ /* We need to generate an XCLAIM that will work in a idempotent fashion:
+ *
+ * XCLAIM <key> <group> <consumer> 0 <id> TIME <milliseconds-unix-time>
+ * RETRYCOUNT <count> FORCE JUSTID LASTID <id>.
+ *
+ * Note that JUSTID is useful in order to avoid that XCLAIM will do
+ * useless work in the slave side, trying to fetch the stream item. */
+ robj *argv[14];
+ argv[0] = createStringObject("XCLAIM",6);
+ argv[1] = key;
+ argv[2] = groupname;
+ argv[3] = createStringObject(nack->consumer->name,sdslen(nack->consumer->name));
+ argv[4] = createStringObjectFromLongLong(0);
+ argv[5] = id;
+ argv[6] = createStringObject("TIME",4);
+ argv[7] = createStringObjectFromLongLong(nack->delivery_time);
+ argv[8] = createStringObject("RETRYCOUNT",10);
+ argv[9] = createStringObjectFromLongLong(nack->delivery_count);
+ argv[10] = createStringObject("FORCE",5);
+ argv[11] = createStringObject("JUSTID",6);
+ argv[12] = createStringObject("LASTID",6);
+ argv[13] = createObjectFromStreamID(&group->last_id);
+ propagate(server.xclaimCommand,c->db->id,argv,14,PROPAGATE_AOF|PROPAGATE_REPL);
+ decrRefCount(argv[0]);
+ decrRefCount(argv[3]);
+ decrRefCount(argv[4]);
+ decrRefCount(argv[6]);
+ decrRefCount(argv[7]);
+ decrRefCount(argv[8]);
+ decrRefCount(argv[9]);
+ decrRefCount(argv[10]);
+ decrRefCount(argv[11]);
+ decrRefCount(argv[12]);
+ decrRefCount(argv[13]);
+}
+
+/* We need this when we want to propoagate the new last-id of a consumer group
+ * that was consumed by XREADGROUP with the NOACK option: in that case we can't
+ * propagate the last ID just using the XCLAIM LASTID option, so we emit
+ *
+ * XGROUP SETID <key> <groupname> <id>
+ */
+void streamPropagateGroupID(client *c, robj *key, streamCG *group, robj *groupname) {
+ robj *argv[5];
+ argv[0] = createStringObject("XGROUP",6);
+ argv[1] = createStringObject("SETID",5);
+ argv[2] = key;
+ argv[3] = groupname;
+ argv[4] = createObjectFromStreamID(&group->last_id);
+ propagate(server.xgroupCommand,c->db->id,argv,5,PROPAGATE_AOF|PROPAGATE_REPL);
+ decrRefCount(argv[0]);
+ decrRefCount(argv[1]);
+ decrRefCount(argv[4]);
+}
+
+/* Send the stream items in the specified range to the client 'c'. The range
+ * the client will receive is between start and end inclusive, if 'count' is
+ * non zero, no more than 'count' elements are sent.
+ *
+ * The 'end' pointer can be NULL to mean that we want all the elements from
+ * 'start' till the end of the stream. If 'rev' is non zero, elements are
+ * produced in reversed order from end to start.
+ *
+ * The function returns the number of entries emitted.
+ *
+ * If group and consumer are not NULL, the function performs additional work:
+ * 1. It updates the last delivered ID in the group in case we are
+ * sending IDs greater than the current last ID.
+ * 2. If the requested IDs are already assigned to some other consumer, the
+ * function will not return it to the client.
+ * 3. An entry in the pending list will be created for every entry delivered
+ * for the first time to this consumer.
+ *
+ * The behavior may be modified passing non-zero flags:
+ *
+ * STREAM_RWR_NOACK: Do not create PEL entries, that is, the point "3" above
+ * is not performed.
+ * STREAM_RWR_RAWENTRIES: Do not emit array boundaries, but just the entries,
+ * and return the number of entries emitted as usually.
+ * This is used when the function is just used in order
+ * to emit data and there is some higher level logic.
+ *
+ * The final argument 'spi' (stream propagation info pointer) is a structure
+ * filled with information needed to propagate the command execution to AOF
+ * and slaves, in the case a consumer group was passed: we need to generate
+ * XCLAIM commands to create the pending list into AOF/slaves in that case.
+ *
+ * If 'spi' is set to NULL no propagation will happen even if the group was
+ * given, but currently such a feature is never used by the code base that
+ * will always pass 'spi' and propagate when a group is passed.
+ *
+ * Note that this function is recursive in certain cases. When it's called
+ * with a non NULL group and consumer argument, it may call
+ * streamReplyWithRangeFromConsumerPEL() in order to get entries from the
+ * consumer pending entries list. However such a function will then call
+ * streamReplyWithRange() in order to emit single entries (found in the
+ * PEL by ID) to the client. This is the use case for the STREAM_RWR_RAWENTRIES
+ * flag.
+ */
+#define STREAM_RWR_NOACK (1<<0) /* Do not create entries in the PEL. */
+#define STREAM_RWR_RAWENTRIES (1<<1) /* Do not emit protocol for array
+ boundaries, just the entries. */
+#define STREAM_RWR_HISTORY (1<<2) /* Only serve consumer local PEL. */
+size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end, size_t count, int rev, streamCG *group, streamConsumer *consumer, int flags, streamPropInfo *spi) {
+ void *arraylen_ptr = NULL;
size_t arraylen = 0;
streamIterator si;
int64_t numfields;
streamID id;
+ int propagate_last_id = 0;
+
+ /* If the client is asking for some history, we serve it using a
+ * different function, so that we return entries *solely* from its
+ * own PEL. This ensures each consumer will always and only see
+ * the history of messages delivered to it and not yet confirmed
+ * as delivered. */
+ if (group && (flags & STREAM_RWR_HISTORY)) {
+ return streamReplyWithRangeFromConsumerPEL(c,s,start,end,count,
+ consumer);
+ }
+ if (!(flags & STREAM_RWR_RAWENTRIES))
+ arraylen_ptr = addReplyDeferredLen(c);
streamIteratorStart(&si,s,start,end,rev);
while(streamIteratorGetID(&si,&id,&numfields)) {
+ /* Update the group last_id if needed. */
+ if (group && streamCompareID(&id,&group->last_id) > 0) {
+ group->last_id = id;
+ propagate_last_id = 1;
+ }
+
/* Emit a two elements array for each item. The first is
* the ID, the second is an array of field-value pairs. */
- sds replyid = sdscatfmt(sdsempty(),"+%U-%U\r\n",id.ms,id.seq);
- addReplyMultiBulkLen(c,2);
- addReplySds(c,replyid);
- addReplyMultiBulkLen(c,numfields*2);
+ addReplyArrayLen(c,2);
+ addReplyStreamID(c,&id);
+
+ addReplyMapLen(c,numfields);
/* Emit the field-value pairs. */
while(numfields--) {
@@ -678,11 +938,115 @@ size_t streamReplyWithRange(client *c, stream *s, streamID *start, streamID *end
addReplyBulkCBuffer(c,key,key_len);
addReplyBulkCBuffer(c,value,value_len);
}
+
+ /* If a group is passed, we need to create an entry in the
+ * PEL (pending entries list) of this group *and* this consumer.
+ *
+ * Note that we cannot be sure about the fact the message is not
+ * already owned by another consumer, because the admin is able
+ * to change the consumer group last delivered ID using the
+ * XGROUP SETID command. So if we find that there is already
+ * a NACK for the entry, we need to associate it to the new
+ * consumer. */
+ if (group && !(flags & STREAM_RWR_NOACK)) {
+ unsigned char buf[sizeof(streamID)];
+ streamEncodeID(buf,&id);
+
+ /* Try to add a new NACK. Most of the time this will work and
+ * will not require extra lookups. We'll fix the problem later
+ * if we find that there is already a entry for this ID. */
+ streamNACK *nack = streamCreateNACK(consumer);
+ int group_inserted =
+ raxTryInsert(group->pel,buf,sizeof(buf),nack,NULL);
+ int consumer_inserted =
+ raxTryInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
+
+ /* Now we can check if the entry was already busy, and
+ * in that case reassign the entry to the new consumer,
+ * or update it if the consumer is the same as before. */
+ if (group_inserted == 0) {
+ streamFreeNACK(nack);
+ nack = raxFind(group->pel,buf,sizeof(buf));
+ serverAssert(nack != raxNotFound);
+ raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
+ /* Update the consumer and NACK metadata. */
+ nack->consumer = consumer;
+ nack->delivery_time = mstime();
+ nack->delivery_count = 1;
+ /* Add the entry in the new consumer local PEL. */
+ raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
+ } else if (group_inserted == 1 && consumer_inserted == 0) {
+ serverPanic("NACK half-created. Should not be possible.");
+ }
+
+ /* Propagate as XCLAIM. */
+ if (spi) {
+ robj *idarg = createObjectFromStreamID(&id);
+ streamPropagateXCLAIM(c,spi->keyname,group,spi->groupname,idarg,nack);
+ decrRefCount(idarg);
+ }
+ } else {
+ if (propagate_last_id)
+ streamPropagateGroupID(c,spi->keyname,group,spi->groupname);
+ }
+
arraylen++;
if (count && count == arraylen) break;
}
streamIteratorStop(&si);
- setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
+ if (arraylen_ptr) setDeferredArrayLen(c,arraylen_ptr,arraylen);
+ return arraylen;
+}
+
+/* This is an helper function for streamReplyWithRange() when called with
+ * group and consumer arguments, but with a range that is referring to already
+ * delivered messages. In this case we just emit messages that are already
+ * in the history of the consumer, fetching the IDs from its PEL.
+ *
+ * Note that this function does not have a 'rev' argument because it's not
+ * possible to iterate in reverse using a group. Basically this function
+ * is only called as a result of the XREADGROUP command.
+ *
+ * This function is more expensive because it needs to inspect the PEL and then
+ * seek into the radix tree of the messages in order to emit the full message
+ * to the client. However clients only reach this code path when they are
+ * fetching the history of already retrieved messages, which is rare. */
+size_t streamReplyWithRangeFromConsumerPEL(client *c, stream *s, streamID *start, streamID *end, size_t count, streamConsumer *consumer) {
+ raxIterator ri;
+ unsigned char startkey[sizeof(streamID)];
+ unsigned char endkey[sizeof(streamID)];
+ streamEncodeID(startkey,start);
+ if (end) streamEncodeID(endkey,end);
+
+ size_t arraylen = 0;
+ void *arraylen_ptr = addReplyDeferredLen(c);
+ raxStart(&ri,consumer->pel);
+ raxSeek(&ri,">=",startkey,sizeof(startkey));
+ while(raxNext(&ri) && (!count || arraylen < count)) {
+ if (end && memcmp(ri.key,end,ri.key_len) > 0) break;
+ streamID thisid;
+ streamDecodeID(ri.key,&thisid);
+ if (streamReplyWithRange(c,s,&thisid,&thisid,1,0,NULL,NULL,
+ STREAM_RWR_RAWENTRIES,NULL) == 0)
+ {
+ /* Note that we may have a not acknowledged entry in the PEL
+ * about a message that's no longer here because was removed
+ * by the user by other means. In that case we signal it emitting
+ * the ID but then a NULL entry for the fields. */
+ addReplyArrayLen(c,2);
+ streamID id;
+ streamDecodeID(ri.key,&id);
+ addReplyStreamID(c,&id);
+ addReplyNullArray(c);
+ } else {
+ streamNACK *nack = ri.data;
+ nack->delivery_time = mstime();
+ nack->delivery_count++;
+ }
+ arraylen++;
+ }
+ raxStop(&ri);
+ setDeferredArrayLen(c,arraylen_ptr,arraylen);
return arraylen;
}
@@ -719,26 +1083,33 @@ int string2ull(const char *s, unsigned long long *value) {
return 1;
}
errno = 0;
- *value = strtoull(s,NULL,10);
- if (errno == EINVAL || errno == ERANGE) return 0; /* strtoull() failed. */
+ char *endptr = NULL;
+ *value = strtoull(s,&endptr,10);
+ if (errno == EINVAL || errno == ERANGE || !(*s != '\0' && *endptr == '\0'))
+ return 0; /* strtoull() failed. */
return 1; /* Conversion done! */
}
/* Parse a stream ID in the format given by clients to Redis, that is
- * <ms>.<seq>, and converts it into a streamID structure. If
+ * <ms>-<seq>, and converts it into a streamID structure. If
* the specified ID is invalid C_ERR is returned and an error is reported
* to the client, otherwise C_OK is returned. The ID may be in incomplete
* form, just stating the milliseconds time part of the stream. In such a case
* the missing part is set according to the value of 'missing_seq' parameter.
+ *
* The IDs "-" and "+" specify respectively the minimum and maximum IDs
- * that can be represented.
+ * that can be represented. If 'strict' is set to 1, "-" and "+" will be
+ * treated as an invalid ID.
*
* If 'c' is set to NULL, no reply is sent to the client. */
-int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
+int streamGenericParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq, int strict) {
char buf[128];
if (sdslen(o->ptr) > sizeof(buf)-1) goto invalid;
memcpy(buf,o->ptr,sdslen(o->ptr)+1);
+ if (strict && (buf[0] == '-' || buf[0] == '+') && buf[1] == '\0')
+ goto invalid;
+
/* Handle the "-" and "+" special cases. */
if (buf[0] == '-' && buf[1] == '\0') {
id->ms = 0;
@@ -750,7 +1121,7 @@ int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq)
return C_OK;
}
- /* Parse <ms>.<seq> form. */
+ /* Parse <ms>-<seq> form. */
char *dot = strchr(buf,'-');
if (dot) *dot = '\0';
unsigned long long ms, seq;
@@ -767,11 +1138,37 @@ invalid:
return C_ERR;
}
-/* XADD key [MAXLEN <count>] <ID or *> [field value] [field value] ... */
+/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
+ * 0, to be used when - and + are acceptable IDs. */
+int streamParseIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
+ return streamGenericParseIDOrReply(c,o,id,missing_seq,0);
+}
+
+/* Wrapper for streamGenericParseIDOrReply() with 'strict' argument set to
+ * 1, to be used when we want to return an error if the special IDs + or -
+ * are provided. */
+int streamParseStrictIDOrReply(client *c, robj *o, streamID *id, uint64_t missing_seq) {
+ return streamGenericParseIDOrReply(c,o,id,missing_seq,1);
+}
+
+/* We propagate MAXLEN ~ <count> as MAXLEN = <resulting-len-of-stream>
+ * otherwise trimming is no longer determinsitic on replicas / AOF. */
+void streamRewriteApproxMaxlen(client *c, stream *s, int maxlen_arg_idx) {
+ robj *maxlen_obj = createStringObjectFromLongLong(s->length);
+ robj *equal_obj = createStringObject("=",1);
+
+ rewriteClientCommandArgument(c,maxlen_arg_idx,maxlen_obj);
+ rewriteClientCommandArgument(c,maxlen_arg_idx-1,equal_obj);
+
+ decrRefCount(equal_obj);
+ decrRefCount(maxlen_obj);
+}
+
+/* XADD key [MAXLEN [~|=] <count>] <ID or *> [field value] [field value] ... */
void xaddCommand(client *c) {
streamID id;
int id_given = 0; /* Was an ID different than "*" specified? */
- long long maxlen = 0; /* 0 means no maximum length. */
+ long long maxlen = -1; /* If left to -1 no trimming is performed. */
int approx_maxlen = 0; /* If 1 only delete whole radix tree nodes, so
the maxium length is not applied verbatim. */
int maxlen_arg_idx = 0; /* Index of the count in MAXLEN, for rewriting. */
@@ -787,31 +1184,35 @@ void xaddCommand(client *c) {
* creation. */
break;
} else if (!strcasecmp(opt,"maxlen") && moreargs) {
+ approx_maxlen = 0;
char *next = c->argv[i+1]->ptr;
/* Check for the form MAXLEN ~ <count>. */
if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {
approx_maxlen = 1;
i++;
+ } else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {
+ i++;
}
if (getLongLongFromObjectOrReply(c,c->argv[i+1],&maxlen,NULL)
!= C_OK) return;
+
+ if (maxlen < 0) {
+ addReplyError(c,"The MAXLEN argument must be >= 0.");
+ return;
+ }
i++;
maxlen_arg_idx = i;
} else {
/* If we are here is a syntax error or a valid ID. */
- if (streamParseIDOrReply(NULL,c->argv[i],&id,0) == C_OK) {
- id_given = 1;
- break;
- } else {
- addReply(c,shared.syntaxerr);
- return;
- }
+ if (streamParseStrictIDOrReply(c,c->argv[i],&id,0) != C_OK) return;
+ id_given = 1;
+ break;
}
}
int field_pos = i+1;
/* Check arity. */
- if ((c->argc - field_pos) < 2 || (c->argc-field_pos % 2) == 1) {
+ if ((c->argc - field_pos) < 2 || ((c->argc-field_pos) % 2) == 1) {
addReplyError(c,"wrong number of arguments for XADD");
return;
}
@@ -827,35 +1228,27 @@ void xaddCommand(client *c) {
&id, id_given ? &id : NULL)
== C_ERR)
{
- addReplyError(c,"The ID specified in XADD is smaller than the "
+ addReplyError(c,"The ID specified in XADD is equal or smaller than the "
"target stream top item");
return;
}
- sds reply = sdscatfmt(sdsempty(),"+%U-%U\r\n",id.ms,id.seq);
- addReplySds(c,reply);
+ addReplyStreamID(c,&id);
signalModifiedKey(c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_STREAM,"xadd",c->argv[1],c->db->id);
server.dirty++;
- /* Remove older elements if MAXLEN was specified. */
- if (maxlen) {
- if (!streamTrimByLength(s,maxlen,approx_maxlen)) {
- /* If no trimming was performed, for instance because approximated
- * trimming length was specified, rewrite the MAXLEN argument
- * as zero, so that the command is propagated without trimming. */
- robj *zeroobj = createStringObjectFromLongLong(0);
- rewriteClientCommandArgument(c,maxlen_arg_idx,zeroobj);
- decrRefCount(zeroobj);
- } else {
+ if (maxlen >= 0) {
+ /* Notify xtrim event if needed. */
+ if (streamTrimByLength(s,maxlen,approx_maxlen)) {
notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
}
+ if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
}
/* Let's rewrite the ID argument with the one actually generated for
* AOF/replication propagation. */
- robj *idarg = createObject(OBJ_STRING,
- sdscatfmt(sdsempty(),"%U-%U",id.ms,id.seq));
+ robj *idarg = createObjectFromStreamID(&id);
rewriteClientCommandArgument(c,i,idarg);
decrRefCount(idarg);
@@ -870,7 +1263,7 @@ void xrangeGenericCommand(client *c, int rev) {
robj *o;
stream *s;
streamID startid, endid;
- long long count = 0;
+ long long count = -1;
robj *startarg = rev ? c->argv[3] : c->argv[2];
robj *endarg = rev ? c->argv[2] : c->argv[3];
@@ -894,10 +1287,17 @@ void xrangeGenericCommand(client *c, int rev) {
}
/* Return the specified range to the user. */
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
- || checkType(c,o,OBJ_STREAM)) return;
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptyarray)) == NULL ||
+ checkType(c,o,OBJ_STREAM)) return;
+
s = o->ptr;
- streamReplyWithRange(c,s,&startid,&endid,count,rev);
+
+ if (count == 0) {
+ addReplyNullArray(c);
+ } else {
+ if (count == -1) count = 0;
+ streamReplyWithRange(c,s,&startid,&endid,count,rev,NULL,NULL,0,NULL);
+ }
}
/* XRANGE key start end [COUNT <n>] */
@@ -919,22 +1319,31 @@ void xlenCommand(client *c) {
addReplyLongLong(c,s->length);
}
-/* XREAD [BLOCK <milliseconds>] [COUNT <count>] [GROUP <groupname> <ttl>]
- * [RETRY <milliseconds> <ttl>] STREAMS key_1 key_2 ... key_N
- * ID_1 ID_2 ... ID_N */
+/* XREAD [BLOCK <milliseconds>] [COUNT <count>] STREAMS key_1 key_2 ... key_N
+ * ID_1 ID_2 ... ID_N
+ *
+ * This function also implements the XREAD-GROUP command, which is like XREAD
+ * but accepting the [GROUP group-name consumer-name] additional option.
+ * This is useful because while XREAD is a read command and can be called
+ * on slaves, XREAD-GROUP is not. */
#define XREAD_BLOCKED_DEFAULT_COUNT 1000
void xreadCommand(client *c) {
long long timeout = -1; /* -1 means, no BLOCK argument given. */
long long count = 0;
int streams_count = 0;
int streams_arg = 0;
+ int noack = 0; /* True if NOACK option was specified. */
#define STREAMID_STATIC_VECTOR_LEN 8
streamID static_ids[STREAMID_STATIC_VECTOR_LEN];
streamID *ids = static_ids;
+ streamCG **groups = NULL;
+ int xreadgroup = sdslen(c->argv[0]->ptr) == 10; /* XREAD or XREADGROUP? */
+ robj *groupname = NULL;
+ robj *consumername = NULL;
/* Parse arguments. */
for (int i = 1; i < c->argc; i++) {
- int moreargs = i != c->argc-1;
+ int moreargs = c->argc-i-1;
char *o = c->argv[i]->ptr;
if (!strcasecmp(o,"BLOCK") && moreargs) {
i++;
@@ -956,6 +1365,22 @@ void xreadCommand(client *c) {
}
streams_count /= 2; /* We have two arguments for each stream. */
break;
+ } else if (!strcasecmp(o,"GROUP") && moreargs >= 2) {
+ if (!xreadgroup) {
+ addReplyError(c,"The GROUP option is only supported by "
+ "XREADGROUP. You called XREAD instead.");
+ return;
+ }
+ groupname = c->argv[i+1];
+ consumername = c->argv[i+2];
+ i += 2;
+ } else if (!strcasecmp(o,"NOACK")) {
+ if (!xreadgroup) {
+ addReplyError(c,"The NOACK option is only supported by "
+ "XREADGROUP. You called XREAD instead.");
+ return;
+ }
+ noack = 1;
} else {
addReply(c,shared.syntaxerr);
return;
@@ -968,17 +1393,52 @@ void xreadCommand(client *c) {
return;
}
- /* Parse the IDs. */
+ /* If the user specified XREADGROUP then it must also
+ * provide the GROUP option. */
+ if (xreadgroup && groupname == NULL) {
+ addReplyError(c,"Missing GROUP option for XREADGROUP");
+ return;
+ }
+
+ /* Parse the IDs and resolve the group name. */
if (streams_count > STREAMID_STATIC_VECTOR_LEN)
ids = zmalloc(sizeof(streamID)*streams_count);
+ if (groupname) groups = zmalloc(sizeof(streamCG*)*streams_count);
for (int i = streams_arg + streams_count; i < c->argc; i++) {
/* Specifying "$" as last-known-id means that the client wants to be
* served with just the messages that will arrive into the stream
* starting from now. */
int id_idx = i - streams_arg - streams_count;
+ robj *key = c->argv[i-streams_count];
+ robj *o = lookupKeyRead(c->db,key);
+ if (o && checkType(c,o,OBJ_STREAM)) goto cleanup;
+ streamCG *group = NULL;
+
+ /* If a group was specified, than we need to be sure that the
+ * key and group actually exist. */
+ if (groupname) {
+ if (o == NULL ||
+ (group = streamLookupCG(o->ptr,groupname->ptr)) == NULL)
+ {
+ addReplyErrorFormat(c, "-NOGROUP No such key '%s' or consumer "
+ "group '%s' in XREADGROUP with GROUP "
+ "option",
+ (char*)key->ptr,(char*)groupname->ptr);
+ goto cleanup;
+ }
+ groups[id_idx] = group;
+ }
+
if (strcmp(c->argv[i]->ptr,"$") == 0) {
- robj *o = lookupKeyRead(c->db,c->argv[i-streams_count]);
+ if (xreadgroup) {
+ addReplyError(c,"The $ ID is meaningless in the context of "
+ "XREADGROUP: you want to read the history of "
+ "this consumer by specifying a proper ID, or "
+ "use the > ID to get new messages. The $ ID would "
+ "just return an empty result set.");
+ goto cleanup;
+ }
if (o) {
stream *s = o->ptr;
ids[id_idx] = s->last_id;
@@ -987,8 +1447,21 @@ void xreadCommand(client *c) {
ids[id_idx].seq = 0;
}
continue;
+ } else if (strcmp(c->argv[i]->ptr,">") == 0) {
+ if (!xreadgroup) {
+ addReplyError(c,"The > ID can be specified only when calling "
+ "XREADGROUP using the GROUP <group> "
+ "<consumer> option.");
+ goto cleanup;
+ }
+ /* We use just the maximum ID to signal this is a ">" ID, anyway
+ * the code handling the blocking clients will have to update the
+ * ID later in order to match the changing consumer group last ID. */
+ ids[id_idx].ms = UINT64_MAX;
+ ids[id_idx].seq = UINT64_MAX;
+ continue;
}
- if (streamParseIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
+ if (streamParseStrictIDOrReply(c,c->argv[i],ids+id_idx,0) != C_OK)
goto cleanup;
}
@@ -1000,28 +1473,71 @@ void xreadCommand(client *c) {
if (o == NULL) continue;
stream *s = o->ptr;
streamID *gt = ids+i; /* ID must be greater than this. */
- if (s->last_id.ms > gt->ms ||
- (s->last_id.ms == gt->ms && s->last_id.seq > gt->seq))
- {
+ int serve_synchronously = 0;
+ int serve_history = 0; /* True for XREADGROUP with ID != ">". */
+
+ /* Check if there are the conditions to serve the client
+ * synchronously. */
+ if (groups) {
+ /* If the consumer is blocked on a group, we always serve it
+ * synchronously (serving its local history) if the ID specified
+ * was not the special ">" ID. */
+ if (gt->ms != UINT64_MAX ||
+ gt->seq != UINT64_MAX)
+ {
+ serve_synchronously = 1;
+ serve_history = 1;
+ } else {
+ /* We also want to serve a consumer in a consumer group
+ * synchronously in case the group top item delivered is smaller
+ * than what the stream has inside. */
+ streamID *last = &groups[i]->last_id;
+ if (s->length && (streamCompareID(&s->last_id, last) > 0)) {
+ serve_synchronously = 1;
+ *gt = *last;
+ }
+ }
+ } else {
+ /* For consumers without a group, we serve synchronously if we can
+ * actually provide at least one item from the stream. */
+ if (s->length && (streamCompareID(&s->last_id, gt) > 0)) {
+ serve_synchronously = 1;
+ }
+ }
+
+ if (serve_synchronously) {
arraylen++;
- if (arraylen == 1) arraylen_ptr = addDeferredMultiBulkLength(c);
+ if (arraylen == 1) arraylen_ptr = addReplyDeferredLen(c);
/* streamReplyWithRange() handles the 'start' ID as inclusive,
* so start from the next ID, since we want only messages with
* IDs greater than start. */
streamID start = *gt;
- start.seq++; /* Can't overflow, it's an uint64_t */
+ start.seq++; /* uint64_t can't overflow in this context. */
/* Emit the two elements sub-array consisting of the name
* of the stream and the data we extracted from it. */
- addReplyMultiBulkLen(c,2);
- addReplyBulk(c,c->argv[i+streams_arg]);
- streamReplyWithRange(c,s,&start,NULL,count,0);
+ if (c->resp == 2) addReplyArrayLen(c,2);
+ addReplyBulk(c,c->argv[streams_arg+i]);
+ streamConsumer *consumer = NULL;
+ if (groups) consumer = streamLookupConsumer(groups[i],
+ consumername->ptr,1);
+ streamPropInfo spi = {c->argv[i+streams_arg],groupname};
+ int flags = 0;
+ if (noack) flags |= STREAM_RWR_NOACK;
+ if (serve_history) flags |= STREAM_RWR_HISTORY;
+ streamReplyWithRange(c,s,&start,NULL,count,0,
+ groups ? groups[i] : NULL,
+ consumer, flags, &spi);
+ if (groups) server.dirty++;
}
}
/* We replied synchronously! Set the top array len and return to caller. */
if (arraylen) {
- setDeferredMultiBulkLength(c,arraylen_ptr,arraylen);
+ if (c->resp == 2)
+ setDeferredArrayLen(c,arraylen_ptr,arraylen);
+ else
+ setDeferredMapLen(c,arraylen_ptr,arraylen);
goto cleanup;
}
@@ -1030,7 +1546,7 @@ void xreadCommand(client *c) {
/* If we are inside a MULTI/EXEC and the list is empty the only thing
* we can do is treating it as a timeout (even with timeout 0). */
if (c->flags & CLIENT_MULTI) {
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
goto cleanup;
}
blockForKeys(c, BLOCKED_STREAM, c->argv+streams_arg, streams_count,
@@ -1039,18 +1555,999 @@ void xreadCommand(client *c) {
* in case the ID provided is too low, we do not want the server to
* block just to serve this client a huge stream of messages. */
c->bpop.xread_count = count ? count : XREAD_BLOCKED_DEFAULT_COUNT;
- c->bpop.xread_group = NULL; /* Not used for now. */
+
+ /* If this is a XREADGROUP + GROUP we need to remember for which
+ * group and consumer name we are blocking, so later when one of the
+ * keys receive more data, we can call streamReplyWithRange() passing
+ * the right arguments. */
+ if (groupname) {
+ incrRefCount(groupname);
+ incrRefCount(consumername);
+ c->bpop.xread_group = groupname;
+ c->bpop.xread_consumer = consumername;
+ c->bpop.xread_group_noack = noack;
+ } else {
+ c->bpop.xread_group = NULL;
+ c->bpop.xread_consumer = NULL;
+ }
goto cleanup;
}
/* No BLOCK option, nor any stream we can serve. Reply as with a
* timeout happened. */
- addReply(c,shared.nullmultibulk);
+ addReplyNullArray(c);
/* Continue to cleanup... */
-cleanup:
- /* Cleanup. */
+cleanup: /* Cleanup. */
+
+ /* The command is propagated (in the READGROUP form) as a side effect
+ * of calling lower level APIs. So stop any implicit propagation. */
+ preventCommandPropagation(c);
if (ids != static_ids) zfree(ids);
+ zfree(groups);
}
+/* -----------------------------------------------------------------------
+ * Low level implementation of consumer groups
+ * ----------------------------------------------------------------------- */
+
+/* Create a NACK entry setting the delivery count to 1 and the delivery
+ * time to the current time. The NACK consumer will be set to the one
+ * specified as argument of the function. */
+streamNACK *streamCreateNACK(streamConsumer *consumer) {
+ streamNACK *nack = zmalloc(sizeof(*nack));
+ nack->delivery_time = mstime();
+ nack->delivery_count = 1;
+ nack->consumer = consumer;
+ return nack;
+}
+
+/* Free a NACK entry. */
+void streamFreeNACK(streamNACK *na) {
+ zfree(na);
+}
+
+/* Free a consumer and associated data structures. Note that this function
+ * will not reassign the pending messages associated with this consumer
+ * nor will delete them from the stream, so when this function is called
+ * to delete a consumer, and not when the whole stream is destroyed, the caller
+ * should do some work before. */
+void streamFreeConsumer(streamConsumer *sc) {
+ raxFree(sc->pel); /* No value free callback: the PEL entries are shared
+ between the consumer and the main stream PEL. */
+ sdsfree(sc->name);
+ zfree(sc);
+}
+
+/* Create a new consumer group in the context of the stream 's', having the
+ * specified name and last server ID. If a consumer group with the same name
+ * already existed NULL is returned, otherwise the pointer to the consumer
+ * group is returned. */
+streamCG *streamCreateCG(stream *s, char *name, size_t namelen, streamID *id) {
+ if (s->cgroups == NULL) s->cgroups = raxNew();
+ if (raxFind(s->cgroups,(unsigned char*)name,namelen) != raxNotFound)
+ return NULL;
+
+ streamCG *cg = zmalloc(sizeof(*cg));
+ cg->pel = raxNew();
+ cg->consumers = raxNew();
+ cg->last_id = *id;
+ raxInsert(s->cgroups,(unsigned char*)name,namelen,cg,NULL);
+ return cg;
+}
+
+/* Free a consumer group and all its associated data. */
+void streamFreeCG(streamCG *cg) {
+ raxFreeWithCallback(cg->pel,(void(*)(void*))streamFreeNACK);
+ raxFreeWithCallback(cg->consumers,(void(*)(void*))streamFreeConsumer);
+ zfree(cg);
+}
+
+/* Lookup the consumer group in the specified stream and returns its
+ * pointer, otherwise if there is no such group, NULL is returned. */
+streamCG *streamLookupCG(stream *s, sds groupname) {
+ if (s->cgroups == NULL) return NULL;
+ streamCG *cg = raxFind(s->cgroups,(unsigned char*)groupname,
+ sdslen(groupname));
+ return (cg == raxNotFound) ? NULL : cg;
+}
+
+/* Lookup the consumer with the specified name in the group 'cg': if the
+ * consumer does not exist it is automatically created as a side effect
+ * of calling this function, otherwise its last seen time is updated and
+ * the existing consumer reference returned. */
+streamConsumer *streamLookupConsumer(streamCG *cg, sds name, int create) {
+ streamConsumer *consumer = raxFind(cg->consumers,(unsigned char*)name,
+ sdslen(name));
+ if (consumer == raxNotFound) {
+ if (!create) return NULL;
+ consumer = zmalloc(sizeof(*consumer));
+ consumer->name = sdsdup(name);
+ consumer->pel = raxNew();
+ raxInsert(cg->consumers,(unsigned char*)name,sdslen(name),
+ consumer,NULL);
+ }
+ consumer->seen_time = mstime();
+ return consumer;
+}
+
+/* Delete the consumer specified in the consumer group 'cg'. The consumer
+ * may have pending messages: they are removed from the PEL, and the number
+ * of pending messages "lost" is returned. */
+uint64_t streamDelConsumer(streamCG *cg, sds name) {
+ streamConsumer *consumer = streamLookupConsumer(cg,name,0);
+ if (consumer == NULL) return 0;
+
+ uint64_t retval = raxSize(consumer->pel);
+
+ /* Iterate all the consumer pending messages, deleting every corresponding
+ * entry from the global entry. */
+ raxIterator ri;
+ raxStart(&ri,consumer->pel);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamNACK *nack = ri.data;
+ raxRemove(cg->pel,ri.key,ri.key_len,NULL);
+ streamFreeNACK(nack);
+ }
+ raxStop(&ri);
+
+ /* Deallocate the consumer. */
+ raxRemove(cg->consumers,(unsigned char*)name,sdslen(name),NULL);
+ streamFreeConsumer(consumer);
+ return retval;
+}
+
+/* -----------------------------------------------------------------------
+ * Consumer groups commands
+ * ----------------------------------------------------------------------- */
+
+/* XGROUP CREATE <key> <groupname> <id or $> [MKSTREAM]
+ * XGROUP SETID <key> <groupname> <id or $>
+ * XGROUP DESTROY <key> <groupname>
+ * XGROUP DELCONSUMER <key> <groupname> <consumername> */
+void xgroupCommand(client *c) {
+ const char *help[] = {
+"CREATE <key> <groupname> <id or $> [opt] -- Create a new consumer group.",
+" option MKSTREAM: create the empty stream if it does not exist.",
+"SETID <key> <groupname> <id or $> -- Set the current group ID.",
+"DESTROY <key> <groupname> -- Remove the specified group.",
+"DELCONSUMER <key> <groupname> <consumer> -- Remove the specified consumer.",
+"HELP -- Prints this help.",
+NULL
+ };
+ stream *s = NULL;
+ sds grpname = NULL;
+ streamCG *cg = NULL;
+ char *opt = c->argv[1]->ptr; /* Subcommand name. */
+ int mkstream = 0;
+ robj *o;
+
+ /* CREATE has an MKSTREAM option that creates the stream if it
+ * does not exist. */
+ if (c->argc == 6 && !strcasecmp(opt,"CREATE")) {
+ if (strcasecmp(c->argv[5]->ptr,"MKSTREAM")) {
+ addReplySubcommandSyntaxError(c);
+ return;
+ }
+ mkstream = 1;
+ grpname = c->argv[3]->ptr;
+ }
+
+ /* Everything but the "HELP" option requires a key and group name. */
+ if (c->argc >= 4) {
+ o = lookupKeyWrite(c->db,c->argv[2]);
+ if (o) {
+ if (checkType(c,o,OBJ_STREAM)) return;
+ s = o->ptr;
+ }
+ grpname = c->argv[3]->ptr;
+ }
+
+ /* Check for missing key/group. */
+ if (c->argc >= 4 && !mkstream) {
+ /* At this point key must exist, or there is an error. */
+ if (s == NULL) {
+ addReplyError(c,
+ "The XGROUP subcommand requires the key to exist. "
+ "Note that for CREATE you may want to use the MKSTREAM "
+ "option to create an empty stream automatically.");
+ return;
+ }
+
+ /* Certain subcommands require the group to exist. */
+ if ((cg = streamLookupCG(s,grpname)) == NULL &&
+ (!strcasecmp(opt,"SETID") ||
+ !strcasecmp(opt,"DELCONSUMER")))
+ {
+ addReplyErrorFormat(c, "-NOGROUP No such consumer group '%s' "
+ "for key name '%s'",
+ (char*)grpname, (char*)c->argv[2]->ptr);
+ return;
+ }
+ }
+
+ /* Dispatch the different subcommands. */
+ if (!strcasecmp(opt,"CREATE") && (c->argc == 5 || c->argc == 6)) {
+ streamID id;
+ if (!strcmp(c->argv[4]->ptr,"$")) {
+ if (s) {
+ id = s->last_id;
+ } else {
+ id.ms = 0;
+ id.seq = 0;
+ }
+ } else if (streamParseStrictIDOrReply(c,c->argv[4],&id,0) != C_OK) {
+ return;
+ }
+
+ /* Handle the MKSTREAM option now that the command can no longer fail. */
+ if (s == NULL) {
+ serverAssert(mkstream);
+ o = createStreamObject();
+ dbAdd(c->db,c->argv[2],o);
+ s = o->ptr;
+ }
+
+ streamCG *cg = streamCreateCG(s,grpname,sdslen(grpname),&id);
+ if (cg) {
+ addReply(c,shared.ok);
+ server.dirty++;
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-create",
+ c->argv[2],c->db->id);
+ } else {
+ addReplySds(c,
+ sdsnew("-BUSYGROUP Consumer Group name already exists\r\n"));
+ }
+ } else if (!strcasecmp(opt,"SETID") && c->argc == 5) {
+ streamID id;
+ if (!strcmp(c->argv[4]->ptr,"$")) {
+ id = s->last_id;
+ } else if (streamParseIDOrReply(c,c->argv[4],&id,0) != C_OK) {
+ return;
+ }
+ cg->last_id = id;
+ addReply(c,shared.ok);
+ server.dirty++;
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-setid",c->argv[2],c->db->id);
+ } else if (!strcasecmp(opt,"DESTROY") && c->argc == 4) {
+ if (cg) {
+ raxRemove(s->cgroups,(unsigned char*)grpname,sdslen(grpname),NULL);
+ streamFreeCG(cg);
+ addReply(c,shared.cone);
+ server.dirty++;
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-destroy",
+ c->argv[2],c->db->id);
+ } else {
+ addReply(c,shared.czero);
+ }
+ } else if (!strcasecmp(opt,"DELCONSUMER") && c->argc == 5) {
+ /* Delete the consumer and returns the number of pending messages
+ * that were yet associated with such a consumer. */
+ long long pending = streamDelConsumer(cg,c->argv[4]->ptr);
+ addReplyLongLong(c,pending);
+ server.dirty++;
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xgroup-delconsumer",
+ c->argv[2],c->db->id);
+ } else if (!strcasecmp(opt,"HELP")) {
+ addReplyHelp(c, help);
+ } else {
+ addReplySubcommandSyntaxError(c);
+ }
+}
+
+/* XSETID <stream> <groupname> <id>
+ *
+ * Set the internal "last ID" of a stream. */
+void xsetidCommand(client *c) {
+ robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
+ if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
+
+ stream *s = o->ptr;
+ streamID id;
+ if (streamParseStrictIDOrReply(c,c->argv[2],&id,0) != C_OK) return;
+
+ /* If the stream has at least one item, we want to check that the user
+ * is setting a last ID that is equal or greater than the current top
+ * item, otherwise the fundamental ID monotonicity assumption is violated. */
+ if (s->length > 0) {
+ streamID maxid;
+ streamIterator si;
+ streamIteratorStart(&si,s,NULL,NULL,1);
+ int64_t numfields;
+ streamIteratorGetID(&si,&maxid,&numfields);
+ streamIteratorStop(&si);
+
+ if (streamCompareID(&id,&maxid) < 0) {
+ addReplyError(c,"The ID specified in XSETID is smaller than the "
+ "target stream top item");
+ return;
+ }
+ }
+ s->last_id = id;
+ addReply(c,shared.ok);
+ server.dirty++;
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xsetid",c->argv[1],c->db->id);
+}
+
+/* XACK <key> <group> <id> <id> ... <id>
+ *
+ * Acknowledge a message as processed. In practical terms we just check the
+ * pendine entries list (PEL) of the group, and delete the PEL entry both from
+ * the group and the consumer (pending messages are referenced in both places).
+ *
+ * Return value of the command is the number of messages successfully
+ * acknowledged, that is, the IDs we were actually able to resolve in the PEL.
+ */
+void xackCommand(client *c) {
+ streamCG *group = NULL;
+ robj *o = lookupKeyRead(c->db,c->argv[1]);
+ if (o) {
+ if (checkType(c,o,OBJ_STREAM)) return; /* Type error. */
+ group = streamLookupCG(o->ptr,c->argv[2]->ptr);
+ }
+
+ /* No key or group? Nothing to ack. */
+ if (o == NULL || group == NULL) {
+ addReply(c,shared.czero);
+ return;
+ }
+
+ int acknowledged = 0;
+ for (int j = 3; j < c->argc; j++) {
+ streamID id;
+ unsigned char buf[sizeof(streamID)];
+ if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
+ streamEncodeID(buf,&id);
+
+ /* Lookup the ID in the group PEL: it will have a reference to the
+ * NACK structure that will have a reference to the consumer, so that
+ * we are able to remove the entry from both PELs. */
+ streamNACK *nack = raxFind(group->pel,buf,sizeof(buf));
+ if (nack != raxNotFound) {
+ raxRemove(group->pel,buf,sizeof(buf),NULL);
+ raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
+ streamFreeNACK(nack);
+ acknowledged++;
+ server.dirty++;
+ }
+ }
+ addReplyLongLong(c,acknowledged);
+}
+
+/* XPENDING <key> <group> [<start> <stop> <count> [<consumer>]]
+ *
+ * If start and stop are omitted, the command just outputs information about
+ * the amount of pending messages for the key/group pair, together with
+ * the minimum and maxium ID of pending messages.
+ *
+ * If start and stop are provided instead, the pending messages are returned
+ * with informations about the current owner, number of deliveries and last
+ * delivery time and so forth. */
+void xpendingCommand(client *c) {
+ int justinfo = c->argc == 3; /* Without the range just outputs general
+ informations about the PEL. */
+ robj *key = c->argv[1];
+ robj *groupname = c->argv[2];
+ robj *consumername = (c->argc == 7) ? c->argv[6] : NULL;
+ streamID startid, endid;
+ long long count;
+
+ /* Start and stop, and the consumer, can be omitted. */
+ if (c->argc != 3 && c->argc != 6 && c->argc != 7) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+
+ /* Parse start/end/count arguments ASAP if needed, in order to report
+ * syntax errors before any other error. */
+ if (c->argc >= 6) {
+ if (getLongLongFromObjectOrReply(c,c->argv[5],&count,NULL) == C_ERR)
+ return;
+ if (count < 0) count = 0;
+ if (streamParseIDOrReply(c,c->argv[3],&startid,0) == C_ERR)
+ return;
+ if (streamParseIDOrReply(c,c->argv[4],&endid,UINT64_MAX) == C_ERR)
+ return;
+ }
+
+ /* Lookup the key and the group inside the stream. */
+ robj *o = lookupKeyRead(c->db,c->argv[1]);
+ streamCG *group;
+
+ if (o && checkType(c,o,OBJ_STREAM)) return;
+ if (o == NULL ||
+ (group = streamLookupCG(o->ptr,groupname->ptr)) == NULL)
+ {
+ addReplyErrorFormat(c, "-NOGROUP No such key '%s' or consumer "
+ "group '%s'",
+ (char*)key->ptr,(char*)groupname->ptr);
+ return;
+ }
+
+ /* XPENDING <key> <group> variant. */
+ if (justinfo) {
+ addReplyArrayLen(c,4);
+ /* Total number of messages in the PEL. */
+ addReplyLongLong(c,raxSize(group->pel));
+ /* First and last IDs. */
+ if (raxSize(group->pel) == 0) {
+ addReplyNull(c); /* Start. */
+ addReplyNull(c); /* End. */
+ addReplyNullArray(c); /* Clients. */
+ } else {
+ /* Start. */
+ raxIterator ri;
+ raxStart(&ri,group->pel);
+ raxSeek(&ri,"^",NULL,0);
+ raxNext(&ri);
+ streamDecodeID(ri.key,&startid);
+ addReplyStreamID(c,&startid);
+
+ /* End. */
+ raxSeek(&ri,"$",NULL,0);
+ raxNext(&ri);
+ streamDecodeID(ri.key,&endid);
+ addReplyStreamID(c,&endid);
+ raxStop(&ri);
+
+ /* Consumers with pending messages. */
+ raxStart(&ri,group->consumers);
+ raxSeek(&ri,"^",NULL,0);
+ void *arraylen_ptr = addReplyDeferredLen(c);
+ size_t arraylen = 0;
+ while(raxNext(&ri)) {
+ streamConsumer *consumer = ri.data;
+ if (raxSize(consumer->pel) == 0) continue;
+ addReplyArrayLen(c,2);
+ addReplyBulkCBuffer(c,ri.key,ri.key_len);
+ addReplyBulkLongLong(c,raxSize(consumer->pel));
+ arraylen++;
+ }
+ setDeferredArrayLen(c,arraylen_ptr,arraylen);
+ raxStop(&ri);
+ }
+ }
+ /* XPENDING <key> <group> <start> <stop> <count> [<consumer>] variant. */
+ else {
+ streamConsumer *consumer = consumername ?
+ streamLookupConsumer(group,consumername->ptr,0):
+ NULL;
+
+ /* If a consumer name was mentioned but it does not exist, we can
+ * just return an empty array. */
+ if (consumername && consumer == NULL) {
+ addReplyArrayLen(c,0);
+ return;
+ }
+
+ rax *pel = consumer ? consumer->pel : group->pel;
+ unsigned char startkey[sizeof(streamID)];
+ unsigned char endkey[sizeof(streamID)];
+ raxIterator ri;
+ mstime_t now = mstime();
+
+ streamEncodeID(startkey,&startid);
+ streamEncodeID(endkey,&endid);
+ raxStart(&ri,pel);
+ raxSeek(&ri,">=",startkey,sizeof(startkey));
+ void *arraylen_ptr = addReplyDeferredLen(c);
+ size_t arraylen = 0;
+
+ while(count && raxNext(&ri) && memcmp(ri.key,endkey,ri.key_len) <= 0) {
+ streamNACK *nack = ri.data;
+
+ arraylen++;
+ count--;
+ addReplyArrayLen(c,4);
+
+ /* Entry ID. */
+ streamID id;
+ streamDecodeID(ri.key,&id);
+ addReplyStreamID(c,&id);
+
+ /* Consumer name. */
+ addReplyBulkCBuffer(c,nack->consumer->name,
+ sdslen(nack->consumer->name));
+
+ /* Milliseconds elapsed since last delivery. */
+ mstime_t elapsed = now - nack->delivery_time;
+ if (elapsed < 0) elapsed = 0;
+ addReplyLongLong(c,elapsed);
+
+ /* Number of deliveries. */
+ addReplyLongLong(c,nack->delivery_count);
+ }
+ raxStop(&ri);
+ setDeferredArrayLen(c,arraylen_ptr,arraylen);
+ }
+}
+
+/* XCLAIM <key> <group> <consumer> <min-idle-time> <ID-1> <ID-2>
+ * [IDLE <milliseconds>] [TIME <mstime>] [RETRYCOUNT <count>]
+ * [FORCE] [JUSTID]
+ *
+ * Gets ownership of one or multiple messages in the Pending Entries List
+ * of a given stream consumer group.
+ *
+ * If the message ID (among the specified ones) exists, and its idle
+ * time greater or equal to <min-idle-time>, then the message new owner
+ * becomes the specified <consumer>. If the minimum idle time specified
+ * is zero, messages are claimed regardless of their idle time.
+ *
+ * All the messages that cannot be found inside the pending entries list
+ * are ignored, but in case the FORCE option is used. In that case we
+ * create the NACK (representing a not yet acknowledged message) entry in
+ * the consumer group PEL.
+ *
+ * This command creates the consumer as side effect if it does not yet
+ * exists. Moreover the command reset the idle time of the message to 0,
+ * even if by using the IDLE or TIME options, the user can control the
+ * new idle time.
+ *
+ * The options at the end can be used in order to specify more attributes
+ * to set in the representation of the pending message:
+ *
+ * 1. IDLE <ms>:
+ * Set the idle time (last time it was delivered) of the message.
+ * If IDLE is not specified, an IDLE of 0 is assumed, that is,
+ * the time count is reset because the message has now a new
+ * owner trying to process it.
+ *
+ * 2. TIME <ms-unix-time>:
+ * This is the same as IDLE but instead of a relative amount of
+ * milliseconds, it sets the idle time to a specific unix time
+ * (in milliseconds). This is useful in order to rewrite the AOF
+ * file generating XCLAIM commands.
+ *
+ * 3. RETRYCOUNT <count>:
+ * Set the retry counter to the specified value. This counter is
+ * incremented every time a message is delivered again. Normally
+ * XCLAIM does not alter this counter, which is just served to clients
+ * when the XPENDING command is called: this way clients can detect
+ * anomalies, like messages that are never processed for some reason
+ * after a big number of delivery attempts.
+ *
+ * 4. FORCE:
+ * Creates the pending message entry in the PEL even if certain
+ * specified IDs are not already in the PEL assigned to a different
+ * client. However the message must be exist in the stream, otherwise
+ * the IDs of non existing messages are ignored.
+ *
+ * 5. JUSTID:
+ * Return just an array of IDs of messages successfully claimed,
+ * without returning the actual message.
+ *
+ * 6. LASTID <id>:
+ * Update the consumer group last ID with the specified ID if the
+ * current last ID is smaller than the provided one.
+ * This is used for replication / AOF, so that when we read from a
+ * consumer group, the XCLAIM that gets propagated to give ownership
+ * to the consumer, is also used in order to update the group current
+ * ID.
+ *
+ * The command returns an array of messages that the user
+ * successfully claimed, so that the caller is able to understand
+ * what messages it is now in charge of. */
+void xclaimCommand(client *c) {
+ streamCG *group = NULL;
+ robj *o = lookupKeyRead(c->db,c->argv[1]);
+ long long minidle; /* Minimum idle time argument. */
+ long long retrycount = -1; /* -1 means RETRYCOUNT option not given. */
+ mstime_t deliverytime = -1; /* -1 means IDLE/TIME options not given. */
+ int force = 0;
+ int justid = 0;
+
+ if (o) {
+ if (checkType(c,o,OBJ_STREAM)) return; /* Type error. */
+ group = streamLookupCG(o->ptr,c->argv[2]->ptr);
+ }
+
+ /* No key or group? Send an error given that the group creation
+ * is mandatory. */
+ if (o == NULL || group == NULL) {
+ addReplyErrorFormat(c,"-NOGROUP No such key '%s' or "
+ "consumer group '%s'", (char*)c->argv[1]->ptr,
+ (char*)c->argv[2]->ptr);
+ return;
+ }
+
+ if (getLongLongFromObjectOrReply(c,c->argv[4],&minidle,
+ "Invalid min-idle-time argument for XCLAIM")
+ != C_OK) return;
+ if (minidle < 0) minidle = 0;
+
+ /* Start parsing the IDs, so that we abort ASAP if there is a syntax
+ * error: the return value of this command cannot be an error in case
+ * the client successfully claimed some message, so it should be
+ * executed in a "all or nothing" fashion. */
+ int j;
+ for (j = 5; j < c->argc; j++) {
+ streamID id;
+ if (streamParseStrictIDOrReply(NULL,c->argv[j],&id,0) != C_OK) break;
+ }
+ int last_id_arg = j-1; /* Next time we iterate the IDs we now the range. */
+
+ /* If we stopped because some IDs cannot be parsed, perhaps they
+ * are trailing options. */
+ mstime_t now = mstime();
+ streamID last_id = {0,0};
+ int propagate_last_id = 0;
+ for (; j < c->argc; j++) {
+ int moreargs = (c->argc-1) - j; /* Number of additional arguments. */
+ char *opt = c->argv[j]->ptr;
+ if (!strcasecmp(opt,"FORCE")) {
+ force = 1;
+ } else if (!strcasecmp(opt,"JUSTID")) {
+ justid = 1;
+ } else if (!strcasecmp(opt,"IDLE") && moreargs) {
+ j++;
+ if (getLongLongFromObjectOrReply(c,c->argv[j],&deliverytime,
+ "Invalid IDLE option argument for XCLAIM")
+ != C_OK) return;
+ deliverytime = now - deliverytime;
+ } else if (!strcasecmp(opt,"TIME") && moreargs) {
+ j++;
+ if (getLongLongFromObjectOrReply(c,c->argv[j],&deliverytime,
+ "Invalid TIME option argument for XCLAIM")
+ != C_OK) return;
+ } else if (!strcasecmp(opt,"RETRYCOUNT") && moreargs) {
+ j++;
+ if (getLongLongFromObjectOrReply(c,c->argv[j],&retrycount,
+ "Invalid RETRYCOUNT option argument for XCLAIM")
+ != C_OK) return;
+ } else if (!strcasecmp(opt,"LASTID") && moreargs) {
+ j++;
+ if (streamParseStrictIDOrReply(c,c->argv[j],&last_id,0) != C_OK) return;
+ } else {
+ addReplyErrorFormat(c,"Unrecognized XCLAIM option '%s'",opt);
+ return;
+ }
+ }
+
+ if (streamCompareID(&last_id,&group->last_id) > 0) {
+ group->last_id = last_id;
+ propagate_last_id = 1;
+ }
+
+ if (deliverytime != -1) {
+ /* If a delivery time was passed, either with IDLE or TIME, we
+ * do some sanity check on it, and set the deliverytime to now
+ * (which is a sane choice usually) if the value is bogus.
+ * To raise an error here is not wise because clients may compute
+ * the idle time doing some math starting from their local time,
+ * and this is not a good excuse to fail in case, for instance,
+ * the computer time is a bit in the future from our POV. */
+ if (deliverytime < 0 || deliverytime > now) deliverytime = now;
+ } else {
+ /* If no IDLE/TIME option was passed, we want the last delivery
+ * time to be now, so that the idle time of the message will be
+ * zero. */
+ deliverytime = now;
+ }
+
+ /* Do the actual claiming. */
+ streamConsumer *consumer = streamLookupConsumer(group,c->argv[3]->ptr,1);
+ void *arraylenptr = addReplyDeferredLen(c);
+ size_t arraylen = 0;
+ for (int j = 5; j <= last_id_arg; j++) {
+ streamID id;
+ unsigned char buf[sizeof(streamID)];
+ if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK)
+ serverPanic("StreamID invalid after check. Should not be possible.");
+ streamEncodeID(buf,&id);
+
+ /* Lookup the ID in the group PEL. */
+ streamNACK *nack = raxFind(group->pel,buf,sizeof(buf));
+
+ /* If FORCE is passed, let's check if at least the entry
+ * exists in the Stream. In such case, we'll crate a new
+ * entry in the PEL from scratch, so that XCLAIM can also
+ * be used to create entries in the PEL. Useful for AOF
+ * and replication of consumer groups. */
+ if (force && nack == raxNotFound) {
+ streamIterator myiterator;
+ streamIteratorStart(&myiterator,o->ptr,&id,&id,0);
+ int64_t numfields;
+ int found = 0;
+ streamID item_id;
+ if (streamIteratorGetID(&myiterator,&item_id,&numfields)) found = 1;
+ streamIteratorStop(&myiterator);
+
+ /* Item must exist for us to create a NACK for it. */
+ if (!found) continue;
+
+ /* Create the NACK. */
+ nack = streamCreateNACK(NULL);
+ raxInsert(group->pel,buf,sizeof(buf),nack,NULL);
+ }
+
+ if (nack != raxNotFound) {
+ /* We need to check if the minimum idle time requested
+ * by the caller is satisfied by this entry.
+ *
+ * Note that the nack could be created by FORCE, in this
+ * case there was no pre-existing entry and minidle should
+ * be ignored, but in that case nick->consumer is NULL. */
+ if (nack->consumer && minidle) {
+ mstime_t this_idle = now - nack->delivery_time;
+ if (this_idle < minidle) continue;
+ }
+ /* Remove the entry from the old consumer.
+ * Note that nack->consumer is NULL if we created the
+ * NACK above because of the FORCE option. */
+ if (nack->consumer)
+ raxRemove(nack->consumer->pel,buf,sizeof(buf),NULL);
+ /* Update the consumer and idle time. */
+ nack->consumer = consumer;
+ nack->delivery_time = deliverytime;
+ /* Set the delivery attempts counter if given, otherwise
+ * autoincrement unless JUSTID option provided */
+ if (retrycount >= 0) {
+ nack->delivery_count = retrycount;
+ } else if (!justid) {
+ nack->delivery_count++;
+ }
+ /* Add the entry in the new consumer local PEL. */
+ raxInsert(consumer->pel,buf,sizeof(buf),nack,NULL);
+ /* Send the reply for this entry. */
+ if (justid) {
+ addReplyStreamID(c,&id);
+ } else {
+ size_t emitted = streamReplyWithRange(c,o->ptr,&id,&id,1,0,
+ NULL,NULL,STREAM_RWR_RAWENTRIES,NULL);
+ if (!emitted) addReplyNull(c);
+ }
+ arraylen++;
+
+ /* Propagate this change. */
+ streamPropagateXCLAIM(c,c->argv[1],group,c->argv[2],c->argv[j],nack);
+ propagate_last_id = 0; /* Will be propagated by XCLAIM itself. */
+ server.dirty++;
+ }
+ }
+ if (propagate_last_id) {
+ streamPropagateGroupID(c,c->argv[1],group,c->argv[2]);
+ server.dirty++;
+ }
+ setDeferredArrayLen(c,arraylenptr,arraylen);
+ preventCommandPropagation(c);
+}
+
+
+/* XDEL <key> [<ID1> <ID2> ... <IDN>]
+ *
+ * Removes the specified entries from the stream. Returns the number
+ * of items actually deleted, that may be different from the number
+ * of IDs passed in case certain IDs do not exist. */
+void xdelCommand(client *c) {
+ robj *o;
+
+ if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL
+ || checkType(c,o,OBJ_STREAM)) return;
+ stream *s = o->ptr;
+
+ /* We need to sanity check the IDs passed to start. Even if not
+ * a big issue, it is not great that the command is only partially
+ * executed because at some point an invalid ID is parsed. */
+ streamID id;
+ for (int j = 2; j < c->argc; j++) {
+ if (streamParseStrictIDOrReply(c,c->argv[j],&id,0) != C_OK) return;
+ }
+
+ /* Actually apply the command. */
+ int deleted = 0;
+ for (int j = 2; j < c->argc; j++) {
+ streamParseStrictIDOrReply(c,c->argv[j],&id,0); /* Retval already checked. */
+ deleted += streamDeleteItem(s,&id);
+ }
+
+ /* Propagate the write if needed. */
+ if (deleted) {
+ signalModifiedKey(c->db,c->argv[1]);
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xdel",c->argv[1],c->db->id);
+ server.dirty += deleted;
+ }
+ addReplyLongLong(c,deleted);
+}
+
+/* General form: XTRIM <key> [... options ...]
+ *
+ * List of options:
+ *
+ * MAXLEN [~|=] <count> -- Trim so that the stream will be capped at
+ * the specified length. Use ~ before the
+ * count in order to demand approximated trimming
+ * (like XADD MAXLEN option).
+ */
+
+#define TRIM_STRATEGY_NONE 0
+#define TRIM_STRATEGY_MAXLEN 1
+void xtrimCommand(client *c) {
+ robj *o;
+
+ /* If the key does not exist, we are ok returning zero, that is, the
+ * number of elements removed from the stream. */
+ if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL
+ || checkType(c,o,OBJ_STREAM)) return;
+ stream *s = o->ptr;
+
+ /* Argument parsing. */
+ int trim_strategy = TRIM_STRATEGY_NONE;
+ long long maxlen = -1; /* If left to -1 no trimming is performed. */
+ int approx_maxlen = 0; /* If 1 only delete whole radix tree nodes, so
+ the maxium length is not applied verbatim. */
+ int maxlen_arg_idx = 0; /* Index of the count in MAXLEN, for rewriting. */
+
+ /* Parse options. */
+ int i = 2; /* Start of options. */
+ for (; i < c->argc; i++) {
+ int moreargs = (c->argc-1) - i; /* Number of additional arguments. */
+ char *opt = c->argv[i]->ptr;
+ if (!strcasecmp(opt,"maxlen") && moreargs) {
+ approx_maxlen = 0;
+ trim_strategy = TRIM_STRATEGY_MAXLEN;
+ char *next = c->argv[i+1]->ptr;
+ /* Check for the form MAXLEN ~ <count>. */
+ if (moreargs >= 2 && next[0] == '~' && next[1] == '\0') {
+ approx_maxlen = 1;
+ i++;
+ } else if (moreargs >= 2 && next[0] == '=' && next[1] == '\0') {
+ i++;
+ }
+ if (getLongLongFromObjectOrReply(c,c->argv[i+1],&maxlen,NULL)
+ != C_OK) return;
+
+ if (maxlen < 0) {
+ addReplyError(c,"The MAXLEN argument must be >= 0.");
+ return;
+ }
+ i++;
+ maxlen_arg_idx = i;
+ } else {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ }
+
+ /* Perform the trimming. */
+ int64_t deleted = 0;
+ if (trim_strategy == TRIM_STRATEGY_MAXLEN) {
+ deleted = streamTrimByLength(s,maxlen,approx_maxlen);
+ } else {
+ addReplyError(c,"XTRIM called without an option to trim the stream");
+ return;
+ }
+
+ /* Propagate the write if needed. */
+ if (deleted) {
+ signalModifiedKey(c->db,c->argv[1]);
+ notifyKeyspaceEvent(NOTIFY_STREAM,"xtrim",c->argv[1],c->db->id);
+ server.dirty += deleted;
+ if (approx_maxlen) streamRewriteApproxMaxlen(c,s,maxlen_arg_idx);
+ }
+ addReplyLongLong(c,deleted);
+}
+
+/* XINFO CONSUMERS <key> <group>
+ * XINFO GROUPS <key>
+ * XINFO STREAM <key>
+ * XINFO HELP. */
+void xinfoCommand(client *c) {
+ const char *help[] = {
+"CONSUMERS <key> <groupname> -- Show consumer groups of group <groupname>.",
+"GROUPS <key> -- Show the stream consumer groups.",
+"STREAM <key> -- Show information about the stream.",
+"HELP -- Print this help.",
+NULL
+ };
+ stream *s = NULL;
+ char *opt;
+ robj *key;
+
+ /* HELP is special. Handle it ASAP. */
+ if (!strcasecmp(c->argv[1]->ptr,"HELP")) {
+ addReplyHelp(c, help);
+ return;
+ } else if (c->argc < 3) {
+ addReplyError(c,"syntax error, try 'XINFO HELP'");
+ return;
+ }
+
+ /* With the exception of HELP handled before any other sub commands, all
+ * the ones are in the form of "<subcommand> <key>". */
+ opt = c->argv[1]->ptr;
+ key = c->argv[2];
+
+ /* Lookup the key now, this is common for all the subcommands but HELP. */
+ robj *o = lookupKeyWriteOrReply(c,key,shared.nokeyerr);
+ if (o == NULL || checkType(c,o,OBJ_STREAM)) return;
+ s = o->ptr;
+
+ /* Dispatch the different subcommands. */
+ if (!strcasecmp(opt,"CONSUMERS") && c->argc == 4) {
+ /* XINFO CONSUMERS <key> <group>. */
+ streamCG *cg = streamLookupCG(s,c->argv[3]->ptr);
+ if (cg == NULL) {
+ addReplyErrorFormat(c, "-NOGROUP No such consumer group '%s' "
+ "for key name '%s'",
+ (char*)c->argv[3]->ptr, (char*)key->ptr);
+ return;
+ }
+
+ addReplyArrayLen(c,raxSize(cg->consumers));
+ raxIterator ri;
+ raxStart(&ri,cg->consumers);
+ raxSeek(&ri,"^",NULL,0);
+ mstime_t now = mstime();
+ while(raxNext(&ri)) {
+ streamConsumer *consumer = ri.data;
+ mstime_t idle = now - consumer->seen_time;
+ if (idle < 0) idle = 0;
+
+ addReplyMapLen(c,3);
+ addReplyBulkCString(c,"name");
+ addReplyBulkCBuffer(c,consumer->name,sdslen(consumer->name));
+ addReplyBulkCString(c,"pending");
+ addReplyLongLong(c,raxSize(consumer->pel));
+ addReplyBulkCString(c,"idle");
+ addReplyLongLong(c,idle);
+ }
+ raxStop(&ri);
+ } else if (!strcasecmp(opt,"GROUPS") && c->argc == 3) {
+ /* XINFO GROUPS <key>. */
+ if (s->cgroups == NULL) {
+ addReplyArrayLen(c,0);
+ return;
+ }
+
+ addReplyArrayLen(c,raxSize(s->cgroups));
+ raxIterator ri;
+ raxStart(&ri,s->cgroups);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ streamCG *cg = ri.data;
+ addReplyMapLen(c,4);
+ addReplyBulkCString(c,"name");
+ addReplyBulkCBuffer(c,ri.key,ri.key_len);
+ addReplyBulkCString(c,"consumers");
+ addReplyLongLong(c,raxSize(cg->consumers));
+ addReplyBulkCString(c,"pending");
+ addReplyLongLong(c,raxSize(cg->pel));
+ addReplyBulkCString(c,"last-delivered-id");
+ addReplyStreamID(c,&cg->last_id);
+ }
+ raxStop(&ri);
+ } else if (!strcasecmp(opt,"STREAM") && c->argc == 3) {
+ /* XINFO STREAM <key> (or the alias XINFO <key>). */
+ addReplyMapLen(c,7);
+ addReplyBulkCString(c,"length");
+ addReplyLongLong(c,s->length);
+ addReplyBulkCString(c,"radix-tree-keys");
+ addReplyLongLong(c,raxSize(s->rax));
+ addReplyBulkCString(c,"radix-tree-nodes");
+ addReplyLongLong(c,s->rax->numnodes);
+ addReplyBulkCString(c,"groups");
+ addReplyLongLong(c,s->cgroups ? raxSize(s->cgroups) : 0);
+ addReplyBulkCString(c,"last-generated-id");
+ addReplyStreamID(c,&s->last_id);
+
+ /* To emit the first/last entry we us the streamReplyWithRange()
+ * API. */
+ int count;
+ streamID start, end;
+ start.ms = start.seq = 0;
+ end.ms = end.seq = UINT64_MAX;
+ addReplyBulkCString(c,"first-entry");
+ count = streamReplyWithRange(c,s,&start,&end,1,0,NULL,NULL,
+ STREAM_RWR_RAWENTRIES,NULL);
+ if (!count) addReplyNull(c);
+ addReplyBulkCString(c,"last-entry");
+ count = streamReplyWithRange(c,s,&start,&end,1,1,NULL,NULL,
+ STREAM_RWR_RAWENTRIES,NULL);
+ if (!count) addReplyNull(c);
+ } else {
+ addReplySubcommandSyntaxError(c);
+ }
+}
diff --git a/src/t_string.c b/src/t_string.c
index 75375f446..5800c5c0c 100644
--- a/src/t_string.c
+++ b/src/t_string.c
@@ -80,7 +80,7 @@ void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire,
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
{
- addReply(c, abort_reply ? abort_reply : shared.nullbulk);
+ addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
return;
}
setKey(c->db,key,val);
@@ -157,7 +157,7 @@ void psetexCommand(client *c) {
int getGenericCommand(client *c) {
robj *o;
- if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
+ if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) == NULL)
return C_OK;
if (o->type != OBJ_STRING) {
@@ -285,14 +285,14 @@ void getrangeCommand(client *c) {
void mgetCommand(client *c) {
int j;
- addReplyMultiBulkLen(c,c->argc-1);
+ addReplyArrayLen(c,c->argc-1);
for (j = 1; j < c->argc; j++) {
robj *o = lookupKeyRead(c->db,c->argv[j]);
if (o == NULL) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
if (o->type != OBJ_STRING) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
addReplyBulk(c,o);
}
@@ -301,24 +301,22 @@ void mgetCommand(client *c) {
}
void msetGenericCommand(client *c, int nx) {
- int j, busykeys = 0;
+ int j;
if ((c->argc % 2) == 0) {
addReplyError(c,"wrong number of arguments for MSET");
return;
}
+
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
- * set nothing at all if at least one already key exists. */
+ * set anything if at least one key alerady exists. */
if (nx) {
for (j = 1; j < c->argc; j += 2) {
if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
- busykeys++;
+ addReply(c, shared.czero);
+ return;
}
}
- if (busykeys) {
- addReply(c, shared.czero);
- return;
- }
}
for (j = 1; j < c->argc; j += 2) {
@@ -361,7 +359,7 @@ void incrDecrCommand(client *c, long long incr) {
new = o;
o->ptr = (void*)((long)value);
} else {
- new = createStringObjectFromLongLong(value);
+ new = createStringObjectFromLongLongForValue(value);
if (o) {
dbOverwrite(c->db,c->argv[1],new);
} else {
diff --git a/src/t_zset.c b/src/t_zset.c
index f7f4c6eb2..ea6f4b848 100644
--- a/src/t_zset.c
+++ b/src/t_zset.c
@@ -244,6 +244,61 @@ int zslDelete(zskiplist *zsl, double score, sds ele, zskiplistNode **node) {
return 0; /* not found */
}
+/* Update the score of an elmenent inside the sorted set skiplist.
+ * Note that the element must exist and must match 'score'.
+ * This function does not update the score in the hash table side, the
+ * caller should take care of it.
+ *
+ * Note that this function attempts to just update the node, in case after
+ * the score update, the node would be exactly at the same position.
+ * Otherwise the skiplist is modified by removing and re-adding a new
+ * element, which is more costly.
+ *
+ * The function returns the updated element skiplist node pointer. */
+zskiplistNode *zslUpdateScore(zskiplist *zsl, double curscore, sds ele, double newscore) {
+ zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
+ int i;
+
+ /* We need to seek to element to update to start: this is useful anyway,
+ * we'll have to update or remove it. */
+ x = zsl->header;
+ for (i = zsl->level-1; i >= 0; i--) {
+ while (x->level[i].forward &&
+ (x->level[i].forward->score < curscore ||
+ (x->level[i].forward->score == curscore &&
+ sdscmp(x->level[i].forward->ele,ele) < 0)))
+ {
+ x = x->level[i].forward;
+ }
+ update[i] = x;
+ }
+
+ /* Jump to our element: note that this function assumes that the
+ * element with the matching score exists. */
+ x = x->level[0].forward;
+ serverAssert(x && curscore == x->score && sdscmp(x->ele,ele) == 0);
+
+ /* If the node, after the score update, would be still exactly
+ * at the same position, we can just update the score without
+ * actually removing and re-inserting the element in the skiplist. */
+ if ((x->backward == NULL || x->backward->score < newscore) &&
+ (x->level[0].forward == NULL || x->level[0].forward->score > newscore))
+ {
+ x->score = newscore;
+ return x;
+ }
+
+ /* No way to reuse the old node: we need to remove and insert a new
+ * one at a different place. */
+ zslDeleteNode(zsl, x, update);
+ zskiplistNode *newnode = zslInsert(zsl,newscore,x->ele);
+ /* We reused the old node x->ele SDS string, free the node now
+ * since zslInsert created a new one. */
+ x->ele = NULL;
+ zslFreeNode(x);
+ return newnode;
+}
+
int zslValueGteMin(double value, zrangespec *spec) {
return spec->minex ? (value > spec->min) : (value >= spec->min);
}
@@ -507,7 +562,7 @@ static int zslParseRange(robj *min, robj *max, zrangespec *spec) {
* + means the max string possible
*
* If the string is valid the *dest pointer is set to the redis object
- * that will be used for the comparision, and ex will be set to 0 or 1
+ * that will be used for the comparison, and ex will be set to 0 or 1
* respectively if the item is exclusive or inclusive. C_OK will be
* returned.
*
@@ -519,12 +574,12 @@ int zslParseLexRangeItem(robj *item, sds *dest, int *ex) {
switch(c[0]) {
case '+':
if (c[1] != '\0') return C_ERR;
- *ex = 0;
+ *ex = 1;
*dest = shared.maxstring;
return C_OK;
case '-':
if (c[1] != '\0') return C_ERR;
- *ex = 0;
+ *ex = 1;
*dest = shared.minstring;
return C_OK;
case '(':
@@ -597,9 +652,8 @@ int zslIsInLexRange(zskiplist *zsl, zlexrangespec *range) {
zskiplistNode *x;
/* Test for ranges that will always be empty. */
- if (sdscmplex(range->min,range->max) > 1 ||
- (sdscmp(range->min,range->max) == 0 &&
- (range->minex || range->maxex)))
+ int cmp = sdscmplex(range->min,range->max);
+ if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
return 0;
x = zsl->tail;
if (x == NULL || !zslLexValueGteMin(x->ele,range))
@@ -872,9 +926,8 @@ int zzlIsInLexRange(unsigned char *zl, zlexrangespec *range) {
unsigned char *p;
/* Test for ranges that will always be empty. */
- if (sdscmplex(range->min,range->max) > 1 ||
- (sdscmp(range->min,range->max) == 0 &&
- (range->minex || range->maxex)))
+ int cmp = sdscmplex(range->min,range->max);
+ if (cmp > 0 || (cmp == 0 && (range->minex || range->maxex)))
return 0;
p = ziplistIndex(zl,-2); /* Last element. */
@@ -1100,8 +1153,8 @@ unsigned char *zzlDeleteRangeByRank(unsigned char *zl, unsigned int start, unsig
* Common sorted set API
*----------------------------------------------------------------------------*/
-unsigned int zsetLength(const robj *zobj) {
- int length = -1;
+unsigned long zsetLength(const robj *zobj) {
+ unsigned long length = 0;
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
length = zzlLength(zobj->ptr);
} else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
@@ -1304,9 +1357,8 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Optimize: check if the element is too large or the list
* becomes too long *before* executing zzlInsert. */
zobj->ptr = zzlInsert(zobj->ptr,ele,score);
- if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries)
- zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
- if (sdslen(ele) > server.zset_max_ziplist_value)
+ if (zzlLength(zobj->ptr) > server.zset_max_ziplist_entries ||
+ sdslen(ele) > server.zset_max_ziplist_value)
zsetConvert(zobj,OBJ_ENCODING_SKIPLIST);
if (newscore) *newscore = score;
*flags |= ZADD_ADDED;
@@ -1341,13 +1393,7 @@ int zsetAdd(robj *zobj, double score, sds ele, int *flags, double *newscore) {
/* Remove and re-insert when score changes. */
if (score != curscore) {
- zskiplistNode *node;
- serverAssert(zslDelete(zs->zsl,curscore,ele,&node));
- znode = zslInsert(zs->zsl,score,node->ele);
- /* We reused the node->ele SDS string, free the node now
- * since zslInsert created a new one. */
- node->ele = NULL;
- zslFreeNode(node);
+ znode = zslUpdateScore(zs->zsl,curscore,ele,score);
/* Note that we did not removed the original element from
* the hash table representing the sorted set, so we just
* update the score. */
@@ -1591,7 +1637,7 @@ reply_to_client:
if (processed)
addReplyDouble(c,score);
else
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else { /* ZADD. */
addReplyLongLong(c,ch ? added+updated : added);
}
@@ -1878,7 +1924,7 @@ void zuiClearIterator(zsetopsrc *op) {
}
}
-int zuiLength(zsetopsrc *op) {
+unsigned long zuiLength(zsetopsrc *op) {
if (op->subject == NULL)
return 0;
@@ -2085,7 +2131,11 @@ int zuiFind(zsetopsrc *op, zsetopval *val, double *score) {
}
int zuiCompareByCardinality(const void *s1, const void *s2) {
- return zuiLength((zsetopsrc*)s1) - zuiLength((zsetopsrc*)s2);
+ unsigned long first = zuiLength((zsetopsrc*)s1);
+ unsigned long second = zuiLength((zsetopsrc*)s2);
+ if (first > second) return 1;
+ if (first < second) return -1;
+ return 0;
}
#define REDIS_AGGR_SUM 1
@@ -2129,7 +2179,7 @@ void zunionInterGenericCommand(client *c, robj *dstkey, int op) {
zsetopsrc *src;
zsetopval zval;
sds tmp;
- unsigned int maxelelen = 0;
+ size_t maxelelen = 0;
robj *dstobj;
zset *dstzset;
zskiplistNode *znode;
@@ -2363,8 +2413,8 @@ void zrangeGenericCommand(client *c, int reverse) {
int withscores = 0;
long start;
long end;
- int llen;
- int rangelen;
+ long llen;
+ long rangelen;
if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != C_OK) ||
(getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != C_OK)) return;
@@ -2376,7 +2426,7 @@ void zrangeGenericCommand(client *c, int reverse) {
return;
}
- if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL
+ if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL
|| checkType(c,zobj,OBJ_ZSET)) return;
/* Sanitize indexes. */
@@ -2388,14 +2438,19 @@ void zrangeGenericCommand(client *c, int reverse) {
/* Invariant: start >= 0, so this test will be true when end < 0.
* The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
- addReply(c,shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
return;
}
if (end >= llen) end = llen-1;
rangelen = (end-start)+1;
- /* Return the result in form of a multi-bulk reply */
- addReplyMultiBulkLen(c, withscores ? (rangelen*2) : rangelen);
+ /* Return the result in form of a multi-bulk reply. RESP3 clients
+ * will receive sub arrays with score->element, while RESP2 returned
+ * a flat array. */
+ if (withscores && c->resp == 2)
+ addReplyArrayLen(c, rangelen*2);
+ else
+ addReplyArrayLen(c, rangelen);
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
unsigned char *zl = zobj->ptr;
@@ -2415,13 +2470,13 @@ void zrangeGenericCommand(client *c, int reverse) {
while (rangelen--) {
serverAssertWithInfo(c,zobj,eptr != NULL && sptr != NULL);
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
+
+ if (withscores && c->resp > 2) addReplyArrayLen(c,2);
if (vstr == NULL)
addReplyBulkLongLong(c,vlong);
else
addReplyBulkCBuffer(c,vstr,vlen);
-
- if (withscores)
- addReplyDouble(c,zzlGetScore(sptr));
+ if (withscores) addReplyDouble(c,zzlGetScore(sptr));
if (reverse)
zzlPrev(zl,&eptr,&sptr);
@@ -2449,9 +2504,9 @@ void zrangeGenericCommand(client *c, int reverse) {
while(rangelen--) {
serverAssertWithInfo(c,zobj,ln != NULL);
ele = ln->ele;
+ if (withscores && c->resp > 2) addReplyArrayLen(c,2);
addReplyBulkCBuffer(c,ele,sdslen(ele));
- if (withscores)
- addReplyDouble(c,ln->score);
+ if (withscores) addReplyDouble(c,ln->score);
ln = reverse ? ln->backward : ln->level[0].forward;
}
} else {
@@ -2519,7 +2574,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
}
/* Ok, lookup the key and get the range */
- if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL ||
+ if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
checkType(c,zobj,OBJ_ZSET)) return;
if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
@@ -2539,7 +2594,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (eptr == NULL) {
- addReply(c, shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
return;
}
@@ -2550,7 +2605,7 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
@@ -2572,19 +2627,18 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
if (!zslValueLteMax(score,&range)) break;
}
- /* We know the element exists, so ziplistGet should always succeed */
+ /* We know the element exists, so ziplistGet should always
+ * succeed */
serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
rangelen++;
+ if (withscores && c->resp > 2) addReplyArrayLen(c,2);
if (vstr == NULL) {
addReplyBulkLongLong(c,vlong);
} else {
addReplyBulkCBuffer(c,vstr,vlen);
}
-
- if (withscores) {
- addReplyDouble(c,score);
- }
+ if (withscores) addReplyDouble(c,score);
/* Move to next node */
if (reverse) {
@@ -2607,14 +2661,14 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (ln == NULL) {
- addReply(c, shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
return;
}
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
@@ -2635,11 +2689,9 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
}
rangelen++;
+ if (withscores && c->resp > 2) addReplyArrayLen(c,2);
addReplyBulkCBuffer(c,ln->ele,sdslen(ln->ele));
-
- if (withscores) {
- addReplyDouble(c,ln->score);
- }
+ if (withscores) addReplyDouble(c,ln->score);
/* Move to next node */
if (reverse) {
@@ -2652,11 +2704,8 @@ void genericZrangebyscoreCommand(client *c, int reverse) {
serverPanic("Unknown sorted set encoding");
}
- if (withscores) {
- rangelen *= 2;
- }
-
- setDeferredMultiBulkLength(c, replylen, rangelen);
+ if (withscores && c->resp == 2) rangelen *= 2;
+ setDeferredArrayLen(c, replylen, rangelen);
}
void zrangebyscoreCommand(client *c) {
@@ -2671,7 +2720,7 @@ void zcountCommand(client *c) {
robj *key = c->argv[1];
robj *zobj;
zrangespec range;
- int count = 0;
+ unsigned long count = 0;
/* Parse the range arguments */
if (zslParseRange(c->argv[2],c->argv[3],&range) != C_OK) {
@@ -2748,7 +2797,7 @@ void zlexcountCommand(client *c) {
robj *key = c->argv[1];
robj *zobj;
zlexrangespec range;
- int count = 0;
+ unsigned long count = 0;
/* Parse the range arguments */
if (zslParseLexRange(c->argv[2],c->argv[3],&range) != C_OK) {
@@ -2856,7 +2905,10 @@ void genericZrangebylexCommand(client *c, int reverse) {
while (remaining) {
if (remaining >= 3 && !strcasecmp(c->argv[pos]->ptr,"limit")) {
if ((getLongFromObjectOrReply(c, c->argv[pos+1], &offset, NULL) != C_OK) ||
- (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) return;
+ (getLongFromObjectOrReply(c, c->argv[pos+2], &limit, NULL) != C_OK)) {
+ zslFreeLexRange(&range);
+ return;
+ }
pos += 3; remaining -= 3;
} else {
zslFreeLexRange(&range);
@@ -2867,7 +2919,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
}
/* Ok, lookup the key and get the range */
- if ((zobj = lookupKeyReadOrReply(c,key,shared.emptymultibulk)) == NULL ||
+ if ((zobj = lookupKeyReadOrReply(c,key,shared.emptyarray)) == NULL ||
checkType(c,zobj,OBJ_ZSET))
{
zslFreeLexRange(&range);
@@ -2890,7 +2942,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (eptr == NULL) {
- addReply(c, shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
zslFreeLexRange(&range);
return;
}
@@ -2902,7 +2954,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
@@ -2954,7 +3006,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* No "first" element in the specified interval. */
if (ln == NULL) {
- addReply(c, shared.emptymultibulk);
+ addReply(c,shared.emptyarray);
zslFreeLexRange(&range);
return;
}
@@ -2962,7 +3014,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
/* We don't know in advance how many matching elements there are in the
* list, so we push this object that will represent the multi-bulk
* length in the output buffer, and will "fix" it later */
- replylen = addDeferredMultiBulkLength(c);
+ replylen = addReplyDeferredLen(c);
/* If there is an offset, just traverse the number of elements without
* checking the score because that is done in the next loop. */
@@ -2997,7 +3049,7 @@ void genericZrangebylexCommand(client *c, int reverse) {
}
zslFreeLexRange(&range);
- setDeferredMultiBulkLength(c, replylen, rangelen);
+ setDeferredArrayLen(c, replylen, rangelen);
}
void zrangebylexCommand(client *c) {
@@ -3023,11 +3075,11 @@ void zscoreCommand(client *c) {
robj *zobj;
double score;
- if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
+ if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
checkType(c,zobj,OBJ_ZSET)) return;
if (zsetScore(zobj,c->argv[2]->ptr,&score) == C_ERR) {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
} else {
addReplyDouble(c,score);
}
@@ -3039,7 +3091,7 @@ void zrankGenericCommand(client *c, int reverse) {
robj *zobj;
long rank;
- if ((zobj = lookupKeyReadOrReply(c,key,shared.nullbulk)) == NULL ||
+ if ((zobj = lookupKeyReadOrReply(c,key,shared.null[c->resp])) == NULL ||
checkType(c,zobj,OBJ_ZSET)) return;
serverAssertWithInfo(c,ele,sdsEncodedObject(ele));
@@ -3047,7 +3099,7 @@ void zrankGenericCommand(client *c, int reverse) {
if (rank >= 0) {
addReplyLongLong(c,rank);
} else {
- addReply(c,shared.nullbulk);
+ addReplyNull(c);
}
}
@@ -3068,3 +3120,186 @@ void zscanCommand(client *c) {
checkType(c,o,OBJ_ZSET)) return;
scanGenericCommand(c,o,cursor);
}
+
+/* This command implements the generic zpop operation, used by:
+ * ZPOPMIN, ZPOPMAX, BZPOPMIN and BZPOPMAX. This function is also used
+ * inside blocked.c in the unblocking stage of BZPOPMIN and BZPOPMAX.
+ *
+ * If 'emitkey' is true also the key name is emitted, useful for the blocking
+ * behavior of BZPOP[MIN|MAX], since we can block into multiple keys.
+ *
+ * The synchronous version instead does not need to emit the key, but may
+ * use the 'count' argument to return multiple items if available. */
+void genericZpopCommand(client *c, robj **keyv, int keyc, int where, int emitkey, robj *countarg) {
+ int idx;
+ robj *key = NULL;
+ robj *zobj = NULL;
+ sds ele;
+ double score;
+ long count = 1;
+
+ /* If a count argument as passed, parse it or return an error. */
+ if (countarg) {
+ if (getLongFromObjectOrReply(c,countarg,&count,NULL) != C_OK)
+ return;
+ if (count <= 0) {
+ addReply(c,shared.emptyarray);
+ return;
+ }
+ }
+
+ /* Check type and break on the first error, otherwise identify candidate. */
+ idx = 0;
+ while (idx < keyc) {
+ key = keyv[idx++];
+ zobj = lookupKeyWrite(c->db,key);
+ if (!zobj) continue;
+ if (checkType(c,zobj,OBJ_ZSET)) return;
+ break;
+ }
+
+ /* No candidate for zpopping, return empty. */
+ if (!zobj) {
+ addReply(c,shared.emptyarray);
+ return;
+ }
+
+ void *arraylen_ptr = addReplyDeferredLen(c);
+ long arraylen = 0;
+
+ /* We emit the key only for the blocking variant. */
+ if (emitkey) addReplyBulk(c,key);
+
+ /* Remove the element. */
+ do {
+ if (zobj->encoding == OBJ_ENCODING_ZIPLIST) {
+ unsigned char *zl = zobj->ptr;
+ unsigned char *eptr, *sptr;
+ unsigned char *vstr;
+ unsigned int vlen;
+ long long vlong;
+
+ /* Get the first or last element in the sorted set. */
+ eptr = ziplistIndex(zl,where == ZSET_MAX ? -2 : 0);
+ serverAssertWithInfo(c,zobj,eptr != NULL);
+ serverAssertWithInfo(c,zobj,ziplistGet(eptr,&vstr,&vlen,&vlong));
+ if (vstr == NULL)
+ ele = sdsfromlonglong(vlong);
+ else
+ ele = sdsnewlen(vstr,vlen);
+
+ /* Get the score. */
+ sptr = ziplistNext(zl,eptr);
+ serverAssertWithInfo(c,zobj,sptr != NULL);
+ score = zzlGetScore(sptr);
+ } else if (zobj->encoding == OBJ_ENCODING_SKIPLIST) {
+ zset *zs = zobj->ptr;
+ zskiplist *zsl = zs->zsl;
+ zskiplistNode *zln;
+
+ /* Get the first or last element in the sorted set. */
+ zln = (where == ZSET_MAX ? zsl->tail :
+ zsl->header->level[0].forward);
+
+ /* There must be an element in the sorted set. */
+ serverAssertWithInfo(c,zobj,zln != NULL);
+ ele = sdsdup(zln->ele);
+ score = zln->score;
+ } else {
+ serverPanic("Unknown sorted set encoding");
+ }
+
+ serverAssertWithInfo(c,zobj,zsetDel(zobj,ele));
+ server.dirty++;
+
+ if (arraylen == 0) { /* Do this only for the first iteration. */
+ char *events[2] = {"zpopmin","zpopmax"};
+ notifyKeyspaceEvent(NOTIFY_ZSET,events[where],key,c->db->id);
+ signalModifiedKey(c->db,key);
+ }
+
+ addReplyBulkCBuffer(c,ele,sdslen(ele));
+ addReplyDouble(c,score);
+ sdsfree(ele);
+ arraylen += 2;
+
+ /* Remove the key, if indeed needed. */
+ if (zsetLength(zobj) == 0) {
+ dbDelete(c->db,key);
+ notifyKeyspaceEvent(NOTIFY_GENERIC,"del",key,c->db->id);
+ break;
+ }
+ } while(--count);
+
+ setDeferredArrayLen(c,arraylen_ptr,arraylen + (emitkey != 0));
+}
+
+/* ZPOPMIN key [<count>] */
+void zpopminCommand(client *c) {
+ if (c->argc > 3) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ genericZpopCommand(c,&c->argv[1],1,ZSET_MIN,0,
+ c->argc == 3 ? c->argv[2] : NULL);
+}
+
+/* ZMAXPOP key [<count>] */
+void zpopmaxCommand(client *c) {
+ if (c->argc > 3) {
+ addReply(c,shared.syntaxerr);
+ return;
+ }
+ genericZpopCommand(c,&c->argv[1],1,ZSET_MAX,0,
+ c->argc == 3 ? c->argv[2] : NULL);
+}
+
+/* BZPOPMIN / BZPOPMAX actual implementation. */
+void blockingGenericZpopCommand(client *c, int where) {
+ robj *o;
+ mstime_t timeout;
+ int j;
+
+ if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout,UNIT_SECONDS)
+ != C_OK) return;
+
+ for (j = 1; j < c->argc-1; j++) {
+ o = lookupKeyWrite(c->db,c->argv[j]);
+ if (o != NULL) {
+ if (o->type != OBJ_ZSET) {
+ addReply(c,shared.wrongtypeerr);
+ return;
+ } else {
+ if (zsetLength(o) != 0) {
+ /* Non empty zset, this is like a normal ZPOP[MIN|MAX]. */
+ genericZpopCommand(c,&c->argv[j],1,where,1,NULL);
+ /* Replicate it as an ZPOP[MIN|MAX] instead of BZPOP[MIN|MAX]. */
+ rewriteClientCommandVector(c,2,
+ where == ZSET_MAX ? shared.zpopmax : shared.zpopmin,
+ c->argv[j]);
+ return;
+ }
+ }
+ }
+ }
+
+ /* If we are inside a MULTI/EXEC and the zset is empty the only thing
+ * we can do is treating it as a timeout (even with timeout 0). */
+ if (c->flags & CLIENT_MULTI) {
+ addReplyNullArray(c);
+ return;
+ }
+
+ /* If the keys do not exist we must block */
+ blockForKeys(c,BLOCKED_ZSET,c->argv + 1,c->argc - 2,timeout,NULL,NULL);
+}
+
+// BZPOPMIN key [key ...] timeout
+void bzpopminCommand(client *c) {
+ blockingGenericZpopCommand(c,ZSET_MIN);
+}
+
+// BZPOPMAX key [key ...] timeout
+void bzpopmaxCommand(client *c) {
+ blockingGenericZpopCommand(c,ZSET_MAX);
+}
diff --git a/src/tls.c b/src/tls.c
new file mode 100644
index 000000000..5fac6902b
--- /dev/null
+++ b/src/tls.c
@@ -0,0 +1,808 @@
+/*
+ * Copyright (c) 2019, Redis Labs
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+#include "server.h"
+#include "connhelpers.h"
+#include "adlist.h"
+
+#ifdef USE_OPENSSL
+
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#define REDIS_TLS_PROTO_TLSv1 (1<<0)
+#define REDIS_TLS_PROTO_TLSv1_1 (1<<1)
+#define REDIS_TLS_PROTO_TLSv1_2 (1<<2)
+#define REDIS_TLS_PROTO_TLSv1_3 (1<<3)
+
+/* Use safe defaults */
+#ifdef TLS1_3_VERSION
+#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2|REDIS_TLS_PROTO_TLSv1_3)
+#else
+#define REDIS_TLS_PROTO_DEFAULT (REDIS_TLS_PROTO_TLSv1_2)
+#endif
+
+extern ConnectionType CT_Socket;
+
+SSL_CTX *redis_tls_ctx;
+
+static int parseProtocolsConfig(const char *str) {
+ int i, count = 0;
+ int protocols = 0;
+
+ if (!str) return REDIS_TLS_PROTO_DEFAULT;
+ sds *tokens = sdssplitlen(str, strlen(str), " ", 1, &count);
+
+ if (!tokens) {
+ serverLog(LL_WARNING, "Invalid tls-protocols configuration string");
+ return -1;
+ }
+ for (i = 0; i < count; i++) {
+ if (!strcasecmp(tokens[i], "tlsv1")) protocols |= REDIS_TLS_PROTO_TLSv1;
+ else if (!strcasecmp(tokens[i], "tlsv1.1")) protocols |= REDIS_TLS_PROTO_TLSv1_1;
+ else if (!strcasecmp(tokens[i], "tlsv1.2")) protocols |= REDIS_TLS_PROTO_TLSv1_2;
+ else if (!strcasecmp(tokens[i], "tlsv1.3")) {
+#ifdef TLS1_3_VERSION
+ protocols |= REDIS_TLS_PROTO_TLSv1_3;
+#else
+ serverLog(LL_WARNING, "TLSv1.3 is specified in tls-protocols but not supported by OpenSSL.");
+ protocols = -1;
+ break;
+#endif
+ } else {
+ serverLog(LL_WARNING, "Invalid tls-protocols specified. "
+ "Use a combination of 'TLSv1', 'TLSv1.1', 'TLSv1.2' and 'TLSv1.3'.");
+ protocols = -1;
+ break;
+ }
+ }
+ sdsfreesplitres(tokens, count);
+
+ return protocols;
+}
+
+/* list of connections with pending data already read from the socket, but not
+ * served to the reader yet. */
+static list *pending_list = NULL;
+
+void tlsInit(void) {
+ ERR_load_crypto_strings();
+ SSL_load_error_strings();
+ SSL_library_init();
+
+ if (!RAND_poll()) {
+ serverLog(LL_WARNING, "OpenSSL: Failed to seed random number generator.");
+ }
+
+ pending_list = listCreate();
+
+ /* Server configuration */
+ server.tls_auth_clients = 1; /* Secure by default */
+}
+
+/* Attempt to configure/reconfigure TLS. This operation is atomic and will
+ * leave the SSL_CTX unchanged if fails.
+ */
+int tlsConfigure(redisTLSContextConfig *ctx_config) {
+ char errbuf[256];
+ SSL_CTX *ctx = NULL;
+
+ if (!ctx_config->cert_file) {
+ serverLog(LL_WARNING, "No tls-cert-file configured!");
+ goto error;
+ }
+
+ if (!ctx_config->key_file) {
+ serverLog(LL_WARNING, "No tls-key-file configured!");
+ goto error;
+ }
+
+ if (!ctx_config->ca_cert_file && !ctx_config->ca_cert_dir) {
+ serverLog(LL_WARNING, "Either tls-ca-cert-file or tls-ca-cert-dir must be configured!");
+ goto error;
+ }
+
+ ctx = SSL_CTX_new(SSLv23_method());
+
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3);
+ SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
+
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+ SSL_CTX_set_options(ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
+#endif
+
+ int protocols = parseProtocolsConfig(ctx_config->protocols);
+ if (protocols == -1) goto error;
+
+ if (!(protocols & REDIS_TLS_PROTO_TLSv1))
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1);
+ if (!(protocols & REDIS_TLS_PROTO_TLSv1_1))
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_1);
+#ifdef SSL_OP_NO_TLSv1_2
+ if (!(protocols & REDIS_TLS_PROTO_TLSv1_2))
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_2);
+#endif
+#ifdef SSL_OP_NO_TLSv1_3
+ if (!(protocols & REDIS_TLS_PROTO_TLSv1_3))
+ SSL_CTX_set_options(ctx, SSL_OP_NO_TLSv1_3);
+#endif
+
+#ifdef SSL_OP_NO_COMPRESSION
+ SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
+#endif
+
+#ifdef SSL_OP_NO_CLIENT_RENEGOTIATION
+ SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_CLIENT_RENEGOTIATION);
+#endif
+
+ if (ctx_config->prefer_server_ciphers)
+ SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+
+ SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE|SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
+ SSL_CTX_set_ecdh_auto(ctx, 1);
+
+ if (SSL_CTX_use_certificate_file(ctx, ctx_config->cert_file, SSL_FILETYPE_PEM) <= 0) {
+ ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+ serverLog(LL_WARNING, "Failed to load certificate: %s: %s", ctx_config->cert_file, errbuf);
+ goto error;
+ }
+
+ if (SSL_CTX_use_PrivateKey_file(ctx, ctx_config->key_file, SSL_FILETYPE_PEM) <= 0) {
+ ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+ serverLog(LL_WARNING, "Failed to load private key: %s: %s", ctx_config->key_file, errbuf);
+ goto error;
+ }
+
+ if (SSL_CTX_load_verify_locations(ctx, ctx_config->ca_cert_file, ctx_config->ca_cert_dir) <= 0) {
+ ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+ serverLog(LL_WARNING, "Failed to configure CA certificate(s) file/directory: %s", errbuf);
+ goto error;
+ }
+
+ if (ctx_config->dh_params_file) {
+ FILE *dhfile = fopen(ctx_config->dh_params_file, "r");
+ DH *dh = NULL;
+ if (!dhfile) {
+ serverLog(LL_WARNING, "Failed to load %s: %s", ctx_config->dh_params_file, strerror(errno));
+ goto error;
+ }
+
+ dh = PEM_read_DHparams(dhfile, NULL, NULL, NULL);
+ fclose(dhfile);
+ if (!dh) {
+ serverLog(LL_WARNING, "%s: failed to read DH params.", ctx_config->dh_params_file);
+ goto error;
+ }
+
+ if (SSL_CTX_set_tmp_dh(ctx, dh) <= 0) {
+ ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
+ serverLog(LL_WARNING, "Failed to load DH params file: %s: %s", ctx_config->dh_params_file, errbuf);
+ DH_free(dh);
+ goto error;
+ }
+
+ DH_free(dh);
+ }
+
+ if (ctx_config->ciphers && !SSL_CTX_set_cipher_list(ctx, ctx_config->ciphers)) {
+ serverLog(LL_WARNING, "Failed to configure ciphers: %s", ctx_config->ciphers);
+ goto error;
+ }
+
+#ifdef TLS1_3_VERSION
+ if (ctx_config->ciphersuites && !SSL_CTX_set_ciphersuites(ctx, ctx_config->ciphersuites)) {
+ serverLog(LL_WARNING, "Failed to configure ciphersuites: %s", ctx_config->ciphersuites);
+ goto error;
+ }
+#endif
+
+ SSL_CTX_free(redis_tls_ctx);
+ redis_tls_ctx = ctx;
+
+ return C_OK;
+
+error:
+ if (ctx) SSL_CTX_free(ctx);
+ return C_ERR;
+}
+
+#ifdef TLS_DEBUGGING
+#define TLSCONN_DEBUG(fmt, ...) \
+ serverLog(LL_DEBUG, "TLSCONN: " fmt, __VA_ARGS__)
+#else
+#define TLSCONN_DEBUG(fmt, ...)
+#endif
+
+ConnectionType CT_TLS;
+
+/* Normal socket connections have a simple events/handler correlation.
+ *
+ * With TLS connections we need to handle cases where during a logical read
+ * or write operation, the SSL library asks to block for the opposite
+ * socket operation.
+ *
+ * When this happens, we need to do two things:
+ * 1. Make sure we register for the even.
+ * 2. Make sure we know which handler needs to execute when the
+ * event fires. That is, if we notify the caller of a write operation
+ * that it blocks, and SSL asks for a read, we need to trigger the
+ * write handler again on the next read event.
+ *
+ */
+
+typedef enum {
+ WANT_READ = 1,
+ WANT_WRITE
+} WantIOType;
+
+#define TLS_CONN_FLAG_READ_WANT_WRITE (1<<0)
+#define TLS_CONN_FLAG_WRITE_WANT_READ (1<<1)
+#define TLS_CONN_FLAG_FD_SET (1<<2)
+
+typedef struct tls_connection {
+ connection c;
+ int flags;
+ SSL *ssl;
+ char *ssl_error;
+ listNode *pending_list_node;
+} tls_connection;
+
+connection *connCreateTLS(void) {
+ tls_connection *conn = zcalloc(sizeof(tls_connection));
+ conn->c.type = &CT_TLS;
+ conn->c.fd = -1;
+ conn->ssl = SSL_new(redis_tls_ctx);
+ return (connection *) conn;
+}
+
+connection *connCreateAcceptedTLS(int fd, int require_auth) {
+ tls_connection *conn = (tls_connection *) connCreateTLS();
+ conn->c.fd = fd;
+ conn->c.state = CONN_STATE_ACCEPTING;
+
+ if (!require_auth) {
+ /* We still verify certificates if provided, but don't require them.
+ */
+ SSL_set_verify(conn->ssl, SSL_VERIFY_PEER, NULL);
+ }
+
+ SSL_set_fd(conn->ssl, conn->c.fd);
+ SSL_set_accept_state(conn->ssl);
+
+ return (connection *) conn;
+}
+
+static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask);
+
+/* Process the return code received from OpenSSL>
+ * Update the want parameter with expected I/O.
+ * Update the connection's error state if a real error has occured.
+ * Returns an SSL error code, or 0 if no further handling is required.
+ */
+static int handleSSLReturnCode(tls_connection *conn, int ret_value, WantIOType *want) {
+ if (ret_value <= 0) {
+ int ssl_err = SSL_get_error(conn->ssl, ret_value);
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_WRITE:
+ *want = WANT_WRITE;
+ return 0;
+ case SSL_ERROR_WANT_READ:
+ *want = WANT_READ;
+ return 0;
+ case SSL_ERROR_SYSCALL:
+ conn->c.last_errno = errno;
+ if (conn->ssl_error) zfree(conn->ssl_error);
+ conn->ssl_error = errno ? zstrdup(strerror(errno)) : NULL;
+ break;
+ default:
+ /* Error! */
+ conn->c.last_errno = 0;
+ if (conn->ssl_error) zfree(conn->ssl_error);
+ conn->ssl_error = zmalloc(512);
+ ERR_error_string_n(ERR_get_error(), conn->ssl_error, 512);
+ break;
+ }
+
+ return ssl_err;
+ }
+
+ return 0;
+}
+
+void registerSSLEvent(tls_connection *conn, WantIOType want) {
+ int mask = aeGetFileEvents(server.el, conn->c.fd);
+
+ switch (want) {
+ case WANT_READ:
+ if (mask & AE_WRITABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
+ if (!(mask & AE_READABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE,
+ tlsEventHandler, conn);
+ break;
+ case WANT_WRITE:
+ if (mask & AE_READABLE) aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
+ if (!(mask & AE_WRITABLE)) aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE,
+ tlsEventHandler, conn);
+ break;
+ default:
+ serverAssert(0);
+ break;
+ }
+}
+
+void updateSSLEvent(tls_connection *conn) {
+ int mask = aeGetFileEvents(server.el, conn->c.fd);
+ int need_read = conn->c.read_handler || (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ);
+ int need_write = conn->c.write_handler || (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE);
+
+ if (need_read && !(mask & AE_READABLE))
+ aeCreateFileEvent(server.el, conn->c.fd, AE_READABLE, tlsEventHandler, conn);
+ if (!need_read && (mask & AE_READABLE))
+ aeDeleteFileEvent(server.el, conn->c.fd, AE_READABLE);
+
+ if (need_write && !(mask & AE_WRITABLE))
+ aeCreateFileEvent(server.el, conn->c.fd, AE_WRITABLE, tlsEventHandler, conn);
+ if (!need_write && (mask & AE_WRITABLE))
+ aeDeleteFileEvent(server.el, conn->c.fd, AE_WRITABLE);
+}
+
+static void tlsHandleEvent(tls_connection *conn, int mask) {
+ int ret;
+
+ TLSCONN_DEBUG("tlsEventHandler(): fd=%d, state=%d, mask=%d, r=%d, w=%d, flags=%d",
+ fd, conn->c.state, mask, conn->c.read_handler != NULL, conn->c.write_handler != NULL,
+ conn->flags);
+
+ ERR_clear_error();
+
+ switch (conn->c.state) {
+ case CONN_STATE_CONNECTING:
+ if (connGetSocketError((connection *) conn)) {
+ conn->c.last_errno = errno;
+ conn->c.state = CONN_STATE_ERROR;
+ } else {
+ if (!(conn->flags & TLS_CONN_FLAG_FD_SET)) {
+ SSL_set_fd(conn->ssl, conn->c.fd);
+ conn->flags |= TLS_CONN_FLAG_FD_SET;
+ }
+ ret = SSL_connect(conn->ssl);
+ if (ret <= 0) {
+ WantIOType want = 0;
+ if (!handleSSLReturnCode(conn, ret, &want)) {
+ registerSSLEvent(conn, want);
+
+ /* Avoid hitting UpdateSSLEvent, which knows nothing
+ * of what SSL_connect() wants and instead looks at our
+ * R/W handlers.
+ */
+ return;
+ }
+
+ /* If not handled, it's an error */
+ conn->c.state = CONN_STATE_ERROR;
+ } else {
+ conn->c.state = CONN_STATE_CONNECTED;
+ }
+ }
+
+ if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
+ conn->c.conn_handler = NULL;
+ break;
+ case CONN_STATE_ACCEPTING:
+ ret = SSL_accept(conn->ssl);
+ if (ret <= 0) {
+ WantIOType want = 0;
+ if (!handleSSLReturnCode(conn, ret, &want)) {
+ /* Avoid hitting UpdateSSLEvent, which knows nothing
+ * of what SSL_connect() wants and instead looks at our
+ * R/W handlers.
+ */
+ registerSSLEvent(conn, want);
+ return;
+ }
+
+ /* If not handled, it's an error */
+ conn->c.state = CONN_STATE_ERROR;
+ } else {
+ conn->c.state = CONN_STATE_CONNECTED;
+ }
+
+ if (!callHandler((connection *) conn, conn->c.conn_handler)) return;
+ conn->c.conn_handler = NULL;
+ break;
+ case CONN_STATE_CONNECTED:
+ {
+ int call_read = ((mask & AE_READABLE) && conn->c.read_handler) ||
+ ((mask & AE_WRITABLE) && (conn->flags & TLS_CONN_FLAG_READ_WANT_WRITE));
+ int call_write = ((mask & AE_WRITABLE) && conn->c.write_handler) ||
+ ((mask & AE_READABLE) && (conn->flags & TLS_CONN_FLAG_WRITE_WANT_READ));
+
+ /* Normally we execute the readable event first, and the writable
+ * event laster. This is useful as sometimes we may be able
+ * to serve the reply of a query immediately after processing the
+ * query.
+ *
+ * However if WRITE_BARRIER is set in the mask, our application is
+ * asking us to do the reverse: never fire the writable event
+ * after the readable. In such a case, we invert the calls.
+ * This is useful when, for instance, we want to do things
+ * in the beforeSleep() hook, like fsynching a file to disk,
+ * before replying to a client. */
+ int invert = conn->c.flags & CONN_FLAG_WRITE_BARRIER;
+
+ if (!invert && call_read) {
+ conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
+ if (!callHandler((connection *) conn, conn->c.read_handler)) return;
+ }
+
+ /* Fire the writable event. */
+ if (call_write) {
+ conn->flags &= ~TLS_CONN_FLAG_WRITE_WANT_READ;
+ if (!callHandler((connection *) conn, conn->c.write_handler)) return;
+ }
+
+ /* If we have to invert the call, fire the readable event now
+ * after the writable one. */
+ if (invert && call_read) {
+ conn->flags &= ~TLS_CONN_FLAG_READ_WANT_WRITE;
+ if (!callHandler((connection *) conn, conn->c.read_handler)) return;
+ }
+
+ /* If SSL has pending that, already read from the socket, we're at
+ * risk of not calling the read handler again, make sure to add it
+ * to a list of pending connection that should be handled anyway. */
+ if ((mask & AE_READABLE)) {
+ if (SSL_pending(conn->ssl) > 0) {
+ if (!conn->pending_list_node) {
+ listAddNodeTail(pending_list, conn);
+ conn->pending_list_node = listLast(pending_list);
+ }
+ } else if (conn->pending_list_node) {
+ listDelNode(pending_list, conn->pending_list_node);
+ conn->pending_list_node = NULL;
+ }
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ updateSSLEvent(conn);
+}
+
+static void tlsEventHandler(struct aeEventLoop *el, int fd, void *clientData, int mask) {
+ UNUSED(el);
+ UNUSED(fd);
+ tls_connection *conn = clientData;
+ tlsHandleEvent(conn, mask);
+}
+
+static void connTLSClose(connection *conn_) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ if (conn->ssl) {
+ SSL_free(conn->ssl);
+ conn->ssl = NULL;
+ }
+
+ if (conn->ssl_error) {
+ zfree(conn->ssl_error);
+ conn->ssl_error = NULL;
+ }
+
+ if (conn->pending_list_node) {
+ listDelNode(pending_list, conn->pending_list_node);
+ conn->pending_list_node = NULL;
+ }
+
+ CT_Socket.close(conn_);
+}
+
+static int connTLSAccept(connection *_conn, ConnectionCallbackFunc accept_handler) {
+ tls_connection *conn = (tls_connection *) _conn;
+ int ret;
+
+ if (conn->c.state != CONN_STATE_ACCEPTING) return C_ERR;
+ ERR_clear_error();
+
+ /* Try to accept */
+ conn->c.conn_handler = accept_handler;
+ ret = SSL_accept(conn->ssl);
+
+ if (ret <= 0) {
+ WantIOType want = 0;
+ if (!handleSSLReturnCode(conn, ret, &want)) {
+ registerSSLEvent(conn, want); /* We'll fire back */
+ return C_OK;
+ } else {
+ conn->c.state = CONN_STATE_ERROR;
+ return C_ERR;
+ }
+ }
+
+ conn->c.state = CONN_STATE_CONNECTED;
+ if (!callHandler((connection *) conn, conn->c.conn_handler)) return C_OK;
+ conn->c.conn_handler = NULL;
+
+ return C_OK;
+}
+
+static int connTLSConnect(connection *conn_, const char *addr, int port, const char *src_addr, ConnectionCallbackFunc connect_handler) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ if (conn->c.state != CONN_STATE_NONE) return C_ERR;
+ ERR_clear_error();
+
+ /* Initiate Socket connection first */
+ if (CT_Socket.connect(conn_, addr, port, src_addr, connect_handler) == C_ERR) return C_ERR;
+
+ /* Return now, once the socket is connected we'll initiate
+ * TLS connection from the event handler.
+ */
+ return C_OK;
+}
+
+static int connTLSWrite(connection *conn_, const void *data, size_t data_len) {
+ tls_connection *conn = (tls_connection *) conn_;
+ int ret, ssl_err;
+
+ if (conn->c.state != CONN_STATE_CONNECTED) return -1;
+ ERR_clear_error();
+ ret = SSL_write(conn->ssl, data, data_len);
+
+ if (ret <= 0) {
+ WantIOType want = 0;
+ if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
+ if (want == WANT_READ) conn->flags |= TLS_CONN_FLAG_WRITE_WANT_READ;
+ updateSSLEvent(conn);
+ errno = EAGAIN;
+ return -1;
+ } else {
+ if (ssl_err == SSL_ERROR_ZERO_RETURN ||
+ ((ssl_err == SSL_ERROR_SYSCALL && !errno))) {
+ conn->c.state = CONN_STATE_CLOSED;
+ return 0;
+ } else {
+ conn->c.state = CONN_STATE_ERROR;
+ return -1;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int connTLSRead(connection *conn_, void *buf, size_t buf_len) {
+ tls_connection *conn = (tls_connection *) conn_;
+ int ret;
+ int ssl_err;
+
+ if (conn->c.state != CONN_STATE_CONNECTED) return -1;
+ ERR_clear_error();
+ ret = SSL_read(conn->ssl, buf, buf_len);
+ if (ret <= 0) {
+ WantIOType want = 0;
+ if (!(ssl_err = handleSSLReturnCode(conn, ret, &want))) {
+ if (want == WANT_WRITE) conn->flags |= TLS_CONN_FLAG_READ_WANT_WRITE;
+ updateSSLEvent(conn);
+
+ errno = EAGAIN;
+ return -1;
+ } else {
+ if (ssl_err == SSL_ERROR_ZERO_RETURN ||
+ ((ssl_err == SSL_ERROR_SYSCALL) && !errno)) {
+ conn->c.state = CONN_STATE_CLOSED;
+ return 0;
+ } else {
+ conn->c.state = CONN_STATE_ERROR;
+ return -1;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static const char *connTLSGetLastError(connection *conn_) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ if (conn->ssl_error) return conn->ssl_error;
+ return NULL;
+}
+
+int connTLSSetWriteHandler(connection *conn, ConnectionCallbackFunc func, int barrier) {
+ conn->write_handler = func;
+ if (barrier)
+ conn->flags |= CONN_FLAG_WRITE_BARRIER;
+ else
+ conn->flags &= ~CONN_FLAG_WRITE_BARRIER;
+ updateSSLEvent((tls_connection *) conn);
+ return C_OK;
+}
+
+int connTLSSetReadHandler(connection *conn, ConnectionCallbackFunc func) {
+ conn->read_handler = func;
+ updateSSLEvent((tls_connection *) conn);
+ return C_OK;
+}
+
+static void setBlockingTimeout(tls_connection *conn, long long timeout) {
+ anetBlock(NULL, conn->c.fd);
+ anetSendTimeout(NULL, conn->c.fd, timeout);
+ anetRecvTimeout(NULL, conn->c.fd, timeout);
+}
+
+static void unsetBlockingTimeout(tls_connection *conn) {
+ anetNonBlock(NULL, conn->c.fd);
+ anetSendTimeout(NULL, conn->c.fd, 0);
+ anetRecvTimeout(NULL, conn->c.fd, 0);
+}
+
+static int connTLSBlockingConnect(connection *conn_, const char *addr, int port, long long timeout) {
+ tls_connection *conn = (tls_connection *) conn_;
+ int ret;
+
+ if (conn->c.state != CONN_STATE_NONE) return C_ERR;
+
+ /* Initiate socket blocking connect first */
+ if (CT_Socket.blocking_connect(conn_, addr, port, timeout) == C_ERR) return C_ERR;
+
+ /* Initiate TLS connection now. We set up a send/recv timeout on the socket,
+ * which means the specified timeout will not be enforced accurately. */
+ SSL_set_fd(conn->ssl, conn->c.fd);
+ setBlockingTimeout(conn, timeout);
+
+ if ((ret = SSL_connect(conn->ssl)) <= 0) {
+ conn->c.state = CONN_STATE_ERROR;
+ return C_ERR;
+ }
+ unsetBlockingTimeout(conn);
+
+ conn->c.state = CONN_STATE_CONNECTED;
+ return C_OK;
+}
+
+static ssize_t connTLSSyncWrite(connection *conn_, char *ptr, ssize_t size, long long timeout) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ setBlockingTimeout(conn, timeout);
+ SSL_clear_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+ int ret = SSL_write(conn->ssl, ptr, size);
+ SSL_set_mode(conn->ssl, SSL_MODE_ENABLE_PARTIAL_WRITE);
+ unsetBlockingTimeout(conn);
+
+ return ret;
+}
+
+static ssize_t connTLSSyncRead(connection *conn_, char *ptr, ssize_t size, long long timeout) {
+ tls_connection *conn = (tls_connection *) conn_;
+
+ setBlockingTimeout(conn, timeout);
+ int ret = SSL_read(conn->ssl, ptr, size);
+ unsetBlockingTimeout(conn);
+
+ return ret;
+}
+
+static ssize_t connTLSSyncReadLine(connection *conn_, char *ptr, ssize_t size, long long timeout) {
+ tls_connection *conn = (tls_connection *) conn_;
+ ssize_t nread = 0;
+
+ setBlockingTimeout(conn, timeout);
+
+ size--;
+ while(size) {
+ char c;
+
+ if (SSL_read(conn->ssl,&c,1) <= 0) {
+ nread = -1;
+ goto exit;
+ }
+ if (c == '\n') {
+ *ptr = '\0';
+ if (nread && *(ptr-1) == '\r') *(ptr-1) = '\0';
+ goto exit;
+ } else {
+ *ptr++ = c;
+ *ptr = '\0';
+ nread++;
+ }
+ size--;
+ }
+exit:
+ unsetBlockingTimeout(conn);
+ return nread;
+}
+
+ConnectionType CT_TLS = {
+ .ae_handler = tlsEventHandler,
+ .accept = connTLSAccept,
+ .connect = connTLSConnect,
+ .blocking_connect = connTLSBlockingConnect,
+ .read = connTLSRead,
+ .write = connTLSWrite,
+ .close = connTLSClose,
+ .set_write_handler = connTLSSetWriteHandler,
+ .set_read_handler = connTLSSetReadHandler,
+ .get_last_error = connTLSGetLastError,
+ .sync_write = connTLSSyncWrite,
+ .sync_read = connTLSSyncRead,
+ .sync_readline = connTLSSyncReadLine,
+};
+
+int tlsHasPendingData() {
+ if (!pending_list)
+ return 0;
+ return listLength(pending_list) > 0;
+}
+
+void tlsProcessPendingData() {
+ listIter li;
+ listNode *ln;
+
+ listRewind(pending_list,&li);
+ while((ln = listNext(&li))) {
+ tls_connection *conn = listNodeValue(ln);
+ tlsHandleEvent(conn, AE_READABLE);
+ }
+}
+
+#else /* USE_OPENSSL */
+
+void tlsInit(void) {
+}
+
+int tlsConfigure(redisTLSContextConfig *ctx_config) {
+ UNUSED(ctx_config);
+ return C_OK;
+}
+
+connection *connCreateTLS(void) {
+ return NULL;
+}
+
+connection *connCreateAcceptedTLS(int fd, int require_auth) {
+ UNUSED(fd);
+ UNUSED(require_auth);
+
+ return NULL;
+}
+
+int tlsHasPendingData() {
+ return 0;
+}
+
+void tlsProcessPendingData() {
+}
+
+#endif
diff --git a/src/tracking.c b/src/tracking.c
new file mode 100644
index 000000000..f7f0fc755
--- /dev/null
+++ b/src/tracking.c
@@ -0,0 +1,296 @@
+/* tracking.c - Client side caching: keys tracking and invalidation
+ *
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "server.h"
+
+/* The tracking table is constituted by 2^24 radix trees (each tree, and the
+ * table itself, are allocated in a lazy way only when needed) tracking
+ * clients that may have certain keys in their local, client side, cache.
+ *
+ * Keys are grouped into 2^24 slots, in a way similar to Redis Cluster hash
+ * slots, however here the function we use is crc64, taking the least
+ * significant 24 bits of the output.
+ *
+ * When a client enables tracking with "CLIENT TRACKING on", each key served to
+ * the client is hashed to one of such slots, and Redis will remember what
+ * client may have keys about such slot. Later, when a key in a given slot is
+ * modified, all the clients that may have local copies of keys in that slot
+ * will receive an invalidation message. There is no distinction of database
+ * number: a single table is used.
+ *
+ * Clients will normally take frequently requested objects in memory, removing
+ * them when invalidation messages are received. A strategy clients may use is
+ * to just cache objects in a dictionary, associating to each cached object
+ * some incremental epoch, or just a timestamp. When invalidation messages are
+ * received clients may store, in a different table, the timestamp (or epoch)
+ * of the invalidation of such given slot: later when accessing objects, the
+ * eviction of stale objects may be performed in a lazy way by checking if the
+ * cached object timestamp is older than the invalidation timestamp for such
+ * objects.
+ *
+ * The output of the 24 bit hash function is very large (more than 16 million
+ * possible slots), so clients that may want to use less resources may only
+ * use the most significant bits instead of the full 24 bits. */
+#define TRACKING_TABLE_SIZE (1<<24)
+rax **TrackingTable = NULL;
+unsigned long TrackingTableUsedSlots = 0;
+robj *TrackingChannelName;
+
+/* Remove the tracking state from the client 'c'. Note that there is not much
+ * to do for us here, if not to decrement the counter of the clients in
+ * tracking mode, because we just store the ID of the client in the tracking
+ * table, so we'll remove the ID reference in a lazy way. Otherwise when a
+ * client with many entries in the table is removed, it would cost a lot of
+ * time to do the cleanup. */
+void disableTracking(client *c) {
+ if (c->flags & CLIENT_TRACKING) {
+ server.tracking_clients--;
+ c->flags &= ~(CLIENT_TRACKING|CLIENT_TRACKING_BROKEN_REDIR);
+ }
+}
+
+/* Enable the tracking state for the client 'c', and as a side effect allocates
+ * the tracking table if needed. If the 'redirect_to' argument is non zero, the
+ * invalidation messages for this client will be sent to the client ID
+ * specified by the 'redirect_to' argument. Note that if such client will
+ * eventually get freed, we'll send a message to the original client to
+ * inform it of the condition. Multiple clients can redirect the invalidation
+ * messages to the same client ID. */
+void enableTracking(client *c, uint64_t redirect_to) {
+ if (c->flags & CLIENT_TRACKING) return;
+ c->flags |= CLIENT_TRACKING;
+ c->flags &= ~CLIENT_TRACKING_BROKEN_REDIR;
+ c->client_tracking_redirection = redirect_to;
+ server.tracking_clients++;
+ if (TrackingTable == NULL) {
+ TrackingTable = zcalloc(sizeof(rax*) * TRACKING_TABLE_SIZE);
+ TrackingChannelName = createStringObject("__redis__:invalidate",20);
+ }
+}
+
+/* This function is called after the excution of a readonly command in the
+ * case the client 'c' has keys tracking enabled. It will populate the
+ * tracking ivalidation table according to the keys the user fetched, so that
+ * Redis will know what are the clients that should receive an invalidation
+ * message with certain groups of keys are modified. */
+void trackingRememberKeys(client *c) {
+ int numkeys;
+ int *keys = getKeysFromCommand(c->cmd,c->argv,c->argc,&numkeys);
+ if (keys == NULL) return;
+
+ for(int j = 0; j < numkeys; j++) {
+ int idx = keys[j];
+ sds sdskey = c->argv[idx]->ptr;
+ uint64_t hash = crc64(0,
+ (unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
+ if (TrackingTable[hash] == NULL) {
+ TrackingTable[hash] = raxNew();
+ TrackingTableUsedSlots++;
+ }
+ raxTryInsert(TrackingTable[hash],
+ (unsigned char*)&c->id,sizeof(c->id),NULL,NULL);
+ }
+ getKeysFreeResult(keys);
+}
+
+void sendTrackingMessage(client *c, long long hash) {
+ int using_redirection = 0;
+ if (c->client_tracking_redirection) {
+ client *redir = lookupClientByID(c->client_tracking_redirection);
+ if (!redir) {
+ /* We need to signal to the original connection that we
+ * are unable to send invalidation messages to the redirected
+ * connection, because the client no longer exist. */
+ if (c->resp > 2) {
+ addReplyPushLen(c,3);
+ addReplyBulkCBuffer(c,"tracking-redir-broken",21);
+ addReplyLongLong(c,c->client_tracking_redirection);
+ }
+ return;
+ }
+ c = redir;
+ using_redirection = 1;
+ }
+
+ /* Only send such info for clients in RESP version 3 or more. However
+ * if redirection is active, and the connection we redirect to is
+ * in Pub/Sub mode, we can support the feature with RESP 2 as well,
+ * by sending Pub/Sub messages in the __redis__:invalidate channel. */
+ if (c->resp > 2) {
+ addReplyPushLen(c,2);
+ addReplyBulkCBuffer(c,"invalidate",10);
+ addReplyLongLong(c,hash);
+ } else if (using_redirection && c->flags & CLIENT_PUBSUB) {
+ robj *msg = createStringObjectFromLongLong(hash);
+ addReplyPubsubMessage(c,TrackingChannelName,msg);
+ decrRefCount(msg);
+ }
+}
+
+/* Invalidates a caching slot: this is actually the low level implementation
+ * of the API that Redis calls externally, that is trackingInvalidateKey(). */
+void trackingInvalidateSlot(uint64_t slot) {
+ if (TrackingTable == NULL || TrackingTable[slot] == NULL) return;
+
+ raxIterator ri;
+ raxStart(&ri,TrackingTable[slot]);
+ raxSeek(&ri,"^",NULL,0);
+ while(raxNext(&ri)) {
+ uint64_t id;
+ memcpy(&id,ri.key,ri.key_len);
+ client *c = lookupClientByID(id);
+ if (c == NULL || !(c->flags & CLIENT_TRACKING)) continue;
+ sendTrackingMessage(c,slot);
+ }
+ raxStop(&ri);
+
+ /* Free the tracking table: we'll create the radix tree and populate it
+ * again if more keys will be modified in this caching slot. */
+ raxFree(TrackingTable[slot]);
+ TrackingTable[slot] = NULL;
+ TrackingTableUsedSlots--;
+}
+
+/* This function is called from signalModifiedKey() or other places in Redis
+ * when a key changes value. In the context of keys tracking, our task here is
+ * to send a notification to every client that may have keys about such caching
+ * slot. */
+void trackingInvalidateKey(robj *keyobj) {
+ if (TrackingTable == NULL || TrackingTableUsedSlots == 0) return;
+
+ sds sdskey = keyobj->ptr;
+ uint64_t hash = crc64(0,
+ (unsigned char*)sdskey,sdslen(sdskey))&(TRACKING_TABLE_SIZE-1);
+ trackingInvalidateSlot(hash);
+}
+
+/* This function is called when one or all the Redis databases are flushed
+ * (dbid == -1 in case of FLUSHALL). Caching slots are not specific for
+ * each DB but are global: currently what we do is sending a special
+ * notification to clients with tracking enabled, invalidating the caching
+ * slot "-1", which means, "all the keys", in order to avoid flooding clients
+ * with many invalidation messages for all the keys they may hold.
+ *
+ * However trying to flush the tracking table here is very costly:
+ * we need scanning 16 million caching slots in the table to check
+ * if they are used, this introduces a big delay. So what we do is to really
+ * flush the table in the case of FLUSHALL. When a FLUSHDB is called instead
+ * we just send the invalidation message to all the clients, but don't
+ * flush the table: it will slowly get garbage collected as more keys
+ * are modified in the used caching slots. */
+void trackingInvalidateKeysOnFlush(int dbid) {
+ if (server.tracking_clients) {
+ listNode *ln;
+ listIter li;
+ listRewind(server.clients,&li);
+ while ((ln = listNext(&li)) != NULL) {
+ client *c = listNodeValue(ln);
+ if (c->flags & CLIENT_TRACKING) {
+ sendTrackingMessage(c,-1);
+ }
+ }
+ }
+
+ /* In case of FLUSHALL, reclaim all the memory used by tracking. */
+ if (dbid == -1 && TrackingTable) {
+ for (int j = 0; j < TRACKING_TABLE_SIZE && TrackingTableUsedSlots > 0; j++) {
+ if (TrackingTable[j] != NULL) {
+ raxFree(TrackingTable[j]);
+ TrackingTable[j] = NULL;
+ TrackingTableUsedSlots--;
+ }
+ }
+
+ /* If there are no clients with tracking enabled, we can even
+ * reclaim the memory used by the table itself. The code assumes
+ * the table is allocated only if there is at least one client alive
+ * with tracking enabled. */
+ if (server.tracking_clients == 0) {
+ zfree(TrackingTable);
+ TrackingTable = NULL;
+ }
+ }
+}
+
+/* Tracking forces Redis to remember information about which client may have
+ * keys about certian caching slots. In workloads where there are a lot of
+ * reads, but keys are hardly modified, the amount of information we have
+ * to remember server side could be a lot: for each 16 millions of caching
+ * slots we may end with a radix tree containing many entries.
+ *
+ * So Redis allows the user to configure a maximum fill rate for the
+ * invalidation table. This function makes sure that we don't go over the
+ * specified fill rate: if we are over, we can just evict informations about
+ * random caching slots, and send invalidation messages to clients like if
+ * the key was modified. */
+void trackingLimitUsedSlots(void) {
+ static unsigned int timeout_counter = 0;
+
+ if (server.tracking_table_max_fill == 0) return; /* No limits set. */
+ unsigned int max_slots =
+ (TRACKING_TABLE_SIZE/100) * server.tracking_table_max_fill;
+ if (TrackingTableUsedSlots <= max_slots) {
+ timeout_counter = 0;
+ return; /* Limit not reached. */
+ }
+
+ /* We have to invalidate a few slots to reach the limit again. The effort
+ * we do here is proportional to the number of times we entered this
+ * function and found that we are still over the limit. */
+ int effort = 100 * (timeout_counter+1);
+
+ /* Let's start at a random position, and perform linear probing, in order
+ * to improve cache locality. However once we are able to find an used
+ * slot, jump again randomly, in order to avoid creating big holes in the
+ * table (that will make this funciton use more resourced later). */
+ while(effort > 0) {
+ unsigned int idx = rand() % TRACKING_TABLE_SIZE;
+ do {
+ effort--;
+ idx = (idx+1) % TRACKING_TABLE_SIZE;
+ if (TrackingTable[idx] != NULL) {
+ trackingInvalidateSlot(idx);
+ if (TrackingTableUsedSlots <= max_slots) {
+ timeout_counter = 0;
+ return; /* Return ASAP: we are again under the limit. */
+ } else {
+ break; /* Jump to next random position. */
+ }
+ }
+ } while(effort > 0);
+ }
+ timeout_counter++;
+}
+
+/* This is just used in order to access the amount of used slots in the
+ * tracking table. */
+unsigned long long trackingGetUsedSlots(void) {
+ return TrackingTableUsedSlots;
+}
diff --git a/src/util.c b/src/util.c
index 36cbc43d3..783bcf83b 100644
--- a/src/util.c
+++ b/src/util.c
@@ -39,6 +39,7 @@
#include <float.h>
#include <stdint.h>
#include <errno.h>
+#include <time.h>
#include "util.h"
#include "sha1.h"
@@ -47,7 +48,7 @@
int stringmatchlen(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase)
{
- while(patternLen) {
+ while(patternLen && stringLen) {
switch(pattern[0]) {
case '*':
while (pattern[1] == '*') {
@@ -170,6 +171,22 @@ int stringmatch(const char *pattern, const char *string, int nocase) {
return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase);
}
+/* Fuzz stringmatchlen() trying to crash it with bad input. */
+int stringmatchlen_fuzz_test(void) {
+ char str[32];
+ char pat[32];
+ int cycles = 10000000;
+ int total_matches = 0;
+ while(cycles--) {
+ int strlen = rand() % sizeof(str);
+ int patlen = rand() % sizeof(pat);
+ for (int j = 0; j < strlen; j++) str[j] = rand() % 128;
+ for (int j = 0; j < patlen; j++) pat[j] = rand() % 128;
+ total_matches += stringmatchlen(pat, patlen, str, strlen, 0);
+ }
+ return total_matches;
+}
+
/* Convert a string representing an amount of memory into the number of
* bytes, so for instance memtoll("1Gb") will return 1073741824 that is
* (1024*1024*1024).
@@ -346,6 +363,7 @@ int string2ll(const char *s, size_t slen, long long *value) {
int negative = 0;
unsigned long long v;
+ /* A zero length string is not a valid number. */
if (plen == slen)
return 0;
@@ -355,6 +373,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
return 1;
}
+ /* Handle negative numbers: just set a flag and continue like if it
+ * was a positive number. Later convert into negative. */
if (p[0] == '-') {
negative = 1;
p++; plen++;
@@ -368,13 +388,11 @@ int string2ll(const char *s, size_t slen, long long *value) {
if (p[0] >= '1' && p[0] <= '9') {
v = p[0]-'0';
p++; plen++;
- } else if (p[0] == '0' && slen == 1) {
- *value = 0;
- return 1;
} else {
return 0;
}
+ /* Parse all the other digits, checking for overflow at every step. */
while (plen < slen && p[0] >= '0' && p[0] <= '9') {
if (v > (ULLONG_MAX / 10)) /* Overflow. */
return 0;
@@ -391,6 +409,8 @@ int string2ll(const char *s, size_t slen, long long *value) {
if (plen < slen)
return 0;
+ /* Convert to negative if needed, and do the final overflow check when
+ * converting from unsigned long long to long long. */
if (negative) {
if (v > ((unsigned long long)(-(LLONG_MIN+1))+1)) /* Overflow. */
return 0;
@@ -427,7 +447,7 @@ int string2l(const char *s, size_t slen, long *lval) {
* a double: no spaces or other characters before or after the string
* representing the number are accepted. */
int string2ld(const char *s, size_t slen, long double *dp) {
- char buf[256];
+ char buf[MAX_LONG_DOUBLE_CHARS];
long double value;
char *eptr;
@@ -451,7 +471,7 @@ int string2ld(const char *s, size_t slen, long double *dp) {
/* Convert a double to a string representation. Returns the number of bytes
* required. The representation should always be parsable by strtod(3).
* This function does not support human-friendly formatting like ld2string
- * does. It is intented mainly to be used inside t_zset.c when writing scores
+ * does. It is intended mainly to be used inside t_zset.c when writing scores
* into a ziplist representing a sorted set. */
int d2string(char *buf, size_t len, double value) {
if (isnan(value)) {
@@ -536,14 +556,12 @@ int ld2string(char *buf, size_t len, long double value, int humanfriendly) {
return l;
}
-/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a
- * given execution of Redis, so that if you are talking with an instance
- * having run_id == A, and you reconnect and it has run_id == B, you can be
- * sure that it is either a different instance or it was restarted. */
-void getRandomHexChars(char *p, unsigned int len) {
- char *charset = "0123456789abcdef";
- unsigned int j;
-
+/* Get random bytes, attempts to get an initial seed from /dev/urandom and
+ * the uses a one way hash function in counter mode to generate a random
+ * stream. However if /dev/urandom is not available, a weaker seed is used.
+ *
+ * This function is not thread safe, since the state is global. */
+void getRandomBytes(unsigned char *p, size_t len) {
/* Global state. */
static int seed_initialized = 0;
static unsigned char seed[20]; /* The SHA1 seed, from /dev/urandom. */
@@ -555,70 +573,56 @@ void getRandomHexChars(char *p, unsigned int len) {
* function we just need non-colliding strings, there are no
* cryptographic security needs. */
FILE *fp = fopen("/dev/urandom","r");
- if (fp && fread(seed,sizeof(seed),1,fp) == 1)
+ if (fp == NULL || fread(seed,sizeof(seed),1,fp) != 1) {
+ /* Revert to a weaker seed, and in this case reseed again
+ * at every call.*/
+ for (unsigned int j = 0; j < sizeof(seed); j++) {
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ pid_t pid = getpid();
+ seed[j] = tv.tv_sec ^ tv.tv_usec ^ pid ^ (long)fp;
+ }
+ } else {
seed_initialized = 1;
+ }
if (fp) fclose(fp);
}
- if (seed_initialized) {
- while(len) {
- unsigned char digest[20];
- SHA1_CTX ctx;
- unsigned int copylen = len > 20 ? 20 : len;
-
- SHA1Init(&ctx);
- SHA1Update(&ctx, seed, sizeof(seed));
- SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
- SHA1Final(digest, &ctx);
- counter++;
-
- memcpy(p,digest,copylen);
- /* Convert to hex digits. */
- for (j = 0; j < copylen; j++) p[j] = charset[p[j] & 0x0F];
- len -= copylen;
- p += copylen;
- }
- } else {
- /* If we can't read from /dev/urandom, do some reasonable effort
- * in order to create some entropy, since this function is used to
- * generate run_id and cluster instance IDs */
- char *x = p;
- unsigned int l = len;
- struct timeval tv;
- pid_t pid = getpid();
-
- /* Use time and PID to fill the initial array. */
- gettimeofday(&tv,NULL);
- if (l >= sizeof(tv.tv_usec)) {
- memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
- l -= sizeof(tv.tv_usec);
- x += sizeof(tv.tv_usec);
- }
- if (l >= sizeof(tv.tv_sec)) {
- memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
- l -= sizeof(tv.tv_sec);
- x += sizeof(tv.tv_sec);
- }
- if (l >= sizeof(pid)) {
- memcpy(x,&pid,sizeof(pid));
- l -= sizeof(pid);
- x += sizeof(pid);
- }
- /* Finally xor it with rand() output, that was already seeded with
- * time() at startup, and convert to hex digits. */
- for (j = 0; j < len; j++) {
- p[j] ^= rand();
- p[j] = charset[p[j] & 0x0F];
- }
+ while(len) {
+ unsigned char digest[20];
+ SHA1_CTX ctx;
+ unsigned int copylen = len > 20 ? 20 : len;
+
+ SHA1Init(&ctx);
+ SHA1Update(&ctx, seed, sizeof(seed));
+ SHA1Update(&ctx, (unsigned char*)&counter,sizeof(counter));
+ SHA1Final(digest, &ctx);
+ counter++;
+
+ memcpy(p,digest,copylen);
+ len -= copylen;
+ p += copylen;
}
}
+/* Generate the Redis "Run ID", a SHA1-sized random number that identifies a
+ * given execution of Redis, so that if you are talking with an instance
+ * having run_id == A, and you reconnect and it has run_id == B, you can be
+ * sure that it is either a different instance or it was restarted. */
+void getRandomHexChars(char *p, size_t len) {
+ char *charset = "0123456789abcdef";
+ size_t j;
+
+ getRandomBytes((unsigned char*)p,len);
+ for (j = 0; j < len; j++) p[j] = charset[p[j] & 0x0F];
+}
+
/* Given the filename, return the absolute path as an SDS string, or NULL
* if it fails for some reason. Note that "filename" may be an absolute path
* already, this will be detected and handled correctly.
*
* The function does not try to normalize everything, but only the obvious
- * case of one or more "../" appearning at the start of "filename"
+ * case of one or more "../" appearing at the start of "filename"
* relative path. */
sds getAbsolutePath(char *filename) {
char cwd[1024];
@@ -665,6 +669,24 @@ sds getAbsolutePath(char *filename) {
return abspath;
}
+/*
+ * Gets the proper timezone in a more portable fashion
+ * i.e timezone variables are linux specific.
+ */
+
+unsigned long getTimeZone(void) {
+#ifdef __linux__
+ return timezone;
+#else
+ struct timeval tv;
+ struct timezone tz;
+
+ gettimeofday(&tv, &tz);
+
+ return tz.tz_minuteswest * 60UL;
+#endif
+}
+
/* Return true if the specified path is just a file basename without any
* relative or absolute path. This function just checks that no / or \
* character exists inside the specified path, that's enough in the
diff --git a/src/util.h b/src/util.h
index 91acde047..b6c01aa59 100644
--- a/src/util.h
+++ b/src/util.h
@@ -40,6 +40,7 @@
int stringmatchlen(const char *p, int plen, const char *s, int slen, int nocase);
int stringmatch(const char *p, const char *s, int nocase);
+int stringmatchlen_fuzz_test(void);
long long memtoll(const char *p, int *err);
uint32_t digits10(uint64_t v);
uint32_t sdigits10(int64_t v);
@@ -50,6 +51,7 @@ int string2ld(const char *s, size_t slen, long double *dp);
int d2string(char *buf, size_t len, double value);
int ld2string(char *buf, size_t len, long double value, int humanfriendly);
sds getAbsolutePath(char *filename);
+unsigned long getTimeZone(void);
int pathIsBaseName(char *path);
#ifdef REDIS_TEST
diff --git a/src/ziplist.c b/src/ziplist.c
index ea27db7fb..ef40d6aa2 100644
--- a/src/ziplist.c
+++ b/src/ziplist.c
@@ -27,7 +27,7 @@
* traversal.
*
* <uint16_t zllen> is the number of entries. When there are more than
- * 2^16-2 entires, this value is set to 2^16-1 and we need to traverse the
+ * 2^16-2 entries, this value is set to 2^16-1 and we need to traverse the
* entire list to know how many items it holds.
*
* <uint8_t zlend> is a special entry representing the end of the ziplist.
@@ -256,7 +256,7 @@
#define ZIPLIST_ENTRY_END(zl) ((zl)+intrev32ifbe(ZIPLIST_BYTES(zl))-1)
/* Increment the number of items field in the ziplist header. Note that this
- * macro should never overflow the unsigned 16 bit integer, since entires are
+ * macro should never overflow the unsigned 16 bit integer, since entries are
* always pushed one at a time. When UINT16_MAX is reached we want the count
* to stay there to signal that a full scan is needed to get the number of
* items inside the ziplist. */
@@ -269,7 +269,7 @@
* Note that this is not how the data is actually encoded, is just what we
* get filled by a function in order to operate more easily. */
typedef struct zlentry {
- unsigned int prevrawlensize; /* Bytes used to encode the previos entry len*/
+ unsigned int prevrawlensize; /* Bytes used to encode the previous entry len*/
unsigned int prevrawlen; /* Previous entry len. */
unsigned int lensize; /* Bytes used to encode this entry type/len.
For example strings have a 1, 2 or 5 bytes
@@ -431,7 +431,7 @@ unsigned int zipStorePrevEntryLength(unsigned char *p, unsigned int len) {
/* Return the length of the previous element, and the number of bytes that
* are used in order to encode the previous element length.
* 'ptr' must point to the prevlen prefix of an entry (that encodes the
- * length of the previos entry in order to navigate the elements backward).
+ * length of the previous entry in order to navigate the elements backward).
* The length of the previous entry is stored in 'prevlen', the number of
* bytes needed to encode the previous entry length are stored in
* 'prevlensize'. */
@@ -576,7 +576,7 @@ void zipEntry(unsigned char *p, zlentry *e) {
/* Create a new empty ziplist. */
unsigned char *ziplistNew(void) {
- unsigned int bytes = ZIPLIST_HEADER_SIZE+1;
+ unsigned int bytes = ZIPLIST_HEADER_SIZE+ZIPLIST_END_SIZE;
unsigned char *zl = zmalloc(bytes);
ZIPLIST_BYTES(zl) = intrev32ifbe(bytes);
ZIPLIST_TAIL_OFFSET(zl) = intrev32ifbe(ZIPLIST_HEADER_SIZE);
diff --git a/src/zmalloc.c b/src/zmalloc.c
index 094dd80fa..e02267fc9 100644
--- a/src/zmalloc.c
+++ b/src/zmalloc.c
@@ -30,6 +30,7 @@
#include <stdio.h>
#include <stdlib.h>
+#include <stdint.h>
/* This function provide us access to the original libc free(). This is useful
* for instance to free results obtained by backtrace_symbols(). We need
@@ -147,6 +148,10 @@ void *zrealloc(void *ptr, size_t size) {
size_t oldsize;
void *newptr;
+ if (size == 0 && ptr != NULL) {
+ zfree(ptr);
+ return NULL;
+ }
if (ptr == NULL) return zmalloc(size);
#ifdef HAVE_MALLOC_SIZE
oldsize = zmalloc_size(ptr);
@@ -163,8 +168,8 @@ void *zrealloc(void *ptr, size_t size) {
if (!newptr) zmalloc_oom_handler(size);
*((size_t*)newptr) = size;
- update_zmalloc_stat_free(oldsize);
- update_zmalloc_stat_alloc(size);
+ update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
+ update_zmalloc_stat_alloc(size+PREFIX_SIZE);
return (char*)newptr+PREFIX_SIZE;
#endif
}
@@ -181,6 +186,9 @@ size_t zmalloc_size(void *ptr) {
if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1));
return size+PREFIX_SIZE;
}
+size_t zmalloc_usable(void *ptr) {
+ return zmalloc_size(ptr)-PREFIX_SIZE;
+}
#endif
void zfree(void *ptr) {
@@ -286,6 +294,26 @@ size_t zmalloc_get_rss(void) {
return t_info.resident_size;
}
+#elif defined(__FreeBSD__)
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/user.h>
+#include <unistd.h>
+
+size_t zmalloc_get_rss(void) {
+ struct kinfo_proc info;
+ size_t infolen = sizeof(info);
+ int mib[4];
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROC;
+ mib[2] = KERN_PROC_PID;
+ mib[3] = getpid();
+
+ if (sysctl(mib, 4, &info, &infolen, NULL, 0) == 0)
+ return (size_t)info.ki_rssize;
+
+ return 0L;
+}
#else
size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just
@@ -297,11 +325,69 @@ size_t zmalloc_get_rss(void) {
}
#endif
-/* Fragmentation = RSS / allocated-bytes */
-float zmalloc_get_fragmentation_ratio(size_t rss) {
- return (float)rss/zmalloc_used_memory();
+#if defined(USE_JEMALLOC)
+
+int zmalloc_get_allocator_info(size_t *allocated,
+ size_t *active,
+ size_t *resident) {
+ uint64_t epoch = 1;
+ size_t sz;
+ *allocated = *resident = *active = 0;
+ /* Update the statistics cached by mallctl. */
+ sz = sizeof(epoch);
+ je_mallctl("epoch", &epoch, &sz, &epoch, sz);
+ sz = sizeof(size_t);
+ /* Unlike RSS, this does not include RSS from shared libraries and other non
+ * heap mappings. */
+ je_mallctl("stats.resident", resident, &sz, NULL, 0);
+ /* Unlike resident, this doesn't not include the pages jemalloc reserves
+ * for re-use (purge will clean that). */
+ je_mallctl("stats.active", active, &sz, NULL, 0);
+ /* Unlike zmalloc_used_memory, this matches the stats.resident by taking
+ * into account all allocations done by this process (not only zmalloc). */
+ je_mallctl("stats.allocated", allocated, &sz, NULL, 0);
+ return 1;
+}
+
+void set_jemalloc_bg_thread(int enable) {
+ /* let jemalloc do purging asynchronously, required when there's no traffic
+ * after flushdb */
+ char val = !!enable;
+ je_mallctl("background_thread", NULL, 0, &val, 1);
+}
+
+int jemalloc_purge() {
+ /* return all unused (reserved) pages to the OS */
+ char tmp[32];
+ unsigned narenas = 0;
+ size_t sz = sizeof(unsigned);
+ if (!je_mallctl("arenas.narenas", &narenas, &sz, NULL, 0)) {
+ sprintf(tmp, "arena.%d.purge", narenas);
+ if (!je_mallctl(tmp, NULL, 0, NULL, 0))
+ return 0;
+ }
+ return -1;
}
+#else
+
+int zmalloc_get_allocator_info(size_t *allocated,
+ size_t *active,
+ size_t *resident) {
+ *allocated = *resident = *active = 0;
+ return 1;
+}
+
+void set_jemalloc_bg_thread(int enable) {
+ ((void)(enable));
+}
+
+int jemalloc_purge() {
+ return 0;
+}
+
+#endif
+
/* Get the sum of the specified field (converted form kb to bytes) in
* /proc/self/smaps. The field must be specified with trailing ":" as it
* apperas in the smaps output.
@@ -353,7 +439,7 @@ size_t zmalloc_get_private_dirty(long pid) {
}
/* Returns the size of physical memory (RAM) in bytes.
- * It looks ugly, but this is the cleanest way to achive cross platform results.
+ * It looks ugly, but this is the cleanest way to achieve cross platform results.
* Cleaned up from:
*
* http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
@@ -392,7 +478,7 @@ size_t zmalloc_get_memory_size(void) {
mib[0] = CTL_HW;
#if defined(HW_REALMEM)
mib[1] = HW_REALMEM; /* FreeBSD. ----------------- */
-#elif defined(HW_PYSMEM)
+#elif defined(HW_PHYSMEM)
mib[1] = HW_PHYSMEM; /* Others. ------------------ */
#endif
unsigned int size = 0; /* 32-bit */
@@ -408,4 +494,20 @@ size_t zmalloc_get_memory_size(void) {
#endif
}
-
+#ifdef REDIS_TEST
+#define UNUSED(x) ((void)(x))
+int zmalloc_test(int argc, char **argv) {
+ void *ptr;
+
+ UNUSED(argc);
+ UNUSED(argv);
+ printf("Initial used memory: %zu\n", zmalloc_used_memory());
+ ptr = zmalloc(123);
+ printf("Allocated 123 bytes; used: %zu\n", zmalloc_used_memory());
+ ptr = zrealloc(ptr, 456);
+ printf("Reallocated to 456 bytes; used: %zu\n", zmalloc_used_memory());
+ zfree(ptr);
+ printf("Freed pointer; used: %zu\n", zmalloc_used_memory());
+ return 0;
+}
+#endif
diff --git a/src/zmalloc.h b/src/zmalloc.h
index 64f2f36aa..b136a910d 100644
--- a/src/zmalloc.h
+++ b/src/zmalloc.h
@@ -63,6 +63,11 @@
#ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
+#ifdef __GLIBC__
+#include <malloc.h>
+#define HAVE_MALLOC_SIZE 1
+#define zmalloc_size(p) malloc_usable_size(p)
+#endif
#endif
/* We can enable the Redis defrag capabilities only if we are using Jemalloc
@@ -79,8 +84,10 @@ void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
-float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);
+int zmalloc_get_allocator_info(size_t *allocated, size_t *active, size_t *resident);
+void set_jemalloc_bg_thread(int enable);
+int jemalloc_purge();
size_t zmalloc_get_private_dirty(long pid);
size_t zmalloc_get_smap_bytes_by_field(char *field, long pid);
size_t zmalloc_get_memory_size(void);
@@ -93,6 +100,13 @@ void *zmalloc_no_tcache(size_t size);
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
+size_t zmalloc_usable(void *ptr);
+#else
+#define zmalloc_usable(p) zmalloc_size(p)
+#endif
+
+#ifdef REDIS_TEST
+int zmalloc_test(int argc, char **argv);
#endif
#endif /* __ZMALLOC_H */
diff --git a/tests/cluster/run.tcl b/tests/cluster/run.tcl
index 93603ddc9..d9a7d7ee5 100644
--- a/tests/cluster/run.tcl
+++ b/tests/cluster/run.tcl
@@ -8,6 +8,7 @@ source ../instances.tcl
source ../../support/cluster.tcl ; # Redis Cluster client.
set ::instances_count 20 ; # How many instances we use at max.
+set ::tlsdir "../../tls"
proc main {} {
parse_options
diff --git a/tests/cluster/tests/04-resharding.tcl b/tests/cluster/tests/04-resharding.tcl
index 0ccbf717d..33f861dc5 100644
--- a/tests/cluster/tests/04-resharding.tcl
+++ b/tests/cluster/tests/04-resharding.tcl
@@ -4,6 +4,7 @@
# are preseved across iterations.
source "../tests/includes/init-tests.tcl"
+source "../../../tests/support/cli.tcl"
test "Create a 5 nodes cluster" {
create_cluster 5 5
@@ -73,12 +74,13 @@ test "Cluster consistency during live resharding" {
flush stdout
set target [dict get [get_myself [randomInt 5]] id]
set tribpid [lindex [exec \
- ../../../src/redis-trib.rb reshard \
- --from all \
- --to $target \
- --slots 100 \
- --yes \
+ ../../../src/redis-cli --cluster reshard \
127.0.0.1:[get_instance_attrib redis 0 port] \
+ --cluster-from all \
+ --cluster-to $target \
+ --cluster-slots 100 \
+ --cluster-yes \
+ {*}[rediscli_tls_config "../../../tests"] \
| [info nameofexecutable] \
../tests/helpers/onlydots.tcl \
&] 0]
diff --git a/tests/cluster/tests/05-slave-selection.tcl b/tests/cluster/tests/05-slave-selection.tcl
index 6efedce5d..bcb0fa1ea 100644
--- a/tests/cluster/tests/05-slave-selection.tcl
+++ b/tests/cluster/tests/05-slave-selection.tcl
@@ -92,3 +92,80 @@ test "Node #10 should eventually replicate node #5" {
fail "#10 didn't became slave of #5"
}
}
+
+source "../tests/includes/init-tests.tcl"
+
+# Create a cluster with 3 master and 15 slaves, so that we have 5
+# slaves for eatch master.
+test "Create a 3 nodes cluster" {
+ create_cluster 3 15
+}
+
+test "Cluster is up" {
+ assert_cluster_state ok
+}
+
+test "The first master has actually 5 slaves" {
+ assert {[llength [lindex [R 0 role] 2]] == 5}
+}
+
+test {Slaves of #0 are instance #3, #6, #9, #12 and #15 as expected} {
+ set port0 [get_instance_attrib redis 0 port]
+ assert {[lindex [R 3 role] 2] == $port0}
+ assert {[lindex [R 6 role] 2] == $port0}
+ assert {[lindex [R 9 role] 2] == $port0}
+ assert {[lindex [R 12 role] 2] == $port0}
+ assert {[lindex [R 15 role] 2] == $port0}
+}
+
+test {Instance #3, #6, #9, #12 and #15 synced with the master} {
+ wait_for_condition 1000 50 {
+ [RI 3 master_link_status] eq {up} &&
+ [RI 6 master_link_status] eq {up} &&
+ [RI 9 master_link_status] eq {up} &&
+ [RI 12 master_link_status] eq {up} &&
+ [RI 15 master_link_status] eq {up}
+ } else {
+ fail "Instance #3 or #6 or #9 or #12 or #15 master link status is not up"
+ }
+}
+
+proc master_detected {instances} {
+ foreach instance [dict keys $instances] {
+ if {[RI $instance role] eq {master}} {
+ return true
+ }
+ }
+
+ return false
+}
+
+test "New Master down consecutively" {
+ set instances [dict create 0 1 3 1 6 1 9 1 12 1 15 1]
+
+ set loops [expr {[dict size $instances]-1}]
+ for {set i 0} {$i < $loops} {incr i} {
+ set master_id -1
+ foreach instance [dict keys $instances] {
+ if {[RI $instance role] eq {master}} {
+ set master_id $instance
+ break;
+ }
+ }
+
+ if {$master_id eq -1} {
+ fail "no master detected, #loop $i"
+ }
+
+ set instances [dict remove $instances $master_id]
+
+ kill_instance redis $master_id
+ wait_for_condition 1000 50 {
+ [master_detected $instances]
+ } else {
+ failover "No failover detected when master $master_id fails"
+ }
+
+ assert_cluster_state ok
+ }
+}
diff --git a/tests/cluster/tests/12-replica-migration-2.tcl b/tests/cluster/tests/12-replica-migration-2.tcl
index 48ecd1d50..dd18a979a 100644
--- a/tests/cluster/tests/12-replica-migration-2.tcl
+++ b/tests/cluster/tests/12-replica-migration-2.tcl
@@ -5,6 +5,7 @@
# other masters have slaves.
source "../tests/includes/init-tests.tcl"
+source "../../../tests/support/cli.tcl"
# Create a cluster with 5 master and 15 slaves, to make sure there are no
# empty masters and make rebalancing simpler to handle during the test.
@@ -31,9 +32,11 @@ test "Each master should have at least two replicas attached" {
set master0_id [dict get [get_myself 0] id]
test "Resharding all the master #0 slots away from it" {
set output [exec \
- ../../../src/redis-trib.rb rebalance \
- --weight ${master0_id}=0 \
- 127.0.0.1:[get_instance_attrib redis 0 port] >@ stdout]
+ ../../../src/redis-cli --cluster rebalance \
+ 127.0.0.1:[get_instance_attrib redis 0 port] \
+ {*}[rediscli_tls_config "../../../tests"] \
+ --cluster-weight ${master0_id}=0 >@ stdout ]
+
}
test "Master #0 should lose its replicas" {
@@ -49,10 +52,11 @@ test "Resharding back some slot to master #0" {
# new resharding.
after 10000
set output [exec \
- ../../../src/redis-trib.rb rebalance \
- --weight ${master0_id}=.01 \
- --use-empty-masters \
- 127.0.0.1:[get_instance_attrib redis 0 port] >@ stdout]
+ ../../../src/redis-cli --cluster rebalance \
+ 127.0.0.1:[get_instance_attrib redis 0 port] \
+ {*}[rediscli_tls_config "../../../tests"] \
+ --cluster-weight ${master0_id}=.01 \
+ --cluster-use-empty-masters >@ stdout]
}
test "Master #0 should re-acquire one or more replicas" {
diff --git a/tests/helpers/bg_block_op.tcl b/tests/helpers/bg_block_op.tcl
new file mode 100644
index 000000000..c8b323308
--- /dev/null
+++ b/tests/helpers/bg_block_op.tcl
@@ -0,0 +1,54 @@
+source tests/support/redis.tcl
+source tests/support/util.tcl
+
+set ::tlsdir "tests/tls"
+
+# This function sometimes writes sometimes blocking-reads from lists/sorted
+# sets. There are multiple processes like this executing at the same time
+# so that we have some chance to trap some corner condition if there is
+# a regression. For this to happen it is important that we narrow the key
+# space to just a few elements, and balance the operations so that it is
+# unlikely that lists and zsets just get more data without ever causing
+# blocking.
+proc bg_block_op {host port db ops tls} {
+ set r [redis $host $port 0 $tls]
+ $r select $db
+
+ for {set j 0} {$j < $ops} {incr j} {
+
+ # List side
+ set k list_[randomInt 10]
+ set k2 list_[randomInt 10]
+ set v [randomValue]
+
+ randpath {
+ randpath {
+ $r rpush $k $v
+ } {
+ $r lpush $k $v
+ }
+ } {
+ $r blpop $k 2
+ } {
+ $r blpop $k $k2 2
+ }
+
+ # Zset side
+ set k zset_[randomInt 10]
+ set k2 zset_[randomInt 10]
+ set v1 [randomValue]
+ set v2 [randomValue]
+
+ randpath {
+ $r zadd $k [randomInt 10000] $v
+ } {
+ $r zadd $k [randomInt 10000] $v [randomInt 10000] $v2
+ } {
+ $r bzpopmin $k 2
+ } {
+ $r bzpopmax $k 2
+ }
+ }
+}
+
+bg_block_op [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
diff --git a/tests/helpers/bg_complex_data.tcl b/tests/helpers/bg_complex_data.tcl
index dffd7c668..e888748a7 100644
--- a/tests/helpers/bg_complex_data.tcl
+++ b/tests/helpers/bg_complex_data.tcl
@@ -1,10 +1,12 @@
source tests/support/redis.tcl
source tests/support/util.tcl
-proc bg_complex_data {host port db ops} {
- set r [redis $host $port]
+set ::tlsdir "tests/tls"
+
+proc bg_complex_data {host port db ops tls} {
+ set r [redis $host $port 0 $tls]
$r select $db
createComplexDataset $r $ops
}
-bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
+bg_complex_data [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3] [lindex $argv 4]
diff --git a/tests/helpers/gen_write_load.tcl b/tests/helpers/gen_write_load.tcl
index 6d1a34516..fd6aad40c 100644
--- a/tests/helpers/gen_write_load.tcl
+++ b/tests/helpers/gen_write_load.tcl
@@ -1,8 +1,10 @@
source tests/support/redis.tcl
-proc gen_write_load {host port seconds} {
+set ::tlsdir "tests/tls"
+
+proc gen_write_load {host port seconds tls} {
set start_time [clock seconds]
- set r [redis $host $port 1]
+ set r [redis $host $port 0 $tls]
$r select 9
while 1 {
$r set [expr rand()] [expr rand()]
@@ -12,4 +14,4 @@ proc gen_write_load {host port seconds} {
}
}
-gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2]
+gen_write_load [lindex $argv 0] [lindex $argv 1] [lindex $argv 2] [lindex $argv 3]
diff --git a/tests/instances.tcl b/tests/instances.tcl
index 357b34818..0a0cbab12 100644
--- a/tests/instances.tcl
+++ b/tests/instances.tcl
@@ -17,6 +17,7 @@ source ../support/test.tcl
set ::verbose 0
set ::valgrind 0
+set ::tls 0
set ::pause_on_error 0
set ::simulate_error 0
set ::failed 0
@@ -69,7 +70,19 @@ proc spawn_instance {type base_port count {conf {}}} {
# Write the instance config file.
set cfgfile [file join $dirname $type.conf]
set cfg [open $cfgfile w]
- puts $cfg "port $port"
+ if {$::tls} {
+ puts $cfg "tls-port $port"
+ puts $cfg "tls-replication yes"
+ puts $cfg "tls-cluster yes"
+ puts $cfg "port 0"
+ puts $cfg [format "tls-cert-file %s/../../tls/redis.crt" [pwd]]
+ puts $cfg [format "tls-key-file %s/../../tls/redis.key" [pwd]]
+ puts $cfg [format "tls-dh-params-file %s/../../tls/redis.dh" [pwd]]
+ puts $cfg [format "tls-ca-cert-file %s/../../tls/ca.crt" [pwd]]
+ puts $cfg "loglevel debug"
+ } else {
+ puts $cfg "port $port"
+ }
puts $cfg "dir ./$dirname"
puts $cfg "logfile log.txt"
# Add additional config files
@@ -88,7 +101,7 @@ proc spawn_instance {type base_port count {conf {}}} {
}
# Push the instance into the right list
- set link [redis 127.0.0.1 $port]
+ set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1
lappend ::${type}_instances [list \
pid $pid \
@@ -148,6 +161,13 @@ proc parse_options {} {
set ::simulate_error 1
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
+ } elseif {$opt eq {--tls}} {
+ package require tls 1.6
+ ::tls::init \
+ -cafile "$::tlsdir/ca.crt" \
+ -certfile "$::tlsdir/redis.crt" \
+ -keyfile "$::tlsdir/redis.key"
+ set ::tls 1
} elseif {$opt eq "--help"} {
puts "Hello, I'm sentinel.tcl and I run Sentinel unit tests."
puts "\nOptions:"
@@ -492,7 +512,7 @@ proc restart_instance {type id} {
}
# Connect with it with a fresh link
- set link [redis 127.0.0.1 $port]
+ set link [redis 127.0.0.1 $port 0 $::tls]
$link reconnect 1
set_instance_attrib $type $id link $link
diff --git a/tests/integration/aof-race.tcl b/tests/integration/aof-race.tcl
index 207f20739..2991e7962 100644
--- a/tests/integration/aof-race.tcl
+++ b/tests/integration/aof-race.tcl
@@ -1,4 +1,4 @@
-set defaults { appendonly {yes} appendfilename {appendonly.aof} }
+set defaults { appendonly {yes} appendfilename {appendonly.aof} aof-use-rdb-preamble {no} }
set server_path [tmpdir server.aof]
set aof_path "$server_path/appendonly.aof"
@@ -13,8 +13,9 @@ tags {"aof"} {
# cleaned after a child responsible for an AOF rewrite exited. This buffer
# was subsequently appended to the new AOF, resulting in duplicate commands.
start_server_aof [list dir $server_path] {
- set client [redis [srv host] [srv port]]
- set bench [open "|src/redis-benchmark -q -p [srv port] -c 20 -n 20000 incr foo" "r+"]
+ set client [redis [srv host] [srv port] 0 $::tls]
+ set bench [open "|src/redis-benchmark -q -s [srv unixsocket] -c 20 -n 20000 incr foo" "r+"]
+
after 100
# Benchmark should be running by now: start background rewrite
@@ -29,7 +30,7 @@ tags {"aof"} {
# Restart server to replay AOF
start_server_aof [list dir $server_path] {
- set client [redis [srv host] [srv port]]
+ set client [redis [srv host] [srv port] 0 $::tls]
assert_equal 20000 [$client get foo]
}
}
diff --git a/tests/integration/aof.tcl b/tests/integration/aof.tcl
index e397faeeb..2734de7f1 100644
--- a/tests/integration/aof.tcl
+++ b/tests/integration/aof.tcl
@@ -52,7 +52,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv]
}
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 5" {
assert {[$client get foo] eq "5"}
@@ -69,7 +69,7 @@ tags {"aof"} {
assert_equal 1 [is_alive $srv]
}
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
assert {[$client get foo] eq "6"}
@@ -170,7 +170,7 @@ tags {"aof"} {
}
test "Fixed AOF: Keyspace should contain values that were parseable" {
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
@@ -194,7 +194,7 @@ tags {"aof"} {
}
test "AOF+SPOP: Set should have 1 member" {
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
@@ -218,7 +218,7 @@ tags {"aof"} {
}
test "AOF+SPOP: Set should have 1 member" {
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
@@ -241,7 +241,7 @@ tags {"aof"} {
}
test "AOF+EXPIRE: List should be empty" {
- set client [redis [dict get $srv host] [dict get $srv port]]
+ set client [redis [dict get $srv host] [dict get $srv port] 0 $::tls]
wait_for_condition 50 100 {
[catch {$client ping} e] == 0
} else {
@@ -257,4 +257,35 @@ tags {"aof"} {
r expire x -1
}
}
+
+ start_server {overrides {appendonly {yes} appendfilename {appendonly.aof} appendfsync always}} {
+ test {AOF fsync always barrier issue} {
+ set rd [redis_deferring_client]
+ # Set a sleep when aof is flushed, so that we have a chance to look
+ # at the aof size and detect if the response of an incr command
+ # arrives before the data was written (and hopefully fsynced)
+ # We create a big reply, which will hopefully not have room in the
+ # socket buffers, and will install a write handler, then we sleep
+ # a big and issue the incr command, hoping that the last portion of
+ # the output buffer write, and the processing of the incr will happen
+ # in the same event loop cycle.
+ # Since the socket buffers and timing are unpredictable, we fuzz this
+ # test with slightly different sizes and sleeps a few times.
+ for {set i 0} {$i < 10} {incr i} {
+ r debug aof-flush-sleep 0
+ r del x
+ r setrange x [expr {int(rand()*5000000)+10000000}] x
+ r debug aof-flush-sleep 500000
+ set aof [file join [lindex [r config get dir] 1] appendonly.aof]
+ set size1 [file size $aof]
+ $rd get x
+ after [expr {int(rand()*30)}]
+ $rd incr new_value
+ $rd read
+ $rd read
+ set size2 [file size $aof]
+ assert {$size1 != $size2}
+ }
+ }
+ }
}
diff --git a/tests/integration/block-repl.tcl b/tests/integration/block-repl.tcl
new file mode 100644
index 000000000..07eceb228
--- /dev/null
+++ b/tests/integration/block-repl.tcl
@@ -0,0 +1,58 @@
+# Test replication of blocking lists and zset operations.
+# Unlike stream operations such operations are "pop" style, so they consume
+# the list or sorted set, and must be replicated correctly.
+
+proc start_bg_block_op {host port db ops tls} {
+ set tclsh [info nameofexecutable]
+ exec $tclsh tests/helpers/bg_block_op.tcl $host $port $db $ops $tls &
+}
+
+proc stop_bg_block_op {handle} {
+ catch {exec /bin/kill -9 $handle}
+}
+
+start_server {tags {"repl"}} {
+ start_server {} {
+ set master [srv -1 client]
+ set master_host [srv -1 host]
+ set master_port [srv -1 port]
+ set slave [srv 0 client]
+
+ set load_handle0 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
+ set load_handle1 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
+ set load_handle2 [start_bg_block_op $master_host $master_port 9 100000 $::tls]
+
+ test {First server should have role slave after SLAVEOF} {
+ $slave slaveof $master_host $master_port
+ after 1000
+ s 0 role
+ } {slave}
+
+ test {Test replication with blocking lists and sorted sets operations} {
+ after 25000
+ stop_bg_block_op $load_handle0
+ stop_bg_block_op $load_handle1
+ stop_bg_block_op $load_handle2
+ set retry 10
+ while {$retry && ([$master debug digest] ne [$slave debug digest])}\
+ {
+ after 1000
+ incr retry -1
+ }
+
+ if {[$master debug digest] ne [$slave debug digest]} {
+ set csv1 [csvdump r]
+ set csv2 [csvdump {r -1}]
+ set fd [open /tmp/repldump1.txt w]
+ puts -nonewline $fd $csv1
+ close $fd
+ set fd [open /tmp/repldump2.txt w]
+ puts -nonewline $fd $csv2
+ close $fd
+ puts "Master - Replica inconsistency"
+ puts "Run diff -u against /tmp/repldump*.txt for more info"
+ }
+ assert_equal [r debug digest] [r -1 debug digest]
+ }
+ }
+}
diff --git a/tests/integration/psync2-reg.tcl b/tests/integration/psync2-reg.tcl
index ba610a3b8..b5ad021e2 100644
--- a/tests/integration/psync2-reg.tcl
+++ b/tests/integration/psync2-reg.tcl
@@ -18,6 +18,7 @@ start_server {} {
set R($j) [srv [expr 0-$j] client]
set R_host($j) [srv [expr 0-$j] host]
set R_port($j) [srv [expr 0-$j] port]
+ set R_unixsocket($j) [srv [expr 0-$j] unixsocket]
if {$debug_msg} {puts "Log file: [srv [expr 0-$j] stdout]"}
}
@@ -29,24 +30,24 @@ start_server {} {
wait_for_condition 50 1000 {
[$R(1) dbsize] == 1 && [$R(2) dbsize] == 1
} else {
- fail "Slaves not replicating from master"
+ fail "Replicas not replicating from master"
}
$R(0) config set repl-backlog-size 10mb
$R(1) config set repl-backlog-size 10mb
}
set cycle_start_time [clock milliseconds]
- set bench_pid [exec src/redis-benchmark -p $R_port(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
+ set bench_pid [exec src/redis-benchmark -s $R_unixsocket(0) -n 10000000 -r 1000 incr __rand_int__ > /dev/null &]
while 1 {
set elapsed [expr {[clock milliseconds]-$cycle_start_time}]
if {$elapsed > $duration*1000} break
if {rand() < .05} {
- test "PSYNC2 #3899 regression: kill first slave" {
+ test "PSYNC2 #3899 regression: kill first replica" {
$R(1) client kill type master
}
}
if {rand() < .05} {
- test "PSYNC2 #3899 regression: kill chained slave" {
+ test "PSYNC2 #3899 regression: kill chained replica" {
$R(2) client kill type master
}
}
diff --git a/tests/integration/psync2.tcl b/tests/integration/psync2.tcl
index 3d9e5527a..d1212b640 100644
--- a/tests/integration/psync2.tcl
+++ b/tests/integration/psync2.tcl
@@ -33,9 +33,8 @@ start_server {} {
set cycle 1
while {([clock seconds]-$start_time) < $duration} {
- test "PSYNC2: --- CYCLE $cycle ---" {
- incr cycle
- }
+ test "PSYNC2: --- CYCLE $cycle ---" {}
+ incr cycle
# Create a random replication layout.
# Start with switching master (this simulates a failover).
@@ -96,7 +95,7 @@ start_server {} {
if {$disconnect} {
$R($slave_id) client kill type master
if {$debug_msg} {
- puts "+++ Breaking link for slave #$slave_id"
+ puts "+++ Breaking link for replica #$slave_id"
}
}
}
@@ -139,6 +138,11 @@ start_server {} {
}
assert {$sum == 4}
}
+
+ # Limit anyway the maximum number of cycles. This is useful when the
+ # test is skipped via --only option of the test suite. In that case
+ # we don't want to see many seconds of this test being just skipped.
+ if {$cycle > 50} break
}
test "PSYNC2: Bring the master back again for next test" {
@@ -154,7 +158,7 @@ start_server {} {
wait_for_condition 50 1000 {
[status $R($master_id) connected_slaves] == 4
} else {
- fail "Slave not reconnecting"
+ fail "Replica not reconnecting"
}
}
@@ -162,20 +166,23 @@ start_server {} {
# Pick a random slave
set slave_id [expr {($master_id+1)%5}]
set sync_count [status $R($master_id) sync_full]
+ set sync_partial [status $R($master_id) sync_partial_ok]
catch {
$R($slave_id) config rewrite
$R($slave_id) debug restart
}
+ # note: just waiting for connected_slaves==4 has a race condition since
+ # we might do the check before the master realized that the slave disconnected
wait_for_condition 50 1000 {
- [status $R($master_id) connected_slaves] == 4
+ [status $R($master_id) sync_partial_ok] == $sync_partial + 1
} else {
- fail "Slave not reconnecting"
+ fail "Replica not reconnecting"
}
set new_sync_count [status $R($master_id) sync_full]
assert {$sync_count == $new_sync_count}
}
- test "PSYNC2: Slave RDB restart with EVALSHA in backlog issue #4483" {
+ test "PSYNC2: Replica RDB restart with EVALSHA in backlog issue #4483" {
# Pick a random slave
set slave_id [expr {($master_id+1)%5}]
set sync_count [status $R($master_id) sync_full]
@@ -190,7 +197,7 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
- fail "Slave not reconnecting"
+ fail "Replica not reconnecting"
}
# Prevent the slave from receiving master updates, and at
@@ -224,7 +231,7 @@ start_server {} {
wait_for_condition 50 1000 {
[status $R($master_id) connected_slaves] == 4
} else {
- fail "Slave not reconnecting"
+ fail "Replica not reconnecting"
}
set new_sync_count [status $R($master_id) sync_full]
assert {$sync_count == $new_sync_count}
@@ -234,7 +241,7 @@ start_server {} {
wait_for_condition 50 1000 {
[$R($master_id) debug digest] == [$R($slave_id) debug digest]
} else {
- fail "Debug digest mismatch between master and slave in post-restart handshake"
+ fail "Debug digest mismatch between master and replica in post-restart handshake"
}
}
diff --git a/tests/integration/rdb.tcl b/tests/integration/rdb.tcl
index 66aad4cc7..b364291ee 100644
--- a/tests/integration/rdb.tcl
+++ b/tests/integration/rdb.tcl
@@ -39,6 +39,25 @@ start_server [list overrides [list "dir" $server_path]] {
} {0000000000000000000000000000000000000000}
}
+start_server [list overrides [list "dir" $server_path]] {
+ test {Test RDB stream encoding} {
+ for {set j 0} {$j < 1000} {incr j} {
+ if {rand() < 0.9} {
+ r xadd stream * foo $j
+ } else {
+ r xadd stream * bar $j
+ }
+ }
+ r xgroup create stream mygroup 0
+ r xreadgroup GROUP mygroup Alice COUNT 1 STREAMS stream >
+ set digest [r debug digest]
+ r debug reload
+ set newdigest [r debug digest]
+ assert {$digest eq $newdigest}
+ r del stream
+ }
+}
+
# Helper function to start a server and kill it, just to check the error
# logged.
set defaults {}
@@ -96,3 +115,17 @@ start_server_and_kill_it [list "dir" $server_path] {
}
}
}
+
+start_server {} {
+ test {Test FLUSHALL aborts bgsave} {
+ r config set rdb-key-save-delay 1000
+ r debug populate 1000
+ r bgsave
+ assert_equal [s rdb_bgsave_in_progress] 1
+ r flushall
+ after 200
+ assert_equal [s rdb_bgsave_in_progress] 0
+ # make sure the server is still writable
+ r set x xx
+ }
+} \ No newline at end of file
diff --git a/tests/integration/redis-cli.tcl b/tests/integration/redis-cli.tcl
index 40e4222e3..5d1635950 100644
--- a/tests/integration/redis-cli.tcl
+++ b/tests/integration/redis-cli.tcl
@@ -1,7 +1,10 @@
+source tests/support/cli.tcl
+
start_server {tags {"cli"}} {
proc open_cli {} {
set ::env(TERM) dumb
- set fd [open [format "|src/redis-cli -p %d -n 9" [srv port]] "r+"]
+ set cmdline [rediscli [srv port] "-n 9"]
+ set fd [open "|$cmdline" "r+"]
fconfigure $fd -buffering none
fconfigure $fd -blocking false
fconfigure $fd -translation binary
@@ -54,8 +57,8 @@ start_server {tags {"cli"}} {
}
proc _run_cli {opts args} {
- set cmd [format "src/redis-cli -p %d -n 9 $args" [srv port]]
- foreach {key value} $opts {
+ set cmd [rediscli [srv port] [list -n 9 {*}$args]]
+ foreach {key value} $args {
if {$key eq "pipe"} {
set cmd "sh -c \"$value | $cmd\""
}
diff --git a/tests/integration/replication-2.tcl b/tests/integration/replication-2.tcl
index 2ff19c3c4..08905f11e 100644
--- a/tests/integration/replication-2.tcl
+++ b/tests/integration/replication-2.tcl
@@ -16,7 +16,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100 {
[r -1 get foo] eq {12345}
} else {
- fail "Write did not reached slave"
+ fail "Write did not reached replica"
}
}
@@ -34,7 +34,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100 {
[r -1 get foo] eq {12345}
} else {
- fail "Write did not reached slave"
+ fail "Write did not reached replica"
}
}
@@ -60,7 +60,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100 {
[r -1 get foo] eq {aaabbb}
} else {
- fail "Write did not reached slave"
+ fail "Write did not reached replica"
}
}
@@ -81,7 +81,7 @@ start_server {tags {"repl"}} {
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
- puts "Master - Slave inconsistency"
+ puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal [r debug digest] [r -1 debug digest]
diff --git a/tests/integration/replication-3.tcl b/tests/integration/replication-3.tcl
index 580be7602..198e698f2 100644
--- a/tests/integration/replication-3.tcl
+++ b/tests/integration/replication-3.tcl
@@ -25,7 +25,7 @@ start_server {tags {"repl"}} {
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
- puts "Master - Slave inconsistency"
+ puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal [r debug digest] [r -1 debug digest]
@@ -98,7 +98,7 @@ start_server {tags {"repl"}} {
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
- puts "Master - Slave inconsistency"
+ puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
diff --git a/tests/integration/replication-4.tcl b/tests/integration/replication-4.tcl
index 1c559b706..54891151b 100644
--- a/tests/integration/replication-4.tcl
+++ b/tests/integration/replication-4.tcl
@@ -1,12 +1,3 @@
-proc start_bg_complex_data {host port db ops} {
- set tclsh [info nameofexecutable]
- exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
-}
-
-proc stop_bg_complex_data {handle} {
- catch {exec /bin/kill -9 $handle}
-}
-
start_server {tags {"repl"}} {
start_server {} {
@@ -47,7 +38,7 @@ start_server {tags {"repl"}} {
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
- puts "Master - Slave inconsistency"
+ puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal [r debug digest] [r -1 debug digest]
diff --git a/tests/integration/replication-psync.tcl b/tests/integration/replication-psync.tcl
index 2b9e13f50..3c98723af 100644
--- a/tests/integration/replication-psync.tcl
+++ b/tests/integration/replication-psync.tcl
@@ -1,23 +1,14 @@
-proc start_bg_complex_data {host port db ops} {
- set tclsh [info nameofexecutable]
- exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops &
-}
-
-proc stop_bg_complex_data {handle} {
- catch {exec /bin/kill -9 $handle}
-}
-
# Creates a master-slave pair and breaks the link continuously to force
# partial resyncs attempts, all this while flooding the master with
# write queries.
#
-# You can specifiy backlog size, ttl, delay before reconnection, test duration
+# You can specify backlog size, ttl, delay before reconnection, test duration
# in seconds, and an additional condition to verify at the end.
#
# If reconnect is > 0, the test actually try to break the connection and
# reconnect with the master, otherwise just the initial synchronization is
# checked for consistency.
-proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless reconnect} {
+proc test_psync {descr duration backlog_size backlog_ttl delay cond mdl sdl reconnect} {
start_server {tags {"repl"}} {
start_server {} {
@@ -28,8 +19,9 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
$master config set repl-backlog-size $backlog_size
$master config set repl-backlog-ttl $backlog_ttl
- $master config set repl-diskless-sync $diskless
+ $master config set repl-diskless-sync $mdl
$master config set repl-diskless-sync-delay 1
+ $slave config set repl-diskless-load $sdl
set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000]
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
@@ -54,13 +46,13 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
}
}
- test "Test replication partial resync: $descr (diskless: $diskless, reconnect: $reconnect)" {
+ test "Test replication partial resync: $descr (diskless: $mdl, $sdl, reconnect: $reconnect)" {
# Now while the clients are writing data, break the maste-slave
# link multiple times.
if ($reconnect) {
for {set j 0} {$j < $duration*10} {incr j} {
after 100
- # catch {puts "MASTER [$master dbsize] keys, SLAVE [$slave dbsize] keys"}
+ # catch {puts "MASTER [$master dbsize] keys, REPLICA [$slave dbsize] keys"}
if {($j % 20) == 0} {
catch {
@@ -79,6 +71,32 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
stop_bg_complex_data $load_handle0
stop_bg_complex_data $load_handle1
stop_bg_complex_data $load_handle2
+
+ # Wait for the slave to reach the "online"
+ # state from the POV of the master.
+ set retry 5000
+ while {$retry} {
+ set info [$master info]
+ if {[string match {*slave0:*state=online*} $info]} {
+ break
+ } else {
+ incr retry -1
+ after 100
+ }
+ }
+ if {$retry == 0} {
+ error "assertion:Slave not correctly synchronized"
+ }
+
+ # Wait that slave acknowledge it is online so
+ # we are sure that DBSIZE and DEBUG DIGEST will not
+ # fail because of timing issues. (-LOADING error)
+ wait_for_condition 5000 100 {
+ [lindex [$slave role] 3] eq {connected}
+ } else {
+ fail "Slave still not connected after some time"
+ }
+
set retry 10
while {$retry && ([$master debug digest] ne [$slave debug digest])}\
{
@@ -96,7 +114,7 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
set fd [open /tmp/repldump2.txt w]
puts -nonewline $fd $csv2
close $fd
- puts "Master - Slave inconsistency"
+ puts "Master - Replica inconsistency"
puts "Run diff -u against /tmp/repldump*.txt for more info"
}
assert_equal [r debug digest] [r -1 debug digest]
@@ -106,23 +124,25 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond diskless rec
}
}
-foreach diskless {no yes} {
- test_psync {no reconnection, just sync} 6 1000000 3600 0 {
- } $diskless 0
+foreach mdl {no yes} {
+ foreach sdl {disabled swapdb} {
+ test_psync {no reconnection, just sync} 6 1000000 3600 0 {
+ } $mdl $sdl 0
- test_psync {ok psync} 6 100000000 3600 0 {
+ test_psync {ok psync} 6 100000000 3600 0 {
assert {[s -1 sync_partial_ok] > 0}
- } $diskless 1
+ } $mdl $sdl 1
- test_psync {no backlog} 6 100 3600 0.5 {
+ test_psync {no backlog} 6 100 3600 0.5 {
assert {[s -1 sync_partial_err] > 0}
- } $diskless 1
+ } $mdl $sdl 1
- test_psync {ok after delay} 3 100000000 3600 3 {
+ test_psync {ok after delay} 3 100000000 3600 3 {
assert {[s -1 sync_partial_ok] > 0}
- } $diskless 1
+ } $mdl $sdl 1
- test_psync {backlog expired} 3 100000000 1 3 {
+ test_psync {backlog expired} 3 100000000 1 3 {
assert {[s -1 sync_partial_err] > 0}
- } $diskless 1
+ } $mdl $sdl 1
+ }
}
diff --git a/tests/integration/replication.tcl b/tests/integration/replication.tcl
index e811cf0ee..4bd1f47f7 100644
--- a/tests/integration/replication.tcl
+++ b/tests/integration/replication.tcl
@@ -32,7 +32,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 1000 {
[string match *handshake* [$slave role]]
} else {
- fail "Slave does not enter handshake state"
+ fail "Replica does not enter handshake state"
}
}
@@ -45,7 +45,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 1000 {
[log_file_matches $slave_log "*Timeout connecting to the MASTER*"]
} else {
- fail "Slave is not able to detect timeout"
+ fail "Replica is not able to detect timeout"
}
}
}
@@ -66,7 +66,7 @@ start_server {tags {"repl"}} {
[lindex [$A role] 0] eq {slave} &&
[string match {*master_link_status:up*} [$A info replication]]
} else {
- fail "Can't turn the instance into a slave"
+ fail "Can't turn the instance into a replica"
}
}
@@ -77,7 +77,7 @@ start_server {tags {"repl"}} {
wait_for_condition 50 100 {
[$A debug digest] eq [$B debug digest]
} else {
- fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
+ fail "Master and replica have different digest: [$A debug digest] VS [$B debug digest]"
}
}
@@ -102,10 +102,10 @@ start_server {tags {"repl"}} {
[lindex [$B role] 0] eq {slave} &&
[string match {*master_link_status:up*} [$B info replication]]
} else {
- fail "Can't turn the instance into a slave"
+ fail "Can't turn the instance into a replica"
}
- # Push elements into the "foo" list of the new slave.
+ # Push elements into the "foo" list of the new replica.
# If the client is still attached to the instance, we'll get
# a desync between the two instances.
$A rpush foo a b c
@@ -116,7 +116,7 @@ start_server {tags {"repl"}} {
[$A lrange foo 0 -1] eq {a b c} &&
[$B lrange foo 0 -1] eq {a b c}
} else {
- fail "Master and slave have different digest: [$A debug digest] VS [$B debug digest]"
+ fail "Master and replica have different digest: [$A debug digest] VS [$B debug digest]"
}
}
}
@@ -135,7 +135,7 @@ start_server {tags {"repl"}} {
s master_link_status
} {down}
- test {The role should immediately be changed to "slave"} {
+ test {The role should immediately be changed to "replica"} {
s role
} {slave}
@@ -154,7 +154,7 @@ start_server {tags {"repl"}} {
wait_for_condition 500 100 {
[r 0 get mykey] eq {bar}
} else {
- fail "SET on master did not propagated on slave"
+ fail "SET on master did not propagated on replica"
}
}
@@ -183,85 +183,449 @@ start_server {tags {"repl"}} {
}
}
-foreach dl {no yes} {
- start_server {tags {"repl"}} {
- set master [srv 0 client]
- $master config set repl-diskless-sync $dl
- set master_host [srv 0 host]
- set master_port [srv 0 port]
- set slaves {}
- set load_handle0 [start_write_load $master_host $master_port 3]
- set load_handle1 [start_write_load $master_host $master_port 5]
- set load_handle2 [start_write_load $master_host $master_port 20]
- set load_handle3 [start_write_load $master_host $master_port 8]
- set load_handle4 [start_write_load $master_host $master_port 4]
- start_server {} {
- lappend slaves [srv 0 client]
+foreach mdl {no yes} {
+ foreach sdl {disabled swapdb} {
+ start_server {tags {"repl"}} {
+ set master [srv 0 client]
+ $master config set repl-diskless-sync $mdl
+ $master config set repl-diskless-sync-delay 1
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+ set slaves {}
start_server {} {
lappend slaves [srv 0 client]
start_server {} {
lappend slaves [srv 0 client]
- test "Connect multiple slaves at the same time (issue #141), diskless=$dl" {
- # Send SLAVEOF commands to slaves
- [lindex $slaves 0] slaveof $master_host $master_port
- [lindex $slaves 1] slaveof $master_host $master_port
- [lindex $slaves 2] slaveof $master_host $master_port
-
- # Wait for all the three slaves to reach the "online"
- # state from the POV of the master.
- set retry 500
- while {$retry} {
- set info [r -3 info]
- if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {
- break
+ start_server {} {
+ lappend slaves [srv 0 client]
+ test "Connect multiple replicas at the same time (issue #141), master diskless=$mdl, replica diskless=$sdl" {
+ # start load handles only inside the test, so that the test can be skipped
+ set load_handle0 [start_bg_complex_data $master_host $master_port 9 100000000]
+ set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000000]
+ set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000000]
+ set load_handle3 [start_write_load $master_host $master_port 8]
+ set load_handle4 [start_write_load $master_host $master_port 4]
+ after 5000 ;# wait for some data to accumulate so that we have RDB part for the fork
+
+ # Send SLAVEOF commands to slaves
+ [lindex $slaves 0] config set repl-diskless-load $sdl
+ [lindex $slaves 1] config set repl-diskless-load $sdl
+ [lindex $slaves 2] config set repl-diskless-load $sdl
+ [lindex $slaves 0] slaveof $master_host $master_port
+ [lindex $slaves 1] slaveof $master_host $master_port
+ [lindex $slaves 2] slaveof $master_host $master_port
+
+ # Wait for all the three slaves to reach the "online"
+ # state from the POV of the master.
+ set retry 500
+ while {$retry} {
+ set info [r -3 info]
+ if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {
+ break
+ } else {
+ incr retry -1
+ after 100
+ }
+ }
+ if {$retry == 0} {
+ error "assertion:Slaves not correctly synchronized"
+ }
+
+ # Wait that slaves acknowledge they are online so
+ # we are sure that DBSIZE and DEBUG DIGEST will not
+ # fail because of timing issues.
+ wait_for_condition 500 100 {
+ [lindex [[lindex $slaves 0] role] 3] eq {connected} &&
+ [lindex [[lindex $slaves 1] role] 3] eq {connected} &&
+ [lindex [[lindex $slaves 2] role] 3] eq {connected}
+ } else {
+ fail "Slaves still not connected after some time"
+ }
+
+ # Stop the write load
+ stop_bg_complex_data $load_handle0
+ stop_bg_complex_data $load_handle1
+ stop_bg_complex_data $load_handle2
+ stop_write_load $load_handle3
+ stop_write_load $load_handle4
+
+ # Make sure that slaves and master have same
+ # number of keys
+ wait_for_condition 500 100 {
+ [$master dbsize] == [[lindex $slaves 0] dbsize] &&
+ [$master dbsize] == [[lindex $slaves 1] dbsize] &&
+ [$master dbsize] == [[lindex $slaves 2] dbsize]
} else {
- incr retry -1
- after 100
+ fail "Different number of keys between master and replica after too long time."
}
+
+ # Check digests
+ set digest [$master debug digest]
+ set digest0 [[lindex $slaves 0] debug digest]
+ set digest1 [[lindex $slaves 1] debug digest]
+ set digest2 [[lindex $slaves 2] debug digest]
+ assert {$digest ne 0000000000000000000000000000000000000000}
+ assert {$digest eq $digest0}
+ assert {$digest eq $digest1}
+ assert {$digest eq $digest2}
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+start_server {tags {"repl"}} {
+ set master [srv 0 client]
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+ start_server {} {
+ test "Master stream is correctly processed while the replica has a script in -BUSY state" {
+ set load_handle0 [start_write_load $master_host $master_port 3]
+ set slave [srv 0 client]
+ $slave config set lua-time-limit 500
+ $slave slaveof $master_host $master_port
+
+ # Wait for the slave to be online
+ wait_for_condition 500 100 {
+ [lindex [$slave role] 3] eq {connected}
+ } else {
+ fail "Replica still not connected after some time"
+ }
+
+ # Wait some time to make sure the master is sending data
+ # to the slave.
+ after 5000
+
+ # Stop the ability of the slave to process data by sendig
+ # a script that will put it in BUSY state.
+ $slave eval {for i=1,3000000000 do end} 0
+
+ # Wait some time again so that more master stream will
+ # be processed.
+ after 2000
+
+ # Stop the write load
+ stop_write_load $load_handle0
+
+ # number of keys
+ wait_for_condition 500 100 {
+ [$master debug digest] eq [$slave debug digest]
+ } else {
+ fail "Different datasets between replica and master"
+ }
+ }
+ }
+}
+
+test {slave fails full sync and diskless load swapdb recovers it} {
+ start_server {tags {"repl"}} {
+ set slave [srv 0 client]
+ set slave_host [srv 0 host]
+ set slave_port [srv 0 port]
+ set slave_log [srv 0 stdout]
+ start_server {} {
+ set master [srv 0 client]
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+
+ # Put different data sets on the master and slave
+ # we need to put large keys on the master since the slave replies to info only once in 2mb
+ $slave debug populate 2000 slave 10
+ $master debug populate 200 master 100000
+ $master config set rdbcompression no
+
+ # Set master and slave to use diskless replication
+ $master config set repl-diskless-sync yes
+ $master config set repl-diskless-sync-delay 0
+ $slave config set repl-diskless-load swapdb
+
+ # Set master with a slow rdb generation, so that we can easily disconnect it mid sync
+ # 10ms per key, with 200 keys is 2 seconds
+ $master config set rdb-key-save-delay 10000
+
+ # Start the replication process...
+ $slave slaveof $master_host $master_port
+
+ # wait for the slave to start reading the rdb
+ wait_for_condition 50 100 {
+ [s -1 loading] eq 1
+ } else {
+ fail "Replica didn't get into loading mode"
+ }
+
+ # make sure that next sync will not start immediately so that we can catch the slave in betweeen syncs
+ $master config set repl-diskless-sync-delay 5
+ # for faster server shutdown, make rdb saving fast again (the fork is already uses the slow one)
+ $master config set rdb-key-save-delay 0
+
+ # waiting slave to do flushdb (key count drop)
+ wait_for_condition 50 100 {
+ 2000 != [scan [regexp -inline {keys\=([\d]*)} [$slave info keyspace]] keys=%d]
+ } else {
+ fail "Replica didn't flush"
+ }
+
+ # make sure we're still loading
+ assert_equal [s -1 loading] 1
+
+ # kill the slave connection on the master
+ set killed [$master client kill type slave]
+
+ # wait for loading to stop (fail)
+ wait_for_condition 50 100 {
+ [s -1 loading] eq 0
+ } else {
+ fail "Replica didn't disconnect"
+ }
+
+ # make sure the original keys were restored
+ assert_equal [$slave dbsize] 2000
+ }
+ }
+}
+
+test {diskless loading short read} {
+ start_server {tags {"repl"}} {
+ set replica [srv 0 client]
+ set replica_host [srv 0 host]
+ set replica_port [srv 0 port]
+ start_server {} {
+ set master [srv 0 client]
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+
+ # Set master and replica to use diskless replication
+ $master config set repl-diskless-sync yes
+ $master config set rdbcompression no
+ $replica config set repl-diskless-load swapdb
+ # Try to fill the master with all types of data types / encodings
+ for {set k 0} {$k < 3} {incr k} {
+ for {set i 0} {$i < 10} {incr i} {
+ r set "$k int_$i" [expr {int(rand()*10000)}]
+ r expire "$k int_$i" [expr {int(rand()*10000)}]
+ r set "$k string_$i" [string repeat A [expr {int(rand()*1000000)}]]
+ r hset "$k hash_small" [string repeat A [expr {int(rand()*10)}]] 0[string repeat A [expr {int(rand()*10)}]]
+ r hset "$k hash_large" [string repeat A [expr {int(rand()*10000)}]] [string repeat A [expr {int(rand()*1000000)}]]
+ r sadd "$k set_small" [string repeat A [expr {int(rand()*10)}]]
+ r sadd "$k set_large" [string repeat A [expr {int(rand()*1000000)}]]
+ r zadd "$k zset_small" [expr {rand()}] [string repeat A [expr {int(rand()*10)}]]
+ r zadd "$k zset_large" [expr {rand()}] [string repeat A [expr {int(rand()*1000000)}]]
+ r lpush "$k list_small" [string repeat A [expr {int(rand()*10)}]]
+ r lpush "$k list_large" [string repeat A [expr {int(rand()*1000000)}]]
+ for {set j 0} {$j < 10} {incr j} {
+ r xadd "$k stream" * foo "asdf" bar "1234"
+ }
+ r xgroup create "$k stream" "mygroup_$i" 0
+ r xreadgroup GROUP "mygroup_$i" Alice COUNT 1 STREAMS "$k stream" >
+ }
+ }
+
+ # Start the replication process...
+ $master config set repl-diskless-sync-delay 0
+ $replica replicaof $master_host $master_port
+
+ # kill the replication at various points
+ set attempts 3
+ if {$::accurate} { set attempts 10 }
+ for {set i 0} {$i < $attempts} {incr i} {
+ # wait for the replica to start reading the rdb
+ # using the log file since the replica only responds to INFO once in 2mb
+ wait_for_log_message -1 "*Loading DB in memory*" 5 2000 1
+
+ # add some additional random sleep so that we kill the master on a different place each time
+ after [expr {int(rand()*100)}]
+
+ # kill the replica connection on the master
+ set killed [$master client kill type replica]
+
+ if {[catch {
+ set res [wait_for_log_message -1 "*Internal error in RDB*" 5 100 10]
+ if {$::verbose} {
+ puts $res
+ }
+ }]} {
+ puts "failed triggering short read"
+ # force the replica to try another full sync
+ $master client kill type replica
+ $master set asdf asdf
+ # the side effect of resizing the backlog is that it is flushed (16k is the min size)
+ $master config set repl-backlog-size [expr {16384 + $i}]
+ }
+ # wait for loading to stop (fail)
+ wait_for_condition 100 10 {
+ [s -1 loading] eq 0
+ } else {
+ fail "Replica didn't disconnect"
+ }
+ }
+ # enable fast shutdown
+ $master config set rdb-key-save-delay 0
+ }
+ }
+}
+
+# get current stime and utime metrics for a thread (since it's creation)
+proc get_cpu_metrics { statfile } {
+ if { [ catch {
+ set fid [ open $statfile r ]
+ set data [ read $fid 1024 ]
+ ::close $fid
+ set data [ split $data ]
+
+ ;## number of jiffies it has been scheduled...
+ set utime [ lindex $data 13 ]
+ set stime [ lindex $data 14 ]
+ } err ] } {
+ error "assertion:can't parse /proc: $err"
+ }
+ set mstime [clock milliseconds]
+ return [ list $mstime $utime $stime ]
+}
+
+# compute %utime and %stime of a thread between two measurements
+proc compute_cpu_usage {start end} {
+ set clock_ticks [exec getconf CLK_TCK]
+ # convert ms time to jiffies and calc delta
+ set dtime [ expr { ([lindex $end 0] - [lindex $start 0]) * double($clock_ticks) / 1000 } ]
+ set utime [ expr { [lindex $end 1] - [lindex $start 1] } ]
+ set stime [ expr { [lindex $end 2] - [lindex $start 2] } ]
+ set pucpu [ expr { ($utime / $dtime) * 100 } ]
+ set pscpu [ expr { ($stime / $dtime) * 100 } ]
+ return [ list $pucpu $pscpu ]
+}
+
+
+# test diskless rdb pipe with multiple replicas, which may drop half way
+start_server {tags {"repl"}} {
+ set master [srv 0 client]
+ $master config set repl-diskless-sync yes
+ $master config set repl-diskless-sync-delay 1
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+ set master_pid [srv 0 pid]
+ # put enough data in the db that the rdb file will be bigger than the socket buffers
+ # and since we'll have key-load-delay of 100, 10000 keys will take at least 1 second
+ # we also need the replica to process requests during transfer (which it does only once in 2mb)
+ $master debug populate 10000 test 10000
+ $master config set rdbcompression no
+ # If running on Linux, we also measure utime/stime to detect possible I/O handling issues
+ set os [catch {exec unamee}]
+ set measure_time [expr {$os == "Linux"} ? 1 : 0]
+ foreach all_drop {no slow fast all} {
+ test "diskless $all_drop replicas drop during rdb pipe" {
+ set replicas {}
+ set replicas_alive {}
+ # start one replica that will read the rdb fast, and one that will be slow
+ start_server {} {
+ lappend replicas [srv 0 client]
+ lappend replicas_alive [srv 0 client]
+ start_server {} {
+ lappend replicas [srv 0 client]
+ lappend replicas_alive [srv 0 client]
+
+ # start replication
+ # it's enough for just one replica to be slow, and have it's write handler enabled
+ # so that the whole rdb generation process is bound to that
+ [lindex $replicas 0] config set repl-diskless-load swapdb
+ [lindex $replicas 0] config set key-load-delay 100
+ [lindex $replicas 0] replicaof $master_host $master_port
+ [lindex $replicas 1] replicaof $master_host $master_port
+
+ # wait for the replicas to start reading the rdb
+ # using the log file since the replica only responds to INFO once in 2mb
+ wait_for_log_message -1 "*Loading DB in memory*" 8 800 10
+
+ if {$measure_time} {
+ set master_statfile "/proc/$master_pid/stat"
+ set master_start_metrics [get_cpu_metrics $master_statfile]
+ set start_time [clock seconds]
+ }
+
+ # wait a while so that the pipe socket writer will be
+ # blocked on write (since replica 0 is slow to read from the socket)
+ after 500
+
+ # add some command to be present in the command stream after the rdb.
+ $master incr $all_drop
+
+ # disconnect replicas depending on the current test
+ if {$all_drop == "all" || $all_drop == "fast"} {
+ exec kill [srv 0 pid]
+ set replicas_alive [lreplace $replicas_alive 1 1]
+ }
+ if {$all_drop == "all" || $all_drop == "slow"} {
+ exec kill [srv -1 pid]
+ set replicas_alive [lreplace $replicas_alive 0 0]
+ }
+
+ # wait for rdb child to exit
+ wait_for_condition 500 100 {
+ [s -2 rdb_bgsave_in_progress] == 0
+ } else {
+ fail "rdb child didn't terminate"
+ }
+
+ # make sure we got what we were aiming for, by looking for the message in the log file
+ if {$all_drop == "all"} {
+ wait_for_log_message -2 "*Diskless rdb transfer, last replica dropped, killing fork child*" 12 1 1
+ }
+ if {$all_drop == "no"} {
+ wait_for_log_message -2 "*Diskless rdb transfer, done reading from pipe, 2 replicas still up*" 12 1 1
+ }
+ if {$all_drop == "slow" || $all_drop == "fast"} {
+ wait_for_log_message -2 "*Diskless rdb transfer, done reading from pipe, 1 replicas still up*" 12 1 1
+ }
+
+ # make sure we don't have a busy loop going thought epoll_wait
+ if {$measure_time} {
+ set master_end_metrics [get_cpu_metrics $master_statfile]
+ set time_elapsed [expr {[clock seconds]-$start_time}]
+ set master_cpu [compute_cpu_usage $master_start_metrics $master_end_metrics]
+ set master_utime [lindex $master_cpu 0]
+ set master_stime [lindex $master_cpu 1]
+ if {$::verbose} {
+ puts "elapsed: $time_elapsed"
+ puts "master utime: $master_utime"
+ puts "master stime: $master_stime"
}
- if {$retry == 0} {
- error "assertion:Slaves not correctly synchronized"
+ if {$all_drop == "all" || $all_drop == "slow"} {
+ assert {$master_utime < 70}
+ assert {$master_stime < 70}
}
+ if {$all_drop == "none" || $all_drop == "fast"} {
+ assert {$master_utime < 15}
+ assert {$master_stime < 15}
+ }
+ }
- # Wait that slaves acknowledge they are online so
+ # verify the data integrity
+ foreach replica $replicas_alive {
+ # Wait that replicas acknowledge they are online so
# we are sure that DBSIZE and DEBUG DIGEST will not
# fail because of timing issues.
- wait_for_condition 500 100 {
- [lindex [[lindex $slaves 0] role] 3] eq {connected} &&
- [lindex [[lindex $slaves 1] role] 3] eq {connected} &&
- [lindex [[lindex $slaves 2] role] 3] eq {connected}
+ wait_for_condition 50 100 {
+ [lindex [$replica role] 3] eq {connected}
} else {
- fail "Slaves still not connected after some time"
+ fail "replicas still not connected after some time"
}
- # Stop the write load
- stop_write_load $load_handle0
- stop_write_load $load_handle1
- stop_write_load $load_handle2
- stop_write_load $load_handle3
- stop_write_load $load_handle4
-
- # Make sure that slaves and master have same
+ # Make sure that replicas and master have same
# number of keys
- wait_for_condition 500 100 {
- [$master dbsize] == [[lindex $slaves 0] dbsize] &&
- [$master dbsize] == [[lindex $slaves 1] dbsize] &&
- [$master dbsize] == [[lindex $slaves 2] dbsize]
+ wait_for_condition 50 100 {
+ [$master dbsize] == [$replica dbsize]
} else {
- fail "Different number of keys between masted and slave after too long time."
+ fail "Different number of keys between master and replicas after too long time."
}
# Check digests
set digest [$master debug digest]
- set digest0 [[lindex $slaves 0] debug digest]
- set digest1 [[lindex $slaves 1] debug digest]
- set digest2 [[lindex $slaves 2] debug digest]
+ set digest0 [$replica debug digest]
assert {$digest ne 0000000000000000000000000000000000000000}
assert {$digest eq $digest0}
- assert {$digest eq $digest1}
- assert {$digest eq $digest2}
}
- }
+ }
}
}
}
diff --git a/tests/modules/Makefile b/tests/modules/Makefile
new file mode 100644
index 000000000..988ecf58a
--- /dev/null
+++ b/tests/modules/Makefile
@@ -0,0 +1,44 @@
+
+# find the OS
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+
+# Compile flags for linux / osx
+ifeq ($(uname_S),Linux)
+ SHOBJ_CFLAGS ?= -W -Wall -fno-common -g -ggdb -std=c99 -O2
+ SHOBJ_LDFLAGS ?= -shared
+else
+ SHOBJ_CFLAGS ?= -W -Wall -dynamic -fno-common -g -ggdb -std=c99 -O2
+ SHOBJ_LDFLAGS ?= -bundle -undefined dynamic_lookup
+endif
+
+.SUFFIXES: .c .so .xo .o
+
+all: commandfilter.so testrdb.so fork.so infotest.so propagate.so hooks.so
+
+.c.xo:
+ $(CC) -I../../src $(CFLAGS) $(SHOBJ_CFLAGS) -fPIC -c $< -o $@
+
+commandfilter.xo: ../../src/redismodule.h
+fork.xo: ../../src/redismodule.h
+testrdb.xo: ../../src/redismodule.h
+infotest.xo: ../../src/redismodule.h
+propagate.xo: ../../src/redismodule.h
+hooks.xo: ../../src/redismodule.h
+
+commandfilter.so: commandfilter.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+fork.so: fork.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+testrdb.so: testrdb.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+infotest.so: infotest.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+propagate.so: propagate.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
+
+hooks.so: hooks.xo
+ $(LD) -o $@ $< $(SHOBJ_LDFLAGS) $(LIBS) -lc
diff --git a/tests/modules/commandfilter.c b/tests/modules/commandfilter.c
new file mode 100644
index 000000000..d25d49c44
--- /dev/null
+++ b/tests/modules/commandfilter.c
@@ -0,0 +1,149 @@
+#define REDISMODULE_EXPERIMENTAL_API
+#include "redismodule.h"
+
+#include <string.h>
+
+static RedisModuleString *log_key_name;
+
+static const char log_command_name[] = "commandfilter.log";
+static const char ping_command_name[] = "commandfilter.ping";
+static const char unregister_command_name[] = "commandfilter.unregister";
+static int in_log_command = 0;
+
+static RedisModuleCommandFilter *filter = NULL;
+
+int CommandFilter_UnregisterCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ (void) argc;
+ (void) argv;
+
+ RedisModule_ReplyWithLongLong(ctx,
+ RedisModule_UnregisterCommandFilter(ctx, filter));
+
+ return REDISMODULE_OK;
+}
+
+int CommandFilter_PingCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ (void) argc;
+ (void) argv;
+
+ RedisModuleCallReply *reply = RedisModule_Call(ctx, "ping", "c", "@log");
+ if (reply) {
+ RedisModule_ReplyWithCallReply(ctx, reply);
+ RedisModule_FreeCallReply(reply);
+ } else {
+ RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
+ }
+
+ return REDISMODULE_OK;
+}
+
+int CommandFilter_LogCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ RedisModuleString *s = RedisModule_CreateString(ctx, "", 0);
+
+ int i;
+ for (i = 1; i < argc; i++) {
+ size_t arglen;
+ const char *arg = RedisModule_StringPtrLen(argv[i], &arglen);
+
+ if (i > 1) RedisModule_StringAppendBuffer(ctx, s, " ", 1);
+ RedisModule_StringAppendBuffer(ctx, s, arg, arglen);
+ }
+
+ RedisModuleKey *log = RedisModule_OpenKey(ctx, log_key_name, REDISMODULE_WRITE|REDISMODULE_READ);
+ RedisModule_ListPush(log, REDISMODULE_LIST_HEAD, s);
+ RedisModule_CloseKey(log);
+ RedisModule_FreeString(ctx, s);
+
+ in_log_command = 1;
+
+ size_t cmdlen;
+ const char *cmdname = RedisModule_StringPtrLen(argv[1], &cmdlen);
+ RedisModuleCallReply *reply = RedisModule_Call(ctx, cmdname, "v", &argv[2], argc - 2);
+ if (reply) {
+ RedisModule_ReplyWithCallReply(ctx, reply);
+ RedisModule_FreeCallReply(reply);
+ } else {
+ RedisModule_ReplyWithSimpleString(ctx, "Unknown command or invalid arguments");
+ }
+
+ in_log_command = 0;
+
+ return REDISMODULE_OK;
+}
+
+void CommandFilter_CommandFilter(RedisModuleCommandFilterCtx *filter)
+{
+ if (in_log_command) return; /* don't process our own RM_Call() from CommandFilter_LogCommand() */
+
+ /* Fun manipulations:
+ * - Remove @delme
+ * - Replace @replaceme
+ * - Append @insertbefore or @insertafter
+ * - Prefix with Log command if @log encounterd
+ */
+ int log = 0;
+ int pos = 0;
+ while (pos < RedisModule_CommandFilterArgsCount(filter)) {
+ const RedisModuleString *arg = RedisModule_CommandFilterArgGet(filter, pos);
+ size_t arg_len;
+ const char *arg_str = RedisModule_StringPtrLen(arg, &arg_len);
+
+ if (arg_len == 6 && !memcmp(arg_str, "@delme", 6)) {
+ RedisModule_CommandFilterArgDelete(filter, pos);
+ continue;
+ }
+ if (arg_len == 10 && !memcmp(arg_str, "@replaceme", 10)) {
+ RedisModule_CommandFilterArgReplace(filter, pos,
+ RedisModule_CreateString(NULL, "--replaced--", 12));
+ } else if (arg_len == 13 && !memcmp(arg_str, "@insertbefore", 13)) {
+ RedisModule_CommandFilterArgInsert(filter, pos,
+ RedisModule_CreateString(NULL, "--inserted-before--", 19));
+ pos++;
+ } else if (arg_len == 12 && !memcmp(arg_str, "@insertafter", 12)) {
+ RedisModule_CommandFilterArgInsert(filter, pos + 1,
+ RedisModule_CreateString(NULL, "--inserted-after--", 18));
+ pos++;
+ } else if (arg_len == 4 && !memcmp(arg_str, "@log", 4)) {
+ log = 1;
+ }
+ pos++;
+ }
+
+ if (log) RedisModule_CommandFilterArgInsert(filter, 0,
+ RedisModule_CreateString(NULL, log_command_name, sizeof(log_command_name)-1));
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ if (RedisModule_Init(ctx,"commandfilter",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (argc != 2) {
+ RedisModule_Log(ctx, "warning", "Log key name not specified");
+ return REDISMODULE_ERR;
+ }
+
+ long long noself = 0;
+ log_key_name = RedisModule_CreateStringFromString(ctx, argv[0]);
+ RedisModule_StringToLongLong(argv[1], &noself);
+
+ if (RedisModule_CreateCommand(ctx,log_command_name,
+ CommandFilter_LogCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,ping_command_name,
+ CommandFilter_PingCommand,"deny-oom",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,unregister_command_name,
+ CommandFilter_UnregisterCommand,"write deny-oom",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if ((filter = RedisModule_RegisterCommandFilter(ctx, CommandFilter_CommandFilter,
+ noself ? REDISMODULE_CMDFILTER_NOSELF : 0))
+ == NULL) return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/tests/modules/fork.c b/tests/modules/fork.c
new file mode 100644
index 000000000..0804e4355
--- /dev/null
+++ b/tests/modules/fork.c
@@ -0,0 +1,84 @@
+#define REDISMODULE_EXPERIMENTAL_API
+#include "redismodule.h"
+
+#include <string.h>
+#include <assert.h>
+#include <unistd.h>
+
+#define UNUSED(V) ((void) V)
+
+int child_pid = -1;
+int exitted_with_code = -1;
+
+void done_handler(int exitcode, int bysignal, void *user_data) {
+ child_pid = -1;
+ exitted_with_code = exitcode;
+ assert(user_data==(void*)0xdeadbeef);
+ UNUSED(bysignal);
+}
+
+int fork_create(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ long long code_to_exit_with;
+ if (argc != 2) {
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+ RedisModule_StringToLongLong(argv[1], &code_to_exit_with);
+ exitted_with_code = -1;
+ child_pid = RedisModule_Fork(done_handler, (void*)0xdeadbeef);
+ if (child_pid < 0) {
+ RedisModule_ReplyWithError(ctx, "Fork failed");
+ return REDISMODULE_OK;
+ } else if (child_pid > 0) {
+ /* parent */
+ RedisModule_ReplyWithLongLong(ctx, child_pid);
+ return REDISMODULE_OK;
+ }
+
+ /* child */
+ RedisModule_Log(ctx, "notice", "fork child started");
+ usleep(200000);
+ RedisModule_Log(ctx, "notice", "fork child exiting");
+ RedisModule_ExitFromChild(code_to_exit_with);
+ /* unreachable */
+ return 0;
+}
+
+int fork_exitcode(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ UNUSED(argv);
+ UNUSED(argc);
+ RedisModule_ReplyWithLongLong(ctx, exitted_with_code);
+ return REDISMODULE_OK;
+}
+
+int fork_kill(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ UNUSED(argv);
+ UNUSED(argc);
+ if (RedisModule_KillForkChild(child_pid) != REDISMODULE_OK)
+ RedisModule_ReplyWithError(ctx, "KillForkChild failed");
+ else
+ RedisModule_ReplyWithLongLong(ctx, 1);
+ child_pid = -1;
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ UNUSED(argv);
+ UNUSED(argc);
+ if (RedisModule_Init(ctx,"fork",1,REDISMODULE_APIVER_1)== REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"fork.create", fork_create,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"fork.exitcode", fork_exitcode,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"fork.kill", fork_kill,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/tests/modules/hooks.c b/tests/modules/hooks.c
new file mode 100644
index 000000000..33b690b2f
--- /dev/null
+++ b/tests/modules/hooks.c
@@ -0,0 +1,79 @@
+/* This module is used to test the server events hooks API.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "redismodule.h"
+
+/* Client state change callback. */
+void clientChangeCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
+{
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(e);
+
+ RedisModuleClientInfo *ci = data;
+ char *keyname = (sub == REDISMODULE_SUBEVENT_CLIENT_CHANGE_CONNECTED) ?
+ "connected" : "disconnected";
+ RedisModuleCallReply *reply;
+ RedisModule_SelectDb(ctx,9);
+ reply = RedisModule_Call(ctx,"RPUSH","cl",keyname,(long)ci->id);
+ RedisModule_FreeCallReply(reply);
+}
+
+void flushdbCallback(RedisModuleCtx *ctx, RedisModuleEvent e, uint64_t sub, void *data)
+{
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(e);
+
+ RedisModuleFlushInfo *fi = data;
+ char *keyname = (sub == REDISMODULE_SUBEVENT_FLUSHDB_START) ?
+ "flush-start" : "flush-end";
+ RedisModuleCallReply *reply;
+ RedisModule_SelectDb(ctx,9);
+ reply = RedisModule_Call(ctx,"RPUSH","cl",keyname,(long)fi->dbnum);
+ RedisModule_FreeCallReply(reply);
+}
+
+/* This function must be present on each Redis module. It is used in order to
+ * register the commands into the Redis server. */
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"testhook",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ RedisModule_SubscribeToServerEvent(ctx,
+ RedisModuleEvent_ClientChange, clientChangeCallback);
+ RedisModule_SubscribeToServerEvent(ctx,
+ RedisModuleEvent_FlushDB, flushdbCallback);
+ return REDISMODULE_OK;
+}
diff --git a/tests/modules/infotest.c b/tests/modules/infotest.c
new file mode 100644
index 000000000..d28410932
--- /dev/null
+++ b/tests/modules/infotest.c
@@ -0,0 +1,41 @@
+#include "redismodule.h"
+
+#include <string.h>
+
+void InfoFunc(RedisModuleInfoCtx *ctx, int for_crash_report) {
+ RedisModule_InfoAddSection(ctx, "");
+ RedisModule_InfoAddFieldLongLong(ctx, "global", -2);
+
+ RedisModule_InfoAddSection(ctx, "Spanish");
+ RedisModule_InfoAddFieldCString(ctx, "uno", "one");
+ RedisModule_InfoAddFieldLongLong(ctx, "dos", 2);
+
+ RedisModule_InfoAddSection(ctx, "Italian");
+ RedisModule_InfoAddFieldLongLong(ctx, "due", 2);
+ RedisModule_InfoAddFieldDouble(ctx, "tre", 3.3);
+
+ RedisModule_InfoAddSection(ctx, "keyspace");
+ RedisModule_InfoBeginDictField(ctx, "db0");
+ RedisModule_InfoAddFieldLongLong(ctx, "keys", 3);
+ RedisModule_InfoAddFieldLongLong(ctx, "expires", 1);
+ RedisModule_InfoEndDictField(ctx);
+
+ if (for_crash_report) {
+ RedisModule_InfoAddSection(ctx, "Klingon");
+ RedisModule_InfoAddFieldCString(ctx, "one", "wa’");
+ RedisModule_InfoAddFieldCString(ctx, "two", "cha’");
+ RedisModule_InfoAddFieldCString(ctx, "three", "wej");
+ }
+
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+ if (RedisModule_Init(ctx,"infotest",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_RegisterInfoFunc(ctx, InfoFunc) == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/tests/modules/propagate.c b/tests/modules/propagate.c
new file mode 100644
index 000000000..f83af1799
--- /dev/null
+++ b/tests/modules/propagate.c
@@ -0,0 +1,104 @@
+/* This module is used to test the propagation (replication + AOF) of
+ * commands, via the RedisModule_Replicate() interface, in asynchronous
+ * contexts, such as callbacks not implementing commands, and thread safe
+ * contexts.
+ *
+ * We create a timer callback and a threads using a thread safe context.
+ * Using both we try to propagate counters increments, and later we check
+ * if the replica contains the changes as expected.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * Copyright (c) 2019, Salvatore Sanfilippo <antirez at gmail dot com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Redis nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define REDISMODULE_EXPERIMENTAL_API
+#include "redismodule.h"
+#include <pthread.h>
+
+/* Timer callback. */
+void timerHandler(RedisModuleCtx *ctx, void *data) {
+ REDISMODULE_NOT_USED(ctx);
+ REDISMODULE_NOT_USED(data);
+
+ static int times = 0;
+
+ RedisModule_Replicate(ctx,"INCR","c","timer");
+ times++;
+
+ if (times < 10)
+ RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
+ else
+ times = 0;
+}
+
+/* The thread entry point. */
+void *threadMain(void *arg) {
+ REDISMODULE_NOT_USED(arg);
+ RedisModuleCtx *ctx = RedisModule_GetThreadSafeContext(NULL);
+ RedisModule_SelectDb(ctx,9); /* Tests ran in database number 9. */
+ for (int i = 0; i < 10; i++) {
+ RedisModule_ThreadSafeContextLock(ctx);
+ RedisModule_Replicate(ctx,"INCR","c","thread");
+ RedisModule_ThreadSafeContextUnlock(ctx);
+ }
+ RedisModule_FreeThreadSafeContext(ctx);
+ return NULL;
+}
+
+int propagateTestCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ RedisModuleTimerID timer_id =
+ RedisModule_CreateTimer(ctx,100,timerHandler,NULL);
+ REDISMODULE_NOT_USED(timer_id);
+
+ pthread_t tid;
+ if (pthread_create(&tid,NULL,threadMain,NULL) != 0)
+ return RedisModule_ReplyWithError(ctx,"-ERR Can't start thread");
+ REDISMODULE_NOT_USED(tid);
+
+ RedisModule_ReplyWithSimpleString(ctx,"OK");
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"propagate-test",1,REDISMODULE_APIVER_1)
+ == REDISMODULE_ERR) return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"propagate-test",
+ propagateTestCommand,
+ "",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+ return REDISMODULE_OK;
+}
diff --git a/tests/modules/testrdb.c b/tests/modules/testrdb.c
new file mode 100644
index 000000000..d73c8bfd3
--- /dev/null
+++ b/tests/modules/testrdb.c
@@ -0,0 +1,240 @@
+#include "redismodule.h"
+
+#include <string.h>
+#include <assert.h>
+
+/* Module configuration, save aux or not? */
+long long conf_aux_count = 0;
+
+/* Registered type */
+RedisModuleType *testrdb_type = NULL;
+
+/* Global values to store and persist to aux */
+RedisModuleString *before_str = NULL;
+RedisModuleString *after_str = NULL;
+
+void *testrdb_type_load(RedisModuleIO *rdb, int encver) {
+ int count = RedisModule_LoadSigned(rdb);
+ if (RedisModule_IsIOError(rdb))
+ return NULL;
+ assert(count==1);
+ assert(encver==1);
+ RedisModuleString *str = RedisModule_LoadString(rdb);
+ return str;
+}
+
+void testrdb_type_save(RedisModuleIO *rdb, void *value) {
+ RedisModuleString *str = (RedisModuleString*)value;
+ RedisModule_SaveSigned(rdb, 1);
+ RedisModule_SaveString(rdb, str);
+}
+
+void testrdb_aux_save(RedisModuleIO *rdb, int when) {
+ if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
+ if (conf_aux_count==0) assert(0);
+ if (when == REDISMODULE_AUX_BEFORE_RDB) {
+ if (before_str) {
+ RedisModule_SaveSigned(rdb, 1);
+ RedisModule_SaveString(rdb, before_str);
+ } else {
+ RedisModule_SaveSigned(rdb, 0);
+ }
+ } else {
+ if (after_str) {
+ RedisModule_SaveSigned(rdb, 1);
+ RedisModule_SaveString(rdb, after_str);
+ } else {
+ RedisModule_SaveSigned(rdb, 0);
+ }
+ }
+}
+
+int testrdb_aux_load(RedisModuleIO *rdb, int encver, int when) {
+ assert(encver == 1);
+ if (conf_aux_count==1) assert(when == REDISMODULE_AUX_AFTER_RDB);
+ if (conf_aux_count==0) assert(0);
+ RedisModuleCtx *ctx = RedisModule_GetContextFromIO(rdb);
+ if (when == REDISMODULE_AUX_BEFORE_RDB) {
+ if (before_str)
+ RedisModule_FreeString(ctx, before_str);
+ before_str = NULL;
+ int count = RedisModule_LoadSigned(rdb);
+ if (RedisModule_IsIOError(rdb))
+ return REDISMODULE_ERR;
+ if (count)
+ before_str = RedisModule_LoadString(rdb);
+ } else {
+ if (after_str)
+ RedisModule_FreeString(ctx, after_str);
+ after_str = NULL;
+ int count = RedisModule_LoadSigned(rdb);
+ if (RedisModule_IsIOError(rdb))
+ return REDISMODULE_ERR;
+ if (count)
+ after_str = RedisModule_LoadString(rdb);
+ }
+ if (RedisModule_IsIOError(rdb))
+ return REDISMODULE_ERR;
+ return REDISMODULE_OK;
+}
+
+void testrdb_type_free(void *value) {
+ if (value)
+ RedisModule_FreeString(NULL, (RedisModuleString*)value);
+}
+
+int testrdb_set_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ if (argc != 2) {
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+
+ if (before_str)
+ RedisModule_FreeString(ctx, before_str);
+ before_str = argv[1];
+ RedisModule_RetainString(ctx, argv[1]);
+ RedisModule_ReplyWithLongLong(ctx, 1);
+ return REDISMODULE_OK;
+}
+
+int testrdb_get_before(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ REDISMODULE_NOT_USED(argv);
+ if (argc != 1){
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+ if (before_str)
+ RedisModule_ReplyWithString(ctx, before_str);
+ else
+ RedisModule_ReplyWithStringBuffer(ctx, "", 0);
+ return REDISMODULE_OK;
+}
+
+int testrdb_set_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ if (argc != 2){
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+
+ if (after_str)
+ RedisModule_FreeString(ctx, after_str);
+ after_str = argv[1];
+ RedisModule_RetainString(ctx, argv[1]);
+ RedisModule_ReplyWithLongLong(ctx, 1);
+ return REDISMODULE_OK;
+}
+
+int testrdb_get_after(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ REDISMODULE_NOT_USED(argv);
+ if (argc != 1){
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+ if (after_str)
+ RedisModule_ReplyWithString(ctx, after_str);
+ else
+ RedisModule_ReplyWithStringBuffer(ctx, "", 0);
+ return REDISMODULE_OK;
+}
+
+int testrdb_set_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ if (argc != 3){
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+
+ RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
+ RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
+ if (str)
+ RedisModule_FreeString(ctx, str);
+ RedisModule_ModuleTypeSetValue(key, testrdb_type, argv[2]);
+ RedisModule_RetainString(ctx, argv[2]);
+ RedisModule_CloseKey(key);
+ RedisModule_ReplyWithLongLong(ctx, 1);
+ return REDISMODULE_OK;
+}
+
+int testrdb_get_key(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
+{
+ if (argc != 2){
+ RedisModule_WrongArity(ctx);
+ return REDISMODULE_OK;
+ }
+
+ RedisModuleKey *key = RedisModule_OpenKey(ctx, argv[1], REDISMODULE_WRITE);
+ RedisModuleString *str = RedisModule_ModuleTypeGetValue(key);
+ RedisModule_CloseKey(key);
+ RedisModule_ReplyWithString(ctx, str);
+ return REDISMODULE_OK;
+}
+
+int RedisModule_OnLoad(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
+ REDISMODULE_NOT_USED(argv);
+ REDISMODULE_NOT_USED(argc);
+
+ if (RedisModule_Init(ctx,"testrdb",1,REDISMODULE_APIVER_1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ RedisModule_SetModuleOptions(ctx, REDISMODULE_OPTIONS_HANDLE_IO_ERRORS);
+
+ if (argc > 0)
+ RedisModule_StringToLongLong(argv[0], &conf_aux_count);
+
+ if (conf_aux_count==0) {
+ RedisModuleTypeMethods datatype_methods = {
+ .version = 1,
+ .rdb_load = testrdb_type_load,
+ .rdb_save = testrdb_type_save,
+ .aof_rewrite = NULL,
+ .digest = NULL,
+ .free = testrdb_type_free,
+ };
+
+ testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
+ if (testrdb_type == NULL)
+ return REDISMODULE_ERR;
+ } else {
+ RedisModuleTypeMethods datatype_methods = {
+ .version = REDISMODULE_TYPE_METHOD_VERSION,
+ .rdb_load = testrdb_type_load,
+ .rdb_save = testrdb_type_save,
+ .aof_rewrite = NULL,
+ .digest = NULL,
+ .free = testrdb_type_free,
+ .aux_load = testrdb_aux_load,
+ .aux_save = testrdb_aux_save,
+ .aux_save_triggers = (conf_aux_count == 1 ?
+ REDISMODULE_AUX_AFTER_RDB :
+ REDISMODULE_AUX_BEFORE_RDB | REDISMODULE_AUX_AFTER_RDB)
+ };
+
+ testrdb_type = RedisModule_CreateDataType(ctx, "test__rdb", 1, &datatype_methods);
+ if (testrdb_type == NULL)
+ return REDISMODULE_ERR;
+ }
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.set.before", testrdb_set_before,"deny-oom",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.get.before", testrdb_get_before,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.set.after", testrdb_set_after,"deny-oom",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.get.after", testrdb_get_after,"",0,0,0) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.set.key", testrdb_set_key,"deny-oom",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ if (RedisModule_CreateCommand(ctx,"testrdb.get.key", testrdb_get_key,"",1,1,1) == REDISMODULE_ERR)
+ return REDISMODULE_ERR;
+
+ return REDISMODULE_OK;
+}
diff --git a/tests/sentinel/run.tcl b/tests/sentinel/run.tcl
index 9a2fcfb49..996af906a 100644
--- a/tests/sentinel/run.tcl
+++ b/tests/sentinel/run.tcl
@@ -6,6 +6,7 @@ cd tests/sentinel
source ../instances.tcl
set ::instances_count 5 ; # How many instances we use at max.
+set ::tlsdir "../../tls"
proc main {} {
parse_options
diff --git a/tests/sentinel/tests/00-base.tcl b/tests/sentinel/tests/00-base.tcl
index a79d0c371..7fb1a8bef 100644
--- a/tests/sentinel/tests/00-base.tcl
+++ b/tests/sentinel/tests/00-base.tcl
@@ -17,7 +17,7 @@ test "Basic failover works if the master is down" {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "At least one Sentinel did not received failover info"
+ fail "At least one Sentinel did not receive failover info"
}
}
restart_instance redis $master_id
@@ -108,7 +108,7 @@ test "Failover works if we configure for absolute agreement" {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "At least one Sentinel did not received failover info"
+ fail "At least one Sentinel did not receive failover info"
}
}
restart_instance redis $master_id
diff --git a/tests/sentinel/tests/01-conf-update.tcl b/tests/sentinel/tests/01-conf-update.tcl
index 4998104d2..d45b1b08e 100644
--- a/tests/sentinel/tests/01-conf-update.tcl
+++ b/tests/sentinel/tests/01-conf-update.tcl
@@ -16,7 +16,7 @@ test "We can failover with Sentinel 1 crashed" {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "Sentinel $id did not received failover info"
+ fail "Sentinel $id did not receive failover info"
}
}
}
@@ -30,7 +30,7 @@ test "After Sentinel 1 is restarted, its config gets updated" {
wait_for_condition 1000 50 {
[lindex [S 1 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "Restarted Sentinel did not received failover info"
+ fail "Restarted Sentinel did not receive failover info"
}
}
diff --git a/tests/sentinel/tests/02-slaves-reconf.tcl b/tests/sentinel/tests/02-slaves-reconf.tcl
index fa15d2efd..28964c968 100644
--- a/tests/sentinel/tests/02-slaves-reconf.tcl
+++ b/tests/sentinel/tests/02-slaves-reconf.tcl
@@ -36,7 +36,7 @@ proc 02_crash_and_failover {} {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "At least one Sentinel did not received failover info"
+ fail "At least one Sentinel did not receive failover info"
}
}
restart_instance redis $master_id
diff --git a/tests/sentinel/tests/05-manual.tcl b/tests/sentinel/tests/05-manual.tcl
index 5214fdce1..ed568aa03 100644
--- a/tests/sentinel/tests/05-manual.tcl
+++ b/tests/sentinel/tests/05-manual.tcl
@@ -12,7 +12,7 @@ test "Manual failover works" {
wait_for_condition 1000 50 {
[lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 1] != $old_port
} else {
- fail "At least one Sentinel did not received failover info"
+ fail "At least one Sentinel did not receive failover info"
}
}
set addr [S 0 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster]
diff --git a/tests/sentinel/tests/07-down-conditions.tcl b/tests/sentinel/tests/07-down-conditions.tcl
index a60656e59..a12ea3151 100644
--- a/tests/sentinel/tests/07-down-conditions.tcl
+++ b/tests/sentinel/tests/07-down-conditions.tcl
@@ -1,6 +1,7 @@
# Test conditions where an instance is considered to be down
source "../tests/includes/init-tests.tcl"
+source "../../../tests/support/cli.tcl"
proc ensure_master_up {} {
wait_for_condition 1000 50 {
@@ -28,7 +29,7 @@ test "Crash the majority of Sentinels to prevent failovers for this unit" {
test "SDOWN is triggered by non-responding but not crashed instance" {
lassign [S 4 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] host port
ensure_master_up
- exec ../../../src/redis-cli -h $host -p $port debug sleep 10 > /dev/null &
+ exec ../../../src/redis-cli -h $host -p $port {*}[rediscli_tls_config "../../../tests"] debug sleep 10 > /dev/null &
ensure_master_down
ensure_master_up
}
@@ -66,3 +67,13 @@ test "SDOWN is triggered by misconfigured instance repling with errors" {
R 0 bgsave
ensure_master_up
}
+
+# We use this test setup to also test command renaming, as a side
+# effect of the master going down if we send PONG instead of PING
+test "SDOWN is triggered if we rename PING to PONG" {
+ ensure_master_up
+ S 4 SENTINEL SET mymaster rename-command PING PONG
+ ensure_master_down
+ S 4 SENTINEL SET mymaster rename-command PING PING
+ ensure_master_up
+}
diff --git a/tests/support/cli.tcl b/tests/support/cli.tcl
new file mode 100644
index 000000000..37c902a50
--- /dev/null
+++ b/tests/support/cli.tcl
@@ -0,0 +1,19 @@
+proc rediscli_tls_config {testsdir} {
+ set tlsdir [file join $testsdir tls]
+ set cert [file join $tlsdir redis.crt]
+ set key [file join $tlsdir redis.key]
+ set cacert [file join $tlsdir ca.crt]
+
+ if {$::tls} {
+ return [list --tls --cert $cert --key $key --cacert $cacert]
+ } else {
+ return {}
+ }
+}
+
+proc rediscli {port {opts {}}} {
+ set cmd [list src/redis-cli -p $port]
+ lappend cmd {*}[rediscli_tls_config "tests"]
+ lappend cmd {*}$opts
+ return $cmd
+}
diff --git a/tests/support/cluster.tcl b/tests/support/cluster.tcl
index 1576053b4..74587e1f7 100644
--- a/tests/support/cluster.tcl
+++ b/tests/support/cluster.tcl
@@ -62,7 +62,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
lassign [split $ip_port :] start_host start_port
if {[catch {
set r {}
- set r [redis $start_host $start_port]
+ set r [redis $start_host $start_port 0 $::tls]
set nodes_descr [$r cluster nodes]
$r close
} e]} {
@@ -107,7 +107,7 @@ proc ::redis_cluster::__method__refresh_nodes_map {id} {
# Connect to the node
set link {}
- catch {set link [redis $host $port]}
+ catch {set link [redis $host $port 0 $::tls]}
# Build this node description as an hash.
set node [dict create \
diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl
index cd8ae3a34..a90ac7f29 100644
--- a/tests/support/redis.tcl
+++ b/tests/support/redis.tcl
@@ -39,8 +39,18 @@ array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks
-proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
- set fd [socket $server $port]
+proc redis {{server 127.0.0.1} {port 6379} {defer 0} {tls 0} {tlsoptions {}}} {
+ if {$tls} {
+ package require tls
+ ::tls::init \
+ -cafile "$::tlsdir/ca.crt" \
+ -certfile "$::tlsdir/redis.crt" \
+ -keyfile "$::tlsdir/redis.key" \
+ {*}$tlsoptions
+ set fd [::tls::socket $server $port]
+ } else {
+ set fd [socket $server $port]
+ }
fconfigure $fd -translation binary
set id [incr ::redis::id]
set ::redis::fd($id) $fd
@@ -48,6 +58,7 @@ proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set ::redis::blocking($id) 1
set ::redis::deferred($id) $defer
set ::redis::reconnect($id) 0
+ set ::redis::tls $tls
::redis::redis_reset_state $id
interp alias {} ::redis::redisHandle$id {} ::redis::__dispatch__ $id
}
@@ -72,7 +83,11 @@ proc ::redis::__dispatch__raw__ {id method argv} {
# Reconnect the link if needed.
if {$fd eq {}} {
lassign $::redis::addr($id) host port
- set ::redis::fd($id) [socket $host $port]
+ if {$::redis::tls} {
+ set ::redis::fd($id) [::tls::socket $host $port]
+ } else {
+ set ::redis::fd($id) [socket $host $port]
+ }
fconfigure $::redis::fd($id) -translation binary
set fd $::redis::fd($id)
}
diff --git a/tests/support/server.tcl b/tests/support/server.tcl
index c36b30775..b20f1ad36 100644
--- a/tests/support/server.tcl
+++ b/tests/support/server.tcl
@@ -4,7 +4,7 @@ set ::valgrind_errors {}
proc start_server_error {config_file error} {
set err {}
- append err "Cant' start the Redis server\n"
+ append err "Can't start the Redis server\n"
append err "CONFIGURATION:"
append err [exec cat $config_file]
append err "\nERROR:"
@@ -92,7 +92,11 @@ proc is_alive config {
proc ping_server {host port} {
set retval 0
if {[catch {
- set fd [socket $host $port]
+ if {$::tls} {
+ set fd [::tls::socket $host $port]
+ } else {
+ set fd [socket $host $port]
+ }
fconfigure $fd -translation binary
puts $fd "PING\r\n"
flush $fd
@@ -136,7 +140,6 @@ proc tags {tags code} {
uplevel 1 $code
set ::tags [lrange $::tags 0 end-[llength $tags]]
}
-
proc start_server {options {code undefined}} {
# If we are running against an external server, we just push the
# host/port pair in the stack the first time
@@ -145,7 +148,7 @@ proc start_server {options {code undefined}} {
set srv {}
dict set srv "host" $::host
dict set srv "port" $::port
- set client [redis $::host $::port]
+ set client [redis $::host $::port 0 $::tls]
dict set srv "client" $client
$client select 9
@@ -178,6 +181,13 @@ proc start_server {options {code undefined}} {
set data [split [exec cat "tests/assets/$baseconfig"] "\n"]
set config {}
+ if {$::tls} {
+ dict set config "tls-cert-file" [format "%s/tests/tls/redis.crt" [pwd]]
+ dict set config "tls-key-file" [format "%s/tests/tls/redis.key" [pwd]]
+ dict set config "tls-dh-params-file" [format "%s/tests/tls/redis.dh" [pwd]]
+ dict set config "tls-ca-cert-file" [format "%s/tests/tls/ca.crt" [pwd]]
+ dict set config "loglevel" "debug"
+ }
foreach line $data {
if {[string length $line] > 0 && [string index $line 0] ne "#"} {
set elements [split $line " "]
@@ -192,7 +202,17 @@ proc start_server {options {code undefined}} {
# start every server on a different port
set ::port [find_available_port [expr {$::port+1}]]
- dict set config port $::port
+ if {$::tls} {
+ dict set config "port" 0
+ dict set config "tls-port" $::port
+ dict set config "tls-cluster" "yes"
+ dict set config "tls-replication" "yes"
+ } else {
+ dict set config port $::port
+ }
+
+ set unixsocket [file normalize [format "%s/%s" [dict get $config "dir"] "socket"]]
+ dict set config "unixsocket" $unixsocket
# apply overrides from global space and arguments
foreach {directive arguments} [concat $::global_overrides $overrides] {
@@ -254,10 +274,11 @@ proc start_server {options {code undefined}} {
}
# setup properties to be able to initialize a client object
+ set port_param [expr $::tls ? {"tls-port"} : {"port"}]
set host $::host
set port $::port
if {[dict exists $config bind]} { set host [dict get $config bind] }
- if {[dict exists $config port]} { set port [dict get $config port] }
+ if {[dict exists $config $port_param]} { set port [dict get $config $port_param] }
# setup config dict
dict set srv "config_file" $config_file
@@ -267,6 +288,7 @@ proc start_server {options {code undefined}} {
dict set srv "port" $port
dict set srv "stdout" $stdout
dict set srv "stderr" $stderr
+ dict set srv "unixsocket" $unixsocket
# if a block of code is supplied, we wait for the server to become
# available, create a client object and kill the server afterwards
@@ -276,6 +298,12 @@ proc start_server {options {code undefined}} {
error_and_quit $config_file $line
}
+ if {$::wait_server} {
+ set msg "server started PID: [dict get $srv "pid"]. press any key to continue..."
+ puts $msg
+ read stdin 1
+ }
+
while 1 {
# check that the server actually started and is ready for connections
if {[exec grep -i "Ready to accept" | wc -l < $stdout] > 0} {
diff --git a/tests/support/test.tcl b/tests/support/test.tcl
index d60eb3c47..2646acecd 100644
--- a/tests/support/test.tcl
+++ b/tests/support/test.tcl
@@ -1,6 +1,8 @@
set ::num_tests 0
set ::num_passed 0
set ::num_failed 0
+set ::num_skipped 0
+set ::num_aborted 0
set ::tests_failed {}
proc fail {msg} {
@@ -13,6 +15,12 @@ proc assert {condition} {
}
}
+proc assert_no_match {pattern value} {
+ if {[string match $pattern $value]} {
+ error "assertion:Expected '$value' to not match '$pattern'"
+ }
+}
+
proc assert_match {pattern value} {
if {![string match $pattern $value]} {
error "assertion:Expected '$value' to match '$pattern'"
@@ -68,10 +76,26 @@ proc test {name code {okpattern undefined}} {
# abort if tagged with a tag to deny
foreach tag $::denytags {
if {[lsearch $::tags $tag] >= 0} {
+ incr ::num_aborted
+ send_data_packet $::test_server_fd ignore $name
return
}
}
+ # abort if test name in skiptests
+ if {[lsearch $::skiptests $name] >= 0} {
+ incr ::num_skipped
+ send_data_packet $::test_server_fd skip $name
+ return
+ }
+
+ # abort if test name in skiptests
+ if {[llength $::only_tests] > 0 && [lsearch $::only_tests $name] < 0} {
+ incr ::num_skipped
+ send_data_packet $::test_server_fd skip $name
+ return
+ }
+
# check if tagged with at least 1 tag to allow when there *is* a list
# of tags to allow, because default policy is to run everything
if {[llength $::allowtags] > 0} {
@@ -82,6 +106,8 @@ proc test {name code {okpattern undefined}} {
}
}
if {$matched < 1} {
+ incr ::num_aborted
+ send_data_packet $::test_server_fd ignore $name
return
}
}
diff --git a/tests/support/util.tcl b/tests/support/util.tcl
index 64c36b326..7ecf5b79c 100644
--- a/tests/support/util.tcl
+++ b/tests/support/util.tcl
@@ -91,6 +91,33 @@ proc wait_for_sync r {
}
}
+proc wait_for_ofs_sync {r1 r2} {
+ wait_for_condition 50 100 {
+ [status $r1 master_repl_offset] eq [status $r2 master_repl_offset]
+ } else {
+ fail "replica didn't sync in time"
+ }
+}
+
+proc wait_for_log_message {srv_idx pattern last_lines maxtries delay} {
+ set retry $maxtries
+ set stdout [srv $srv_idx stdout]
+ while {$retry} {
+ set result [exec tail -$last_lines < $stdout]
+ set result [split $result "\n"]
+ foreach line $result {
+ if {[string match $pattern $line]} {
+ return $line
+ }
+ }
+ incr retry -1
+ after $delay
+ }
+ if {$retry == 0} {
+ fail "log message of '$pattern' not found"
+ }
+}
+
# Random integer between 0 and max (excluded).
proc randomInt {max} {
expr {int(rand()*$max)}
@@ -368,10 +395,38 @@ proc colorstr {color str} {
# of seconds to the specified Redis instance.
proc start_write_load {host port seconds} {
set tclsh [info nameofexecutable]
- exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
+ exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds $::tls &
}
# Stop a process generating write load executed with start_write_load.
proc stop_write_load {handle} {
catch {exec /bin/kill -9 $handle}
}
+
+proc K { x y } { set x }
+
+# Shuffle a list. From Tcl wiki. Originally from Steve Cohen that improved
+# other versions. Code should be under public domain.
+proc lshuffle {list} {
+ set n [llength $list]
+ while {$n>0} {
+ set j [expr {int(rand()*$n)}]
+ lappend slist [lindex $list $j]
+ incr n -1
+ set temp [lindex $list $n]
+ set list [lreplace [K $list [set list {}]] $j $j $temp]
+ }
+ return $slist
+}
+
+# Execute a background process writing complex data for the specified number
+# of ops to the specified Redis instance.
+proc start_bg_complex_data {host port db ops} {
+ set tclsh [info nameofexecutable]
+ exec $tclsh tests/helpers/bg_complex_data.tcl $host $port $db $ops $::tls &
+}
+
+# Stop a process generating write load executed with start_bg_complex_data.
+proc stop_bg_complex_data {handle} {
+ catch {exec /bin/kill -9 $handle}
+}
diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl
index 7def9a7f6..cb7e4e328 100644
--- a/tests/test_helper.tcl
+++ b/tests/test_helper.tcl
@@ -27,12 +27,15 @@ set ::all_tests {
unit/type/zset
unit/type/hash
unit/type/stream
+ unit/type/stream-cgroups
unit/sort
unit/expire
unit/other
unit/multi
unit/quit
unit/aofrw
+ unit/acl
+ integration/block-repl
integration/replication
integration/replication-2
integration/replication-3
@@ -59,6 +62,8 @@ set ::all_tests {
unit/hyperloglog
unit/lazyfree
unit/wait
+ unit/pendingquerybuf
+ unit/tls
}
# Index to the next test to run in the ::all_tests list.
set ::next_test 0
@@ -67,11 +72,16 @@ set ::host 127.0.0.1
set ::port 21111
set ::traceleaks 0
set ::valgrind 0
+set ::tls 0
set ::stack_logging 0
set ::verbose 0
set ::quiet 0
set ::denytags {}
+set ::skiptests {}
set ::allowtags {}
+set ::only_tests {}
+set ::single_tests {}
+set ::skip_till ""
set ::external 0; # If "1" this means, we are running against external instance
set ::file ""; # If set, runs only the tests in this comma separated list
set ::curfile ""; # Hold the filename of the current suite
@@ -80,6 +90,11 @@ set ::force_failure 0
set ::timeout 600; # 10 minutes without progresses will quit the test.
set ::last_progress [clock seconds]
set ::active_servers {} ; # Pids of active Redis instances.
+set ::dont_clean 0
+set ::wait_server 0
+set ::stop_on_failure 0
+set ::loop 0
+set ::tlsdir "tests/tls"
# Set to 1 when we are running in client mode. The Redis test uses a
# server-client model to run tests simultaneously. The server instance
@@ -134,7 +149,7 @@ proc reconnect {args} {
set host [dict get $srv "host"]
set port [dict get $srv "port"]
set config [dict get $srv "config"]
- set client [redis $host $port]
+ set client [redis $host $port 0 $::tls]
dict set srv "client" $client
# select the right db when we don't have to authenticate
@@ -154,7 +169,7 @@ proc redis_deferring_client {args} {
}
# create client that defers reading reply
- set client [redis [srv $level "host"] [srv $level "port"] 1]
+ set client [redis [srv $level "host"] [srv $level "port"] 1 $::tls]
# select the right db and read the response (OK)
$client select 9
@@ -173,6 +188,9 @@ proc s {args} {
}
proc cleanup {} {
+ if {$::dont_clean} {
+ return
+ }
if {!$::quiet} {puts -nonewline "Cleanup: may take some time... "}
flush stdout
catch {exec rm -rf {*}[glob tests/tmp/redis.conf.*]}
@@ -189,7 +207,7 @@ proc test_server_main {} {
if {!$::quiet} {
puts "Starting test server at port $port"
}
- socket -server accept_test_clients -myaddr 127.0.0.1 $port
+ socket -server accept_test_clients -myaddr 127.0.0.1 $port
# Start the client instances
set ::clients_pids {}
@@ -222,6 +240,7 @@ proc test_server_cron {} {
if {$elapsed > $::timeout} {
set err "\[[colorstr red TIMEOUT]\]: clients state report follows."
puts $err
+ lappend ::failed_tests $err
show_clients_state
kill_clients
force_kill_all_servers
@@ -246,6 +265,8 @@ proc accept_test_clients {fd addr port} {
# testing: just used to signal that a given test started.
# ok: a test was executed with success.
# err: a test was executed with an error.
+# skip: a test was skipped by skipfile or individual test options.
+# ignore: a test was skipped by a group tag.
# exception: there was a runtime exception while executing the test.
# done: all the specified test file was processed, this test client is
# ready to accept a new task.
@@ -274,11 +295,24 @@ proc read_from_test_client fd {
puts "\[[colorstr green $status]\]: $data"
}
set ::active_clients_task($fd) "(OK) $data"
+ } elseif {$status eq {skip}} {
+ if {!$::quiet} {
+ puts "\[[colorstr yellow $status]\]: $data"
+ }
+ } elseif {$status eq {ignore}} {
+ if {!$::quiet} {
+ puts "\[[colorstr cyan $status]\]: $data"
+ }
} elseif {$status eq {err}} {
set err "\[[colorstr red $status]\]: $data"
puts $err
lappend ::failed_tests $err
set ::active_clients_task($fd) "(ERR) $data"
+ if {$::stop_on_failure} {
+ puts -nonewline "(Test stopped, press enter to continue)"
+ flush stdout
+ gets stdin
+ }
} elseif {$status eq {exception}} {
puts "\[[colorstr red $status]\]: $data"
kill_clients
@@ -341,6 +375,9 @@ proc signal_idle_client fd {
send_data_packet $fd run [lindex $::all_tests $::next_test]
lappend ::active_clients $fd
incr ::next_test
+ if {$::loop && $::next_test == [llength $::all_tests]} {
+ set ::next_test 0
+ }
} else {
lappend ::idle_clients $fd
if {[llength $::active_clients] == 0} {
@@ -403,11 +440,20 @@ proc print_help_screen {} {
"--stack-logging Enable OSX leaks/malloc stack logging."
"--accurate Run slow randomized tests for more iterations."
"--quiet Don't show individual tests."
- "--single <unit> Just execute the specified unit (see next option)."
+ "--single <unit> Just execute the specified unit (see next option). this option can be repeated."
"--list-tests List all the available test units."
+ "--only <test> Just execute the specified test by test name. this option can be repeated."
+ "--skip-till <unit> Skip all units until (and including) the specified one."
"--clients <num> Number of test clients (default 16)."
"--timeout <sec> Test timeout in seconds (default 10 min)."
"--force-failure Force the execution of a test that always fails."
+ "--config <k> <v> Extra config file argument."
+ "--skipfile <file> Name of a file containing test names that should be skipped (one per line)."
+ "--dont-clean Don't delete redis log files after the run."
+ "--stop Blocks once the first test fails."
+ "--loop Execute the specified set of tests forever."
+ "--wait-server Wait after server is started (so that you can attach a debugger)."
+ "--tls Run tests in TLS mode."
"--help Print this help screen."
} "\n"]
}
@@ -425,6 +471,17 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
}
incr j
+ } elseif {$opt eq {--config}} {
+ set arg2 [lindex $argv [expr $j+2]]
+ lappend ::global_overrides $arg
+ lappend ::global_overrides $arg2
+ incr j 2
+ } elseif {$opt eq {--skipfile}} {
+ incr j
+ set fp [open $arg r]
+ set file_data [read $fp]
+ close $fp
+ set ::skiptests [split $file_data "\n"]
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
} elseif {$opt eq {--stack-logging}} {
@@ -433,6 +490,13 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
} elseif {$opt eq {--quiet}} {
set ::quiet 1
+ } elseif {$opt eq {--tls}} {
+ package require tls 1.6
+ set ::tls 1
+ ::tls::init \
+ -cafile "$::tlsdir/ca.crt" \
+ -certfile "$::tlsdir/redis.crt" \
+ -keyfile "$::tlsdir/redis.key"
} elseif {$opt eq {--host}} {
set ::external 1
set ::host $arg
@@ -445,13 +509,21 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--force-failure}} {
set ::force_failure 1
} elseif {$opt eq {--single}} {
- set ::all_tests $arg
+ lappend ::single_tests $arg
+ incr j
+ } elseif {$opt eq {--only}} {
+ lappend ::only_tests $arg
+ incr j
+ } elseif {$opt eq {--skip-till}} {
+ set ::skip_till $arg
incr j
} elseif {$opt eq {--list-tests}} {
foreach t $::all_tests {
puts $t
}
exit 0
+ } elseif {$opt eq {--verbose}} {
+ set ::verbose 1
} elseif {$opt eq {--client}} {
set ::client 1
set ::test_server_port $arg
@@ -459,6 +531,14 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} elseif {$opt eq {--clients}} {
set ::numclients $arg
incr j
+ } elseif {$opt eq {--dont-clean}} {
+ set ::dont_clean 1
+ } elseif {$opt eq {--wait-server}} {
+ set ::wait_server 1
+ } elseif {$opt eq {--stop}} {
+ set ::stop_on_failure 1
+ } elseif {$opt eq {--loop}} {
+ set ::loop 1
} elseif {$opt eq {--timeout}} {
set ::timeout $arg
incr j
@@ -471,8 +551,36 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
}
}
+# If --skil-till option was given, we populate the list of single tests
+# to run with everything *after* the specified unit.
+if {$::skip_till != ""} {
+ set skipping 1
+ foreach t $::all_tests {
+ if {$skipping == 0} {
+ lappend ::single_tests $t
+ }
+ if {$t == $::skip_till} {
+ set skipping 0
+ }
+ }
+ if {$skipping} {
+ puts "test $::skip_till not found"
+ exit 0
+ }
+}
+
+# Override the list of tests with the specific tests we want to run
+# in case there was some filter, that is --single or --skip-till options.
+if {[llength $::single_tests] > 0} {
+ set ::all_tests $::single_tests
+}
+
proc attach_to_replication_stream {} {
- set s [socket [srv 0 "host"] [srv 0 "port"]]
+ if {$::tls} {
+ set s [::tls::socket [srv 0 "host"] [srv 0 "port"]]
+ } else {
+ set s [socket [srv 0 "host"] [srv 0 "port"]]
+ }
fconfigure $s -translation binary
puts -nonewline $s "SYNC\r\n"
flush $s
diff --git a/tests/unit/acl.tcl b/tests/unit/acl.tcl
new file mode 100644
index 000000000..2205d2d86
--- /dev/null
+++ b/tests/unit/acl.tcl
@@ -0,0 +1,144 @@
+start_server {tags {"acl"}} {
+ test {Connections start with the default user} {
+ r ACL WHOAMI
+ } {default}
+
+ test {It is possible to create new users} {
+ r ACL setuser newuser
+ }
+
+ test {New users start disabled} {
+ r ACL setuser newuser >passwd1
+ catch {r AUTH newuser passwd1} err
+ set err
+ } {*WRONGPASS*}
+
+ test {Enabling the user allows the login} {
+ r ACL setuser newuser on +acl
+ r AUTH newuser passwd1
+ r ACL WHOAMI
+ } {newuser}
+
+ test {Only the set of correct passwords work} {
+ r ACL setuser newuser >passwd2
+ catch {r AUTH newuser passwd1} e
+ assert {$e eq "OK"}
+ catch {r AUTH newuser passwd2} e
+ assert {$e eq "OK"}
+ catch {r AUTH newuser passwd3} e
+ set e
+ } {*WRONGPASS*}
+
+ test {It is possible to remove passwords from the set of valid ones} {
+ r ACL setuser newuser <passwd1
+ catch {r AUTH newuser passwd1} e
+ set e
+ } {*WRONGPASS*}
+
+ test {Test password hashes can be added} {
+ r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
+ catch {r AUTH newuser passwd4} e
+ assert {$e eq "OK"}
+ }
+
+ test {Test password hashes validate input} {
+ # Validate Length
+ catch {r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e} e
+ # Validate character outside set
+ catch {r ACL setuser newuser #34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4eq} e
+ set e
+ } {*Error in ACL SETUSER modifier*}
+
+ test {ACL GETUSER returns the password hash instead of the actual password} {
+ set passstr [dict get [r ACL getuser newuser] passwords]
+ assert_match {*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*} $passstr
+ assert_no_match {*passwd4*} $passstr
+ }
+
+ test {Test hashed passwords removal} {
+ r ACL setuser newuser !34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6
+ set passstr [dict get [r ACL getuser newuser] passwords]
+ assert_no_match {*34344e4d60c2b6d639b7bd22e18f2b0b91bc34bf0ac5f9952744435093cfb4e6*} $passstr
+ }
+
+ test {By default users are not able to access any command} {
+ catch {r SET foo bar} e
+ set e
+ } {*NOPERM*}
+
+ test {By default users are not able to access any key} {
+ r ACL setuser newuser +set
+ catch {r SET foo bar} e
+ set e
+ } {*NOPERM*key*}
+
+ test {It's possible to allow the access of a subset of keys} {
+ r ACL setuser newuser allcommands ~foo:* ~bar:*
+ r SET foo:1 a
+ r SET bar:2 b
+ catch {r SET zap:3 c} e
+ r ACL setuser newuser allkeys; # Undo keys ACL
+ set e
+ } {*NOPERM*key*}
+
+ test {Users can be configured to authenticate with any password} {
+ r ACL setuser newuser nopass
+ r AUTH newuser zipzapblabla
+ } {OK}
+
+ test {ACLs can exclude single commands} {
+ r ACL setuser newuser -ping
+ r INCR mycounter ; # Should not raise an error
+ catch {r PING} e
+ set e
+ } {*NOPERM*}
+
+ test {ACLs can include or exclude whole classes of commands} {
+ r ACL setuser newuser -@all +@set +acl
+ r SADD myset a b c; # Should not raise an error
+ r ACL setuser newuser +@all -@string
+ r SADD myset a b c; # Again should not raise an error
+ # String commands instead should raise an error
+ catch {r SET foo bar} e
+ r ACL setuser newuser allcommands; # Undo commands ACL
+ set e
+ } {*NOPERM*}
+
+ test {ACLs can include single subcommands} {
+ r ACL setuser newuser +@all -client
+ r ACL setuser newuser +client|id +client|setname
+ r CLIENT ID; # Should not fail
+ r CLIENT SETNAME foo ; # Should not fail
+ catch {r CLIENT KILL type master} e
+ set e
+ } {*NOPERM*}
+
+ # Note that the order of the generated ACL rules is not stable in Redis
+ # so we need to match the different parts and not as a whole string.
+ test {ACL GETUSER is able to translate back command permissions} {
+ # Subtractive
+ r ACL setuser newuser reset +@all ~* -@string +incr -debug +debug|digest
+ set cmdstr [dict get [r ACL getuser newuser] commands]
+ assert_match {*+@all*} $cmdstr
+ assert_match {*-@string*} $cmdstr
+ assert_match {*+incr*} $cmdstr
+ assert_match {*-debug +debug|digest**} $cmdstr
+
+ # Additive
+ r ACL setuser newuser reset +@string -incr +acl +debug|digest +debug|segfault
+ set cmdstr [dict get [r ACL getuser newuser] commands]
+ assert_match {*-@all*} $cmdstr
+ assert_match {*+@string*} $cmdstr
+ assert_match {*-incr*} $cmdstr
+ assert_match {*+debug|digest*} $cmdstr
+ assert_match {*+debug|segfault*} $cmdstr
+ assert_match {*+acl*} $cmdstr
+ }
+
+ test {ACL #5998 regression: memory leaks adding / removing subcommands} {
+ r AUTH default ""
+ r ACL setuser newuser reset -debug +debug|a +debug|b +debug|c
+ r ACL setuser newuser -debug
+ # The test framework will detect a leak if any.
+ }
+}
diff --git a/tests/unit/aofrw.tcl b/tests/unit/aofrw.tcl
index dff7588ff..1a686a2fa 100644
--- a/tests/unit/aofrw.tcl
+++ b/tests/unit/aofrw.tcl
@@ -64,7 +64,7 @@ start_server {tags {"aofrw"}} {
}
}
-start_server {tags {"aofrw"}} {
+start_server {tags {"aofrw"} overrides {aof-use-rdb-preamble no}} {
test {Turning off AOF kills the background writing child if any} {
r config set appendonly yes
waitForBgrewriteaof r
diff --git a/tests/unit/auth.tcl b/tests/unit/auth.tcl
index 633cda95c..9080d4bf7 100644
--- a/tests/unit/auth.tcl
+++ b/tests/unit/auth.tcl
@@ -2,14 +2,14 @@ start_server {tags {"auth"}} {
test {AUTH fails if there is no password configured server side} {
catch {r auth foo} err
set _ $err
- } {ERR*no password*}
+ } {ERR*any password*}
}
start_server {tags {"auth"} overrides {requirepass foobar}} {
test {AUTH fails when a wrong password is given} {
catch {r auth wrong!} err
set _ $err
- } {ERR*invalid password}
+ } {WRONGPASS*}
test {Arbitrary command gives an error when AUTH is required} {
catch {r set foo bar} err
diff --git a/tests/unit/dump.tcl b/tests/unit/dump.tcl
index 8bb0165c6..062d803b5 100644
--- a/tests/unit/dump.tcl
+++ b/tests/unit/dump.tcl
@@ -25,6 +25,39 @@ start_server {tags {"dump"}} {
assert {$ttl >= (2569591501-3000) && $ttl <= 2569591501}
r get foo
} {bar}
+
+ test {RESTORE can set an absolute expire} {
+ r set foo bar
+ set encoded [r dump foo]
+ r del foo
+ set now [clock milliseconds]
+ r restore foo [expr $now+3000] $encoded absttl
+ set ttl [r pttl foo]
+ assert {$ttl >= 2900 && $ttl <= 3100}
+ r get foo
+ } {bar}
+
+ test {RESTORE can set LRU} {
+ r set foo bar
+ set encoded [r dump foo]
+ r del foo
+ r config set maxmemory-policy allkeys-lru
+ r restore foo 0 $encoded idletime 1000
+ set idle [r object idletime foo]
+ assert {$idle >= 1000 && $idle <= 1010}
+ r get foo
+ } {bar}
+
+ test {RESTORE can set LFU} {
+ r set foo bar
+ set encoded [r dump foo]
+ r del foo
+ r config set maxmemory-policy allkeys-lfu
+ r restore foo 0 $encoded freq 100
+ set freq [r object freq foo]
+ assert {$freq == 100}
+ r get foo
+ } {bar}
test {RESTORE returns an error of the key already exists} {
r set foo bar
@@ -246,7 +279,7 @@ start_server {tags {"dump"}} {
set e
} {*empty string*}
- test {MIGRATE with mutliple keys migrate just existing ones} {
+ test {MIGRATE with multiple keys migrate just existing ones} {
set first [srv 0 client]
r set key1 "v1"
r set key2 "v2"
@@ -329,7 +362,7 @@ start_server {tags {"dump"}} {
r -1 lpush list a b c d
$second config set requirepass foobar2
catch {r -1 migrate $second_host $second_port list 9 5000 AUTH foobar} err
- assert_match {*invalid password*} $err
+ assert_match {*WRONGPASS*} $err
}
}
}
diff --git a/tests/unit/expire.tcl b/tests/unit/expire.tcl
index eddc7c303..de24eabed 100644
--- a/tests/unit/expire.tcl
+++ b/tests/unit/expire.tcl
@@ -121,7 +121,7 @@ start_server {tags {"expire"}} {
list $a $b
} {somevalue {}}
- test {TTL returns tiem to live in seconds} {
+ test {TTL returns time to live in seconds} {
r del x
r setex x 10 somevalue
set ttl [r ttl x]
diff --git a/tests/unit/geo.tcl b/tests/unit/geo.tcl
index 604697be4..76b9bda38 100644
--- a/tests/unit/geo.tcl
+++ b/tests/unit/geo.tcl
@@ -61,6 +61,7 @@ set regression_vectors {
{939895 151 59.149620271823181 65.204186651485145}
{1412 156 149.29737817929004 15.95807862745508}
{564862 149 84.062063109158544 -65.685403922426232}
+ {1546032440391 16751 -1.8175081637769495 20.665668878082954}
}
set rv_idx 0
@@ -128,7 +129,7 @@ start_server {tags {"geo"}} {
r del points
r geoadd points -5.6 42.6 test
lindex [r geohash points test] 0
- } {ezs42e44yx0}
+ } {ezs42e44yx}
test {GEOPOS simple} {
r del points
@@ -274,8 +275,19 @@ start_server {tags {"geo"}} {
foreach place $diff {
set mydist [geo_distance $lon $lat $search_lon $search_lat]
set mydist [expr $mydist/1000]
- if {($mydist / $radius_km) > 0.999} {incr rounding_errors}
+ if {($mydist / $radius_km) > 0.999} {
+ incr rounding_errors
+ continue
+ }
+ if {$mydist < $radius_m} {
+ # This is a false positive for redis since given the
+ # same points the higher precision calculation provided
+ # by TCL shows the point within range
+ incr rounding_errors
+ continue
+ }
}
+
# Make sure this is a real error and not a rounidng issue.
if {[llength $diff] == $rounding_errors} {
set res $res2; # Error silenced
diff --git a/tests/unit/hyperloglog.tcl b/tests/unit/hyperloglog.tcl
index 7d36b7a35..712fcc641 100644
--- a/tests/unit/hyperloglog.tcl
+++ b/tests/unit/hyperloglog.tcl
@@ -115,6 +115,34 @@ start_server {tags {"hll"}} {
set e
} {*WRONGTYPE*}
+ test {Fuzzing dense/sparse encoding: Redis should always detect errors} {
+ for {set j 0} {$j < 1000} {incr j} {
+ r del hll
+ set items {}
+ set numitems [randomInt 3000]
+ for {set i 0} {$i < $numitems} {incr i} {
+ lappend items [expr {rand()}]
+ }
+ r pfadd hll {*}$items
+
+ # Corrupt it in some random way.
+ for {set i 0} {$i < 5} {incr i} {
+ set len [r strlen hll]
+ set pos [randomInt $len]
+ set byte [randstring 1 1 binary]
+ r setrange hll $pos $byte
+ # Don't modify more bytes 50% of times
+ if {rand() < 0.5} break
+ }
+
+ # Use the hyperloglog to check if it crashes
+ # Redis in some way.
+ catch {
+ r pfcount hll
+ }
+ }
+ }
+
test {PFADD, PFCOUNT, PFMERGE type checking works} {
r set foo bar
catch {r pfadd foo 1} e
diff --git a/tests/unit/introspection-2.tcl b/tests/unit/introspection-2.tcl
index 350a8a016..55f656956 100644
--- a/tests/unit/introspection-2.tcl
+++ b/tests/unit/introspection-2.tcl
@@ -1,3 +1,9 @@
+proc cmdstat {cmd} {
+ if {[regexp "\r\ncmdstat_$cmd:(.*?)\r\n" [r info commandstats] _ value]} {
+ set _ $value
+ }
+}
+
start_server {tags {"introspection"}} {
test {TTL and TYPYE do not alter the last access time of a key} {
r set foo bar
@@ -20,4 +26,55 @@ start_server {tags {"introspection"}} {
r set key2 2
r touch key0 key1 key2 key3
} 2
+
+ test {command stats for GEOADD} {
+ r config resetstat
+ r GEOADD foo 0 0 bar
+ assert_match {*calls=1,*} [cmdstat geoadd]
+ assert_match {} [cmdstat zadd]
+ }
+
+ test {command stats for EXPIRE} {
+ r config resetstat
+ r SET foo bar
+ r EXPIRE foo 0
+ assert_match {*calls=1,*} [cmdstat expire]
+ assert_match {} [cmdstat del]
+ }
+
+ test {command stats for BRPOP} {
+ r config resetstat
+ r LPUSH list foo
+ r BRPOP list 0
+ assert_match {*calls=1,*} [cmdstat brpop]
+ assert_match {} [cmdstat rpop]
+ }
+
+ test {command stats for MULTI} {
+ r config resetstat
+ r MULTI
+ r set foo bar
+ r GEOADD foo2 0 0 bar
+ r EXPIRE foo2 0
+ r EXEC
+ assert_match {*calls=1,*} [cmdstat multi]
+ assert_match {*calls=1,*} [cmdstat exec]
+ assert_match {*calls=1,*} [cmdstat set]
+ assert_match {*calls=1,*} [cmdstat expire]
+ assert_match {*calls=1,*} [cmdstat geoadd]
+ }
+
+ test {command stats for scripts} {
+ r config resetstat
+ r set mykey myval
+ r eval {
+ redis.call('set', KEYS[1], 0)
+ redis.call('expire', KEYS[1], 0)
+ redis.call('geoadd', KEYS[1], 0, 0, "bar")
+ } 1 mykey
+ assert_match {*calls=1,*} [cmdstat eval]
+ assert_match {*calls=2,*} [cmdstat set]
+ assert_match {*calls=1,*} [cmdstat expire]
+ assert_match {*calls=1,*} [cmdstat geoadd]
+ }
}
diff --git a/tests/unit/introspection.tcl b/tests/unit/introspection.tcl
index f6477d9c5..2581eb83d 100644
--- a/tests/unit/introspection.tcl
+++ b/tests/unit/introspection.tcl
@@ -1,7 +1,7 @@
start_server {tags {"introspection"}} {
test {CLIENT LIST} {
r client list
- } {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
+ } {*addr=*:* fd=* age=* idle=* flags=N db=9 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=* obl=0 oll=0 omem=0 events=r cmd=client*}
test {MONITOR can log executed commands} {
set rd [redis_deferring_client]
diff --git a/tests/unit/limits.tcl b/tests/unit/limits.tcl
index b37ea9b0f..38ba76208 100644
--- a/tests/unit/limits.tcl
+++ b/tests/unit/limits.tcl
@@ -1,4 +1,9 @@
start_server {tags {"limits"} overrides {maxclients 10}} {
+ if {$::tls} {
+ set expected_code "*I/O error*"
+ } else {
+ set expected_code "*ERR max*reached*"
+ }
test {Check if maxclients works refusing connections} {
set c 0
catch {
@@ -12,5 +17,5 @@ start_server {tags {"limits"} overrides {maxclients 10}} {
} e
assert {$c > 8 && $c <= 10}
set e
- } {*ERR max*reached*}
+ } $expected_code
}
diff --git a/tests/unit/maxmemory.tcl b/tests/unit/maxmemory.tcl
index 0c3f6b32c..0f64ddc18 100644
--- a/tests/unit/maxmemory.tcl
+++ b/tests/unit/maxmemory.tcl
@@ -142,3 +142,102 @@ start_server {tags {"maxmemory"}} {
}
}
}
+
+proc test_slave_buffers {test_name cmd_count payload_len limit_memory pipeline} {
+ start_server {tags {"maxmemory"}} {
+ start_server {} {
+ set slave_pid [s process_id]
+ test "$test_name" {
+ set slave [srv 0 client]
+ set slave_host [srv 0 host]
+ set slave_port [srv 0 port]
+ set master [srv -1 client]
+ set master_host [srv -1 host]
+ set master_port [srv -1 port]
+
+ # add 100 keys of 100k (10MB total)
+ for {set j 0} {$j < 100} {incr j} {
+ $master setrange "key:$j" 100000 asdf
+ }
+
+ # make sure master doesn't disconnect slave because of timeout
+ $master config set repl-timeout 1200 ;# 20 minutes (for valgrind and slow machines)
+ $master config set maxmemory-policy allkeys-random
+ $master config set client-output-buffer-limit "replica 100000000 100000000 300"
+ $master config set repl-backlog-size [expr {10*1024}]
+
+ $slave slaveof $master_host $master_port
+ wait_for_condition 50 100 {
+ [s 0 master_link_status] eq {up}
+ } else {
+ fail "Replication not started."
+ }
+
+ # measure used memory after the slave connected and set maxmemory
+ set orig_used [s -1 used_memory]
+ set orig_client_buf [s -1 mem_clients_normal]
+ set orig_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
+ set orig_used_no_repl [expr {$orig_used - $orig_mem_not_counted_for_evict}]
+ set limit [expr {$orig_used - $orig_mem_not_counted_for_evict + 20*1024}]
+
+ if {$limit_memory==1} {
+ $master config set maxmemory $limit
+ }
+
+ # put the slave to sleep
+ set rd_slave [redis_deferring_client]
+ exec kill -SIGSTOP $slave_pid
+
+ # send some 10mb worth of commands that don't increase the memory usage
+ if {$pipeline == 1} {
+ set rd_master [redis_deferring_client -1]
+ for {set k 0} {$k < $cmd_count} {incr k} {
+ $rd_master setrange key:0 0 [string repeat A $payload_len]
+ }
+ for {set k 0} {$k < $cmd_count} {incr k} {
+ #$rd_master read
+ }
+ } else {
+ for {set k 0} {$k < $cmd_count} {incr k} {
+ $master setrange key:0 0 [string repeat A $payload_len]
+ }
+ }
+
+ set new_used [s -1 used_memory]
+ set slave_buf [s -1 mem_clients_slaves]
+ set client_buf [s -1 mem_clients_normal]
+ set mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
+ set used_no_repl [expr {$new_used - $mem_not_counted_for_evict}]
+ set delta [expr {($used_no_repl - $client_buf) - ($orig_used_no_repl - $orig_client_buf)}]
+
+ assert {[$master dbsize] == 100}
+ assert {$slave_buf > 2*1024*1024} ;# some of the data may have been pushed to the OS buffers
+ set delta_max [expr {$cmd_count / 2}] ;# 1 byte unaccounted for, with 1M commands will consume some 1MB
+ assert {$delta < $delta_max && $delta > -$delta_max}
+
+ $master client kill type slave
+ set killed_used [s -1 used_memory]
+ set killed_slave_buf [s -1 mem_clients_slaves]
+ set killed_mem_not_counted_for_evict [s -1 mem_not_counted_for_evict]
+ set killed_used_no_repl [expr {$killed_used - $killed_mem_not_counted_for_evict}]
+ set delta_no_repl [expr {$killed_used_no_repl - $used_no_repl}]
+ assert {$killed_slave_buf == 0}
+ assert {$delta_no_repl > -$delta_max && $delta_no_repl < $delta_max}
+
+ }
+ # unfreeze slave process (after the 'test' succeeded or failed, but before we attempt to terminate the server
+ exec kill -SIGCONT $slave_pid
+ }
+ }
+}
+
+# test that slave buffer are counted correctly
+# we wanna use many small commands, and we don't wanna wait long
+# so we need to use a pipeline (redis_deferring_client)
+# that may cause query buffer to fill and induce eviction, so we disable it
+test_slave_buffers {slave buffer are counted correctly} 1000000 10 0 1
+
+# test that slave buffer don't induce eviction
+# test again with fewer (and bigger) commands without pipeline, but with eviction
+test_slave_buffers "replica buffer don't induce eviction" 100000 100 1 0
+
diff --git a/tests/unit/memefficiency.tcl b/tests/unit/memefficiency.tcl
index f452f0224..d152e212c 100644
--- a/tests/unit/memefficiency.tcl
+++ b/tests/unit/memefficiency.tcl
@@ -36,50 +36,178 @@ start_server {tags {"memefficiency"}} {
}
}
-if 0 {
- start_server {tags {"defrag"}} {
- if {[string match {*jemalloc*} [s mem_allocator]]} {
- test "Active defrag" {
- r config set activedefrag no
- r config set active-defrag-threshold-lower 5
- r config set active-defrag-ignore-bytes 2mb
- r config set maxmemory 100mb
- r config set maxmemory-policy allkeys-lru
- r debug populate 700000 asdf 150
- r debug populate 170000 asdf 300
- set frag [s mem_fragmentation_ratio]
- assert {$frag >= 1.7}
- r config set activedefrag yes
- after 1500 ;# active defrag tests the status once a second.
- set hits [s active_defrag_hits]
+start_server {tags {"defrag"}} {
+ if {[string match {*jemalloc*} [s mem_allocator]]} {
+ test "Active defrag" {
+ r config set activedefrag no
+ r config set active-defrag-threshold-lower 5
+ r config set active-defrag-cycle-min 65
+ r config set active-defrag-cycle-max 75
+ r config set active-defrag-ignore-bytes 2mb
+ r config set maxmemory 100mb
+ r config set maxmemory-policy allkeys-lru
+ r debug populate 700000 asdf 150
+ r debug populate 170000 asdf 300
+ r ping ;# trigger eviction following the previous population
+ after 120 ;# serverCron only updates the info once in 100ms
+ set frag [s allocator_frag_ratio]
+ if {$::verbose} {
+ puts "frag $frag"
+ }
+ assert {$frag >= 1.4}
+ catch {r config set activedefrag yes} e
+ if {![string match {DISABLED*} $e]} {
+ # Wait for the active defrag to start working (decision once a
+ # second).
+ wait_for_condition 50 100 {
+ [s active_defrag_running] ne 0
+ } else {
+ fail "defrag not started."
+ }
- # wait for the active defrag to stop working
- set tries 0
- while { True } {
- incr tries
- after 500
- set prev_hits $hits
- set hits [s active_defrag_hits]
- if {$hits == $prev_hits} {
- break
- }
- assert {$tries < 100}
+ # Wait for the active defrag to stop working.
+ wait_for_condition 150 100 {
+ [s active_defrag_running] eq 0
+ } else {
+ after 120 ;# serverCron only updates the info once in 100ms
+ puts [r info memory]
+ puts [r memory malloc-stats]
+ fail "defrag didn't stop."
}
- # TODO: we need to expose more accurate fragmentation info
- # i.e. the allocator used and active pages
- # instead we currently look at RSS so we need to ask for purge
- r memory purge
+ # Test the the fragmentation is lower.
+ after 120 ;# serverCron only updates the info once in 100ms
+ set frag [s allocator_frag_ratio]
+ if {$::verbose} {
+ puts "frag $frag"
+ }
+ assert {$frag < 1.1}
+ } else {
+ set _ ""
+ }
+ } {}
- # Test the the fragmentation is lower and that the defragger
- # stopped working
- set frag [s mem_fragmentation_ratio]
- assert {$frag < 1.55}
- set misses [s active_defrag_misses]
- after 500
- set misses2 [s active_defrag_misses]
- assert {$misses2 == $misses}
+ test "Active defrag big keys" {
+ r flushdb
+ r config resetstat
+ r config set save "" ;# prevent bgsave from interfereing with save below
+ r config set activedefrag no
+ r config set active-defrag-max-scan-fields 1000
+ r config set active-defrag-threshold-lower 5
+ r config set active-defrag-cycle-min 65
+ r config set active-defrag-cycle-max 75
+ r config set active-defrag-ignore-bytes 2mb
+ r config set maxmemory 0
+ r config set list-max-ziplist-size 5 ;# list of 10k items will have 2000 quicklist nodes
+ r config set stream-node-max-entries 5
+ r hmset hash h1 v1 h2 v2 h3 v3
+ r lpush list a b c d
+ r zadd zset 0 a 1 b 2 c 3 d
+ r sadd set a b c d
+ r xadd stream * item 1 value a
+ r xadd stream * item 2 value b
+ r xgroup create stream mygroup 0
+ r xreadgroup GROUP mygroup Alice COUNT 1 STREAMS stream >
+
+ # create big keys with 10k items
+ set rd [redis_deferring_client]
+ for {set j 0} {$j < 10000} {incr j} {
+ $rd hset bighash $j [concat "asdfasdfasdf" $j]
+ $rd lpush biglist [concat "asdfasdfasdf" $j]
+ $rd zadd bigzset $j [concat "asdfasdfasdf" $j]
+ $rd sadd bigset [concat "asdfasdfasdf" $j]
+ $rd xadd bigstream * item 1 value a
}
- }
+ for {set j 0} {$j < 50000} {incr j} {
+ $rd read ; # Discard replies
+ }
+
+ set expected_frag 1.7
+ if {$::accurate} {
+ # scale the hash to 1m fields in order to have a measurable the latency
+ for {set j 10000} {$j < 1000000} {incr j} {
+ $rd hset bighash $j [concat "asdfasdfasdf" $j]
+ }
+ for {set j 10000} {$j < 1000000} {incr j} {
+ $rd read ; # Discard replies
+ }
+ # creating that big hash, increased used_memory, so the relative frag goes down
+ set expected_frag 1.3
+ }
+
+ # add a mass of string keys
+ for {set j 0} {$j < 500000} {incr j} {
+ $rd setrange $j 150 a
+ }
+ for {set j 0} {$j < 500000} {incr j} {
+ $rd read ; # Discard replies
+ }
+ assert {[r dbsize] == 500010}
+
+ # create some fragmentation
+ for {set j 0} {$j < 500000} {incr j 2} {
+ $rd del $j
+ }
+ for {set j 0} {$j < 500000} {incr j 2} {
+ $rd read ; # Discard replies
+ }
+ assert {[r dbsize] == 250010}
+
+ # start defrag
+ after 120 ;# serverCron only updates the info once in 100ms
+ set frag [s allocator_frag_ratio]
+ if {$::verbose} {
+ puts "frag $frag"
+ }
+ assert {$frag >= $expected_frag}
+ r config set latency-monitor-threshold 5
+ r latency reset
+
+ set digest [r debug digest]
+ catch {r config set activedefrag yes} e
+ if {![string match {DISABLED*} $e]} {
+ # wait for the active defrag to start working (decision once a second)
+ wait_for_condition 50 100 {
+ [s active_defrag_running] ne 0
+ } else {
+ fail "defrag not started."
+ }
+
+ # wait for the active defrag to stop working
+ wait_for_condition 500 100 {
+ [s active_defrag_running] eq 0
+ } else {
+ after 120 ;# serverCron only updates the info once in 100ms
+ puts [r info memory]
+ puts [r memory malloc-stats]
+ fail "defrag didn't stop."
+ }
+
+ # test the the fragmentation is lower
+ after 120 ;# serverCron only updates the info once in 100ms
+ set frag [s allocator_frag_ratio]
+ set max_latency 0
+ foreach event [r latency latest] {
+ lassign $event eventname time latency max
+ if {$eventname == "active-defrag-cycle"} {
+ set max_latency $max
+ }
+ }
+ if {$::verbose} {
+ puts "frag $frag"
+ puts "max latency $max_latency"
+ puts [r latency latest]
+ puts [r latency history active-defrag-cycle]
+ }
+ assert {$frag < 1.1}
+ # due to high fragmentation, 10hz, and active-defrag-cycle-max set to 75,
+ # we expect max latency to be not much higher than 75ms
+ assert {$max_latency <= 120}
+ }
+ # verify the data isn't corrupted or changed
+ set newdigest [r debug digest]
+ assert {$digest eq $newdigest}
+ r save ;# saving an rdb iterates over all the data / pointers
+ } {OK}
}
}
diff --git a/tests/unit/moduleapi/commandfilter.tcl b/tests/unit/moduleapi/commandfilter.tcl
new file mode 100644
index 000000000..6078f64f2
--- /dev/null
+++ b/tests/unit/moduleapi/commandfilter.tcl
@@ -0,0 +1,84 @@
+set testmodule [file normalize tests/modules/commandfilter.so]
+
+start_server {tags {"modules"}} {
+ r module load $testmodule log-key 0
+
+ test {Command Filter handles redirected commands} {
+ r set mykey @log
+ r lrange log-key 0 -1
+ } "{set mykey @log}"
+
+ test {Command Filter can call RedisModule_CommandFilterArgDelete} {
+ r rpush mylist elem1 @delme elem2
+ r lrange mylist 0 -1
+ } {elem1 elem2}
+
+ test {Command Filter can call RedisModule_CommandFilterArgInsert} {
+ r del mylist
+ r rpush mylist elem1 @insertbefore elem2 @insertafter elem3
+ r lrange mylist 0 -1
+ } {elem1 --inserted-before-- @insertbefore elem2 @insertafter --inserted-after-- elem3}
+
+ test {Command Filter can call RedisModule_CommandFilterArgReplace} {
+ r del mylist
+ r rpush mylist elem1 @replaceme elem2
+ r lrange mylist 0 -1
+ } {elem1 --replaced-- elem2}
+
+ test {Command Filter applies on RM_Call() commands} {
+ r del log-key
+ r commandfilter.ping
+ r lrange log-key 0 -1
+ } "{ping @log}"
+
+ test {Command Filter applies on Lua redis.call()} {
+ r del log-key
+ r eval "redis.call('ping', '@log')" 0
+ r lrange log-key 0 -1
+ } "{ping @log}"
+
+ test {Command Filter applies on Lua redis.call() that calls a module} {
+ r del log-key
+ r eval "redis.call('commandfilter.ping')" 0
+ r lrange log-key 0 -1
+ } "{ping @log}"
+
+ test {Command Filter is unregistered implicitly on module unload} {
+ r del log-key
+ r module unload commandfilter
+ r set mykey @log
+ r lrange log-key 0 -1
+ } {}
+
+ r module load $testmodule log-key 0
+
+ test {Command Filter unregister works as expected} {
+ # Validate reloading succeeded
+ r del log-key
+ r set mykey @log
+ assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
+
+ # Unregister
+ r commandfilter.unregister
+ r del log-key
+
+ r set mykey @log
+ r lrange log-key 0 -1
+ } {}
+
+ r module unload commandfilter
+ r module load $testmodule log-key 1
+
+ test {Command Filter REDISMODULE_CMDFILTER_NOSELF works as expected} {
+ r set mykey @log
+ assert_equal "{set mykey @log}" [r lrange log-key 0 -1]
+
+ r del log-key
+ r commandfilter.ping
+ assert_equal {} [r lrange log-key 0 -1]
+
+ r eval "redis.call('commandfilter.ping')" 0
+ assert_equal {} [r lrange log-key 0 -1]
+ }
+
+}
diff --git a/tests/unit/moduleapi/fork.tcl b/tests/unit/moduleapi/fork.tcl
new file mode 100644
index 000000000..f7d7e47d5
--- /dev/null
+++ b/tests/unit/moduleapi/fork.tcl
@@ -0,0 +1,32 @@
+set testmodule [file normalize tests/modules/fork.so]
+
+proc count_log_message {pattern} {
+ set result [exec grep -c $pattern < [srv 0 stdout]]
+}
+
+start_server {tags {"modules"}} {
+ r module load $testmodule
+
+ test {Module fork} {
+ # the argument to fork.create is the exitcode on termination
+ r fork.create 3
+ wait_for_condition 20 100 {
+ [r fork.exitcode] != -1
+ } else {
+ fail "fork didn't terminate"
+ }
+ r fork.exitcode
+ } {3}
+
+ test {Module fork kill} {
+ r fork.create 3
+ after 20
+ r fork.kill
+ after 100
+
+ assert {[count_log_message "fork child started"] eq "2"}
+ assert {[count_log_message "Received SIGUSR1 in child"] eq "1"}
+ assert {[count_log_message "fork child exiting"] eq "1"}
+ }
+
+}
diff --git a/tests/unit/moduleapi/hooks.tcl b/tests/unit/moduleapi/hooks.tcl
new file mode 100644
index 000000000..7a727902d
--- /dev/null
+++ b/tests/unit/moduleapi/hooks.tcl
@@ -0,0 +1,28 @@
+set testmodule [file normalize tests/modules/hooks.so]
+
+tags "modules" {
+ start_server {} {
+ r module load $testmodule
+ test {Test clients connection / disconnection hooks} {
+ for {set j 0} {$j < 2} {incr j} {
+ set rd1 [redis_deferring_client]
+ $rd1 close
+ }
+ assert {[r llen connected] > 1}
+ assert {[r llen disconnected] > 1}
+ }
+
+ test {Test flushdb hooks} {
+ r flushall ;# Note: only the "end" RPUSH will survive
+ r select 1
+ r flushdb
+ r select 2
+ r flushdb
+ r select 9
+ assert {[r llen flush-start] == 2}
+ assert {[r llen flush-end] == 3}
+ assert {[r lrange flush-start 0 -1] eq {1 2}}
+ assert {[r lrange flush-end 0 -1] eq {-1 1 2}}
+ }
+ }
+}
diff --git a/tests/unit/moduleapi/infotest.tcl b/tests/unit/moduleapi/infotest.tcl
new file mode 100644
index 000000000..659ee79d7
--- /dev/null
+++ b/tests/unit/moduleapi/infotest.tcl
@@ -0,0 +1,63 @@
+set testmodule [file normalize tests/modules/infotest.so]
+
+# Return value for INFO property
+proc field {info property} {
+ if {[regexp "\r\n$property:(.*?)\r\n" $info _ value]} {
+ set _ $value
+ }
+}
+
+start_server {tags {"modules"}} {
+ r module load $testmodule log-key 0
+
+ test {module info all} {
+ set info [r info all]
+ # info all does not contain modules
+ assert { ![string match "*Spanish*" $info] }
+ assert { ![string match "*infotest_*" $info] }
+ assert { [string match "*used_memory*" $info] }
+ }
+
+ test {module info everything} {
+ set info [r info everything]
+ # info everything contains all default sections, but not ones for crash report
+ assert { [string match "*infotest_global*" $info] }
+ assert { [string match "*Spanish*" $info] }
+ assert { [string match "*Italian*" $info] }
+ assert { [string match "*used_memory*" $info] }
+ assert { ![string match "*Klingon*" $info] }
+ field $info infotest_dos
+ } {2}
+
+ test {module info modules} {
+ set info [r info modules]
+ # info all does not contain modules
+ assert { [string match "*Spanish*" $info] }
+ assert { [string match "*infotest_global*" $info] }
+ assert { ![string match "*used_memory*" $info] }
+ }
+
+ test {module info one module} {
+ set info [r info INFOTEST]
+ # info all does not contain modules
+ assert { [string match "*Spanish*" $info] }
+ assert { ![string match "*used_memory*" $info] }
+ field $info infotest_global
+ } {-2}
+
+ test {module info one section} {
+ set info [r info INFOTEST_SPANISH]
+ assert { ![string match "*used_memory*" $info] }
+ assert { ![string match "*Italian*" $info] }
+ assert { ![string match "*infotest_global*" $info] }
+ field $info infotest_uno
+ } {one}
+
+ test {module info dict} {
+ set info [r info infotest_keyspace]
+ set keyspace [field $info infotest_db0]
+ set keys [scan [regexp -inline {keys\=([\d]*)} $keyspace] keys=%d]
+ } {3}
+
+ # TODO: test crash report.
+}
diff --git a/tests/unit/moduleapi/propagate.tcl b/tests/unit/moduleapi/propagate.tcl
new file mode 100644
index 000000000..71307ce33
--- /dev/null
+++ b/tests/unit/moduleapi/propagate.tcl
@@ -0,0 +1,30 @@
+set testmodule [file normalize tests/modules/propagate.so]
+
+tags "modules" {
+ test {Modules can propagate in async and threaded contexts} {
+ start_server {} {
+ set replica [srv 0 client]
+ set replica_host [srv 0 host]
+ set replica_port [srv 0 port]
+ start_server [list overrides [list loadmodule "$testmodule"]] {
+ set master [srv 0 client]
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+
+ # Start the replication process...
+ $replica replicaof $master_host $master_port
+ wait_for_sync $replica
+
+ after 1000
+ $master propagate-test
+
+ wait_for_condition 5000 10 {
+ ([$replica get timer] eq "10") && \
+ ([$replica get thread] eq "10")
+ } else {
+ fail "The two counters don't match the expected value."
+ }
+ }
+ }
+ }
+}
diff --git a/tests/unit/moduleapi/testrdb.tcl b/tests/unit/moduleapi/testrdb.tcl
new file mode 100644
index 000000000..c72570002
--- /dev/null
+++ b/tests/unit/moduleapi/testrdb.tcl
@@ -0,0 +1,122 @@
+set testmodule [file normalize tests/modules/testrdb.so]
+
+proc restart_and_wait {} {
+ catch {
+ r debug restart
+ }
+
+ # wait for the server to come back up
+ set retry 50
+ while {$retry} {
+ if {[catch { r ping }]} {
+ after 100
+ } else {
+ break
+ }
+ incr retry -1
+ }
+}
+
+tags "modules" {
+ start_server [list overrides [list loadmodule "$testmodule"]] {
+ test {modules are able to persist types} {
+ r testrdb.set.key key1 value1
+ assert_equal "value1" [r testrdb.get.key key1]
+ r debug reload
+ assert_equal "value1" [r testrdb.get.key key1]
+ }
+
+ test {modules global are lost without aux} {
+ r testrdb.set.before global1
+ assert_equal "global1" [r testrdb.get.before]
+ restart_and_wait
+ assert_equal "" [r testrdb.get.before]
+ }
+ }
+
+ start_server [list overrides [list loadmodule "$testmodule 2"]] {
+ test {modules are able to persist globals before and after} {
+ r testrdb.set.before global1
+ r testrdb.set.after global2
+ assert_equal "global1" [r testrdb.get.before]
+ assert_equal "global2" [r testrdb.get.after]
+ restart_and_wait
+ assert_equal "global1" [r testrdb.get.before]
+ assert_equal "global2" [r testrdb.get.after]
+ }
+
+ }
+
+ start_server [list overrides [list loadmodule "$testmodule 1"]] {
+ test {modules are able to persist globals just after} {
+ r testrdb.set.after global2
+ assert_equal "global2" [r testrdb.get.after]
+ restart_and_wait
+ assert_equal "global2" [r testrdb.get.after]
+ }
+ }
+
+ tags {repl} {
+ test {diskless loading short read with module} {
+ start_server [list overrides [list loadmodule "$testmodule"]] {
+ set replica [srv 0 client]
+ set replica_host [srv 0 host]
+ set replica_port [srv 0 port]
+ start_server [list overrides [list loadmodule "$testmodule"]] {
+ set master [srv 0 client]
+ set master_host [srv 0 host]
+ set master_port [srv 0 port]
+
+ # Set master and replica to use diskless replication
+ $master config set repl-diskless-sync yes
+ $master config set rdbcompression no
+ $replica config set repl-diskless-load swapdb
+ for {set k 0} {$k < 30} {incr k} {
+ r testrdb.set.key key$k [string repeat A [expr {int(rand()*1000000)}]]
+ }
+
+ # Start the replication process...
+ $master config set repl-diskless-sync-delay 0
+ $replica replicaof $master_host $master_port
+
+ # kill the replication at various points
+ set attempts 3
+ if {$::accurate} { set attempts 10 }
+ for {set i 0} {$i < $attempts} {incr i} {
+ # wait for the replica to start reading the rdb
+ # using the log file since the replica only responds to INFO once in 2mb
+ wait_for_log_message -1 "*Loading DB in memory*" 5 2000 1
+
+ # add some additional random sleep so that we kill the master on a different place each time
+ after [expr {int(rand()*100)}]
+
+ # kill the replica connection on the master
+ set killed [$master client kill type replica]
+
+ if {[catch {
+ set res [wait_for_log_message -1 "*Internal error in RDB*" 5 100 10]
+ if {$::verbose} {
+ puts $res
+ }
+ }]} {
+ puts "failed triggering short read"
+ # force the replica to try another full sync
+ $master client kill type replica
+ $master set asdf asdf
+ # the side effect of resizing the backlog is that it is flushed (16k is the min size)
+ $master config set repl-backlog-size [expr {16384 + $i}]
+ }
+ # wait for loading to stop (fail)
+ wait_for_condition 100 10 {
+ [s -1 loading] eq 0
+ } else {
+ fail "Replica didn't disconnect"
+ }
+ }
+ # enable fast shutdown
+ $master config set rdb-key-save-delay 0
+ }
+ }
+ }
+ }
+}
diff --git a/tests/unit/multi.tcl b/tests/unit/multi.tcl
index 6655bf62c..9fcef71d6 100644
--- a/tests/unit/multi.tcl
+++ b/tests/unit/multi.tcl
@@ -306,4 +306,18 @@ start_server {tags {"multi"}} {
}
close_replication_stream $repl
}
+
+ test {DISCARD should not fail during OOM} {
+ set rd [redis_deferring_client]
+ $rd config set maxmemory 1
+ assert {[$rd read] eq {OK}}
+ r multi
+ catch {r set x 1} e
+ assert_match {OOM*} $e
+ r discard
+ $rd config set maxmemory 0
+ assert {[$rd read] eq {OK}}
+ $rd close
+ r ping
+ } {PONG}
}
diff --git a/tests/unit/obuf-limits.tcl b/tests/unit/obuf-limits.tcl
index 5d625cf45..c45bf8e86 100644
--- a/tests/unit/obuf-limits.tcl
+++ b/tests/unit/obuf-limits.tcl
@@ -15,7 +15,7 @@ start_server {tags {"obuf-limits"}} {
if {![regexp {omem=([0-9]+)} $c - omem]} break
if {$omem > 200000} break
}
- assert {$omem >= 90000 && $omem < 200000}
+ assert {$omem >= 70000 && $omem < 200000}
$rd1 close
}
diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl
index 1d21b561a..7720c055a 100644
--- a/tests/unit/other.tcl
+++ b/tests/unit/other.tcl
@@ -83,6 +83,7 @@ start_server {tags {"other"}} {
} {1}
test {Same dataset digest if saving/reloading as AOF?} {
+ r config set aof-use-rdb-preamble no
r bgrewriteaof
waitForBgrewriteaof r
r debug loadaof
@@ -126,6 +127,7 @@ start_server {tags {"other"}} {
test {EXPIRES after AOF reload (without rewrite)} {
r flushdb
r config set appendonly yes
+ r config set aof-use-rdb-preamble no
r set x somevalue
r expire x 1000
r setex y 2000 somevalue
@@ -164,7 +166,11 @@ start_server {tags {"other"}} {
tags {protocol} {
test {PIPELINING stresser (also a regression for the old epoll bug)} {
- set fd2 [socket $::host $::port]
+ if {$::tls} {
+ set fd2 [::tls::socket $::host $::port]
+ } else {
+ set fd2 [socket $::host $::port]
+ }
fconfigure $fd2 -encoding binary -translation binary
puts -nonewline $fd2 "SELECT 9\r\n"
flush $fd2
diff --git a/tests/unit/pendingquerybuf.tcl b/tests/unit/pendingquerybuf.tcl
new file mode 100644
index 000000000..bee85db36
--- /dev/null
+++ b/tests/unit/pendingquerybuf.tcl
@@ -0,0 +1,35 @@
+proc info_memory {r property} {
+ if {[regexp "\r\n$property:(.*?)\r\n" [{*}$r info memory] _ value]} {
+ set _ $value
+ }
+}
+
+proc prepare_value {size} {
+ set _v "c"
+ for {set i 1} {$i < $size} {incr i} {
+ append _v 0
+ }
+ return $_v
+}
+
+start_server {tags {"wait"}} {
+start_server {} {
+ set slave [srv 0 client]
+ set slave_host [srv 0 host]
+ set slave_port [srv 0 port]
+ set master [srv -1 client]
+ set master_host [srv -1 host]
+ set master_port [srv -1 port]
+
+ test "pending querybuf: check size of pending_querybuf after set a big value" {
+ $slave slaveof $master_host $master_port
+ set _v [prepare_value [expr 32*1024*1024]]
+ $master set key $_v
+ after 2000
+ set m_usedmemory [info_memory $master used_memory]
+ set s_usedmemory [info_memory $slave used_memory]
+ if { $s_usedmemory > $m_usedmemory + 10*1024*1024 } {
+ fail "the used_memory of replica is much larger than master. Master:$m_usedmemory Replica:$s_usedmemory"
+ }
+ }
+}}
diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl
index ac99c3abb..4dfdc6f59 100644
--- a/tests/unit/protocol.tcl
+++ b/tests/unit/protocol.tcl
@@ -72,7 +72,11 @@ start_server {tags {"protocol"}} {
foreach seq [list "\x00" "*\x00" "$\x00"] {
incr c
test "Protocol desync regression test #$c" {
- set s [socket [srv 0 host] [srv 0 port]]
+ if {$::tls} {
+ set s [::tls::socket [srv 0 host] [srv 0 port]]
+ } else {
+ set s [socket [srv 0 host] [srv 0 port]]
+ }
puts -nonewline $s $seq
set payload [string repeat A 1024]"\n"
set test_start [clock seconds]
diff --git a/tests/unit/scan.tcl b/tests/unit/scan.tcl
index 1d84f128d..9f9ff4df2 100644
--- a/tests/unit/scan.tcl
+++ b/tests/unit/scan.tcl
@@ -53,6 +53,51 @@ start_server {tags {"scan"}} {
assert_equal 100 [llength $keys]
}
+ test "SCAN TYPE" {
+ r flushdb
+ # populate only creates strings
+ r debug populate 1000
+
+ # Check non-strings are excluded
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "list"]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 0 [llength $keys]
+
+ # Check strings are included
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "string"]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 1000 [llength $keys]
+
+ # Check all three args work together
+ set cur 0
+ set keys {}
+ while 1 {
+ set res [r scan $cur type "string" match "key:*" count 10]
+ set cur [lindex $res 0]
+ set k [lindex $res 1]
+ lappend keys {*}$k
+ if {$cur == 0} break
+ }
+
+ assert_equal 1000 [llength $keys]
+ }
+
foreach enc {intset hashtable} {
test "SSCAN with encoding $enc" {
# Create the Set
@@ -236,4 +281,50 @@ start_server {tags {"scan"}} {
set first_score [lindex $res 1]
assert {$first_score != 0}
}
+
+ test "SCAN regression test for issue #4906" {
+ for {set k 0} {$k < 100} {incr k} {
+ r del set
+ r sadd set x; # Make sure it's not intset encoded
+ set toremove {}
+ unset -nocomplain found
+ array set found {}
+
+ # Populate the set
+ set numele [expr {101+[randomInt 1000]}]
+ for {set j 0} {$j < $numele} {incr j} {
+ r sadd set $j
+ if {$j >= 100} {
+ lappend toremove $j
+ }
+ }
+
+ # Start scanning
+ set cursor 0
+ set iteration 0
+ set del_iteration [randomInt 10]
+ while {!($cursor == 0 && $iteration != 0)} {
+ lassign [r sscan set $cursor] cursor items
+
+ # Mark found items. We expect to find from 0 to 99 at the end
+ # since those elements will never be removed during the scanning.
+ foreach i $items {
+ set found($i) 1
+ }
+ incr iteration
+ # At some point remove most of the items to trigger the
+ # rehashing to a smaller hash table.
+ if {$iteration == $del_iteration} {
+ r srem set {*}$toremove
+ }
+ }
+
+ # Verify that SSCAN reported everything from 0 to 99
+ for {set j 0} {$j < 100} {incr j} {
+ if {![info exists found($j)]} {
+ fail "SSCAN element missing $j"
+ }
+ }
+ }
+ }
}
diff --git a/tests/unit/scripting.tcl b/tests/unit/scripting.tcl
index be82e1559..b3e1c48e6 100644
--- a/tests/unit/scripting.tcl
+++ b/tests/unit/scripting.tcl
@@ -148,9 +148,11 @@ start_server {tags {"scripting"}} {
test {EVAL - Scripts can't run certain commands} {
set e {}
+ r debug lua-always-replicate-commands 0
catch {
r eval "redis.pcall('randomkey'); return redis.pcall('set','x','ciao')" 0
} e
+ r debug lua-always-replicate-commands 1
set e
} {*not allowed after*}
@@ -299,9 +301,12 @@ start_server {tags {"scripting"}} {
} {b534286061d4b9e4026607613b95c06c06015ae8 loaded}
test "In the context of Lua the output of random commands gets ordered" {
+ r debug lua-always-replicate-commands 0
r del myset
r sadd myset a b c d e f g h i l m n o p q r s t u v z aa aaa azz
- r eval {return redis.call('smembers',KEYS[1])} 1 myset
+ set res [r eval {return redis.call('smembers',KEYS[1])} 1 myset]
+ r debug lua-always-replicate-commands 1
+ set res
} {a aa aaa azz b c d e f g h i l m n o p q r s t u v z}
test "SORT is normally not alpha re-ordered for the scripting engine" {
@@ -397,6 +402,7 @@ start_server {tags {"scripting"}} {
test {EVAL processes writes from AOF in read-only slaves} {
r flushall
r config set appendonly yes
+ r config set aof-use-rdb-preamble no
r eval {redis.call("set",KEYS[1],"100")} 1 foo
r eval {redis.call("incr",KEYS[1])} 1 foo
r eval {redis.call("incr",KEYS[1])} 1 foo
@@ -516,7 +522,7 @@ start_server {tags {"scripting"}} {
# Note: keep this test at the end of this server stanza because it
# kills the server.
test {SHUTDOWN NOSAVE can kill a timedout script anyway} {
- # The server sould be still unresponding to normal commands.
+ # The server could be still unresponding to normal commands.
catch {r ping} e
assert_match {BUSY*} $e
catch {r shutdown nosave}
@@ -536,7 +542,7 @@ foreach cmdrepl {0 1} {
r debug lua-always-replicate-commands 1
}
- test "Before the slave connects we issue two EVAL commands $rt" {
+ test "Before the replica connects we issue two EVAL commands $rt" {
# One with an error, but still executing a command.
# SHA is: 67164fc43fa971f76fd1aaeeaf60c1c178d25876
catch {
@@ -547,13 +553,13 @@ foreach cmdrepl {0 1} {
r eval {return redis.call('incr',KEYS[1])} 1 x
} {2}
- test "Connect a slave to the master instance $rt" {
+ test "Connect a replica to the master instance $rt" {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 role] eq {slave} &&
[string match {*master_link_status:up*} [r -1 info replication]]
} else {
- fail "Can't turn the instance into a slave"
+ fail "Can't turn the instance into a replica"
}
}
@@ -586,7 +592,7 @@ foreach cmdrepl {0 1} {
wait_for_condition 50 100 {
[r -1 lrange a 0 -1] eq [r lrange a 0 -1]
} else {
- fail "Expected list 'a' in slave and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
+ fail "Expected list 'a' in replica and master to be the same, but they are respectively '[r -1 lrange a 0 -1]' and '[r lrange a 0 -1]'"
}
set res
} {a 1}
@@ -621,7 +627,7 @@ foreach cmdrepl {0 1} {
wait_for_condition 50 100 {
[r -1 debug digest] eq [r debug digest]
} else {
- fail "Master-Slave desync after Lua script using SELECT."
+ fail "Master-Replica desync after Lua script using SELECT."
}
}
}
@@ -629,14 +635,14 @@ foreach cmdrepl {0 1} {
}
start_server {tags {"scripting repl"}} {
- start_server {overrides {appendonly yes}} {
- test "Connect a slave to the master instance" {
+ start_server {overrides {appendonly yes aof-use-rdb-preamble no}} {
+ test "Connect a replica to the master instance" {
r -1 slaveof [srv 0 host] [srv 0 port]
wait_for_condition 50 100 {
[s -1 role] eq {slave} &&
[string match {*master_link_status:up*} [r -1 info replication]]
} else {
- fail "Can't turn the instance into a slave"
+ fail "Can't turn the instance into a replica"
}
}
@@ -654,11 +660,13 @@ start_server {tags {"scripting repl"}} {
} {1}
test "Redis.set_repl() must be issued after replicate_commands()" {
+ r debug lua-always-replicate-commands 0
catch {
r eval {
redis.set_repl(redis.REPL_ALL);
} 0
} e
+ r debug lua-always-replicate-commands 1
set e
} {*only after turning on*}
@@ -688,7 +696,7 @@ start_server {tags {"scripting repl"}} {
wait_for_condition 50 100 {
[r -1 mget a b c d] eq {1 {} {} 4}
} else {
- fail "Only a and c should be replicated to slave"
+ fail "Only a and c should be replicated to replica"
}
# Master should have everything right now
@@ -727,7 +735,7 @@ start_server {tags {"scripting repl"}} {
wait_for_condition 50 100 {
[r get time] eq [r -1 get time]
} else {
- fail "Time key does not match between master and slave"
+ fail "Time key does not match between master and replica"
}
}
}
diff --git a/tests/unit/slowlog.tcl b/tests/unit/slowlog.tcl
index fce02498b..22f088103 100644
--- a/tests/unit/slowlog.tcl
+++ b/tests/unit/slowlog.tcl
@@ -78,4 +78,16 @@ start_server {tags {"slowlog"} overrides {slowlog-log-slower-than 1000000}} {
set e [lindex [r slowlog get] 0]
assert_equal {lastentry_client} [lindex $e 5]
}
+
+ test {SLOWLOG - can be disabled} {
+ r config set slowlog-max-len 1
+ r config set slowlog-log-slower-than 1
+ r slowlog reset
+ r debug sleep 0.2
+ assert_equal [r slowlog len] 1
+ r config set slowlog-log-slower-than -1
+ r slowlog reset
+ r debug sleep 0.2
+ assert_equal [r slowlog len] 0
+ }
}
diff --git a/tests/unit/tls.tcl b/tests/unit/tls.tcl
new file mode 100644
index 000000000..950f65557
--- /dev/null
+++ b/tests/unit/tls.tcl
@@ -0,0 +1,105 @@
+start_server {tags {"tls"}} {
+ if {$::tls} {
+ package require tls
+
+ test {TLS: Not accepting non-TLS connections on a TLS port} {
+ set s [redis [srv 0 host] [srv 0 port]]
+ catch {$s PING} e
+ set e
+ } {*I/O error*}
+
+ test {TLS: Verify tls-auth-clients behaves as expected} {
+ set s [redis [srv 0 host] [srv 0 port]]
+ ::tls::import [$s channel]
+ catch {$s PING} e
+ assert_match {*error*} $e
+
+ r CONFIG SET tls-auth-clients no
+
+ set s [redis [srv 0 host] [srv 0 port]]
+ ::tls::import [$s channel]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-auth-clients yes
+ }
+
+ test {TLS: Verify tls-protocols behaves as expected} {
+ r CONFIG SET tls-protocols TLSv1
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 0}]
+ catch {$s PING} e
+ assert_match {*I/O error*} $e
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1 1}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-protocols TLSv1.1
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 0}]
+ catch {$s PING} e
+ assert_match {*I/O error*} $e
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.1 1}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-protocols TLSv1.2
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 0}]
+ catch {$s PING} e
+ assert_match {*I/O error*} $e
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-tls1.2 1}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-protocols ""
+ }
+
+ test {TLS: Verify tls-ciphers behaves as expected} {
+ r CONFIG SET tls-protocols TLSv1.2
+ r CONFIG SET tls-ciphers "DEFAULT:-AES128-SHA256"
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES128-SHA256"}]
+ catch {$s PING} e
+ assert_match {*I/O error*} $e
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES256-SHA256"}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-ciphers "DEFAULT"
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "-ALL:AES128-SHA256"}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ r CONFIG SET tls-protocols ""
+ r CONFIG SET tls-ciphers "DEFAULT"
+ }
+
+ test {TLS: Verify tls-prefer-server-ciphers behaves as expected} {
+ r CONFIG SET tls-protocols TLSv1.2
+ r CONFIG SET tls-ciphers "AES128-SHA256:AES256-SHA256"
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "AES256-SHA256:AES128-SHA256"}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ assert_equal "AES256-SHA256" [dict get [::tls::status [$s channel]] cipher]
+
+ r CONFIG SET tls-prefer-server-ciphers yes
+
+ set s [redis [srv 0 host] [srv 0 port] 0 1 {-cipher "AES256-SHA256:AES128-SHA256"}]
+ catch {$s PING} e
+ assert_match {PONG} $e
+
+ assert_equal "AES128-SHA256" [dict get [::tls::status [$s channel]] cipher]
+
+ r CONFIG SET tls-protocols ""
+ r CONFIG SET tls-ciphers "DEFAULT"
+ }
+ }
+}
diff --git a/tests/unit/type/list.tcl b/tests/unit/type/list.tcl
index 1557082a2..676896a75 100644
--- a/tests/unit/type/list.tcl
+++ b/tests/unit/type/list.tcl
@@ -436,8 +436,11 @@ start_server {
test "$pop: with non-integer timeout" {
set rd [redis_deferring_client]
- $rd $pop blist1 1.1
- assert_error "ERR*not an integer*" {$rd read}
+ r del blist1
+ $rd $pop blist1 0.1
+ r rpush blist1 foo
+ assert_equal {blist1 foo} [$rd read]
+ assert_equal 0 [r exists blist1]
}
test "$pop: with zero timeout should block indefinitely" {
diff --git a/tests/unit/type/stream-cgroups.tcl b/tests/unit/type/stream-cgroups.tcl
new file mode 100644
index 000000000..34d4061c2
--- /dev/null
+++ b/tests/unit/type/stream-cgroups.tcl
@@ -0,0 +1,291 @@
+start_server {
+ tags {"stream"}
+} {
+ test {XGROUP CREATE: creation and duplicate group name detection} {
+ r DEL mystream
+ r XADD mystream * foo bar
+ r XGROUP CREATE mystream mygroup $
+ catch {r XGROUP CREATE mystream mygroup $} err
+ set err
+ } {BUSYGROUP*}
+
+ test {XGROUP CREATE: automatic stream creation fails without MKSTREAM} {
+ r DEL mystream
+ catch {r XGROUP CREATE mystream mygroup $} err
+ set err
+ } {ERR*}
+
+ test {XGROUP CREATE: automatic stream creation works with MKSTREAM} {
+ r DEL mystream
+ r XGROUP CREATE mystream mygroup $ MKSTREAM
+ } {OK}
+
+ test {XREADGROUP will return only new elements} {
+ r XADD mystream * a 1
+ r XADD mystream * b 2
+ # XREADGROUP should return only the new elements "a 1" "b 1"
+ # and not the element "foo bar" which was pre existing in the
+ # stream (see previous test)
+ set reply [
+ r XREADGROUP GROUP mygroup client-1 STREAMS mystream ">"
+ ]
+ assert {[llength [lindex $reply 0 1]] == 2}
+ lindex $reply 0 1 0 1
+ } {a 1}
+
+ test {XREADGROUP can read the history of the elements we own} {
+ # Add a few more elements
+ r XADD mystream * c 3
+ r XADD mystream * d 4
+ # Read a few elements using a different consumer name
+ set reply [
+ r XREADGROUP GROUP mygroup client-2 STREAMS mystream ">"
+ ]
+ assert {[llength [lindex $reply 0 1]] == 2}
+ assert {[lindex $reply 0 1 0 1] eq {c 3}}
+
+ set r1 [r XREADGROUP GROUP mygroup client-1 COUNT 10 STREAMS mystream 0]
+ set r2 [r XREADGROUP GROUP mygroup client-2 COUNT 10 STREAMS mystream 0]
+ assert {[lindex $r1 0 1 0 1] eq {a 1}}
+ assert {[lindex $r2 0 1 0 1] eq {c 3}}
+ }
+
+ test {XPENDING is able to return pending items} {
+ set pending [r XPENDING mystream mygroup - + 10]
+ assert {[llength $pending] == 4}
+ for {set j 0} {$j < 4} {incr j} {
+ set item [lindex $pending $j]
+ if {$j < 2} {
+ set owner client-1
+ } else {
+ set owner client-2
+ }
+ assert {[lindex $item 1] eq $owner}
+ assert {[lindex $item 1] eq $owner}
+ }
+ }
+
+ test {XPENDING can return single consumer items} {
+ set pending [r XPENDING mystream mygroup - + 10 client-1]
+ assert {[llength $pending] == 2}
+ }
+
+ test {XACK is able to remove items from the client/group PEL} {
+ set pending [r XPENDING mystream mygroup - + 10 client-1]
+ set id1 [lindex $pending 0 0]
+ set id2 [lindex $pending 1 0]
+ assert {[r XACK mystream mygroup $id1] eq 1}
+ set pending [r XPENDING mystream mygroup - + 10 client-1]
+ assert {[llength $pending] == 1}
+ set id [lindex $pending 0 0]
+ assert {$id eq $id2}
+ set global_pel [r XPENDING mystream mygroup - + 10]
+ assert {[llength $global_pel] == 3}
+ }
+
+ test {XACK can't remove the same item multiple times} {
+ assert {[r XACK mystream mygroup $id1] eq 0}
+ }
+
+ test {XACK is able to accept multiple arguments} {
+ # One of the IDs was already removed, so it should ack
+ # just ID2.
+ assert {[r XACK mystream mygroup $id1 $id2] eq 1}
+ }
+
+ test {PEL NACK reassignment after XGROUP SETID event} {
+ r del events
+ r xadd events * f1 v1
+ r xadd events * f1 v1
+ r xadd events * f1 v1
+ r xadd events * f1 v1
+ r xgroup create events g1 $
+ r xadd events * f1 v1
+ set c [llength [lindex [r xreadgroup group g1 c1 streams events >] 0 1]]
+ assert {$c == 1}
+ r xgroup setid events g1 -
+ set c [llength [lindex [r xreadgroup group g1 c2 streams events >] 0 1]]
+ assert {$c == 5}
+ }
+
+ test {XREADGROUP will not report data on empty history. Bug #5577} {
+ r del events
+ r xadd events * a 1
+ r xadd events * b 2
+ r xadd events * c 3
+ r xgroup create events mygroup 0
+
+ # Current local PEL should be empty
+ set res [r xpending events mygroup - + 10]
+ assert {[llength $res] == 0}
+
+ # So XREADGROUP should read an empty history as well
+ set res [r xreadgroup group mygroup myconsumer count 3 streams events 0]
+ assert {[llength [lindex $res 0 1]] == 0}
+
+ # We should fetch all the elements in the stream asking for >
+ set res [r xreadgroup group mygroup myconsumer count 3 streams events >]
+ assert {[llength [lindex $res 0 1]] == 3}
+
+ # Now the history is populated with three not acked entries
+ set res [r xreadgroup group mygroup myconsumer count 3 streams events 0]
+ assert {[llength [lindex $res 0 1]] == 3}
+ }
+
+ test {XREADGROUP history reporting of deleted entries. Bug #5570} {
+ r del mystream
+ r XGROUP CREATE mystream mygroup $ MKSTREAM
+ r XADD mystream 1 field1 A
+ r XREADGROUP GROUP mygroup myconsumer STREAMS mystream >
+ r XADD mystream MAXLEN 1 2 field1 B
+ r XREADGROUP GROUP mygroup myconsumer STREAMS mystream >
+
+ # Now we have two pending entries, however one should be deleted
+ # and one should be ok (we should only see "B")
+ set res [r XREADGROUP GROUP mygroup myconsumer STREAMS mystream 0-1]
+ assert {[lindex $res 0 1 0] == {1-0 {}}}
+ assert {[lindex $res 0 1 1] == {2-0 {field1 B}}}
+ }
+
+ test {XCLAIM can claim PEL items from another consumer} {
+ # Add 3 items into the stream, and create a consumer group
+ r del mystream
+ set id1 [r XADD mystream * a 1]
+ set id2 [r XADD mystream * b 2]
+ set id3 [r XADD mystream * c 3]
+ r XGROUP CREATE mystream mygroup 0
+
+ # Client 1 reads item 1 from the stream without acknowledgements.
+ # Client 2 then claims pending item 1 from the PEL of client 1
+ set reply [
+ r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
+ ]
+ assert {[llength [lindex $reply 0 1 0 1]] == 2}
+ assert {[lindex $reply 0 1 0 1] eq {a 1}}
+ r debug sleep 0.2
+ set reply [
+ r XCLAIM mystream mygroup client2 10 $id1
+ ]
+ assert {[llength [lindex $reply 0 1]] == 2}
+ assert {[lindex $reply 0 1] eq {a 1}}
+
+ # Client 1 reads another 2 items from stream
+ r XREADGROUP GROUP mygroup client1 count 2 STREAMS mystream >
+ r debug sleep 0.2
+
+ # Delete item 2 from the stream. Now client 1 has PEL that contains
+ # only item 3. Try to use client 2 to claim the deleted item 2
+ # from the PEL of client 1, this should return nil
+ r XDEL mystream $id2
+ set reply [
+ r XCLAIM mystream mygroup client2 10 $id2
+ ]
+ assert {[llength $reply] == 1}
+ assert_equal "" [lindex $reply 0]
+
+ # Delete item 3 from the stream. Now client 1 has PEL that is empty.
+ # Try to use client 2 to claim the deleted item 3 from the PEL
+ # of client 1, this should return nil
+ r debug sleep 0.2
+ r XDEL mystream $id3
+ set reply [
+ r XCLAIM mystream mygroup client2 10 $id3
+ ]
+ assert {[llength $reply] == 1}
+ assert_equal "" [lindex $reply 0]
+ }
+
+ test {XCLAIM without JUSTID increments delivery count} {
+ # Add 3 items into the stream, and create a consumer group
+ r del mystream
+ set id1 [r XADD mystream * a 1]
+ set id2 [r XADD mystream * b 2]
+ set id3 [r XADD mystream * c 3]
+ r XGROUP CREATE mystream mygroup 0
+
+ # Client 1 reads item 1 from the stream without acknowledgements.
+ # Client 2 then claims pending item 1 from the PEL of client 1
+ set reply [
+ r XREADGROUP GROUP mygroup client1 count 1 STREAMS mystream >
+ ]
+ assert {[llength [lindex $reply 0 1 0 1]] == 2}
+ assert {[lindex $reply 0 1 0 1] eq {a 1}}
+ r debug sleep 0.2
+ set reply [
+ r XCLAIM mystream mygroup client2 10 $id1
+ ]
+ assert {[llength [lindex $reply 0 1]] == 2}
+ assert {[lindex $reply 0 1] eq {a 1}}
+
+ set reply [
+ r XPENDING mystream mygroup - + 10
+ ]
+ assert {[llength [lindex $reply 0]] == 4}
+ assert {[lindex $reply 0 3] == 2}
+
+ # Client 3 then claims pending item 1 from the PEL of client 2 using JUSTID
+ r debug sleep 0.2
+ set reply [
+ r XCLAIM mystream mygroup client3 10 $id1 JUSTID
+ ]
+ assert {[llength $reply] == 1}
+ assert {[lindex $reply 0] eq $id1}
+
+ set reply [
+ r XPENDING mystream mygroup - + 10
+ ]
+ assert {[llength [lindex $reply 0]] == 4}
+ assert {[lindex $reply 0 3] == 2}
+ }
+
+ start_server {} {
+ set master [srv -1 client]
+ set master_host [srv -1 host]
+ set master_port [srv -1 port]
+ set slave [srv 0 client]
+
+ foreach noack {0 1} {
+ test "Consumer group last ID propagation to slave (NOACK=$noack)" {
+ $slave slaveof $master_host $master_port
+ wait_for_condition 50 100 {
+ [s 0 master_link_status] eq {up}
+ } else {
+ fail "Replication not started."
+ }
+
+ $master del stream
+ $master xadd stream * a 1
+ $master xadd stream * a 2
+ $master xadd stream * a 3
+ $master xgroup create stream mygroup 0
+
+ # Consume the first two items on the master
+ for {set j 0} {$j < 2} {incr j} {
+ if {$noack} {
+ set item [$master xreadgroup group mygroup \
+ myconsumer COUNT 1 NOACK STREAMS stream >]
+ } else {
+ set item [$master xreadgroup group mygroup \
+ myconsumer COUNT 1 STREAMS stream >]
+ }
+ set id [lindex $item 0 1 0 0]
+ if {$noack == 0} {
+ assert {[$master xack stream mygroup $id] eq "1"}
+ }
+ }
+
+ wait_for_ofs_sync $master $slave
+
+ # Turn slave into master
+ $slave slaveof no one
+
+ set item [$slave xreadgroup group mygroup myconsumer \
+ COUNT 1 STREAMS stream >]
+
+ # The consumed enty should be the third
+ set myentry [lindex $item 0 1 0 1]
+ assert {$myentry eq {a 3}}
+ }
+ }
+ }
+}
diff --git a/tests/unit/type/stream.tcl b/tests/unit/type/stream.tcl
index d7b5ca2a8..a7415ae8d 100644
--- a/tests/unit/type/stream.tcl
+++ b/tests/unit/type/stream.tcl
@@ -145,17 +145,17 @@ start_server {
}
test {XREAD with non empty stream} {
- set res [r XREAD COUNT 1 STREAMS mystream 0.0]
+ set res [r XREAD COUNT 1 STREAMS mystream 0-0]
assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {item 0}}
}
test {Non blocking XREAD with empty streams} {
- set res [r XREAD STREAMS s1 s2 0.0 0.0]
+ set res [r XREAD STREAMS s1 s2 0-0 0-0]
assert {$res eq {}}
}
test {XREAD with non empty second stream} {
- set res [r XREAD COUNT 1 STREAMS nostream mystream 0.0 0.0]
+ set res [r XREAD COUNT 1 STREAMS nostream mystream 0-0 0-0]
assert {[lindex $res 0 0] eq {mystream}}
assert {[lrange [lindex $res 0 1 0 1] 0 1] eq {item 0}}
}
@@ -172,7 +172,7 @@ start_server {
test {Blocking XREAD waiting old data} {
set rd [redis_deferring_client]
- $rd XREAD BLOCK 20000 STREAMS s1 s2 s3 $ 0.0 $
+ $rd XREAD BLOCK 20000 STREAMS s1 s2 s3 $ 0-0 $
r XADD s2 * foo abcd1234
set res [$rd read]
assert {[lindex $res 0 0] eq {s2}}
@@ -234,6 +234,53 @@ start_server {
assert {[lindex $res 0 1 1 1] eq {field two}}
}
+ test {XDEL basic test} {
+ r del somestream
+ r xadd somestream * foo value0
+ set id [r xadd somestream * foo value1]
+ r xadd somestream * foo value2
+ r xdel somestream $id
+ assert {[r xlen somestream] == 2}
+ set result [r xrange somestream - +]
+ assert {[lindex $result 0 1 1] eq {value0}}
+ assert {[lindex $result 1 1 1] eq {value2}}
+ }
+
+ # Here the idea is to check the consistency of the stream data structure
+ # as we remove all the elements down to zero elements.
+ test {XDEL fuzz test} {
+ r del somestream
+ set ids {}
+ set x 0; # Length of the stream
+ while 1 {
+ lappend ids [r xadd somestream * item $x]
+ incr x
+ # Add enough elements to have a few radix tree nodes inside the stream.
+ if {[dict get [r xinfo stream somestream] radix-tree-keys] > 20} break
+ }
+
+ # Now remove all the elements till we reach an empty stream
+ # and after every deletion, check that the stream is sane enough
+ # to report the right number of elements with XRANGE: this will also
+ # force accessing the whole data structure to check sanity.
+ assert {[r xlen somestream] == $x}
+
+ # We want to remove elements in random order to really test the
+ # implementation in a better way.
+ set ids [lshuffle $ids]
+ foreach id $ids {
+ assert {[r xdel somestream $id] == 1}
+ incr x -1
+ assert {[r xlen somestream] == $x}
+ # The test would be too slow calling XRANGE for every iteration.
+ # Do it every 100 removal.
+ if {$x % 100 == 0} {
+ set res [r xrange somestream - +]
+ assert {[llength $res] == $x}
+ }
+ }
+ }
+
test {XRANGE fuzzing} {
set low_id [lindex $items 0 0]
set high_id [lindex $items end 0]
@@ -253,4 +300,114 @@ start_server {
}
}
}
+
+ test {XREVRANGE regression test for issue #5006} {
+ # Add non compressed entries
+ r xadd teststream 1234567891230 key1 value1
+ r xadd teststream 1234567891240 key2 value2
+ r xadd teststream 1234567891250 key3 value3
+
+ # Add SAMEFIELD compressed entries
+ r xadd teststream2 1234567891230 key1 value1
+ r xadd teststream2 1234567891240 key1 value2
+ r xadd teststream2 1234567891250 key1 value3
+
+ assert_equal [r xrevrange teststream 1234567891245 -] {{1234567891240-0 {key2 value2}} {1234567891230-0 {key1 value1}}}
+
+ assert_equal [r xrevrange teststream2 1234567891245 -] {{1234567891240-0 {key1 value2}} {1234567891230-0 {key1 value1}}}
+ }
+}
+
+start_server {tags {"stream"} overrides {appendonly yes}} {
+ test {XADD with MAXLEN > xlen can propagate correctly} {
+ for {set j 0} {$j < 100} {incr j} {
+ r XADD mystream * xitem v
+ }
+ r XADD mystream MAXLEN 200 * xitem v
+ incr j
+ assert {[r xlen mystream] == $j}
+ r debug loadaof
+ r XADD mystream * xitem v
+ incr j
+ assert {[r xlen mystream] == $j}
+ }
+}
+
+start_server {tags {"stream"} overrides {appendonly yes}} {
+ test {XADD with ~ MAXLEN can propagate correctly} {
+ for {set j 0} {$j < 100} {incr j} {
+ r XADD mystream * xitem v
+ }
+ r XADD mystream MAXLEN ~ $j * xitem v
+ incr j
+ assert {[r xlen mystream] == $j}
+ r config set stream-node-max-entries 1
+ r debug loadaof
+ r XADD mystream * xitem v
+ incr j
+ assert {[r xlen mystream] == $j}
+ }
+}
+
+start_server {tags {"stream"} overrides {appendonly yes stream-node-max-entries 10}} {
+ test {XTRIM with ~ MAXLEN can propagate correctly} {
+ for {set j 0} {$j < 100} {incr j} {
+ r XADD mystream * xitem v
+ }
+ r XTRIM mystream MAXLEN ~ 85
+ assert {[r xlen mystream] == 90}
+ r config set stream-node-max-entries 1
+ r debug loadaof
+ r XADD mystream * xitem v
+ incr j
+ assert {[r xlen mystream] == 91}
+ }
+}
+
+start_server {tags {"xsetid"}} {
+ test {XADD can CREATE an empty stream} {
+ r XADD mystream MAXLEN 0 * a b
+ assert {[dict get [r xinfo stream mystream] length] == 0}
+ }
+
+ test {XSETID can set a specific ID} {
+ r XSETID mystream "200-0"
+ assert {[dict get [r xinfo stream mystream] last-generated-id] == "200-0"}
+ }
+
+ test {XSETID cannot SETID with smaller ID} {
+ r XADD mystream * a b
+ catch {r XSETID mystream "1-1"} err
+ r XADD mystream MAXLEN 0 * a b
+ set err
+ } {ERR*smaller*}
+
+ test {XSETID cannot SETID on non-existent key} {
+ catch {r XSETID stream 1-1} err
+ set _ $err
+ } {ERR no such key}
+}
+
+start_server {tags {"stream"} overrides {appendonly yes aof-use-rdb-preamble no}} {
+ test {Empty stream can be rewrite into AOF correctly} {
+ r XADD mystream MAXLEN 0 * a b
+ assert {[dict get [r xinfo stream mystream] length] == 0}
+ r bgrewriteaof
+ waitForBgrewriteaof r
+ r debug loadaof
+ assert {[dict get [r xinfo stream mystream] length] == 0}
+ }
+
+ test {Stream can be rewrite into AOF correctly after XDEL lastid} {
+ r XSETID mystream 0-0
+ r XADD mystream 1-1 a b
+ r XADD mystream 2-2 a b
+ assert {[dict get [r xinfo stream mystream] length] == 2}
+ r XDEL mystream 2-2
+ r bgrewriteaof
+ waitForBgrewriteaof r
+ r debug loadaof
+ assert {[dict get [r xinfo stream mystream] length] == 1}
+ assert {[dict get [r xinfo stream mystream] last-generated-id] == "2-2"}
+ }
}
diff --git a/tests/unit/type/zset.tcl b/tests/unit/type/zset.tcl
index 564825ae9..a8c817f6e 100644
--- a/tests/unit/type/zset.tcl
+++ b/tests/unit/type/zset.tcl
@@ -84,7 +84,7 @@ start_server {tags {"zset"}} {
set err
} {ERR*}
- test "ZADD NX with non exisitng key" {
+ test "ZADD NX with non existing key" {
r del ztmp
r zadd ztmp nx 10 x 20 y 30 z
assert {[r zcard ztmp] == 3}
@@ -388,7 +388,7 @@ start_server {tags {"zset"}} {
0 omega}
}
- test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZCOUNT basics" {
+ test "ZRANGEBYLEX/ZREVRANGEBYLEX/ZLEXCOUNT basics" {
create_default_lex_zset
# inclusive range
@@ -416,6 +416,22 @@ start_server {tags {"zset"}} {
assert_equal {} [r zrevrangebylex zset \[elez \[elex]
assert_equal {} [r zrevrangebylex zset (hill (omega]
}
+
+ test "ZLEXCOUNT advanced" {
+ create_default_lex_zset
+
+ assert_equal 9 [r zlexcount zset - +]
+ assert_equal 0 [r zlexcount zset + -]
+ assert_equal 0 [r zlexcount zset + \[c]
+ assert_equal 0 [r zlexcount zset \[c -]
+ assert_equal 8 [r zlexcount zset \[bar +]
+ assert_equal 5 [r zlexcount zset \[bar \[foo]
+ assert_equal 4 [r zlexcount zset \[bar (foo]
+ assert_equal 4 [r zlexcount zset (bar \[foo]
+ assert_equal 3 [r zlexcount zset (bar (foo]
+ assert_equal 5 [r zlexcount zset - (foo]
+ assert_equal 1 [r zlexcount zset (maxstring +]
+ }
test "ZRANGEBYSLEX with LIMIT" {
create_default_lex_zset
@@ -648,6 +664,75 @@ start_server {tags {"zset"}} {
}
}
}
+
+ test "Basic ZPOP with a single key - $encoding" {
+ r del zset
+ assert_equal {} [r zpopmin zset]
+ create_zset zset {-1 a 1 b 2 c 3 d 4 e}
+ assert_equal {a -1} [r zpopmin zset]
+ assert_equal {b 1} [r zpopmin zset]
+ assert_equal {e 4} [r zpopmax zset]
+ assert_equal {d 3} [r zpopmax zset]
+ assert_equal {c 2} [r zpopmin zset]
+ assert_equal 0 [r exists zset]
+ r set foo bar
+ assert_error "*WRONGTYPE*" {r zpopmin foo}
+ }
+
+ test "ZPOP with count - $encoding" {
+ r del z1 z2 z3 foo
+ r set foo bar
+ assert_equal {} [r zpopmin z1 2]
+ assert_error "*WRONGTYPE*" {r zpopmin foo 2}
+ create_zset z1 {0 a 1 b 2 c 3 d}
+ assert_equal {a 0 b 1} [r zpopmin z1 2]
+ assert_equal {d 3 c 2} [r zpopmax z1 2]
+ }
+
+ test "BZPOP with a single existing sorted set - $encoding" {
+ set rd [redis_deferring_client]
+ create_zset zset {0 a 1 b 2 c}
+
+ $rd bzpopmin zset 5
+ assert_equal {zset a 0} [$rd read]
+ $rd bzpopmin zset 5
+ assert_equal {zset b 1} [$rd read]
+ $rd bzpopmax zset 5
+ assert_equal {zset c 2} [$rd read]
+ assert_equal 0 [r exists zset]
+ }
+
+ test "BZPOP with multiple existing sorted sets - $encoding" {
+ set rd [redis_deferring_client]
+ create_zset z1 {0 a 1 b 2 c}
+ create_zset z2 {3 d 4 e 5 f}
+
+ $rd bzpopmin z1 z2 5
+ assert_equal {z1 a 0} [$rd read]
+ $rd bzpopmax z1 z2 5
+ assert_equal {z1 c 2} [$rd read]
+ assert_equal 1 [r zcard z1]
+ assert_equal 3 [r zcard z2]
+
+ $rd bzpopmax z2 z1 5
+ assert_equal {z2 f 5} [$rd read]
+ $rd bzpopmin z2 z1 5
+ assert_equal {z2 d 3} [$rd read]
+ assert_equal 1 [r zcard z1]
+ assert_equal 1 [r zcard z2]
+ }
+
+ test "BZPOP second sorted set has members - $encoding" {
+ set rd [redis_deferring_client]
+ r del z1
+ create_zset z2 {3 d 4 e 5 f}
+ $rd bzpopmax z1 z2 5
+ assert_equal {z2 f 5} [$rd read]
+ $rd bzpopmin z2 z1 5
+ assert_equal {z2 d 3} [$rd read]
+ assert_equal 0 [r zcard z1]
+ assert_equal 1 [r zcard z2]
+ }
}
basics ziplist
@@ -1025,10 +1110,121 @@ start_server {tags {"zset"}} {
}
assert_equal {} $err
}
+
+ test "BZPOPMIN, ZADD + DEL should not awake blocked client" {
+ set rd [redis_deferring_client]
+ r del zset
+
+ $rd bzpopmin zset 0
+ r multi
+ r zadd zset 0 foo
+ r del zset
+ r exec
+ r del zset
+ r zadd zset 1 bar
+ $rd read
+ } {zset bar 1}
+
+ test "BZPOPMIN, ZADD + DEL + SET should not awake blocked client" {
+ set rd [redis_deferring_client]
+ r del list
+
+ r del zset
+
+ $rd bzpopmin zset 0
+ r multi
+ r zadd zset 0 foo
+ r del zset
+ r set zset foo
+ r exec
+ r del zset
+ r zadd zset 1 bar
+ $rd read
+ } {zset bar 1}
+
+ test "BZPOPMIN with same key multiple times should work" {
+ set rd [redis_deferring_client]
+ r del z1 z2
+
+ # Data arriving after the BZPOPMIN.
+ $rd bzpopmin z1 z2 z2 z1 0
+ r zadd z1 0 a
+ assert_equal [$rd read] {z1 a 0}
+ $rd bzpopmin z1 z2 z2 z1 0
+ r zadd z2 1 b
+ assert_equal [$rd read] {z2 b 1}
+
+ # Data already there.
+ r zadd z1 0 a
+ r zadd z2 1 b
+ $rd bzpopmin z1 z2 z2 z1 0
+ assert_equal [$rd read] {z1 a 0}
+ $rd bzpopmin z1 z2 z2 z1 0
+ assert_equal [$rd read] {z2 b 1}
+ }
+
+ test "MULTI/EXEC is isolated from the point of view of BZPOPMIN" {
+ set rd [redis_deferring_client]
+ r del zset
+ $rd bzpopmin zset 0
+ r multi
+ r zadd zset 0 a
+ r zadd zset 1 b
+ r zadd zset 2 c
+ r exec
+ $rd read
+ } {zset a 0}
+
+ test "BZPOPMIN with variadic ZADD" {
+ set rd [redis_deferring_client]
+ r del zset
+ if {$::valgrind} {after 100}
+ $rd bzpopmin zset 0
+ if {$::valgrind} {after 100}
+ assert_equal 2 [r zadd zset -1 foo 1 bar]
+ if {$::valgrind} {after 100}
+ assert_equal {zset foo -1} [$rd read]
+ assert_equal {bar} [r zrange zset 0 -1]
+ }
+
+ test "BZPOPMIN with zero timeout should block indefinitely" {
+ set rd [redis_deferring_client]
+ r del zset
+ $rd bzpopmin zset 0
+ after 1000
+ r zadd zset 0 foo
+ assert_equal {zset foo 0} [$rd read]
+ }
}
tags {"slow"} {
stressers ziplist
stressers skiplist
}
+
+ test {ZSET skiplist order consistency when elements are moved} {
+ set original_max [lindex [r config get zset-max-ziplist-entries] 1]
+ r config set zset-max-ziplist-entries 0
+ for {set times 0} {$times < 10} {incr times} {
+ r del zset
+ for {set j 0} {$j < 1000} {incr j} {
+ r zadd zset [randomInt 50] ele-[randomInt 10]
+ }
+
+ # Make sure that element ordering is correct
+ set prev_element {}
+ set prev_score -1
+ foreach {element score} [r zrange zset 0 -1 WITHSCORES] {
+ # Assert that elements are in increasing ordering
+ assert {
+ $prev_score < $score ||
+ ($prev_score == $score &&
+ [string compare $prev_element $element] == -1)
+ }
+ set prev_element $element
+ set prev_score $score
+ }
+ }
+ r config set zset-max-ziplist-entries $original_max
+ }
}
diff --git a/tests/unit/wait.tcl b/tests/unit/wait.tcl
index e2f5d2942..c9cfa6ed4 100644
--- a/tests/unit/wait.tcl
+++ b/tests/unit/wait.tcl
@@ -1,3 +1,5 @@
+source tests/support/cli.tcl
+
start_server {tags {"wait"}} {
start_server {} {
set slave [srv 0 client]
@@ -31,7 +33,8 @@ start_server {} {
}
test {WAIT should not acknowledge 1 additional copy if slave is blocked} {
- exec src/redis-cli -h $slave_host -p $slave_port debug sleep 5 > /dev/null 2> /dev/null &
+ set cmd [rediscli $slave_port "-h $slave_host debug sleep 5"]
+ exec {*}$cmd > /dev/null 2> /dev/null &
after 1000 ;# Give redis-cli the time to execute the command.
$master set foo 0
$master incr foo
diff --git a/utils/corrupt_rdb.c b/utils/corrupt_rdb.c
index 7ba9caeee..df9c93ed8 100644
--- a/utils/corrupt_rdb.c
+++ b/utils/corrupt_rdb.c
@@ -21,8 +21,9 @@ int main(int argc, char **argv) {
}
srand(time(NULL));
+ char *filename = argv[1];
cycles = atoi(argv[2]);
- fd = open("dump.rdb",O_RDWR);
+ fd = open(filename,O_RDWR);
if (fd == -1) {
perror("open");
exit(1);
diff --git a/utils/create-cluster/README b/utils/create-cluster/README
index f2a89839b..37a3080db 100644
--- a/utils/create-cluster/README
+++ b/utils/create-cluster/README
@@ -15,8 +15,8 @@ To create a cluster, follow these steps:
1. Edit create-cluster and change the start / end port, depending on the
number of instances you want to create.
2. Use "./create-cluster start" in order to run the instances.
-3. Use "./create-cluster create" in order to execute redis-trib create, so that
-an actual Redis cluster will be created.
+3. Use "./create-cluster create" in order to execute redis-cli --cluster create, so that
+an actual Redis cluster will be created. (If you're accessing your setup via a local container, ensure that the CLUSTER_HOST value is changed to your local IP)
4. Now you are ready to play with the cluster. AOF files and logs for each instances are created in the current directory.
In order to stop a cluster:
diff --git a/utils/create-cluster/create-cluster b/utils/create-cluster/create-cluster
index d821683f6..9ffd462ae 100755
--- a/utils/create-cluster/create-cluster
+++ b/utils/create-cluster/create-cluster
@@ -1,10 +1,12 @@
#!/bin/bash
# Settings
+CLUSTER_HOST=127.0.0.1
PORT=30000
TIMEOUT=2000
NODES=6
REPLICAS=1
+PROTECTED_MODE=yes
# You may want to put the above config parameters into config.sh in order to
# override the defaults without modifying this script.
@@ -22,7 +24,7 @@ then
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
echo "Starting $PORT"
- ../../src/redis-server --port $PORT --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
+ ../../src/redis-server --port $PORT --protected-mode $PROTECTED_MODE --cluster-enabled yes --cluster-config-file nodes-${PORT}.conf --cluster-node-timeout $TIMEOUT --appendonly yes --appendfilename appendonly-${PORT}.aof --dbfilename dump-${PORT}.rdb --logfile ${PORT}.log --daemonize yes
done
exit 0
fi
@@ -32,9 +34,9 @@ then
HOSTS=""
while [ $((PORT < ENDPORT)) != "0" ]; do
PORT=$((PORT+1))
- HOSTS="$HOSTS 127.0.0.1:$PORT"
+ HOSTS="$HOSTS $CLUSTER_HOST:$PORT"
done
- ../../src/redis-trib.rb create --replicas $REPLICAS $HOSTS
+ ../../src/redis-cli --cluster create $HOSTS --cluster-replicas $REPLICAS
exit 0
fi
@@ -94,7 +96,7 @@ fi
echo "Usage: $0 [start|create|stop|watch|tail|clean]"
echo "start -- Launch Redis Cluster instances."
-echo "create -- Create a cluster using redis-trib create."
+echo "create -- Create a cluster using redis-cli --cluster create."
echo "stop -- Stop Redis Cluster instances."
echo "watch -- Show CLUSTER NODES output (first 30 lines) of first node."
echo "tail <id> -- Run tail -f of instance at base port + ID."
diff --git a/utils/gen-test-certs.sh b/utils/gen-test-certs.sh
new file mode 100755
index 000000000..a46edc55a
--- /dev/null
+++ b/utils/gen-test-certs.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+mkdir -p tests/tls
+openssl genrsa -out tests/tls/ca.key 4096
+openssl req \
+ -x509 -new -nodes -sha256 \
+ -key tests/tls/ca.key \
+ -days 3650 \
+ -subj '/O=Redis Test/CN=Certificate Authority' \
+ -out tests/tls/ca.crt
+openssl genrsa -out tests/tls/redis.key 2048
+openssl req \
+ -new -sha256 \
+ -key tests/tls/redis.key \
+ -subj '/O=Redis Test/CN=Server' | \
+ openssl x509 \
+ -req -sha256 \
+ -CA tests/tls/ca.crt \
+ -CAkey tests/tls/ca.key \
+ -CAserial tests/tls/ca.txt \
+ -CAcreateserial \
+ -days 365 \
+ -out tests/tls/redis.crt
+openssl dhparam -out tests/tls/redis.dh 2048
diff --git a/utils/generate-command-help.rb b/utils/generate-command-help.rb
index f3dfb31b3..29acef69d 100755
--- a/utils/generate-command-help.rb
+++ b/utils/generate-command-help.rb
@@ -14,7 +14,8 @@ GROUPS = [
"scripting",
"hyperloglog",
"cluster",
- "geo"
+ "geo",
+ "stream"
].freeze
GROUPS_BY_NAME = Hash[*
diff --git a/utils/hashtable/README b/utils/hashtable/README
index e2862f012..87a76c9a5 100644
--- a/utils/hashtable/README
+++ b/utils/hashtable/README
@@ -5,7 +5,7 @@ rehashing.c
Visually show buckets in the two hash tables between rehashings. Also stress
test getRandomKeys() implementation, that may actually disappear from
-Redis soon, however visualizaiton some code is reusable in new bugs
+Redis soon, however visualization some code is reusable in new bugs
investigation.
Compile with:
diff --git a/utils/hyperloglog/hll-err.rb b/utils/hyperloglog/hll-err.rb
index 75bb8e424..2c71ac5ef 100644
--- a/utils/hyperloglog/hll-err.rb
+++ b/utils/hyperloglog/hll-err.rb
@@ -18,7 +18,7 @@ while true do
elements << ele
i += 1
}
- r.pfadd('hll',*elements)
+ r.pfadd('hll',elements)
}
approx = r.pfcount('hll')
abs_err = (approx-i).abs
diff --git a/utils/install_server.sh b/utils/install_server.sh
index 7eb341417..8e5753bc6 100755
--- a/utils/install_server.sh
+++ b/utils/install_server.sh
@@ -43,6 +43,9 @@
#
# /!\ This script should be run as root
#
+# NOTE: This script will not work on Mac OSX.
+# It supports Debian and Ubuntu Linux.
+#
################################################################################
die () {
diff --git a/utils/redis_init_script b/utils/redis_init_script
index 4dfe98047..006db87e5 100755
--- a/utils/redis_init_script
+++ b/utils/redis_init_script
@@ -3,6 +3,14 @@
# Simple Redis init.d script conceived to work on Linux systems
# as it does use of the /proc filesystem.
+### BEGIN INIT INFO
+# Provides: redis_6379
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: Redis data structure server
+# Description: Redis data structure server. See https://redis.io
+### END INIT INFO
+
REDISPORT=6379
EXEC=/usr/local/bin/redis-server
CLIEXEC=/usr/local/bin/redis-cli
diff --git a/utils/releasetools/changelog.tcl b/utils/releasetools/changelog.tcl
index 9b3a2cddc..06e38ba99 100755
--- a/utils/releasetools/changelog.tcl
+++ b/utils/releasetools/changelog.tcl
@@ -1,12 +1,17 @@
#!/usr/bin/env tclsh
-if {[llength $::argv] != 2} {
- puts "Usage: $::argv0 <branch> <version>"
+if {[llength $::argv] != 2 && [llength $::argv] != 3} {
+ puts "Usage: $::argv0 <branch> <version> \[<num-commits>\]"
exit 1
}
set branch [lindex $::argv 0]
set ver [lindex $::argv 1]
+if {[llength $::argv] == 3} {
+ set count [lindex ::$argv 2]
+} else {
+ set count 100
+}
set template {
================================================================================
@@ -21,7 +26,7 @@ append template "\n\n"
set date [clock format [clock seconds]]
set template [string map [list %ver% $ver %date% $date] $template]
-append template [exec git log $branch~100..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
+append template [exec git log $branch~$count..$branch "--format=format:%an in commit %h:%n %s" --shortstat]
#Older, more verbose version.
#
diff --git a/utils/srandmember/README.md b/utils/srandmember/README.md
new file mode 100644
index 000000000..d3da1e82f
--- /dev/null
+++ b/utils/srandmember/README.md
@@ -0,0 +1,14 @@
+The utilities in this directory plot the distribution of SRANDMEMBER to
+evaluate how fair it is.
+
+See http://theshfl.com/redis_sets for more information on the topic that lead
+to such investigation fix.
+
+showdist.rb -- shows the distribution of the frequency elements are returned.
+ The x axis is the number of times elements were returned, and
+ the y axis is how many elements were returned with such
+ frequency.
+
+showfreq.rb -- shows the frequency each element was returned.
+ The x axis is the element number.
+ The y axis is the times it was returned.
diff --git a/utils/srandmember/showdist.rb b/utils/srandmember/showdist.rb
new file mode 100644
index 000000000..243585700
--- /dev/null
+++ b/utils/srandmember/showdist.rb
@@ -0,0 +1,33 @@
+require 'redis'
+
+r = Redis.new
+r.select(9)
+r.del("myset");
+r.sadd("myset",(0..999).to_a)
+freq = {}
+100.times {
+ res = r.pipelined {
+ 1000.times {
+ r.srandmember("myset")
+ }
+ }
+ res.each{|ele|
+ freq[ele] = 0 if freq[ele] == nil
+ freq[ele] += 1
+ }
+}
+
+# Convert into frequency distribution
+dist = {}
+freq.each{|item,count|
+ dist[count] = 0 if dist[count] == nil
+ dist[count] += 1
+}
+
+min = dist.keys.min
+max = dist.keys.max
+(min..max).each{|x|
+ count = dist[x]
+ count = 0 if count == nil
+ puts "#{x} -> #{"*"*count}"
+}
diff --git a/utils/srandmember/showfreq.rb b/utils/srandmember/showfreq.rb
new file mode 100644
index 000000000..fd47bc0ca
--- /dev/null
+++ b/utils/srandmember/showfreq.rb
@@ -0,0 +1,23 @@
+require 'redis'
+
+r = Redis.new
+r.select(9)
+r.del("myset");
+r.sadd("myset",(0..999).to_a)
+freq = {}
+500.times {
+ res = r.pipelined {
+ 1000.times {
+ r.srandmember("myset")
+ }
+ }
+ res.each{|ele|
+ freq[ele] = 0 if freq[ele] == nil
+ freq[ele] += 1
+ }
+}
+
+# Print the frequency each element was yeld to process it with gnuplot
+freq.each{|item,count|
+ puts "#{item} #{count}"
+}
diff --git a/utils/tracking_collisions.c b/utils/tracking_collisions.c
new file mode 100644
index 000000000..cd64b36c5
--- /dev/null
+++ b/utils/tracking_collisions.c
@@ -0,0 +1,76 @@
+/* This is a small program used in order to understand the collison rate
+ * of CRC64 (ISO version) VS other stronger hashing functions in the context
+ * of hashing keys for the Redis "tracking" feature (client side caching
+ * assisted by the server).
+ *
+ * The program attempts to hash keys with common names in the form of
+ *
+ * prefix:<counter>
+ *
+ * And counts the resulting collisons generated in the 24 bits of output
+ * needed for the tracking feature invalidation table (16 millions + entries)
+ *
+ * Compile with:
+ *
+ * cc -O2 ./tracking_collisions.c ../src/crc64.c ../src/sha1.c
+ * ./a.out
+ *
+ * --------------------------------------------------------------------------
+ *
+ * Copyright (C) 2019 Salvatore Sanfilippo
+ * This code is released under the BSD 2 clause license.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include "../src/crc64.h"
+#include "../src/sha1.h"
+
+#define TABLE_SIZE (1<<24)
+int Table[TABLE_SIZE];
+
+uint64_t crc64Hash(char *key, size_t len) {
+ return crc64(0,(unsigned char*)key,len);
+}
+
+uint64_t sha1Hash(char *key, size_t len) {
+ SHA1_CTX ctx;
+ unsigned char hash[20];
+
+ SHA1Init(&ctx);
+ SHA1Update(&ctx,(unsigned char*)key,len);
+ SHA1Final(hash,&ctx);
+ uint64_t hash64;
+ memcpy(&hash64,hash,sizeof(hash64));
+ return hash64;
+}
+
+/* Test the hashing function provided as callback and return the
+ * number of collisions found. */
+unsigned long testHashingFunction(uint64_t (*hash)(char *, size_t)) {
+ unsigned long collisions = 0;
+ memset(Table,0,sizeof(Table));
+ char *prefixes[] = {"object", "message", "user", NULL};
+ for (int i = 0; prefixes[i] != NULL; i++) {
+ for (int j = 0; j < TABLE_SIZE/2; j++) {
+ char keyname[128];
+ size_t keylen = snprintf(keyname,sizeof(keyname),"%s:%d",
+ prefixes[i],j);
+ uint64_t bucket = hash(keyname,keylen) % TABLE_SIZE;
+ if (Table[bucket]) {
+ collisions++;
+ } else {
+ Table[bucket] = 1;
+ }
+ }
+ }
+ return collisions;
+}
+
+int main(void) {
+ printf("SHA1 : %lu\n", testHashingFunction(sha1Hash));
+ printf("CRC64: %lu\n", testHashingFunction(crc64Hash));
+ return 0;
+}