From: Timo Aaltonen Date: Mon, 19 Jun 2023 16:13:30 +0000 (+0100) Subject: Import 389-ds-base_2.3.4+dfsg1-1.debian.tar.xz X-Git-Tag: archive/raspbian/2.3.4+dfsg1-1.1+rpi1^2^2~5^2 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=d4d7130fe5a74bb827ec83d298e15e64717afc84;p=389-ds-base.git Import 389-ds-base_2.3.4+dfsg1-1.debian.tar.xz [dgit import tarball 389-ds-base 2.3.4+dfsg1-1 389-ds-base_2.3.4+dfsg1-1.debian.tar.xz] --- d4d7130fe5a74bb827ec83d298e15e64717afc84 diff --git a/389-ds-base-dev.install b/389-ds-base-dev.install new file mode 100644 index 0000000..6f305f1 --- /dev/null +++ b/389-ds-base-dev.install @@ -0,0 +1,8 @@ +usr/include/dirsrv/* +usr/include/svrcore.h +usr/lib/*/dirsrv/libldaputil.so +usr/lib/*/dirsrv/libns-dshttpd.so +usr/lib/*/dirsrv/librewriters.so +usr/lib/*/dirsrv/libslapd.so +usr/lib/*/libsvrcore.so +usr/lib/*/pkgconfig/* diff --git a/389-ds-base-libs.install b/389-ds-base-libs.install new file mode 100644 index 0000000..d072a44 --- /dev/null +++ b/389-ds-base-libs.install @@ -0,0 +1,6 @@ +usr/lib/*/dirsrv/lib/libjemalloc.so.* +usr/lib/*/dirsrv/libldaputil.so.* +usr/lib/*/dirsrv/libns-dshttpd.so.* +usr/lib/*/dirsrv/librewriters.so.* +usr/lib/*/dirsrv/libslapd.so.* +usr/lib/*/libsvrcore.so.* diff --git a/389-ds-base-libs.lintian-overrides b/389-ds-base-libs.lintian-overrides new file mode 100644 index 0000000..e4a0c15 --- /dev/null +++ b/389-ds-base-libs.lintian-overrides @@ -0,0 +1 @@ +custom-library-search-path diff --git a/389-ds-base.default b/389-ds-base.default new file mode 100644 index 0000000..14beb77 --- /dev/null +++ b/389-ds-base.default @@ -0,0 +1,6 @@ +# Defaults for dirsrv +# +# This is a POSIX shell fragment + +# Enable bindnow hardening +LD_BIND_NOW=1 diff --git a/389-ds-base.dirs b/389-ds-base.dirs new file mode 100644 index 0000000..f12d71e --- /dev/null +++ b/389-ds-base.dirs @@ -0,0 +1,2 @@ +var/log/dirsrv +var/lib/dirsrv diff --git a/389-ds-base.install b/389-ds-base.install new file mode 100644 index 0000000..3f91867 --- /dev/null +++ b/389-ds-base.install @@ -0,0 +1,37 @@ +etc/dirsrv/config/ +etc/dirsrv/schema/*.ldif +etc/systemd/ +lib/systemd/system/dirsrv-snmp.service +lib/systemd/system/dirsrv.target +lib/systemd/system/dirsrv@.service +lib/systemd/system/dirsrv@.service.d/custom.conf +usr/bin/dbscan +usr/bin/ds-logpipe +usr/bin/ds-replcheck +usr/bin/ldclt +usr/bin/logconv +usr/bin/pwdhash +usr/lib/*/dirsrv/plugins/*.so +usr/lib/*/dirsrv/python/ +usr/libexec/dirsrv/dscontainer +usr/libexec/ds_selinux_restorecon.sh +usr/libexec/ds_systemd_ask_password_acl +usr/lib/sysctl.d/70-dirsrv.conf +usr/sbin/ldap-agent +usr/sbin/ns-slapd +usr/sbin/openldap_to_ds +usr/share/dirsrv/data +usr/share/dirsrv/inf +usr/share/dirsrv/mibs +usr/share/dirsrv/schema +usr/share/gdb/auto-load/usr/sbin/ns-slapd-gdb.py +usr/share/man/man1/dbscan.1 +usr/share/man/man1/ds-logpipe.1 +usr/share/man/man1/ds-replcheck.1 +usr/share/man/man1/ldap-agent.1 +usr/share/man/man1/ldclt.1 +usr/share/man/man1/logconv.1 +usr/share/man/man1/pwdhash.1 +usr/share/man/man5/*.5 +usr/share/man/man8/ns-slapd.8 +usr/share/man/man8/openldap_to_ds.8 diff --git a/389-ds-base.links b/389-ds-base.links new file mode 100644 index 0000000..2f83bc6 --- /dev/null +++ b/389-ds-base.links @@ -0,0 +1 @@ +/dev/null lib/systemd/system/dirsrv.service diff --git a/389-ds-base.lintian-overrides b/389-ds-base.lintian-overrides new file mode 100644 index 0000000..693de7c --- /dev/null +++ b/389-ds-base.lintian-overrides @@ -0,0 +1,5 @@ +# these are bogus warnings, no libs shipped in a public libdir +unused-shlib-entry-in-control-file + +# plugins +custom-library-search-path diff --git a/389-ds-base.postinst b/389-ds-base.postinst new file mode 100644 index 0000000..413fb60 --- /dev/null +++ b/389-ds-base.postinst @@ -0,0 +1,35 @@ +#!/bin/sh +set -e + +. /usr/share/debconf/confmodule + +CONFIG_DIR=/etc/dirsrv +OUT=/dev/null +INSTANCES=`ls -d /etc/dirsrv/slapd-* 2>/dev/null | grep -v removed | sed 's/.*slapd-//'` + +if [ "$1" = configure ]; then + # lets give them a user/group in all cases. + if ! getent passwd dirsrv > $OUT; then + adduser --quiet --system --home /var/lib/dirsrv \ + --disabled-password --group \ + --gecos "389 Directory Server user" \ + --no-create-home \ + dirsrv > $OUT + fi + + chown -R dirsrv:dirsrv /etc/dirsrv/ /var/log/dirsrv/ /var/lib/dirsrv/ > $OUT || true + chmod 750 /etc/dirsrv/ /var/log/dirsrv/ /var/lib/dirsrv/ > $OUT || true +fi + +invoke_failure() { + # invoke-rc.d failed, likely because no instance has been configured yet + # but exit with an error if an instance is configured and the invoke failed + if [ -z $INSTANCES ]; then + echo "... because no instance has been configured yet." + else + exit 1 + fi +} + + +#DEBHELPER# diff --git a/389-ds-base.postrm b/389-ds-base.postrm new file mode 100644 index 0000000..0a70e0e --- /dev/null +++ b/389-ds-base.postrm @@ -0,0 +1,16 @@ +#!/bin/sh +set -e + +. /usr/share/debconf/confmodule + +if [ "$1" = "purge" ]; then + if getent group dirsrv > /dev/null; then + deluser --system dirsrv || true + fi + rm -f /etc/systemd/system/dirsrv.target.wants/dirsrv@*.service + rm -rf /etc/dirsrv + rm -rf /var/lib/dirsrv + rm -rf /var/log/dirsrv +fi + +#DEBHELPER# diff --git a/389-ds-base.prerm b/389-ds-base.prerm new file mode 100644 index 0000000..bfa9c61 --- /dev/null +++ b/389-ds-base.prerm @@ -0,0 +1,14 @@ +#!/bin/sh -e +set -e + +#DEBHELPER# + +if [ "$1" = "purge" ]; then + # remove all installed instances + for FILE in `ls -d /etc/dirsrv/slapd-* 2>/dev/null | sed -n '/\.removed$/!$'` + do + if [ -d "$FILE" ] ; then + dsctl $FILE remove --do-it + fi + done +fi diff --git a/README.Debian b/README.Debian new file mode 100644 index 0000000..eba838e --- /dev/null +++ b/README.Debian @@ -0,0 +1,12 @@ +To complete the 389 Directory Server installation just run /usr/sbin/setup-ds. + +If you experience problems accessing the Directory Server, check with +"netstat -tapen |grep 389" and verify that the server is not listening only +to ipv6 (check for ^tcp6). In such case you will need to tweak the cn=config +DIT with something like the following: + +dn: cn=config +changetype: modify +add: nsslapd-listenhost +nsslapd-listenhost: + diff --git a/changelog b/changelog new file mode 100644 index 0000000..0111c6e --- /dev/null +++ b/changelog @@ -0,0 +1,1078 @@ +389-ds-base (2.3.4+dfsg1-1) unstable; urgency=medium + + [ Timo Aaltonen ] + * New upstream release. + * patches: Drop upstreamed or obsolete patches. + * control: Add dependency on python3-cryptography. + + [ Peter Michael Green ] + * Improve clean target. + * Use ln -fs instead of ln -s to allow resuming build after fixing errors. + * Fix build with base64 0.21. (Closes: #1037345) + + -- Timo Aaltonen Mon, 19 Jun 2023 19:13:30 +0300 + +389-ds-base (2.3.1+dfsg1-1) unstable; urgency=medium + + * Repackage the source, filter vendored crates and allow building with + packaged crates. + * d/vendor: Add concread 0.2.21 and uuid 0.8. + * allow-newer-crates.diff, control: Add a bunch of rust crates to + build-deps, and patch toml files to allow newer crates. + * concread: Mark tcache as non-default, fix checksums and ease a dep. + * Update copyright for vendored bits. + + -- Timo Aaltonen Tue, 24 Jan 2023 13:21:19 +0200 + +389-ds-base (2.3.1-1) unstable; urgency=medium + + * New upstream release. (Closes: #1028177) + - CVE-2022-1949 (Closes: #1016446) + - CVE-2022-2850 (Closes: #1018054) + * watch: Updated to use releases instead of tags. + * control: Add liblmdb-dev to build-depends. + * control: Add libjson-c-dev to build-depends. + * control: Build-depend on libpcre2-dev. (Closes: #1000017) + * Build with rust enabled. + * rules: Don't use a separate builddir for now. + * 5610-fix-linking.diff: Fix linking libslapd.so. + * dont-run-rpm.diff: Use dpkg-query to check for cockpit/firewalld. + (Closes: #1010136) + + -- Timo Aaltonen Fri, 20 Jan 2023 23:15:49 +0200 + +389-ds-base (2.0.15-1) unstable; urgency=medium + + * New upstream release. + + -- Timo Aaltonen Wed, 13 Apr 2022 14:11:20 +0300 + +389-ds-base (2.0.14-1) unstable; urgency=medium + + * New upstream release. + * install: Updated. + * control: Bump policy to 4.6.0. + + -- Timo Aaltonen Thu, 10 Feb 2022 20:00:45 +0200 + +389-ds-base (2.0.11-2) unstable; urgency=medium + + * Revert a commit that makes dscreate to fail. + + -- Timo Aaltonen Wed, 15 Dec 2021 23:23:15 +0200 + +389-ds-base (2.0.11-1) unstable; urgency=medium + + * New upstream release. + * missing-sources: Removed, all the minified javascript files were + removed upstream some time ago. + * install: Updated. + * control: Bump debhelper to 13. + * Override some lintian errors. + * watch: Update the url. + + -- Timo Aaltonen Wed, 15 Dec 2021 21:03:20 +0200 + +389-ds-base (1.4.4.17-1) unstable; urgency=medium + + * New upstream release. + - CVE-2021-3652 (Closes: #991405) + * tests: Add isolation-container to restrictions. + * Add a dependency to libjemalloc2, and add a symlink to it so the + preload works. (Closes: #992696) + * CVE-2017-15135.patch: Dropped, fixed by upstream issue #4817. + + -- Timo Aaltonen Mon, 18 Oct 2021 18:36:30 +0300 + +389-ds-base (1.4.4.16-1) unstable; urgency=medium + + * New upstream release. + * fix-s390x-failure.diff: Dropped, upstream. + * watch: Updated to use github. + * copyright: Fix 'globbing-patterns-out-of-order'. + + -- Timo Aaltonen Mon, 16 Aug 2021 09:54:52 +0300 + +389-ds-base (1.4.4.11-1) unstable; urgency=medium + + * New upstream release. + * fix-s390x-failure.diff: Fix a crash on big-endian architectures like + s390x. + + -- Timo Aaltonen Thu, 28 Jan 2021 13:03:32 +0200 + +389-ds-base (1.4.4.10-1) unstable; urgency=medium + + * New upstream release. + * CVE-2017-15135.patch: Refreshed. + * source: Update diff-ignore. + * install: Drop libsds which got removed. + * control: Add libnss3-tools to cockpit-389-ds Depends. (Closes: + #965004) + * control: Drop python3-six from depends. + + -- Timo Aaltonen Thu, 21 Jan 2021 22:16:28 +0200 + +389-ds-base (1.4.4.9-1) unstable; urgency=medium + + * New upstream release. + * fix-prlog-include.diff: Dropped, upstream. + + -- Timo Aaltonen Fri, 18 Dec 2020 15:29:20 +0200 + +389-ds-base (1.4.4.8-1) unstable; urgency=medium + + * New upstream release. + * fix-systemctl-path.diff, drop-old-man.diff: Dropped, obsolete. + * fix-prlog-include.diff: Fix build by dropping nspr4/ prefix. + * install, rules: Clean up perl cruft that got removed upstream. + * install: Add openldap_to_ds. + * watch: Follow 1.4.4.x. + + -- Timo Aaltonen Thu, 12 Nov 2020 15:57:11 +0200 + +389-ds-base (1.4.4.4-1) unstable; urgency=medium + + * New upstream release. + * watch: Update upstream git repo url. + * control: Add python3-dateutil to build-depends. + * copyright: Drop duplicate globbing patterns. + * lintian: Drop obsolete overrides. + * postinst: Drop obsolete rule to upgrade the instances. + * prerm: Use dsctl instead of remove-ds. + + -- Timo Aaltonen Tue, 22 Sep 2020 09:23:30 +0300 + +389-ds-base (1.4.4.3-1) unstable; urgency=medium + + * New upstream release. + * fix-db-home-dir.diff: Dropped, upstream. + + -- Timo Aaltonen Tue, 02 Jun 2020 11:33:44 +0300 + +389-ds-base (1.4.3.6-2) unstable; urgency=medium + + * fix-db-home-dir.diff: Set db_home_dir same as db_dir to fix an issue + starting a newly created instance. + + -- Timo Aaltonen Tue, 21 Apr 2020 20:19:06 +0300 + +389-ds-base (1.4.3.6-1) unstable; urgency=medium + + * New upstream release. + * install: Updated. + + -- Timo Aaltonen Mon, 20 Apr 2020 15:01:35 +0300 + +389-ds-base (1.4.3.4-1) unstable; urgency=medium + + * New upstream release. + * Add debian/gitlab-ci.yml. + - allow blhc to fail + * control: Bump policy to 4.5.0. + * control: Use https url for upstream. + * control: Use canonical URL in Vcs-Browser. + * copyright: Use spaces rather than tabs to start continuation lines. + * Add lintian-overrides for the source, cockpit index.js has long lines. + + -- Timo Aaltonen Wed, 18 Mar 2020 08:47:32 +0200 + +389-ds-base (1.4.3.2-1) unstable; urgency=medium + + * New upstream release. + * prerm: Fix slapd install path. (Closes: #945583) + * install: Updated. + * control: Use debhelper-compat. + + -- Timo Aaltonen Wed, 12 Feb 2020 19:39:22 +0200 + +389-ds-base (1.4.2.4-1) unstable; urgency=medium + + * New upstream release. + - CVE-2019-14824 deref plugin displays restricted attributes + (Closes: #944150) + * fix-obsolete-target.diff: Dropped, obsolete + drop-old-man.diff: Refreshed + * control: Add python3-packaging to build-depends and python3-lib389 depends. + * dev,libs.install: Nunc-stans got dropped. + * source/local-options: Add some files to diff-ignore. + * rules: Refresh list of files to purge. + * rules: Update dh_auto_clean override. + + -- Timo Aaltonen Wed, 27 Nov 2019 00:00:59 +0200 + +389-ds-base (1.4.1.6-4) unstable; urgency=medium + + * tests: Redirect stderr to stdout. + + -- Timo Aaltonen Tue, 17 Sep 2019 01:37:39 +0300 + +389-ds-base (1.4.1.6-3) unstable; urgency=medium + + * control: Add openssl to python3-lib389 depends. + + -- Timo Aaltonen Fri, 13 Sep 2019 07:32:27 +0300 + +389-ds-base (1.4.1.6-2) unstable; urgency=medium + + * Restore perl build partly, setup-ds is still needed for upgrades + until Ubuntu 20.04 is released (for versions << 1.4.0.9). + + -- Timo Aaltonen Thu, 12 Sep 2019 14:50:36 +0300 + +389-ds-base (1.4.1.6-1) unstable; urgency=medium + + * New upstream release. + * control: Drop direct depends on python from 389-ds-base. (Closes: + #936102) + * Drop -legacy-tools and other obsolete scripts. + * use-bash-instead-of-sh.diff, rename-online-scripts.diff, perl-use- + move-instead-of-rename.diff: Dropped, obsolete. + * rules: Fix dsconf/dscreate/dsctl/dsidm manpage section. + * tests/setup: Migrate to dscreate. + * control: Add libnss3-tools to python3-lib389 depends. (Closes: #920025) + + -- Timo Aaltonen Wed, 11 Sep 2019 17:01:03 +0300 + +389-ds-base (1.4.1.5-1) unstable; urgency=medium + + * New upstream release. + * watch: Use https. + * control: Bump policy to 4.4.0. + * Bump debhelper to 12. + * patches: fix-dsctl-remove.diff, fix-nss-path.diff, icu_pkg-config.patch + removed, upstream. Others refreshed. + * rules: Pass --enable-perl, we still need the perl tools. + * *.install: Updated. + + -- Timo Aaltonen Wed, 10 Jul 2019 10:05:31 +0300 + +389-ds-base (1.4.0.22-1) unstable; urgency=medium + + * New upstream bugfix release. + * control: Drop 389-ds-base from -legacy-tools Depends. (Closes: + #924265) + * fix-dsctl-remove.diff: Don't hardcode sysconfig. (Closes: #925221) + + -- Timo Aaltonen Sat, 06 Apr 2019 00:32:06 +0300 + +389-ds-base (1.4.0.21-1) unstable; urgency=medium + + * New upstream release. + * Run offline upgrade only when upgrading from versions below 1.4.0.9, + ns-slapd itself handles upgrades in newer versions. + * rules: Actually install the minified javascript files. (Closes: + #913820) + + -- Timo Aaltonen Tue, 12 Feb 2019 16:28:15 +0200 + +389-ds-base (1.4.0.20-3) unstable; urgency=medium + + * control: 389-ds-base should depend on the legacy tools for now. + (Closes: #919420) + + -- Timo Aaltonen Wed, 16 Jan 2019 11:30:51 +0200 + +389-ds-base (1.4.0.20-2) unstable; urgency=medium + + * Upload to unstable. + + -- Timo Aaltonen Mon, 14 Jan 2019 20:03:58 +0200 + +389-ds-base (1.4.0.20-1) experimental; urgency=medium + + * New upstream release. (Closes: #913821) + * fix-nss-path.diff: Fix includes. + * Build ds* manpages, add missing build-depends. + * Move deprecated tools in a new subpackage. + * control: Add python3-lib389 to 389-ds-base depends. + + -- Timo Aaltonen Sun, 13 Jan 2019 21:13:22 +0200 + +389-ds-base (1.4.0.19-3) unstable; urgency=medium + + [ Jelmer Vernooij ] + * Use secure copyright file specification URI. + * Trim trailing whitespace. + * Use secure URI in Vcs control header. + + [ Hugh McMaster ] + * control: Mark 389-ds-base-libs{,-dev} M-A: same, cockpit-389-ds M-A: + foreign and arch:all. (Closes: #916118) + * Use pkg-config to detect icu. (Closes: #916115) + + -- Timo Aaltonen Wed, 02 Jan 2019 12:43:23 +0200 + +389-ds-base (1.4.0.19-2) unstable; urgency=medium + + * rules: Add -latomic to LDFLAGS on archs failing to build. (Closes: + #910982) + + -- Timo Aaltonen Thu, 06 Dec 2018 01:06:37 +0200 + +389-ds-base (1.4.0.19-1) unstable; urgency=medium + + * New upstream release. + * control: Make C/R backports-compatible. (Closes: #910796) + * use-packaged-js.diff: Dropped, packaged versions don't work. + (Closes: #913820) + * Follow upstream, and drop python3-dirsrvtests. + * cockpit-389-ds.install: Updated. + + -- Timo Aaltonen Mon, 03 Dec 2018 15:56:40 +0200 + +389-ds-base (1.4.0.18-1) unstable; urgency=medium + + * New upstream release. + - CVE-2018-14624 (Closes: #907778) + - CVE-2018-14638 (Closes: #908859) + * control: Build on any arch again. + * perl-use-move-instead-of-rename.diff: Use copy instead of move, + except when restoring files in case of an error. + * Move the new utils (dsconf, dscreate, dsctl, dsidm) to python3- + lib389. + * control: Add python3-argcomplete to python3-lib389 depends. (Closes: + #910761) + + -- Timo Aaltonen Thu, 11 Oct 2018 00:56:02 +0300 + +389-ds-base (1.4.0.16-1) unstable; urgency=medium + + * New upstream release. + * control: 389-ds-base-dev provides libsvrcore-dev. (Closes: #907140) + * perl-use-move-instead-of-rename.diff: Fix upgrade on systems where + /var is on a separate partition: (Closes: #905184) + + -- Timo Aaltonen Thu, 27 Sep 2018 22:39:34 +0300 + +389-ds-base (1.4.0.15-2) unstable; urgency=medium + + * control: Build cockpit-389-ds only on 64bit and i386. + + -- Timo Aaltonen Thu, 23 Aug 2018 08:54:06 +0300 + +389-ds-base (1.4.0.15-1) unstable; urgency=medium + + * New upstream release + - CVE-2018-10935 (Closes: #906985) + * control: Add libcrack2-dev to build-depends. + + -- Timo Aaltonen Thu, 23 Aug 2018 00:46:45 +0300 + +389-ds-base (1.4.0.13-1) experimental; urgency=medium + + * New upstream release. + - CVE-2018-10850 (Closes: #903501) + * control: Update maintainer address. + * control: Upstream dropped support for non-64bit architectures, so + build only on supported 64bit archs (amd64, arm64, mips64el, + ppc64el, s390x). + * control: svrcore got merged here, drop it from build-depends. + * ftbs_lsoftotkn3.diff: Dropped, obsolete. + * control: Add rsync to build-depends. + * libs, dev, control: Add libsvrcore files, replace old package. + * base: Add new scripts, add python3-selinux, -semanage, -sepolicy to + depends. + * Add a package for cockpit-389-ds. + * rules: Clean up cruft left after build. + * control: Drop dh_systemd from build-depends, bump debhelper to 11. + * Add varions libjs packages to cockpit-389-ds Depends, add the rest + to d/missing-sources. + * copyright: Updated. (Closes: #904760) + * control: Modify 389-ds to depend on cockpit-389-ds and drop the old + GUI packages which are deprecated upstream. + * dont-build-new-manpages.diff: Debian doesn't have argparse-manpage, + so in order to not FTBFS don't build new manpages. + * base.install: Add man5/*. + + -- Timo Aaltonen Tue, 31 Jul 2018 23:46:17 +0300 + +389-ds-base (1.3.8.2-1) unstable; urgency=medium + + * New upstream release. + * fix-saslpath.diff: Updated to support ppc64el and s390x. (LP: + #1764744) + * CVE-2017-15135.patch: Refreshed + + -- Timo Aaltonen Fri, 01 Jun 2018 11:21:19 +0300 + +389-ds-base (1.3.7.10-1) unstable; urgency=medium + + * New upstream release. + - fix CVE-2018-1054 (Closes: #892124) + * control: Update maintainer address, freeipa-team handles this from + now on. Drop kklimonda from uploaders. + * control: Update VCS urls. + + -- Timo Aaltonen Tue, 13 Mar 2018 11:32:29 +0200 + +389-ds-base (1.3.7.9-1) unstable; urgency=medium + + * New upstream release. + - CVE-2017-15134 (Closes: #888452) + * patches: Fix CVE-2017-15135. (Closes: #888451) + * tests: Add some debug output. + + -- Timo Aaltonen Mon, 05 Feb 2018 16:25:09 +0200 + +389-ds-base (1.3.7.8-4) unstable; urgency=medium + + * tests: Drop python3-lib389 from depends, it's not used currently + anyway. + + -- Timo Aaltonen Thu, 21 Dec 2017 15:42:04 +0200 + +389-ds-base (1.3.7.8-3) unstable; urgency=medium + + * tests/control: Depend on python3-lib389. + + -- Timo Aaltonen Wed, 20 Dec 2017 23:54:43 +0200 + +389-ds-base (1.3.7.8-2) unstable; urgency=medium + + * Fix autopkgtest to be robust in the face of changed iproute2 output. + + -- Timo Aaltonen Wed, 20 Dec 2017 15:57:26 +0200 + +389-ds-base (1.3.7.8-1) unstable; urgency=medium + + * New upstream release. + * Package python3-lib389 and python3-dirsrvtests. + * control: Add python3 depends to 389-ds-base, since it ships a few + python scripts. + + -- Timo Aaltonen Tue, 12 Dec 2017 17:32:27 +0200 + +389-ds-base (1.3.7.5-1) unstable; urgency=medium + + * New upstream release. + * patches: ftbfs-fix.diff, reproducible-build.diff dropped (upstream) + others refreshed. + * *.install: Updated. + + -- Timo Aaltonen Wed, 04 Oct 2017 10:33:45 +0300 + +389-ds-base (1.3.6.7-5) unstable; urgency=medium + + * Move all libs from base to -libs, add B/R. (Closes: #874764) + + -- Timo Aaltonen Thu, 21 Sep 2017 16:44:13 +0300 + +389-ds-base (1.3.6.7-4) unstable; urgency=medium + + * control, install: Fix library/dev-link installs, add Breaks/Replaces + to fit, and drop obsolete B/R. + + -- Timo Aaltonen Wed, 30 Aug 2017 00:19:41 +0300 + +389-ds-base (1.3.6.7-3) unstable; urgency=medium + + * ftbfs-fix.diff: Fix build. (Closes: #873120) + + -- Timo Aaltonen Mon, 28 Aug 2017 15:09:02 +0300 + +389-ds-base (1.3.6.7-2) unstable; urgency=medium + + * control: Bump policy to 4.1.0, no changes. + * rules: Override dh_missing. + * control: Add libltdl-dev to build-depends. (Closes: #872979) + + -- Timo Aaltonen Thu, 24 Aug 2017 12:15:03 +0300 + +389-ds-base (1.3.6.7-1) unstable; urgency=medium + + * New upstream release + - fix CVE-2017-7551 (Closes: #870752) + * fix-tests.diff: Dropped, fixed upstream. + + -- Timo Aaltonen Tue, 22 Aug 2017 16:30:11 +0300 + +389-ds-base (1.3.6.5-1) experimental; urgency=medium + + * New upstream release. + - fix-bsd.patch, support-kfreebsd.patch, fix-48986-cve-2017-2591.diff: + Dropped, upstream. + * *.install: Updated. + * control: Add doxygen, libcmocka-dev, libevent-dev to build-deps. + * rules: Enable cmocka tests. + * fix-tests.diff: Fix building the tests. + + -- Timo Aaltonen Wed, 10 May 2017 09:38:30 +0300 + +389-ds-base (1.3.5.17-2) unstable; urgency=medium + + * fix-upstream-49245.diff: Pull commits from upstream 1.3.5.x, which + remove rest of the asm code. (Closes: #862194) + + -- Timo Aaltonen Wed, 10 May 2017 09:25:03 +0300 + +389-ds-base (1.3.5.17-1) unstable; urgency=medium + + * New upstream bugfix release. + - CVE-2017-2668 (Closes: #860125) + * watch: Updated. + + -- Timo Aaltonen Tue, 09 May 2017 11:06:14 +0300 + +389-ds-base (1.3.5.15-2) unstable; urgency=medium + + * fix-48986-cve-2017-2591.diff: Fix upstream ticket 48986, + CVE-2017-2591. (Closes: #851769) + + -- Timo Aaltonen Fri, 27 Jan 2017 00:01:53 +0200 + +389-ds-base (1.3.5.15-1) unstable; urgency=medium + + * New upstream release. + - CVE-2016-5405 (Closes: #842121) + + -- Timo Aaltonen Wed, 16 Nov 2016 11:01:00 +0200 + +389-ds-base (1.3.5.14-1) unstable; urgency=medium + + * New upstream release. + * postrm: Remove /etc/dirsrv, /var/lib/dirsrv and /var/log/dirsrv on + purge. + * control: Bump build-dep on libsvrcore-dev to ensure it has support + for systemd password agent. + + -- Timo Aaltonen Fri, 28 Oct 2016 01:42:27 +0300 + +389-ds-base (1.3.5.13-1) unstable; urgency=medium + + * New upstream release. + * control: Bump policy to 3.9.8, no changes. + * patches/default_user: Dropped, upstream. + * support-non-nss-libldap.diff: Dropped, upstream. + * fix-obsolete-target.diff: Updated. + * patches: Refreshed. + * control: Add libsystemd-dev to build-deps. + * control: Add acl to -base depends. + + -- Timo Aaltonen Wed, 12 Oct 2016 11:11:20 +0300 + +389-ds-base (1.3.4.14-2) unstable; urgency=medium + + * tests: Add simple autopkgtests. + * postinst: Start instances after offline update. + * control, rules: Drop -dbg packages. + * control: Drop conflicts on slapd. (Closes: #822532) + + -- Timo Aaltonen Mon, 03 Oct 2016 17:53:26 +0300 + +389-ds-base (1.3.4.14-1) unstable; urgency=medium + + * New upstream release. + * support-non-nss-libldap.diff: Refreshed. + + -- Timo Aaltonen Mon, 29 Aug 2016 10:17:41 +0300 + +389-ds-base (1.3.4.9-1) unstable; urgency=medium + + * New upstream release. + * support-non-nss-libldap.diff: Support libldap built against gnutls. + (LP: #1564179) + + -- Timo Aaltonen Mon, 18 Apr 2016 18:08:14 +0300 + +389-ds-base (1.3.4.8-4) unstable; urgency=medium + + * use-perl-move.diff: Dropped, 'rename' is more reliable. + + -- Timo Aaltonen Wed, 30 Mar 2016 08:38:24 +0300 + +389-ds-base (1.3.4.8-3) unstable; urgency=medium + + * use-perl-move.diff: Fix 60upgradeschemafiles.pl to use File::Copy. + (Closes: #818578) + + -- Timo Aaltonen Fri, 18 Mar 2016 11:15:23 +0200 + +389-ds-base (1.3.4.8-2) unstable; urgency=medium + + * postinst: Silence ls and adduser. + * Drop the init file, we depend on systemd anyway. + * rules: Don't enable dirsrv-snmp.service by default. + * postrm: Clean up /var/lib/dirsrv/scripts-* on purge. + * user-perl-move.diff: Use move instead of rename during upgrade. + (Closes: #775550) + + -- Timo Aaltonen Thu, 17 Mar 2016 08:13:38 +0200 + +389-ds-base (1.3.4.8-1) unstable; urgency=medium + + * New upstream release. + + -- Timo Aaltonen Mon, 22 Feb 2016 07:58:40 +0200 + +389-ds-base (1.3.4.5-2) unstable; urgency=medium + + * fix-systemctl-path.diff: Use correct path to /bin/systemctl. + (Closes: #779653) + + -- Timo Aaltonen Wed, 09 Dec 2015 08:31:20 +0200 + +389-ds-base (1.3.4.5-1) unstable; urgency=medium + + * New upstream release. + * patches: Refreshed. + + -- Timo Aaltonen Wed, 09 Dec 2015 08:14:56 +0200 + +389-ds-base (1.3.3.13-1) unstable; urgency=medium + + * New upstream release. + * control: Add systemd to 389-ds-base Depends. (Closes: #794301) + * postrm: Clean target.wants in postrm. + * reproducible-build.diff: Make builds reproducible. Thanks, Chris + Lamb! (Closes: #799010) + + -- Timo Aaltonen Tue, 20 Oct 2015 14:25:05 +0300 + +389-ds-base (1.3.3.12-1) unstable; urgency=medium + + * New upstream release + - fix CVE-2015-3230 (Closes: #789202) + + -- Timo Aaltonen Wed, 24 Jun 2015 11:47:50 +0300 + +389-ds-base (1.3.3.10-1) unstable; urgency=medium + + * New upstream release + - fix CVE-2015-1854 (Closes: #783923) + * postinst: Stop actual instances instead of 'dirsrv' on upgrade, and + use service(8) instead of invoke-rc.d. + + -- Timo Aaltonen Thu, 07 May 2015 07:58:35 +0300 + +389-ds-base (1.3.3.9-1) experimental; urgency=medium + + * New upstream bugfix release. + - Drop cve-2014-8*.diff, upstream. + + -- Timo Aaltonen Thu, 02 Apr 2015 14:47:20 +0300 + +389-ds-base (1.3.3.5-4) unstable; urgency=medium + + * Security fixes (Closes: #779909) + - cve-2014-8105.diff: Fix for CVE-2014-8105 + - cve-2014-8112.diff: Fix for CVE-2014-8112 + + -- Timo Aaltonen Mon, 09 Mar 2015 10:53:03 +0200 + +389-ds-base (1.3.3.5-3) unstable; urgency=medium + + * use-bash-instead-of-sh.diff: Drop admin_scripts.diff and patch the + scripts to use bash instead of trying to fix bashisms. (Closes: + #772195) + + -- Timo Aaltonen Fri, 16 Jan 2015 15:40:23 +0200 + +389-ds-base (1.3.3.5-2) unstable; urgency=medium + + * fix-saslpath.diff: Fix SASL library path. + + -- Timo Aaltonen Sat, 25 Oct 2014 01:48:34 +0300 + +389-ds-base (1.3.3.5-1) unstable; urgency=medium + + * New upstream bugfix release. + * control: Bump policy, no changes. + + -- Timo Aaltonen Mon, 20 Oct 2014 09:57:14 +0300 + +389-ds-base (1.3.3.3-1) unstable; urgency=medium + + * New upstream release. + * Dropped upstreamed patches, refresh others. + * control, rules, 389-ds-base.install: Add support for systemd. + * fix-obsolete-target.diff: Drop syslog.target from the service files. + * 389-ds-base.links: Mask the initscript so that it's not used with systemd. + + -- Timo Aaltonen Mon, 06 Oct 2014 17:13:01 +0300 + +389-ds-base (1.3.2.23-2) unstable; urgency=medium + + * Team upload. + * Add fix-bsd.patch and support-kfreebsd.patch to fix the build failure + on kFreeBSD. + + -- Benjamin Drung Wed, 03 Sep 2014 15:32:22 +0200 + +389-ds-base (1.3.2.23-1) unstable; urgency=medium + + * New bugfix release. + * watch: Update the url. + * control: Update Vcs-Browser url to use cgit. + + -- Timo Aaltonen Mon, 01 Sep 2014 13:32:59 +0300 + +389-ds-base (1.3.2.21-1) unstable; urgency=medium + + * New upstream release. + - CVE-2014-3562 (Closes: #757437) + + -- Timo Aaltonen Fri, 08 Aug 2014 10:48:55 +0300 + +389-ds-base (1.3.2.19-1) unstable; urgency=medium + + * New upstream release. + * admin_scripts.diff: Updated to fix more bashisms. + * watch: Update the url. + * Install failedbinds.py and logregex.py scripts. + * init: Use status from init-functions. + * control: Update my email. + + -- Timo Aaltonen Tue, 08 Jul 2014 15:50:11 +0300 + +389-ds-base (1.3.2.9-1.1) unstable; urgency=medium + + * Non-maintainer upload. + * Apply fix for CVE-2014-0132, see like named patch (Closes: 741600) + * Fix m4-macro for libsrvcore and add missing B-D on libpci-dev + (Closes: #745821) + + -- Tobias Frost Fri, 25 Apr 2014 15:11:16 +0200 + +389-ds-base (1.3.2.9-1) unstable; urgency=low + + * New upstream release. + - fixes CVE-2013-0336 (Closes: #704077) + - fixes CVE-2013-1897 (Closes: #704421) + - fixes CVE-2013-2219 (Closes: #718325) + - fixes CVE-2013-4283 (Closes: #721222) + - fixes CVE-2013-4485 (Closes: #730115) + * Drop fix-CVE-2013-0312.diff, upstream. + * rules: Add new scripts to rename. + * fix-sasl-path.diff: Use a triplet path to find libsasl2. (LP: + #1088822) + * admin_scripts.diff: Add patch from upstream #47511 to fix bashisms. + * control: Add ldap-utils to -base depends. + * rules, rename-online-scripts.diff: Some scripts with .pl suffix are + meant for an online server, so instead of overwriting the offline + scripts use -online suffix. + * rules: Enable parallel build, but limit the jobs to 1 for + dh_auto_install. + * control: Bump policy to 3.9.5, no changes. + * rules: Add get-orig-source target. + * lintian-overrides: Drop obsolete entries, add comments for the rest. + + -- Timo Aaltonen Mon, 03 Feb 2014 11:08:50 +0200 + +389-ds-base (1.3.0.3-1) unstable; urgency=low + + * New upstream release. + * control: Bump the policy to 3.9.4, no changes. + * fix-CVE-2013-0312.diff: Patch to fix handling LDAPv3 control data. + + -- Timo Aaltonen Mon, 11 Mar 2013 14:23:20 +0200 + +389-ds-base (1.2.11.17-1) UNRELEASED; urgency=low + + * New upstream release. + * watch: Add a comment about the upstream git tree. + * fix-cve-2012-4450.diff: Remove, upstream. + + -- Timo Aaltonen Sat, 01 Dec 2012 14:22:13 +0200 + +389-ds-base (1.2.11.15-1) unstable; urgency=low + + * New upstream release. + * Add fix-cve-2012-4450.diff. (Closes: #688942) + * dirsrv.init: Fix stop() to remove the pidfile only when the process + is finished. (Closes: #689389) + * copyright: Update the source url. + * control: Drop quilt from build-depends, since using 3.0 (quilt) + * lintian-overrides: Add an override for hardening-no-fortify- + functions, since it's a false positive in this case. + * control: Drop dpkg-dev from build-depends, no need to specify it + directly. + * copyright: Add myself as a copyright holder for debian/*. + * 389-ds-base.prerm: Add 'set -e'. + * rules: drop DEB_HOST_MULTIARCH, dh9 handles it. + + -- Timo Aaltonen Wed, 03 Oct 2012 19:33:52 +0300 + +389-ds-base (1.2.11.7-5) unstable; urgency=low + + * control: Drop debconf-utils and po-debconf from build-depends. + * control: Add libnetaddr-ip-perl and libsocket-getaddrinfo-perl to + 389-ds-base Depends for ipv6 support. (Closes: #682847) + + -- Timo Aaltonen Mon, 30 Jul 2012 13:12:23 +0200 + +389-ds-base (1.2.11.7-4) unstable; urgency=low + + * debian/po: Remove, leftover from the template purge. (Closes: #681543) + + -- Timo Aaltonen Thu, 19 Jul 2012 23:12:01 +0300 + +389-ds-base (1.2.11.7-3) unstable; urgency=low + + * 389-ds-base.config: Removed, the debconf template is no more. + (Closes: #680351) + * control: Remove duplicate 'the' from the 389-ds description. + + -- Timo Aaltonen Wed, 11 Jul 2012 11:59:36 +0300 + +389-ds-base (1.2.11.7-2) unstable; urgency=low + + * control: Stop hardcoding libs to binary depends. (Closes: #679790) + * control: Add libnspr4-dev and libldap2-dev to 389-ds-base-dev + Depends. (Closes: #679742) + * l10n review (Closes: #679870) : + - Drop the debconf template, and rewrap README.Debian. + - control: Update the descriptions + + -- Timo Aaltonen Tue, 03 Jul 2012 17:58:20 +0300 + +389-ds-base (1.2.11.7-1) unstable; urgency=low + + [ Timo Aaltonen ] + * New upstream release. + * watch: Fix the url. + * patches/remove_license_prompt: Dropped, included upstream. + * patches/default_user: Refreshed. + * control: Change the VCS header to point to the git repository. + * control: Rename last remnants of Fedora to 389. + * changelog, control: Be consistent with the naming; renamed the source + to just '389-ds-base', which matches upstream tarball naming. + * control: Wrap Depends. + * compat, control: Bump compat to 9, and debhelper build-dep to (>= 9). + * rules: Switch to dh. + * Move dirsrv.lintian to dirsrv.lintian-overrides, adjust dirsrv.install. + * *.dirs: Clean up. + * control: Build-depend on dh-autoreconf, drop duplicate bdeps. + * Fold dirsrv-tools into the main package. + * Build against libldap2-dev (>= 2.4.28). + * Rename binary package to 389-ds-base. + * -dev.install: Install the pkgconfig file. + * rules: Enable PIE hardening. + * Add a default file, currently sets LD_BIND_NOW=1. + * control: 'dbgen' uses old perl libs, add libperl4-corelibs-perl + dependency to 389-ds-base. + * rules: Add --fail-missing for dh_install, remove files not needed + and make sure to install the rest. + * rules, control: Fix the installation name of ds-logpipe.py, add + python dependency to 389-ds-base.. + * libns-dshttpd is internal to the server, ship it in 389-ds-base. + * Rename libdirsrv{-dev,0} -> 389-ds-base-{dev,libs}, includes only + libslapd and headers for external plugin development. + * control: Breaks/Replaces old libdirsrv-dev/libdirsrv0/dirsrv. + * Drop hyphen_used_as_minus, applied upstream. + * copyright: Use DEP5 format. + * Cherry-pick upstream commit ee320163c6 to get rid of unnecessary + and non-free MIB's from the tree, and build a dfsg compliant tarball. + * lintian-overrides: Update, create one for -libs. + * Fix the initscript to create the lockdir, and refactor code into separate + functions. + * Drop obsolete entries from copyright, and make it lintian clean. + * debian/po: Refer to the correct file after rename. + * control: Bump Standards-Version to 3.9.3, no changes. + * postinst: Drop unused 'lastversion'. + * patches: Add DEP3 compliant headers. + * rules, postinst: Add an error handler function for dh_installinit, so + that clean installs don't fail due to missing configuration. + * postinst: Run the update tool. + * dirsrv.init: + - Make the start and stop functions much simpler and LSB compliant + - Fix starting multiple instances + - Use '-b' for start-stop-daemon, since ns-slapd doesn't detach properly + * control: Add 389-ds metapackage. + * control: Change libdb4.8-dev build-depends to libdb-dev, since this version + supports db5.x. + * 389-ds-base.prerm: Add prerm script for removing installed instances on + purge. + + [ Krzysztof Klimonda ] + * dirsrv.init: + - return 0 code if there are no instances configured and tweak message + so it doesn't indicate a failure. + + -- Krzysztof Klimonda Tue, 27 Mar 2012 14:26:16 +0200 + +389-directory-server (1.2.6.1-5) unstable; urgency=low + + * Removed db_stop from dirsrv.postinst + * Fix short description in libdirsrv0-dbg + + -- Michele Baldessari Wed, 20 Oct 2010 20:24:20 +0200 + +389-directory-server (1.2.6.1-4) unstable; urgency=low + + * Make libicu dep dependent on dpkg-vendor + + -- Michele Baldessari Mon, 18 Oct 2010 21:21:52 +0200 + +389-directory-server (1.2.6.1-3) unstable; urgency=low + + * Remove dirsrv user and group in postrm + * Clean up postrm and postinst + + -- Michele Baldessari Sun, 17 Oct 2010 21:54:08 +0200 + +389-directory-server (1.2.6.1-2) unstable; urgency=low + + * Fix QUILT_STAMPFN + + -- Michele Baldessari Sun, 17 Oct 2010 15:03:34 +0200 + +389-directory-server (1.2.6.1-1) unstable; urgency=low + + * New upstream + + -- Michele Baldessari Sat, 16 Oct 2010 23:08:09 +0200 + +389-directory-server (1.2.6-2) unstable; urgency=low + + * Update my email address + + -- Michele Baldessari Sat, 16 Oct 2010 22:34:19 +0200 + +389-directory-server (1.2.6-1) unstable; urgency=low + + * New upstream + * s/Fedora/389/g to clean up the branding + * Remove automatic configuration (breaks too often with every update) + * Remove dirsrv.config translation, no questions are asked anymore + * Fix old changelog versions with proper ~ on rc versions + * Update policy to 3.9.1 + * Improve README.Debian + * Depend on libicu44 + * Remove /var/run/dirsrv from the postinst scripts (managed by init script) + + -- Michele Baldessari Sat, 04 Sep 2010 11:58:21 +0200 + +389-directory-server (1.2.6~rc7-1) unstable; urgency=low + + * New upstream + + -- Michele Baldessari Fri, 03 Sep 2010 20:06:08 +0200 + +389-directory-server (1.2.6~a3-1) unstable; urgency=low + + * New upstream + * Rename man page remove-ds.pl in remove-ds + * Removed Debian.source + + -- Michele Baldessari Sun, 23 May 2010 22:12:13 +0200 + +389-directory-server (1.2.6~a2-1) unstable; urgency=low + + * New upstream + * Removed speling_fixes patch, applied upstream + + -- Michele Baldessari Sun, 23 May 2010 13:36:25 +0200 + +389-directory-server (1.2.5-1) unstable; urgency=low + + * New upstream + * Add libpcre3-dev Build-dep + * ldap-agent moved ti /usr/sbin + * Fix spelling errors in code and manpages + * Fix some lintian warnings + * Bump policy to 3.8.3 + * Ignore lintian warning pkg-has-shlibs-control-file-but-no-actual-shared-libs + as the shlibs file is for dirsrv plugins + * Upgraded deps to libicu42 and libdb4.8 + * Do create /var/lib/dirsrv as dirsrv user's home + * Added libsasl2-modules-gssapi-mit as a dependency for dirsrv (needed by + mandatory LDAP SASL mechs) + * Install all files of etc/dirsrv/config + * Add some missing start scripts in usr/sbin + * Fixed a bug in the dirsrv.init script + * Switch to dpkg-source 3.0 (quilt) format + * Bump policy to 3.8.4 + + -- Michele Baldessari Sun, 23 May 2010 12:31:24 +0200 + +389-directory-server (1.2.1-0) unstable; urgency=low + + * Rename of source package (note, since this is still staging work no + replace or upgrade is in place) + * Update watch file + * New Upstream + + -- Michele Baldessari Fri, 12 Jun 2009 22:08:42 +0200 + +fedora-directory-server (1.2.0-1) unstable; urgency=low + + * New upstream release + * Add missing libkrb5-dev dependency + * Fix section of -dbg packages + * Fix all "dpatch-missing-description" lintian warnings + + -- Michele Baldessari Wed, 22 Apr 2009 23:36:22 +0200 + +fedora-directory-server (1.1.3-1) unstable; urgency=low + + * New upstream + * Added watch file + * Make setup-ds use dirsrv:dirsrv user/group as defaults + * Added VCS-* fields + * --enable-autobind + * Add ldap/servers/plugins/replication/winsync-plugin.h to libdirsrv-dev + + -- Michele Baldessari Mon, 24 Nov 2008 22:42:26 +0100 + +fedora-directory-server (1.1.2-2) unstable; urgency=low + + * Fixed build+configure twice issue + * Added Conflicts: slapd (thanks Alessandro) + + -- Michele Baldessari Tue, 23 Sep 2008 21:12:44 +0200 + +fedora-directory-server (1.1.2-1) unstable; urgency=low + + * New upstream + * Removed /usr/sbin PATH from postinst script + + -- Michele Baldessari Sat, 20 Sep 2008 20:10:52 +0000 + +fedora-directory-server (1.1.1-0) unstable; urgency=low + + * New upstream + * Don't apply patch for 439829, fixed upstream + * Bump to policy 3.8.0 + * Added README.source + + -- Michele Baldessari Fri, 22 Aug 2008 00:09:40 +0200 + +fedora-directory-server (1.1.0-4) unstable; urgency=low + + * dirsrv should depend on libmozilla-ldap-perl (thanks Mathias Kaufmann + ) + + -- Michele Baldessari Sun, 20 Jul 2008 18:41:58 +0200 + +fedora-directory-server (1.1.0-3) unstable; urgency=low + + * Fix up some descriptions + + -- Michele Baldessari Sun, 25 May 2008 21:36:32 +0200 + +fedora-directory-server (1.1.0-2) unstable; urgency=low + + * Silenced init warning messages when chowning pid directory + + -- Michele Baldessari Wed, 21 May 2008 23:08:32 +0200 + +fedora-directory-server (1.1.0-1) unstable; urgency=low + + * Removed template lintian warning + * Cleaned up manpages + + -- Michele Baldessari Sun, 18 May 2008 13:39:58 +0200 + +fedora-directory-server (1.1.0-0) unstable; urgency=low + + * Initial release (Closes: #497098). + * Fixed postinst after renaming setup-ds.pl to setup-ds + * Applied patch from https://bugzilla.redhat.com/show_bug.cgi?id=439829 to + fix segfault against late NSS versions + * Switched to parseable copyright format + * Source package is lintian clean now + * Added initial manpage patch + * Switched to dh_install + + -- Michele Baldessari Thu, 27 Mar 2008 23:56:17 +0200 diff --git a/cockpit-389-ds.install b/cockpit-389-ds.install new file mode 100644 index 0000000..d3f77dc --- /dev/null +++ b/cockpit-389-ds.install @@ -0,0 +1,2 @@ +usr/share/cockpit/389-console/ +usr/share/metainfo/389-console/org.port389.cockpit_console.metainfo.xml diff --git a/control b/control new file mode 100644 index 0000000..ed2ab5d --- /dev/null +++ b/control @@ -0,0 +1,201 @@ +Source: 389-ds-base +Section: net +Priority: optional +Maintainer: Debian FreeIPA Team +Uploaders: + Timo Aaltonen , +Build-Depends: + libcmocka-dev, + cargo, + debhelper-compat (= 13), + dh-cargo, + dh-python, + doxygen, + libbz2-dev, + libcrack2-dev, + libdb-dev, + libevent-dev, + libicu-dev, + libjson-c-dev, + libkrb5-dev, + libldap2-dev (>= 2.4.28), + liblmdb-dev, + libltdl-dev, + libnspr4-dev, + libnss3-dev, + libpam0g-dev, + libpci-dev, + libpcre2-dev, + libperl-dev, + librust-base64-0.21-dev, + librust-cbindgen-dev, + librust-cc-dev, + librust-crossbeam-dev, + librust-fernet-dev, + librust-jobserver-dev, + librust-lru-dev, + librust-openssl-dev, + librust-parking-lot-dev, + librust-paste-dev, + librust-rand-dev, + librust-smallvec-dev, + librust-tokio-dev, + libsasl2-dev, + libsnmp-dev, + libssl-dev, + libsystemd-dev, + pkg-config, + python3-all-dev, + python3-argcomplete, + python3-argparse-manpage, + python3-cryptography, + python3-dateutil, + python3-ldap, + python3-packaging, + python3-selinux, + python3-sepolicy, + python3-setuptools, + rsync, + rustc, + zlib1g-dev, +Standards-Version: 4.6.0 +Vcs-Git: https://salsa.debian.org/freeipa-team/389-ds-base.git +Vcs-Browser: https://salsa.debian.org/freeipa-team/389-ds-base +Homepage: https://directory.fedoraproject.org + +Package: 389-ds +Architecture: all +Depends: + 389-ds-base, + cockpit-389-ds, + ${misc:Depends}, +Description: 389 Directory Server suite - metapackage + Based on the Lightweight Directory Access Protocol (LDAP), the 389 + Directory Server is designed to manage large directories of users and + resources robustly and scalably. + . + This is a metapackage depending on the LDAPv3 server and a Cockpit UI plugin + for administration. + +Package: 389-ds-base-libs +Section: libs +Architecture: any +Multi-Arch: same +Pre-Depends: ${misc:Pre-Depends} +Depends: ${misc:Depends}, ${shlibs:Depends}, + libjemalloc2, +Breaks: 389-ds-base (<< 1.3.6.7-5), + 389-ds-base-dev (<< 1.3.6.7-4), + libsvrcore0, +Replaces: 389-ds-base (<< 1.3.6.7-5), + 389-ds-base-dev (<< 1.3.6.7-4), + libsvrcore0, +Description: 389 Directory Server suite - libraries + Based on the Lightweight Directory Access Protocol (LDAP), the 389 + Directory Server is designed to manage large directories of users and + resources robustly and scalably. + . + This package contains core libraries for the 389 Directory Server. + +Package: 389-ds-base-dev +Section: libdevel +Architecture: any +Multi-Arch: same +Depends: + 389-ds-base-libs (= ${binary:Version}), + libldap2-dev, + libnspr4-dev, + ${misc:Depends}, + ${shlibs:Depends}, +Breaks: 389-ds-base (<< 1.3.6.7-4), + libsvrcore-dev, +Replaces: 389-ds-base (<< 1.3.6.7-4), + libsvrcore-dev, +Provides: + libsvrcore-dev, +Description: 389 Directory Server suite - development files + Based on the Lightweight Directory Access Protocol (LDAP), the 389 + Directory Server is designed to manage large directories of users and + resources robustly and scalably. + . + This package contains development headers for the core libraries + of the 389 Directory Server, useful for developing plugins without + having to install the server itself. + +Package: 389-ds-base +Architecture: any +Pre-Depends: debconf (>= 0.5) | debconf-2.0 +Depends: + 389-ds-base-libs (= ${binary:Version}), + adduser, + acl, + ldap-utils, + libmozilla-ldap-perl, + libnetaddr-ip-perl, + libsocket-getaddrinfo-perl, + libsasl2-modules-gssapi-mit, + perl, + python3-lib389, + python3-selinux, + python3-semanage, + python3-sepolicy, + systemd, + ${misc:Depends}, + ${shlibs:Depends}, + ${python3:Depends}, +Replaces: 389-ds-base-legacy-tools +Description: 389 Directory Server suite - server + Based on the Lightweight Directory Access Protocol (LDAP), the 389 + Directory Server is designed to manage large directories of users and + resources robustly and scalably. + . + Its key features include: + * four-way multi-master replication; + * great scalability; + * extensive documentation; + * Active Directory user and group synchronization; + * secure authentication and transport; + * support for LDAPv3; + * graphical management console; + * on-line, zero downtime update of schema, configuration, and + in-tree Access Control Information. + +Package: python3-lib389 +Architecture: all +Depends: ${misc:Depends}, ${python3:Depends}, + libnss3-tools, + openssl, + python3-argcomplete, + python3-cryptography, + python3-dateutil, + python3-ldap, + python3-packaging, + python3-pyasn1, + python3-pyasn1-modules, + python3-pytest, +Conflicts: python-lib389 (<< 1.3.7.8), + 389-ds-base (<< 1.4.0.18-1~), +Replaces: python-lib389 (<< 1.3.7.8), + 389-ds-base (<< 1.4.0.18-1~), +Description: Python3 module for accessing and configuring the 389 Directory Server + This Python3 module contains tools and libraries for accessing, testing, + and configuring the 389 Directory Server. + +Package: cockpit-389-ds +Architecture: all +Multi-Arch: foreign +Depends: ${misc:Depends}, + cockpit, + libjs-bootstrap, + libjs-c3, + libjs-d3, + libjs-jquery-datatables, + libjs-jquery-datatables-extensions, + libjs-jquery-jstree, + libjs-moment, + libnss3-tools, + python3, + python3-lib389, +Description: Cockpit user interface for 389 Directory Server + This package includes a Cockpit UI plugin for configuring and administering + the 389 Directory Server. diff --git a/copyright b/copyright new file mode 100644 index 0000000..e7653ff --- /dev/null +++ b/copyright @@ -0,0 +1,614 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-name: 389-ds-base +Source: http://directory.fedoraproject.org/wiki/Source +Files-Excluded: + src/Cargo.lock + src/librslapd/Cargo.lock + vendor + +Files: * +Copyright: 2001 Sun Microsystems, Inc. + 2005 Red Hat, Inc. +License: GPL-3+ and Other + +Files: ldap/libraries/libavl/*.[ch] ldap/servers/slapd/abandon.c + ldap/servers/slapd/add.c ldap/servers/slapd/bind.c + ldap/servers/slapd/bulk_import.c ldap/servers/slapd/compare.c + ldap/servers/slapd/delete.c ldap/servers/slapd/detach.c + ldap/servers/slapd/globals.c ldap/servers/slapd/modify.c + ldap/servers/slapd/modrdn.c ldap/servers/slapd/monitor.c + ldap/servers/slapd/search.c ldap/servers/slapd/unbind.c +Copyright: 1993 Regents of the University of Michigan + 2001 Sun Microsystems, Inc. + 2005 Red Hat, Inc. +License: GPL-3+ and Other + +Files: ldap/servers/slapd/tools/ldaptool.h +Copyright: 1998 Netscape Communication Corporation +License: GPL-2+ or LGPL-2.1 or MPL-1.1 + +Files: ldap/servers/slapd/tools/ldaptool-sasl.c + ldap/servers/slapd/tools/ldaptool-sasl.h +Copyright: 2005 Sun Microsystems, Inc. +License: GPL-2+ or LGPL-2.1 or MPL-1.1 + +Files: m4/* +Copyright: 2006-2017 Red Hat, Inc. + 2016 William Brown +License: GPL-3+ + +Files: src/svrcore/* +Copyright: 2016 Red Hat, Inc. +License: MPL-2.0 + +Files: debian/* +Copyright: 2008 Michele Baldessari + 2012 Timo Aaltonen +License: GPL-2+ or LGPL-2.1 or MPL-1.1 + +Files: debian/vendor/concread/* +Copyright: 2018-2022 William Brown +License: MPL-2.0 + +Files: debian/vendor/uuid/* +Copyright: + 2013-2018 Ashley Mannix + 2013-2018 Christopher Armstrong + 2013-2018 Dylan DPC + 2013-2018 Hunar Roop Kahlon +License: Apache-2.0 or MIT + +License: Other + In addition, as a special exception, Red Hat, Inc. gives You the additional + right to link the code of this Program with code not covered under the GNU + General Public License ("Non-GPL Code") and to distribute linked combinations + including the two, subject to the limitations in this paragraph. Non-GPL Code + permitted under this exception must only link to the code of this Program + through those well defined interfaces identified in the file named EXCEPTION + found in the source code files (the "Approved Interfaces"). The files of + Non-GPL Code may instantiate templates or use macros or inline functions from + the Approved Interfaces without causing the resulting work to be covered by + the GNU General Public License. Only Red Hat, Inc. may make changes or + additions to the list of Approved Interfaces. You must obey the GNU General + Public License in all respects for all of the Program code and other code used + in conjunction with the Program except the Non-GPL Code covered by this + exception. If you modify this file, you may extend this exception to your + version of the file, but you are not obligated to do so. If you do not wish to + provide this exception without modification, you must delete this exception + statement from your version and license this file solely under the GPL without + exception. + +License: BSD-3-clause + 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 the Dojo Foundation 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. + + +License: GPL-2 or GPL-2+ + On Debian machines the full text of the GNU General Public License + can be found in the file /usr/share/common-licenses/GPL-2. + +License: GPL-3+ + On Debian machines the full text of the GNU General Public License v3 + can be found in the file /usr/share/common-licenses/GPL-3. + +License: LGPL-2.1 + On Debian machines the full text of the GNU General Public License + can be found in the file /usr/share/common-licenses/LGPL-2.1. + +License: MPL-1.1 + MOZILLA PUBLIC LICENSE + Version 1.1 + . + --------------- + . + 1. Definitions. + . + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + . + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + . + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + . + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + . + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + . + 1.5. "Executable" means Covered Code in any form other than Source + Code. + . + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + . + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + . + 1.8. "License" means this document. + . + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + . + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + . + B. Any new file that contains any part of the Original Code or + previous Modifications. + . + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + . + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + . + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + . + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + . + 2. Source Code License. + . + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + . + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + . + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + . + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + . + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + . + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + . + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + . + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + . + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + . + 3. Distribution Obligations. + . + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + . + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + . + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + . + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + . + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + . + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + . + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + . + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + . + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + . + 4. Inability to Comply Due to Statute or Regulation. + . + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + . + 5. Application of this License. + . + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + . + 6. Versions of the License. + . + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + . + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + . + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + . + 7. DISCLAIMER OF WARRANTY. + . + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + . + 8. TERMINATION. + . + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + . + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + . + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + . + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + . + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + . + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + . + 9. LIMITATION OF LIABILITY. + . + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + . + 10. U.S. GOVERNMENT END USERS. + . + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + . + 11. MISCELLANEOUS. + . + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + . + 12. RESPONSIBILITY FOR CLAIMS. + . + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + . + 13. MULTIPLE-LICENSED CODE. + . + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + . + EXHIBIT A -Mozilla Public License. + . + ``The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + . + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + . + The Original Code is ______________________________________. + . + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + . + Contributor(s): ______________________________________. + . + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + . + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] + +License: MPL-2.0 + On Debian machines the full text of the Mozilla Public License version 2.0 + can be found in the file /usr/share/common-licenses/MPL-2.0. + +License: Apache-2.0 + Debian systems provide the Apache 2.0 license in + /usr/share/common-licenses/Apache-2.0 + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/gitlab-ci.yml b/gitlab-ci.yml new file mode 100644 index 0000000..4545f3e --- /dev/null +++ b/gitlab-ci.yml @@ -0,0 +1,6 @@ +include: + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml + - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml + +blhc: + allow_failure: true diff --git a/patches/allow-newer-crates.diff b/patches/allow-newer-crates.diff new file mode 100644 index 0000000..a0a6b92 --- /dev/null +++ b/patches/allow-newer-crates.diff @@ -0,0 +1,84 @@ +--- a/src/librslapd/Cargo.toml ++++ b/src/librslapd/Cargo.toml +@@ -15,8 +15,8 @@ crate-type = ["staticlib", "lib"] + [dependencies] + slapd = { path = "../slapd" } + libc = "0.2" +-concread = "^0.2.20" ++concread = "0.*" + + [build-dependencies] +-cbindgen = "0.9" ++cbindgen = "0.*" + +--- a/src/librnsslapd/Cargo.toml ++++ b/src/librnsslapd/Cargo.toml +@@ -21,5 +21,5 @@ slapd = { path = "../slapd" } + libc = "0.2" + + [build-dependencies] +-cbindgen = "0.9" ++cbindgen = "0.*" + +--- a/src/slapd/Cargo.toml ++++ b/src/slapd/Cargo.toml +@@ -7,4 +7,4 @@ edition = "2018" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + + [dependencies] +-fernet = "0.1" ++fernet = "0.*" +--- a/src/slapi_r_plugin/Cargo.toml ++++ b/src/slapi_r_plugin/Cargo.toml +@@ -17,6 +17,6 @@ crate-type = ["staticlib", "lib"] + + [dependencies] + libc = "0.2" +-paste = "0.1" ++paste = "1.*" + # lazy_static = "1.4" + uuid = { version = "0.8", features = [ "v4" ] } +--- a/src/plugins/entryuuid/Cargo.toml ++++ b/src/plugins/entryuuid/Cargo.toml +@@ -13,7 +13,7 @@ crate-type = ["staticlib", "lib"] + + [dependencies] + libc = "0.2" +-paste = "0.1" ++paste = "1.*" + slapi_r_plugin = { path="../../slapi_r_plugin" } + uuid = { version = "0.8", features = [ "v4" ] } + +--- a/src/plugins/entryuuid_syntax/Cargo.toml ++++ b/src/plugins/entryuuid_syntax/Cargo.toml +@@ -13,7 +13,7 @@ crate-type = ["staticlib", "lib"] + + [dependencies] + libc = "0.2" +-paste = "0.1" ++paste = "1.*" + slapi_r_plugin = { path="../../slapi_r_plugin" } + uuid = { version = "0.8", features = [ "v4" ] } + +--- a/src/plugins/pwdchan/Cargo.toml ++++ b/src/plugins/pwdchan/Cargo.toml +@@ -13,7 +13,7 @@ crate-type = ["staticlib", "lib"] + + [dependencies] + libc = "0.2" +-paste = "0.1" ++paste = "1.*" + slapi_r_plugin = { path="../../slapi_r_plugin" } + uuid = { version = "0.8", features = [ "v4" ] } + openssl = { version = "0.10" } +--- a/Makefile.am ++++ b/Makefile.am +@@ -70,7 +70,7 @@ RUST_LDFLAGS = -ldl -lpthread -lgcc_s -l + endif + RUST_DEFINES = -DRUST_ENABLE + if RUST_ENABLE_OFFLINE +-RUST_OFFLINE = --locked --offline ++RUST_OFFLINE = --offline + else + RUST_OFFLINE = + endif diff --git a/patches/base64-0.21.diff b/patches/base64-0.21.diff new file mode 100644 index 0000000..f992a1c --- /dev/null +++ b/patches/base64-0.21.diff @@ -0,0 +1,65 @@ +Description: update for base64 0.21 +Author: Peter Michael Green + +Index: 389-ds-base-2.3.1+dfsg1.new/src/plugins/pwdchan/src/lib.rs +=================================================================== +--- 389-ds-base-2.3.1+dfsg1.new.orig/src/plugins/pwdchan/src/lib.rs ++++ 389-ds-base-2.3.1+dfsg1.new/src/plugins/pwdchan/src/lib.rs +@@ -42,6 +42,12 @@ macro_rules! ab64_to_b64 { + }}; + } + ++use base64::engine::GeneralPurpose; ++use base64::engine::GeneralPurposeConfig; ++use base64::Engine; ++use base64::alphabet; ++static BASE64CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new().with_decode_allow_trailing_bits(true); ++static BASE64ENGINE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD,BASE64CONFIG); + impl PwdChanCrypto { + #[inline(always)] + fn pbkdf2_decompose(encrypted: &str) -> Result<(usize, Vec, Vec), PluginError> { +@@ -62,7 +68,7 @@ impl PwdChanCrypto { + .ok_or(PluginError::MissingValue) + .and_then(|ab64| { + let s = ab64_to_b64!(ab64); +- base64::decode_config(&s, base64::STANDARD.decode_allow_trailing_bits(true)) ++ BASE64ENGINE.decode(&s) + .map_err(|e| { + log_error!(ErrorLevel::Error, "Invalid Base 64 {} -> {:?}", s, e); + PluginError::InvalidBase64 +@@ -74,7 +80,7 @@ impl PwdChanCrypto { + .ok_or(PluginError::MissingValue) + .and_then(|ab64| { + let s = ab64_to_b64!(ab64); +- base64::decode_config(&s, base64::STANDARD.decode_allow_trailing_bits(true)) ++ BASE64ENGINE.decode(&s) + .map_err(|e| { + log_error!(ErrorLevel::Error, "Invalid Base 64 {} -> {:?}", s, e); + PluginError::InvalidBase64 +@@ -152,11 +158,11 @@ impl PwdChanCrypto { + PluginError::Format + })?; + // the base64 salt +- base64::encode_config_buf(&salt, base64::STANDARD, &mut output); ++ BASE64ENGINE.encode_string(&salt, &mut output); + // Push the delim + output.push_str("$"); + // Finally the base64 hash +- base64::encode_config_buf(&hash_input, base64::STANDARD, &mut output); ++ BASE64ENGINE.encode_string(&hash_input, &mut output); + // Return it + Ok(output) + } +Index: 389-ds-base-2.3.1+dfsg1.new/src/plugins/pwdchan/Cargo.toml +=================================================================== +--- 389-ds-base-2.3.1+dfsg1.new.orig/src/plugins/pwdchan/Cargo.toml ++++ 389-ds-base-2.3.1+dfsg1.new/src/plugins/pwdchan/Cargo.toml +@@ -17,7 +17,7 @@ paste = "1.*" + slapi_r_plugin = { path="../../slapi_r_plugin" } + uuid = { version = "0.8", features = [ "v4" ] } + openssl = { version = "0.10" } +-base64 = "0.13" ++base64 = "0.21" + + [build-dependencies] + cc = { version = "1.0", features = ["parallel"] } diff --git a/patches/fix-saslpath.diff b/patches/fix-saslpath.diff new file mode 100644 index 0000000..fef40b9 --- /dev/null +++ b/patches/fix-saslpath.diff @@ -0,0 +1,57 @@ +--- a/ldap/servers/slapd/ldaputil.c ++++ b/ldap/servers/slapd/ldaputil.c +@@ -827,10 +827,14 @@ ldaputil_get_saslpath() + if (PR_SUCCESS != PR_Access(saslpath, PR_ACCESS_EXISTS)) { + #ifdef CPU_arm + /* the 64-bit ARMv8 architecture. */ +- saslpath = "/usr/lib/aarch64-linux-gnu"; ++ saslpath = "/usr/lib/aarch64-linux-gnu/sasl2"; ++#elif defined(CPU_powerpc64le) ++ saslpath = "/usr/lib/powerpc64le-linux-gnu/sasl2"; ++#elif defined(CPU_s390x) ++ saslpath = "/usr/lib/s390x-linux-gnu/sasl2"; + #else + /* Try x86_64 gnu triplet */ +- saslpath = "/usr/lib/x86_64-linux-gnu"; ++ saslpath = "/usr/lib/x86_64-linux-gnu/sasl2"; + #endif + } + #else +@@ -838,14 +842,14 @@ ldaputil_get_saslpath() + if (PR_SUCCESS != PR_Access(saslpath, PR_ACCESS_EXISTS)) { + #ifdef CPU_arm + /* the latest 32 bit ARM architecture using the hard-float version of EABI. */ +- saslpath = "/usr/lib/arm-linux-gnueabihf"; ++ saslpath = "/usr/lib/arm-linux-gnueabihf/sasl2"; + if (PR_SUCCESS != PR_Access(saslpath, PR_ACCESS_EXISTS)) { + /* the 32 bit ARM architecture of EABI. */ +- saslpath = "/usr/lib/arm-linux-gnueabi"; ++ saslpath = "/usr/lib/arm-linux-gnueabi/sasl2"; + } + #else + /* Try i386 gnu triplet */ +- saslpath = "/usr/lib/i386-linux-gnu"; ++ saslpath = "/usr/lib/i386-linux-gnu/sasl2"; + #endif + } + #endif +--- a/configure.ac ++++ b/configure.ac +@@ -655,7 +655,8 @@ case $host in + arm-*-linux*) + AC_DEFINE([CPU_arm], [], [cpu type arm]) + ;; +- ppc64le-*-linux*) ++ powerpc64le-*-linux*) ++ AC_DEFINE([CPU_powerpc64le], [], [cpu type powerpc64le]) + ;; + ppc64-*-linux*) + ;; +@@ -664,6 +665,7 @@ case $host in + s390-*-linux*) + ;; + s390x-*-linux*) ++ AC_DEFINE([CPU_s390x], [], [cpu type s390x]) + ;; + esac + # some programs use the native thread library directly diff --git a/patches/series b/patches/series new file mode 100644 index 0000000..9925e78 --- /dev/null +++ b/patches/series @@ -0,0 +1,4 @@ +fix-saslpath.diff +use-packaged-rust-registry.diff +allow-newer-crates.diff +base64-0.21.diff diff --git a/patches/use-packaged-rust-registry.diff b/patches/use-packaged-rust-registry.diff new file mode 100644 index 0000000..02936c9 --- /dev/null +++ b/patches/use-packaged-rust-registry.diff @@ -0,0 +1,8 @@ +--- a/.cargo/config.in ++++ b/.cargo/config.in +@@ -3,4 +3,4 @@ registry = "https://github.com/rust-lang + @rust_vendor_sources@ + + [source.vendored-sources] +-directory = "./vendor" ++directory = "./debian/vendor" diff --git a/python3-lib389.install b/python3-lib389.install new file mode 100644 index 0000000..4483e2b --- /dev/null +++ b/python3-lib389.install @@ -0,0 +1,10 @@ +usr/lib/python3/dist-packages/lib389-* +usr/lib/python3/dist-packages/lib389/ +usr/sbin/dsconf +usr/sbin/dscreate +usr/sbin/dsctl +usr/sbin/dsidm +usr/share/man/man8/dsconf.8 +usr/share/man/man8/dscreate.8 +usr/share/man/man8/dsctl.8 +usr/share/man/man8/dsidm.8 diff --git a/rules b/rules new file mode 100755 index 0000000..2df17f4 --- /dev/null +++ b/rules @@ -0,0 +1,90 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +export DEB_BUILD_MAINT_OPTIONS = hardening=+pie + + +ifneq (,$(filter $(DEB_HOST_ARCH), armel m68k mips mipsel powerpc powerpcspe sh4)) + export DEB_LDFLAGS_MAINT_APPEND=-latomic +endif + +REALFILE = \ + bin/ds-logpipe.py \ + bin/logconv.pl \ + share/man/man1/ds-logpipe.py.1 \ + share/man/man1/logconv.pl.1 \ + +%: + dh $@ --with python3 + +override_dh_auto_clean: + dh_auto_clean + rm -f aclocal.m4 config.* ltmain.sh m4/libtool.m4 m4/lt*.m4 + rm -f ldap/servers/snmp/ldap-agent.conf + rm -rf src/lib389/build src/lib389/lib389.egg-info + find src/lib389/ -name '__pycache__' -exec rm -rf '{}' ';' + rm -f src/lib389/man/*.8 + rm -f ldap/admin/src/*.inf rust-nsslapd-private.h src/Cargo.lock + rm -f wrappers/dirsrv-snmp.service wrappers/dirsrv.target wrappers/dirsrv@.service wrappers/dirsrv@.service.d/custom.conf wrappers/ds_selinux_restorecon.sh wrappers/ds_systemd_ask_password_acl + rm -f debian/vendor/*-* + rm -f config.sub config.guess + +override_dh_auto_configure: + dh_auto_configure -- \ + --with-openldap \ + --with-systemd \ + --with-systemdsystemunitdir=/lib/systemd/system \ + --with-systemdsystemconfdir=/etc/systemd/system \ + --with-systemdgroupname=dirsrv.target \ + --with-tmpfiles-d=/etc/tmpfiles.d \ + --enable-autobind \ + --enable-cmocka \ + --enable-icu \ + --enable-perl \ + --enable-rust \ + --enable-rust-offline + + (cd debian/vendor && for i in `ls /usr/share/cargo/registry`; do \ + ln -fs /usr/share/cargo/registry/$$i .; done) + +override_dh_auto_build: + (cd src/lib389 && python3 setup.py build) + dh_auto_build + +override_dh_auto_install: + (cd src/lib389 && python3 setup.py install --install-layout=deb --root ../../debian/tmp) + + dh_auto_install --max-parallel=1 + +override_dh_install: + # lets do the renaming here afterall, instead of in 389-ds-base.install + for file in $(REALFILE); do mv -f $(CURDIR)/debian/tmp/usr/$$file \ + $(CURDIR)/debian/tmp/usr/`echo $$file | \ + sed -s 's/\.pl//;s/\.py//'`; \ + done + # purge .la files + find $(CURDIR)/debian/tmp -name "*.la" -type f -exec rm -f "{}" \; + + mkdir -p $(CURDIR)/debian/tmp/etc/systemd/system/dirsrv.target.wants + + # fix the manpage section, argparse-manpage hardcodes it as 1 + sed -i "1s/\"1\"/\"8\"/" debian/tmp/usr/share/man/man8/dsconf.8 + sed -i "1s/\"1\"/\"8\"/" debian/tmp/usr/share/man/man8/dscreate.8 + sed -i "1s/\"1\"/\"8\"/" debian/tmp/usr/share/man/man8/dsctl.8 + sed -i "1s/\"1\"/\"8\"/" debian/tmp/usr/share/man/man8/dsidm.8 + + # link to jemalloc + mkdir -p $(CURDIR)/debian/tmp/usr/lib/$(DEB_BUILD_MULTIARCH)/dirsrv/lib/ + ln -s /usr/lib/$(DEB_BUILD_MULTIARCH)/libjemalloc.so.2 \ + $(CURDIR)/debian/tmp/usr/lib/$(DEB_BUILD_MULTIARCH)/dirsrv/lib/ + + dh_install + +override_dh_missing: + dh_missing --fail-missing + +override_dh_installsystemd: + dh_installsystemd -p389-ds-base --no-enable dirsrv-snmp.service + +override_dh_shlibdeps: + dh_shlibdeps -l"debian/389-ds-base/usr/lib/$(DEB_HOST_MULTIARCH)/dirsrv" -a diff --git a/source/format b/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/source/include-binaries b/source/include-binaries new file mode 100644 index 0000000..7c47825 --- /dev/null +++ b/source/include-binaries @@ -0,0 +1,14 @@ +debian/vendor/concread/static/cow_arc_4.png +debian/vendor/concread/static/cow_arc_5.png +debian/vendor/concread/static/cow_arc_2.png +debian/vendor/concread/static/cow_arc_7.png +debian/vendor/concread/static/cow_arc_3.png +debian/vendor/concread/static/arc_1.png +debian/vendor/concread/static/cow_arc_8.png +debian/vendor/concread/static/cow_arc_1.png +debian/vendor/concread/static/cow_3.png +debian/vendor/concread/static/cow_arc_6.png +debian/vendor/concread/static/cow_arc_9.png +debian/vendor/concread/static/arc_2.png +debian/vendor/concread/static/cow_1.png +debian/vendor/concread/static/cow_2.png diff --git a/source/lintian-overrides b/source/lintian-overrides new file mode 100644 index 0000000..f0e3ffc --- /dev/null +++ b/source/lintian-overrides @@ -0,0 +1,2 @@ +# it just has long lines +389-ds-base source: source-is-missing src/cockpit/389-console/cockpit_dist/index.js line length is 312 characters (>256) diff --git a/tests/control b/tests/control new file mode 100644 index 0000000..dc84954 --- /dev/null +++ b/tests/control @@ -0,0 +1,6 @@ +Tests: setup +Depends: + 389-ds-base, +Restrictions: + isolation-container, + needs-root, diff --git a/tests/setup b/tests/setup new file mode 100644 index 0000000..0ffa366 --- /dev/null +++ b/tests/setup @@ -0,0 +1,36 @@ +#!/bin/sh + +# hack for lxc +IP=`ip route get 1.1.1.1 | sed -n -e's/.*src //; s/ .*//; p; q'` +echo "IP address is $IP" + +HOSTNAME=`cat /etc/hosts| grep '127.0.1.1' | awk '{print $NF; exit}'` +echo "Hostname was: $HOSTNAME" + +if [ -z $HOSTNAME ]; then + HOSTNAME=autopkgtest + hostname $HOSTNAME + echo $HOSTNAME > /etc/hostname +fi + +echo "$IP $HOSTNAME.debci $HOSTNAME" >> /etc/hosts + +echo "/etc/hosts now has:" +cat /etc/hosts + +cat << EOF > /tmp/debci.inf +[general] +full_machine_name = $HOSTNAME.debci +strict_host_checking = False +[slapd] +group = dirsrv +instance_name = debci +port = 1389 +root_dn = cn=Directory Manager +root_password = Secret123 +user = dirsrv +[backend-userroot] +suffix = dc=example,dc=com +EOF + +/usr/sbin/dscreate from-file /tmp/debci.inf 2>&1 diff --git a/vendor/concread/.cargo-checksum.json b/vendor/concread/.cargo-checksum.json new file mode 100644 index 0000000..84475b2 --- /dev/null +++ b/vendor/concread/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CACHE.md":"258e585db81ee9582e1f7e7246026b49b3f617dae4459ec52437024b00ba5dff","CODE_OF_CONDUCT.md":"f32933e0090f012d336e8b2f2301967e8a27cbc896aa3860811a944d05b58964","CONTRIBUTORS.md":"1edff6e840fc50412ac698cd7e5ebe660574760b492d4febe94feb0c066b062f","Cargo.toml":"5f835dd7ba73fa096d0dc20b1a1ab77cd326a7b31518c43d201fccc1a8766bc9","LICENSE.md":"32ee9dbf6196874fc9d406c54a888a6c4cbb9aa4a7f35b46befeaff43a78fe85","Makefile":"de35f7df990b5c047785da63ad560ecadac746bf19d2ab8457fc2ba0224ad46a","README.md":"f70aafccb01764a1aa4d83e3f7fbeab245f7bfa3d3bafecea9614439ff97b487","asan_test.sh":"7355f359e34a6198e895c18fbb616fa45a44666c0a82787f704454e83687be4c","benches/arccache.rs":"6f1f23abb2b577c21aa7ee3d4bd7413fcb49a60d59b1220e1afaeda99f4e6b0e","benches/hashmap_benchmark.rs":"306d085f88f7ce40b8709aff37375482f8edb3c891cbb3da5d9d70a95c2d6cca","src/arcache/ll.rs":"2bd7eb2e73f80112765a0e1792edc27b58269dfc419498ef02ad68fb63141abc","src/arcache/mod.rs":"5911e162123373e241a6bf2b6355cda5480f9ff2f3fa2901c08a7010ad82c00a","src/arcache/traits.rs":"09ea380ea38efe3e35c2036a2ae233388a5367706d155c4aa3c9234d03eef8ea","src/bptree/asynch.rs":"69eff00e05b7b85a12468304374422b2ce1d8598b50918b0efe6eb77036249a4","src/bptree/impl.rs":"b54a7d53e23bf0ff23ebd43ea7ab19708cc383766706051f207bfb62f5e64793","src/bptree/mod.rs":"5e8d2004865dd2064a550070ca3b49a1975ac877cd6749f035806335f3a393a1","src/cowcell/asynch.rs":"04a1c424c083625f92524547bd21d20aadec4d20e2eedfff4db90f2aef920d6c","src/cowcell/mod.rs":"621c243c301f80ebcf65a636489c9450e22940bc5bc9cfb1e5db7943f4e3543f","src/ebrcell/mod.rs":"cf2a2042ac41039ca5fb4212f1fec58bfa1695b88f9c7f7864e01dafa84ccf35","src/hashmap/asynch.rs":"f8418906a23cf06e73fb1330988da4ab0f53ae58c4d5fd58ab5105f8fcbcc414","src/hashmap/impl.rs":"9695dcfde2fd6b27766adddd225d6390a44cc757ebf9b2f5879f6f1b9a7f29dc","src/hashmap/mod.rs":"bbd33d7f50a28b9a1254d4aaed72f5122e9f01e68e0c43331f90582a17056657","src/internals/bptree/cursor.rs":"83b250db73eb09e1cf7367e8c33700e9b9659695fd565e813549b7f20f9ba777","src/internals/bptree/iter.rs":"dfcf50a3ff5b052dc719b2c68b0bafcfdd0c0c8d8173f3e227a7dd6c8f1be773","src/internals/bptree/macros.rs":"568826a43238474d1f92f7dbf1671690790b35a3b8c88d0d6c5dd35aca54857a","src/internals/bptree/mod.rs":"67ba38e16d96c0d239b72cf0b7be5f27a7a82a29e3a31afc5477175f92b4c57f","src/internals/bptree/node.rs":"afe7217f187d5d7a086dab3bc6fadbae0456e4f8518aa12153f877590380d585","src/internals/bptree/states.rs":"da1ce34cbe6bc449e9e5172ed1fd2a296f2ac197b17ca855f2d40aada7d32a63","src/internals/hashmap/cursor.rs":"7d9c47d7e31e984670cd2161be54475dc468df5b00df26dc89f9848eb1a587ea","src/internals/hashmap/iter.rs":"f02f372e34d2af685eebbcc4db71cc5985c904c1bbd14dd13f48921ea58e5c5f","src/internals/hashmap/macros.rs":"f1236cd794e0e8d7ee2e89428b60c1c3437f34e6be32217de2d61dddce7c6b90","src/internals/hashmap/mod.rs":"ffa693b755ef92da14b001e08a1154e263bd9540ae993d4f79ddf5979e2dcbd6","src/internals/hashmap/node.rs":"7d86f28969185f28de91b2c816990f231adb2c5985c4cbf0efcfdd07ae94ef9a","src/internals/hashmap/simd.rs":"4ef1fd5a0b6218823eafd727b7ff1a738dfc0c59d1e3afbb2ace8f0513967bf3","src/internals/hashmap/states.rs":"9366f29bdfaeb3ee9744268b5b2283209f791a260045312e8489515f8bd3900a","src/internals/lincowcell/mod.rs":"9caa826c9b6758e79d90c10c5dad4bfeaf46a8773784537750814f700fa13428","src/internals/lincowcell_async/mod.rs":"c38ec2efbd02219ebec68aa0b31e824262941093d1cfb3e441e6c98b375df6ab","src/internals/mod.rs":"18db9c4cb457fd06a85d668a69bd290ad13a7fae971a926015daf148424e19cc","src/lib.rs":"6b368bb45ae5648ec8b4c1eed9af0795dba07487f73d553b210eeb0902e82cdd","src/threadcache/mod.rs":"43328459ade4c1abb8174ded0f77f264a3931dbae82991e70765747d519882c6","src/unsound.rs":"60ed0cad28434083fe7cccd17af4995381cdd9e4dfb685da5b14e802150074fb","src/unsound2.rs":"02b72de153d9f5fac901ea59a9ef3d1ce9b8e3e18bc80d9d19b2d6aa8c7b5022","src/unsound3.rs":"019ba913656558f91e9a160cbc05fe78b1e4a44acc1bb74d38281ebea71edd77","src/utils.rs":"d07e962bab8936d5396bc7542af7ce2812504358c4e68dd2322f521c066476cd","static/arc_1.png":"94ff0d24a15d5feda27f0316f8a1ca82291f816d8705b1d0743e03e39791bbe2","static/arc_2.png":"9932b1b8e7f44a833f4ffdb710af984199711d6c9c3349a51ab122a22d0ebbe7","static/cow_1.png":"f340e6d143589efdbbfb8c62c4fdddc97dcb213e0cff2c04f7f93bf380fd36f3","static/cow_2.png":"20f550b67109cd042170da95ff6faecda043d333c55f48ec19ced8bb9dce1eee","static/cow_3.png":"137a24e70196bd628522733b55ed1d92cf3f9936be39d9f0864201424a25bd88","static/cow_arc_1.png":"97a45ad9b55721381aff07e921fedc863209629a4ae4f8c160cad7059ecad795","static/cow_arc_2.png":"178e681ba6e2e0a33a7b6a08b9895b628dad74cda56e9e25b59288376e9f01ce","static/cow_arc_3.png":"37dac32e173b14faf37b502a110ab1e4f47f30710cfc65714b9fef0b79f64307","static/cow_arc_4.png":"f4b962b9ccd9b765523f50b752b9bb85525cbf8a4e6cdc15048f9363579e6719","static/cow_arc_5.png":"d6b122ec844d0d19312cc57667769f2955bc7b8667449b1c13a7359a4debc8a2","static/cow_arc_6.png":"847018f7f5e0813b26c2b636a2ca8549f475339de24a21dd2e0e21d90dbd77f1","static/cow_arc_7.png":"3df9b3d1153c7b1de52a30a46e9bdba75e2412ad51ef158437a356f16b3bd1be","static/cow_arc_8.png":"68f405191ef400b8f854102e98fac92ddd31c274bcff7739e47483a39dba9612","static/cow_arc_9.png":"eb60637be4fb951ac54a1e3de0fd33e0bb2f5a4d19c3519bd8f23c02b8fc240d"},"package":"dcc9816f5ac93ebd51c37f7f9a6bf2b40dfcd42978ad2aea5d542016e9244cf6"} diff --git a/vendor/concread/CACHE.md b/vendor/concread/CACHE.md new file mode 100644 index 0000000..07da5cc --- /dev/null +++ b/vendor/concread/CACHE.md @@ -0,0 +1,416 @@ + +Concurrent Adaptive Replacement Cache +William Brown, SUSE Labs +wbrown@suse.de + +2020-04-23 + +# Concurrent Adaptive Replacement Cache + +Caching is a very important aspect of computing, helping to improve the performance of applications +based on various work factors. Modern systems (especially servers) are highly concurrent, so it is +important that we are able to provide caching strategies for concurrent systems. However, to date +the majority of work in concurrent data-structures has focused on lock free or systems that do not +require transactional guarantees. While this is applicable in many cases, it does not suit all +applications, limiting many applications to mutexs or read-write locks around existing +data-structures. + +In this document, I will present a strategy to create a concurrently readable adaptive replacement +cache. This cache guarantees temporal consistency, as well as providing ACID guarantees, and +serialisable properties of the cache, while maintaining the invariants that define an adaptive +replacement cache. Additionally, this cache is able to support multiple concurrent readers with isolated +transactions, and serialised writers. This algorithm also has interested side effects with regard +to observing cache interactions, allowing it to have more accurate retention behaviour +of cached data. + +## Concurrent Systems + +To understand the reasons why a concurrent cache are relevant requires exploration of modern +concurrent systems. Modern CPU's while masquerading as a simple device that executes instructions +in order, are actually concurrent, out of order task execution machines, with asynchronous memory +views. At any point in time this means a CPU's view of memory will be either "latest" or any +point within a timeline into the past [fn 0]. + +This is due to each CPU's cache being independent to the caches of every other CPU in a system, both +within a die package and across a NUMA boundary. Updates to the memory from one CPU, requires +coordination based on the MESI [r 0]. Each cache maintains it's own MESI state machine, and +they coordinate through inter-processor communication when required. + +Examining MESI, it becomes apparent that it's best to keep cache-lines from becoming `Invalid` as +this state causes the highest level of inter processor communication to validate the content of the cache +which adds significant delays to any operation. + +It becomes simple to see that the fastest lifecycle for memory should be that new memory is allocated +or used, which is set to `Exclusive`/`Modified`. Once all writeable actions have completed, the memory +may be accessed by other cores which moves the state to `Shared`. Only once all cores have completed +their operations, the memory is freed. At no point does it move through the `Invalid` state, which +has the highest penalties for inter-processor communication. This means that once a value becomes +`Shared` it can not be modified again in-place. + +Knowing this, with highly parallel systems, we want to determine a method of keeping application +level memory in these states that helps to improve our systems performance. These states are +similar to concurrent readability, a property of systems where a single writer exists with many +parallel readers, but the parallel readers have a guaranteed snapshot (transaction) of memory. This +can be achieved through copy on write, which matches closely to the model of MESI states we want +to achieve. + +As a result, applications that will benefit highly from these designs are: + +* High concurrency read oriented +* Transactional + +## Single Thread Adaptive Replacement Cache + +ARC (Adaptive Replacement Cache) is a design proposed by IBM [r 1, 2], which improves upon strategies like +least replacement used. ARC was used with great success in ZFS [r 3] for caching of objects into +memory. A traditional LRU uses a single DLL (double linked list) with a fast lookup structure such as +a hash-map to offset into nodes of the DLL. ARC uses 4 DLL's, with lists for frequent, recent, ghost frequent +and ghost recent items to be stored, as well as a weight factor `p`. The operation of `p` is one +of the most important aspects of how ARC operates. + +`p` is initially set to 0, and only increases toward positive numbers or back toward 0. `p` +represents "demand on the recent set". Initially, there is no demand on the recent set. + +We start with an empty cache (of max size 4) and `p=0`. We add 4 elements, which are added to the +recent set (as they do not exist in any other set at this time). We then add another 4 items. This +displaces the original 4 element's keys to the ghost-recent set. + +The 4 elements in the main cache are now accessed again. This promotes them from recent to frequent. + +We then attempt to read from one element that in the ghost-recent set. We re-include the element into +the cache, causing there to be 3 elements in the frequent set, and 1 in the recent set. Since the key was +found in the ghost-recent set this indicates demand, so `p` is adjusted to 1. Assuming this process +happened again, this causes another element to be included in recent, so `p` adjusts again to 2. + +If an item in the recent set now is touched again, it would be promoted to frequent, displacing +an item, so that frequent and recent both stay at size 2 (there is an empty slot now in recent). +An access to a new value, would be included in recent as there is a free slot available. + +If we then miss on an item that was excluded from frequent by seeing it in the ghost set, this indicates +demand on frequent items, so `p` is reduced by 1. This would continue until `p` is 0. + +It is important to note that the ghost set sizes are also affected by `p`. When `p` is 0, the ghost-frequent +size is 0, along with the recent set size of 0. The size of ghost-recent and frequent must therefore be +cache max. As p increases, ghost-frequent and recent are increased in size, while ghost-recent and frequent +decrease. This way as items are evicted and `p` shifts, we do not have a set of infinite items that +may cause evictions or `p` shifts unexpectedly. + +

+ +

+ +

+ +

+ +With the cache always adapting `p` between recent inclusions and high frequency accesses, the +cache is able to satisfy a variety of workloads, and other research [r 7] has shown it has a better hit +rate then many other cache strategies, for a low overhead of administration. Additionally, it is +resistant to a number of cache invalidation/poisoning patterns such as repeated hits on a single item causing +items to never be evicted (affects LFU) or scans of large sets causing complete eviction (affects LRU). + +To explain, an item that is in the frequent set that is accessed many times in an LFU, would not +be able to be evicted as it's counter has become very high and another item would need as many hits +to displace it - this may cause stale data to remain in the cache. However in ARC, if a frequent item +is accessed many times, but then different data becomes accessed highly, this would cause demand on +recent and then promotions to frequent, which would quickly displace the item that previously was +accessed many times. + +In terms of resistance to scans, LRU on a scan would have many items evicted and included rapidly. +However in ARC, because many items may be invalidated and included, only the items in the ghost-recent +set would cause a change in the weight of `p`, so any other items stored in the frequent set would +not be evicted due to the scan. + +This makes ARC an attractive choice for an application cache, and as mentioned has already been +proven through it's use in systems like ZFS, and even the Linux Memory Management subsystem +has considered ARC viable [r 8]. + +However, due to the presence of multiple linked lists, and the updates required such as moving items +from one set to another, it's non possible to do this with atomic CPU instructions. To remain a +consistent data-structure, these changes must only be performed by a single thread. This poses +a problem to modern concurrent systems. A single thread cache behind a mutex or read write lock becomes +and obvious serialisation point, and having a cache per thread eliminates much of the value of +shared CPU caches as each CPU would have to have duplicated content - and thus smaller per +thread caches which may hinder large datasets from effective caching. + +## Concurrent B+Tree + +A concurrently readable B+tree design exists, and is used in btrfs [r 4]. The design of this is +so that any update copies only the affected tree node, and the nodes on the path to the leaf. +This means that on a tree with a large number of nodes, a single write only requires a copy +of a minimal set of nodes. For example, given a tree where each node has 7 descendants, and the +tree has 823543 nodes (4941258 key value pairs), to update a single node only requires 6 nodes +to be copied. + +

+ +

+ +

+ +

+ +

+ +

+ +This copy on write property as previously described has a valuable property that if we preserve +previous tree roots, they remain valid and whole trees, where new roots point to their own complete +and valid tree. This allows readers across multiple transaction generations to have stable and +consistent views to a key-value set, while allowing parallel writes to continue to occur in the +tree. Additionally, because nodes are cloned, they are never removed from the `Shared` cache +state, with the new content becoming `Exclusive`/`Modified` until the new root is committed - at +which time initial readers will begin to see the new root and its value. The number of nodes that +require inclusion to perceive a new tree version is minimal, as many older nodes remain +identical and in their `Shared` state. + +This has excellent behaviours for filesystems, but additionally, it's useful for databases as well. +Wired Tiger [r 5] uses a concurrent tree to achieve high performance for mongo db. + +A question that arises is if this excess copying of data could cause excess cache invalidation +as there are multiple copies of the data now existing. This is certainly true that the copying would +have to invalidate some items of the CPU cache, however, those items would be the least recent +used items of the cache, but also that during a write the work-set would remain resident in the CPU's +L1 cache, without affecting the cache's of other CPUs. As mentioned the value of a copy on write +structure is that the number of updates required relative to the size of the dataset is small, which +also will limit the amount of CPU cache invalidation occurring. + +## Concurrent ARC Design + +To create a concurrently readable cache, some constraints must be defined. + +* Readers must always have a correct "point in time" view of the cache and its data +* Readers must be able to trigger cache inclusions +* Readers must be able to track cache hits accurately +* Readers are isolated from all other readers and writer actions +* Writers must always have a correct "point in time" view +* Writers must be able to rollback changes without penalty +* Writers must be able to trigger cache inclusions +* Writers must be able to track cache hits accurately +* Writers are isolated from all readers +* The cache must maintain correct temporal ordering of items in the cache +* The cache must properly update hit and inclusions based on readers and writers +* The cache must provide ARC semantics for management of items +* The cache must be concurrently readable and transactional +* The overhead compared to single thread ARC is minimal + +To clarify why these requirements are important - while it may seem obvious that readers +and writers must be able to track inclusions and hits correctly, when we compare to a read/write +lock, if the cache was in a read lock we could not alter the state of the ARC, meaning that +tracking information is lost - as we want to have many concurrent readers, it is important we track +their access patterns, as readers represent the majority of demand on our cache system. + +In order to satisfy these requirements, an extended structure is needed to allow asynchronous +communication between the readers/writer and the ARC layer, due to ARC not being thread safe (double linked lists). + +A multiple producer - single consumer (mpsc) queue is added to the primary structure, which allows +readers to send their data to the writer asynchronously, so that reader events can be acted on. +Additionally, an extra linked list called "haunted" is added to track keys that have existed in +the set. This creates the following pseudo structures: + + Arc { + Mutex<{ + p, + freq_list, + rec_list, + ghost_freq_list, + ghost_rec_list, + haunted_list, + rx_channel, + }>, + cache_set, + max_capacity, + tx_channel, + } + + ArcRead { + cache_set_ro, + thread_local_set, + tx_channel, + timestamp, + } + + ArcWrite { + cache_set_rw, + thread_local_set, + hit_array, + } + +The majority of the challenge is during the writer commit. To understand this we need to understand +what the readers and writers are doing and how they communicate to the commit phase. + +

+ +

+ +A reader acts like a normal cache - on a request it attempts to find the item in its thread local +set. If it is found, we return the item. If it is not found, we attempt to search the +read only cache set. Again if found we return, else we indicate we do not have the item. On a hit +the channel is issued a `Hit(timestamp, K)`, notifying the writer that a thread has the intent +to access the item K at timestamp. + +If the reader misses, the caller may choose to include the item into the reader transaction. This +is achieved by adding the item to the thread local set, allowing each reader to build a small +thread local set of items relevant to that operation. In addition, when an item is added to the +thread local set, an inclusion message is sent to the channel, consisting of `Inc(K, V, transaction_id, timestamp)`. +This transaction id is from the read only cache transaction that is occurring. + +

+ +

+ +At the end of the read operation, the thread local set is discarded - any included items have been +sent via the channel already. This allows long running readers to influence the commits of +shorter reader cycles, so that other readers that may spawn can benefit from the inclusions of +this reader. + +The writer acts in a very similar manner. During its operation, cache misses are stored into +the thread local set, and hit's are store in the hit array. Dirty items (new, or modified) may +be stored in the thread local set. By searching the thread local set first, we will always +return items that are relevant to this operation that may have been dirtied by the current +thread. + +

+ +

+ +A writer does *not* alter the properties of the Arc during its operation - this is critical, as +it allows the writer to be rolled back at any time, without affecting the state of the cache +set or the frequency lists. While we may lose the details of the hit array from the writer, this is +not a significant loss of data in my view, as the readers hit data matters more. + +It is during the commit of a writer, when the caller has confirmed that they want the changes in +the writer to now persist and be visible to future writers that we perform all cache management +actions. + +### Commit Phase + +During the commit of the writer we perform a number of steps to update the Arc state. First the +commit phase notes the current monotonic timestamp and the current transaction id of the writer. + +The commit then drains the complete writer thread local state into the main cache, updating the +cache item states as each item is implied as a hit or inclusion event. Each item's transaction +id is updated to the transaction id of the writer. + +

+ +

+ +Next the commit drains from the mpsc channel until it is empty or the hit or include items timestamp +exceeds the timestamp from the start of the commit operation. This exists so that a commit will not +drain forever on a busy read cache, only updating the cache to the point in time at which the +commit phase began. Items from the channel are included only if their transaction id is equal to +or greater than the transaction id of the item existing in the cache. If the transaction id is +lower, this acts as a hit instead of an inclusion to affect the weightings of the caches. + +

+ +

+ +This detail, where items are only updated if their transaction id is greater or equal is one of +the most important to maintain temporal consistency, and is the reason for the existence of the +new haunted set. The haunted set maintains the transaction id of all keys when they were evicted from +the cache. Consider the following series of events. + + Reader Begins tx1 + writer begins tx2 + writer alters E + writer commit + + reader begins tx3 + reader quiesce -> Evict E + + reader reads E + reader sends Inc(E) + + reader quiesce (*) + +In this sequence, as the reader at tx1 issues the inclusion, its view of entry E would be older +than the actual DB state of E - this would cause the inclusion of an outdated entry at the last +reader quiesce point, corrupting the data and losing changes. With the haunted set, the key to +item E would be in the haunted set from tx3, causing the include of E from tx1 to be tracked as +a hit rather than an include. +It is for this reason, that all changes to the cache must be tracked by transaction order, +and that the cache must track all items ever included in the haunted set to understand at what +transaction id they were last observed in the cache to preserve temporal ordering and consistency. + +The commit then drains the writer hit set into the cache. This is because the writer exists +somewhat in time after the readers, so it's an approximation of temporal ordering of events, and +gives weight to the written items in the cache from being evicted suddenly. + +

+ +

+ +Finally, the caches are evicted to their relevant sizes based on the updates to the p weight +factor. All items that are evicted are sent to the haunted set with the current transaction id +to protect them from incorrect inclusion in the future. + +

+ +

+ +

+ +

+ +

+ +

+ +## Side Effects of this Algorithm + +An interesting side effect of this delayed inclusion, and the batched eviction in the commit phase +is that the cache now has temporal observability, where we can have the effects from many +threads at many points in time updating the cache and its items. This gives the cache a limited +clairvoyance effect [6], so that items may not be evicted then rapidly included, rather remaining +in the cache, and that items that are evicted are less demanded by not just this thread, but +by the set of threads contributing to the channel and cache updates. + +However, this comes at a cost - this algorithm is extremely memory intensive. A mutex cache will +always maintain a near perfect amount of items in memory based on the requested max. This system +will regularly, and almost always exceed that. This is because the included items in the queue +are untracked, each thread has a thread local set, the haunted set must keep all keys +ever perceived and that during the commit phase items are not evicted until the entire state is +known causing ballooning. As a result implementors or deployments may need to reduce the cache size +to prevent exceeding memory limits. On a modern system however, this may not be a concern in +many cases however. + +Future changes could be to use bounded channels, and to have the channel drop items during high +memory pressure or high levels of include events, but this weakens the ability of the cache to have +a clairvoyant effect. Another option could be to split the hit and inclusion channels such that +the hits are unbounded due to their small size, and that it is the hit tracking that gives us the +insight into what items should be prioritised. Currently the thread sets are also unbounded, and +these could be bounded to help reduce issues. + +## Relevant Applications + +Applications which have a read-mostly, and serialised writes will benefit highly from this design. +Some examples are: + +* LDAP +* Key-Value store databases +* Web-server File Caching + +## Acknowledgements + +* Ilias Stamatiss (Reviewer) + +## References + +* 0 - https://en.wikipedia.org/wiki/MESI_protocol +* 1 - https://web.archive.org/web/20100329071954/http://www.almaden.ibm.com/StorageSystems/projects/arc/ +* 2 - https://www.usenix.org/system/files/login/articles/1180-Megiddo.pdf +* 3 - https://www.zfsbuild.com/2010/04/15/explanation-of-arc-and-l2arc/ +* 4 - https://domino.research.ibm.com/library/cyberdig.nsf/papers/6E1C5B6A1B6EDD9885257A38006B6130/$File/rj10501.pdf +* 5 - http://www.wiredtiger.com/ +* 6 - https://en.wikipedia.org/wiki/Cache_replacement_policies#B%C3%A9l%C3%A1dy's_algorithm +* 7 - http://www.cs.biu.ac.il/~wiseman/2os/2os/os2.pdf +* 8 - https://linux-mm.org/AdvancedPageReplacement + +## Footnote + +* 0 - It is yet unknown to the author if it is possible to have a CPU with the capability of predicting +the future content of memory and being able to cache that reliably, but I'm sure that someone is trying +to develop it. + diff --git a/vendor/concread/CODE_OF_CONDUCT.md b/vendor/concread/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..af12304 --- /dev/null +++ b/vendor/concread/CODE_OF_CONDUCT.md @@ -0,0 +1,43 @@ +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. +Our Standards + +Examples of behavior that contributes to creating a positive environment include: + + Using welcoming and inclusive language + Being respectful of differing viewpoints and experiences + Gracefully accepting constructive criticism + Focusing on what is best for the community + Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + + The use of sexualized language or imagery and unwelcome sexual attention or advances + Trolling, insulting/derogatory comments, and personal or political attacks + Public or private harassment + Publishing others’ private information, such as a physical or electronic address, without explicit permission + Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. +Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at: + +* william at blackhats.net.au + +All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project’s leadership. + +## Attribution + +This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html diff --git a/vendor/concread/CONTRIBUTORS.md b/vendor/concread/CONTRIBUTORS.md new file mode 100644 index 0000000..0f4af30 --- /dev/null +++ b/vendor/concread/CONTRIBUTORS.md @@ -0,0 +1,10 @@ +## Author + +* William Brown (Firstyear): william@blackhats.net.au + +## Contributors + +* Jake (slipperyBishop) +* Youngsuk Kim (@JOE1994) + + diff --git a/vendor/concread/Cargo.toml b/vendor/concread/Cargo.toml new file mode 100644 index 0000000..daa0638 --- /dev/null +++ b/vendor/concread/Cargo.toml @@ -0,0 +1,82 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "concread" +version = "0.2.21" +authors = ["William Brown "] +description = "Concurrently Readable Data-Structures for Rust" +homepage = "https://github.com/kanidm/concread/" +documentation = "https://docs.rs/concread/latest/concread/" +readme = "README.md" +keywords = ["concurrency", "lru", "arc", "hashmap", "btree"] +categories = ["data-structures", "memory-management", "caching", "concurrency"] +license = "MPL-2.0" +resolver = "2" + +[lib] +name = "concread" +path = "src/lib.rs" + +[[bench]] +name = "hashmap_benchmark" +harness = false + +[[bench]] +name = "arccache" +harness = false +[dependencies.ahash] +version = "0.7" + +[dependencies.crossbeam] +version = "0.8" + +[dependencies.crossbeam-epoch] +version = "0.9" + +[dependencies.crossbeam-utils] +version = "0.8" + +[dependencies.lru] +version = "0.7" + +[dependencies.parking_lot] +version = "0.*" + +[dependencies.rand] +version = "0.8" + +[dependencies.smallvec] +version = "1.4" + +[dependencies.tokio] +version = "1" +features = ["sync", "rt", "macros"] +optional = true +[dev-dependencies.criterion] +version = "0.3" +features = ["html_reports"] + +[dev-dependencies.function_name] +version = "0.2" + +[dev-dependencies.time] +version = "0.3" + +[dev-dependencies.uuid] +version = "0.8" + +[features] +tcache = [] +asynch = ["tokio"] +default = ["asynch"] +skinny = [] diff --git a/vendor/concread/LICENSE.md b/vendor/concread/LICENSE.md new file mode 100644 index 0000000..52d1351 --- /dev/null +++ b/vendor/concread/LICENSE.md @@ -0,0 +1,374 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. + diff --git a/vendor/concread/Makefile b/vendor/concread/Makefile new file mode 100644 index 0000000..5024238 --- /dev/null +++ b/vendor/concread/Makefile @@ -0,0 +1,5 @@ + +check: + cargo test + cargo outdated -R + cargo audit diff --git a/vendor/concread/README.md b/vendor/concread/README.md new file mode 100644 index 0000000..113b08a --- /dev/null +++ b/vendor/concread/README.md @@ -0,0 +1,107 @@ +Concread +======== + +Concurrently readable datastructures for Rust. + +Concurrently readable is often referred to as Copy-On-Write, Multi-Version-Concurrency-Control. + +These structures allow multiple readers with transactions +to proceed while single writers can operate. A reader is guaranteed the content +will remain the same for the duration of the read, and readers do not block writers. +Writers are serialised, just like a mutex. + +This library contains concurrently readable Cell types and Map/Cache types. + +When do I want to use these? +---------------------------- + +You can use these in place of a RwLock, and will likely see improvements in +parallel throughput. + +The best use is in place of mutex/rwlock, where the reader exists for a +non-trivial amount of time. + +For example, if you have a RwLock where the lock is taken, data changed or read, and dropped +immediately, this probably won't help you. + +However, if you have a RwLock where you hold the read lock for any amount of time, +writers will begin to stall - or inversely, the writer will cause readers to block +and wait as the writer proceeds. + +Concurrently readable avoids this because readers never stall readers/writers, writers +never stall or block a readers. This means that you gain in parallel throughput +as stalls are reduced. + +This library also has a concurrently readable BTreeMap, HashMap and Adaptive Replacement Cache. +These are best used when you have at least 512 bytes worth of data in your Cell, as they only copy +what is required for an update. + +If you do not required key-ordering, then the HashMap will likely be the best choice +for most applications. + +What is concurrently readable? +------------------------------ + +In a multithread application, data is commonly needed to be shared between threads. +In sharing this there are multiple policies for this - Atomics for single integer +reads, Mutexs for single thread access, RwLock for many readers or one writer, +all the way to Lock Free which allows multiple read and writes of queues. + +Lock Free however has the limitation of being built on Atomics. This means it can +really only update small amounts of data at a time consistently. It also means +that you don't have transactional behaviours. While this is great for queues, +it's not so good for a tree or hashmap where you want the state to be consistent +from the state to the end of an operation. In the few places that lock free trees +exist, they have the properly that as each thread is updating the tree, the changes +are visibile immediately to all other readers. Your data could change before you +know it. + +Mutexs and RwLock on the other hand allow much more complex structures to be protected. +The guarantee that all readers see the same data, always, and that writers are +the only writer. But they cause stalls on other threads waiting to access them. +RwLock for example can see large delays if a reader won't yield, and OS policy +can cause reader/writer to starve if the priority favours the other. + +Concurrently readable structures sit in between these two points. They provide +multiple concurrent readers, with transactional behaviour, while allowing single +writers to proceed simultaneously. + +This is achieved by having writers copy the internal data before they modify +it. This allows readers to access old data, without modification, and allows +the writer to change the data inplace before commiting. Once the new data is +stored, old readers continue to access their old data - new readers will +see the new data. + +This is a space-time trade off, using more memory to achieve better parallel +behaviour. + +Safety +------ + +This library has extensive testing, and passes it's test suite under [miri], a rust +undefined behaviour checker. If you find an issue however, please let us know so we can +fix it! + +To check with miri OR asan on nightly: + + # Follow the miri readme setup steps + cargo clean && MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test + RUSTC_FLAGS="-Z sanitizer=address" cargo test + +Note: Miri requires isolation to be disabled so that clock monotonic can be used in ARC for cache channels. + +[miri]: https://github.com/rust-lang/miri + +SIMD +---- + +There is support for SIMD in ARC if you are using a nightly compiler. To use this, you need to compile +with: + + RUSTFLAGS="-C target-feature=+avx2,+avx" cargo ... --features=concread/simd_support + +Contributing +------------ + +Please open an issue, pr or contact me directly by email (see github) + diff --git a/vendor/concread/asan_test.sh b/vendor/concread/asan_test.sh new file mode 100755 index 0000000..596aa31 --- /dev/null +++ b/vendor/concread/asan_test.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +RUSTC_BOOTSTRAP=1 RUSTFLAGS="-Z sanitizer=address" cargo test $@ + diff --git a/vendor/concread/benches/arccache.rs b/vendor/concread/benches/arccache.rs new file mode 100644 index 0000000..9aaf238 --- /dev/null +++ b/vendor/concread/benches/arccache.rs @@ -0,0 +1,615 @@ +use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput}; +use function_name::named; +use rand::distributions::uniform::SampleUniform; +use rand::{thread_rng, Rng}; +use std::collections::HashMap; +use std::fmt::Debug; +use std::hash::Hash; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::{Duration, Instant}; +// use uuid::Uuid; + +use concread::arcache::ARCache; +use concread::threadcache::ThreadLocal; +use criterion::measurement::{Measurement, ValueFormatter}; + +pub static RUNNING: AtomicBool = AtomicBool::new(false); + +/* + * A fixed dataset size, with various % of cache pressure (5, 10, 20, 40, 60, 80, 110) + * ^ + * \--- then vary the dataset sizes. Inclusions are always processed here. + * -- vary the miss time/penalty as well? + * + * -- this measures time to complete. + * + * As above but could we measure hit rate as well? + * + */ +#[derive(Debug)] +struct DataPoint { + elapsed: Duration, + csize: usize, + hit_count: u32, + attempt: u32, + hit_pct: f64, +} + +#[derive(Clone)] +enum AccessPattern +where + T: SampleUniform + PartialOrd + Clone, +{ + Random(T, T), +} + +impl AccessPattern +where + T: SampleUniform + PartialOrd + Clone, +{ + fn next(&self) -> T { + match self { + AccessPattern::Random(min, max) => { + let mut rng = thread_rng(); + rng.gen_range(min.clone()..max.clone()) + } + } + } +} + +pub struct HitPercentageFormatter; + +impl ValueFormatter for HitPercentageFormatter { + fn format_value(&self, value: f64) -> String { + // eprintln!("⚠️ format_value -> {:?}", value); + format!("{}%", value) + } + + fn format_throughput(&self, _throughput: &Throughput, value: f64) -> String { + // eprintln!("⚠️ format_throughput -> {:?}", value); + format!("{}%", value) + } + + fn scale_values(&self, _typical_value: f64, _values: &mut [f64]) -> &'static str { + // eprintln!("⚠️ scale_values -> typ {:?} : {:?}", typical_value, values); + // panic!(); + "%" + } + + fn scale_throughputs( + &self, + _typical_value: f64, + _throughput: &Throughput, + _values: &mut [f64], + ) -> &'static str { + // eprintln!("⚠️ scale_throughputs -> {:?}", values); + "%" + } + + fn scale_for_machines(&self, _values: &mut [f64]) -> &'static str { + // eprintln!("⚠️ scale_machines -> {:?}", values); + "%" + } +} + +pub struct HitPercentage; + +impl Measurement for HitPercentage { + type Intermediate = (u32, u32); + type Value = (u32, u32); + + fn start(&self) -> Self::Intermediate { + unreachable!("HitPercentage requires the use of iter_custom"); + } + + fn end(&self, i: Self::Intermediate) -> Self::Value { + // eprintln!("⚠️ end -> {:?}", i); + i + } + + fn add(&self, v1: &Self::Value, v2: &Self::Value) -> Self::Value { + // eprintln!("⚠️ add -> {:?} + {:?}", v1, v2); + (v1.0 + v2.0, v1.1 + v2.1) + } + + fn zero(&self) -> Self::Value { + // eprintln!("⚠️ zero -> (0,0)"); + (0, 0) + } + + fn to_f64(&self, val: &Self::Value) -> f64 { + let x = (f64::from(val.0) / f64::from(val.1)) * 100.0; + // eprintln!("⚠️ to_f64 -> {:?} -> {:?}", val, x); + x + } + + fn formatter(&self) -> &dyn ValueFormatter { + &HitPercentageFormatter + } +} + +fn tlocal_multi_thread_worker( + mut cache: ThreadLocal, + backing_set: Arc>, + backing_set_delay: Option, + access_pattern: AccessPattern, +) where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static + SampleUniform + PartialOrd, + V: Clone + Debug + Sync + Send + 'static, +{ + while RUNNING.load(Ordering::Relaxed) { + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + let mut rd_txn = cache.read(); + // hit/miss process. + if !rd_txn.contains_key(&k) { + if let Some(delay) = backing_set_delay { + thread::sleep(delay); + } + rd_txn.insert(k, v); + } + } +} + +fn run_tlocal_multi_thread_test( + // Number of iterations + iters: u64, + // Number of iters to warm the cache. + warm_iters: u64, + // pct of backing set size to configure into the cache. + cache_size_pct: u64, + // backing set. + backing_set: HashMap, + // backing set access delay on miss + backing_set_delay: Option, + // How to lookup keys during each iter. + access_pattern: AccessPattern, + // How many threads? + thread_count: usize, +) -> DataPoint +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static + SampleUniform + PartialOrd, + V: Clone + Debug + Sync + Send + 'static, +{ + assert!(thread_count > 1); + + let mut csize = ((backing_set.len() / 100) * (cache_size_pct as usize)) / thread_count; + if csize == 0 { + csize = 1; + } + + let mut tlocals = ThreadLocal::new(4, csize); + let mut cache = tlocals.pop().expect("Can't get local cache."); + + let backing_set = Arc::new(backing_set); + + // Setup our sync + RUNNING.store(true, Ordering::Relaxed); + + // Warm up + let mut wr_txn = cache.write(); + for _i in 0..warm_iters { + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + // hit/miss process. + if !wr_txn.contains_key(&k) { + wr_txn.insert(k, v); + } + } + wr_txn.commit(); + + // Start some bg threads. + let handles: Vec<_> = tlocals + .into_iter() + .map(|cache| { + // Build the threads. + let back_set = backing_set.clone(); + let back_set_delay = backing_set_delay.clone(); + let pat = access_pattern.clone(); + thread::spawn(move || tlocal_multi_thread_worker(cache, back_set, back_set_delay, pat)) + }) + .collect(); + + // We do our measurement in this thread. + let mut elapsed = Duration::from_secs(0); + let mut hit_count = 0; + let mut attempt = 0; + + for _i in 0..iters { + attempt += 1; + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + let start = Instant::now(); + let mut wr_txn = cache.write(); + // hit/miss process. + if wr_txn.contains_key(&k) { + hit_count += 1; + } else { + if let Some(delay) = backing_set_delay { + thread::sleep(delay); + } + wr_txn.insert(k, v); + } + wr_txn.commit(); + elapsed = elapsed.checked_add(start.elapsed()).unwrap(); + } + + // Stop our bg threads (how to signal?) + RUNNING.store(false, Ordering::Relaxed); + + // Join them. + handles + .into_iter() + .for_each(|th| th.join().expect("Can't join thread")); + + // Return our data. + let hit_pct = (f64::from(hit_count as u32) / f64::from(iters as u32)) * 100.0; + DataPoint { + elapsed, + csize, + hit_count, + attempt, + hit_pct, + } +} + +fn multi_thread_worker( + arc: Arc>, + backing_set: Arc>, + backing_set_delay: Option, + access_pattern: AccessPattern, +) where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static + SampleUniform + PartialOrd, + V: Clone + Debug + Sync + Send + 'static, +{ + while RUNNING.load(Ordering::Relaxed) { + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + let mut rd_txn = arc.read(); + // hit/miss process. + if !rd_txn.contains_key(&k) { + if let Some(delay) = backing_set_delay { + thread::sleep(delay); + } + rd_txn.insert(k, v); + } + } +} + +fn run_multi_thread_test( + // Number of iterations + iters: u64, + // Number of iters to warm the cache. + warm_iters: u64, + // pct of backing set size to configure into the cache. + cache_size_pct: u64, + // backing set. + backing_set: HashMap, + // backing set access delay on miss + backing_set_delay: Option, + // How to lookup keys during each iter. + access_pattern: AccessPattern, + // How many threads? + thread_count: usize, +) -> DataPoint +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static + SampleUniform + PartialOrd, + V: Clone + Debug + Sync + Send + 'static, +{ + assert!(thread_count > 1); + + let mut csize = (backing_set.len() / 100) * (cache_size_pct as usize); + if csize == 0 { + csize = 1; + } + + let arc: Arc> = Arc::new(ARCache::new_size_watermark(csize, 0, 0)); + + let backing_set = Arc::new(backing_set); + + // Setup our sync + RUNNING.store(true, Ordering::Relaxed); + + // Warm up + let mut wr_txn = arc.write(); + for _i in 0..warm_iters { + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + // hit/miss process. + if !wr_txn.contains_key(&k) { + wr_txn.insert(k, v); + } + } + wr_txn.commit(); + + // Start some bg threads. + let handles: Vec<_> = (0..(thread_count - 1)) + .into_iter() + .map(|_| { + // Build the threads. + let cache = arc.clone(); + let back_set = backing_set.clone(); + let back_set_delay = backing_set_delay.clone(); + let pat = access_pattern.clone(); + thread::spawn(move || multi_thread_worker(cache, back_set, back_set_delay, pat)) + }) + .collect(); + + // We do our measurement in this thread. + let mut elapsed = Duration::from_secs(0); + let mut hit_count = 0; + let mut attempt = 0; + + for _i in 0..iters { + attempt += 1; + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + let start = Instant::now(); + let mut wr_txn = arc.write(); + // eprintln!("lock took - {:?}", start.elapsed()); + // hit/miss process. + if wr_txn.contains_key(&k) { + hit_count += 1; + } else { + if let Some(delay) = backing_set_delay { + thread::sleep(delay); + } + wr_txn.insert(k, v); + } + wr_txn.commit(); + elapsed = elapsed.checked_add(start.elapsed()).unwrap(); + } + + // Stop our bg threads (how to signal?) + RUNNING.store(false, Ordering::Relaxed); + + // Join them. + handles + .into_iter() + .for_each(|th| th.join().expect("Can't join thread")); + + // Return our data. + let hit_pct = (f64::from(hit_count as u32) / f64::from(iters as u32)) * 100.0; + DataPoint { + elapsed, + csize, + hit_count, + attempt, + hit_pct, + } +} + +fn run_single_thread_test( + // Number of iterations + iters: u64, + // Number of iters to warm the cache. + warm_iters: u64, + // pct of backing set size to configure into the cache. + cache_size_pct: u64, + // backing set. + backing_set: HashMap, + // backing set access delay on miss + backing_set_delay: Option, + // How to lookup keys during each iter. + access_pattern: AccessPattern, +) -> DataPoint +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static + SampleUniform + PartialOrd, + V: Clone + Debug + Sync + Send + 'static, +{ + let mut csize = (backing_set.len() / 100) * (cache_size_pct as usize); + if csize == 0 { + csize = 1; + } + + let arc: ARCache = ARCache::new_size_watermark(csize, 0, 0); + + let mut elapsed = Duration::from_secs(0); + let mut hit_count = 0; + let mut attempt = 0; + + let mut wr_txn = arc.write(); + for _i in 0..warm_iters { + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + // hit/miss process. + if !wr_txn.contains_key(&k) { + wr_txn.insert(k, v); + } + } + wr_txn.commit(); + + for _i in 0..iters { + attempt += 1; + let k = access_pattern.next(); + let v = backing_set.get(&k).cloned().unwrap(); + + let start = Instant::now(); + let mut wr_txn = arc.write(); + // hit/miss process. + if wr_txn.contains_key(&k) { + hit_count += 1; + } else { + if let Some(delay) = backing_set_delay { + thread::sleep(delay); + } + wr_txn.insert(k, v); + } + wr_txn.commit(); + elapsed = elapsed.checked_add(start.elapsed()).unwrap(); + } + + let hit_pct = (f64::from(hit_count as u32) / f64::from(iters as u32)) * 100.0; + DataPoint { + elapsed, + csize, + hit_count, + attempt, + hit_pct, + } +} + +macro_rules! tlocal_multi_thread_x_small_latency { + ($c:expr, $max:expr, $measure:expr) => { + let mut group = $c.benchmark_group(function_name!()); + group.warm_up_time(Duration::from_secs(10)); + group.measurement_time(Duration::from_secs(60)); + for pct in &[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110] { + group.bench_with_input(BenchmarkId::from_parameter(pct), &$max, |b, &max| { + b.iter_custom(|iters| { + let mut backing_set: HashMap = HashMap::with_capacity(max); + (0..$max).for_each(|i| { + backing_set.insert(i, i); + }); + let data = run_tlocal_multi_thread_test( + iters, + iters / 5, + *pct, + backing_set, + Some(Duration::from_nanos(5)), + AccessPattern::Random(0, max), + 4, + ); + println!("{:?}", data); + data.elapsed + }) + }); + } + group.finish(); + }; +} + +macro_rules! basic_multi_thread_x_small_latency { + ($c:expr, $max:expr, $measure:expr) => { + let mut group = $c.benchmark_group(function_name!()); + group.warm_up_time(Duration::from_secs(10)); + group.measurement_time(Duration::from_secs(60)); + for pct in &[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110] { + group.bench_with_input(BenchmarkId::from_parameter(pct), &$max, |b, &max| { + b.iter_custom(|iters| { + let mut backing_set: HashMap = HashMap::with_capacity(max); + (0..$max).for_each(|i| { + backing_set.insert(i, i); + }); + let data = run_multi_thread_test( + iters, + iters / 5, + *pct, + backing_set, + Some(Duration::from_nanos(5)), + AccessPattern::Random(0, max), + 4, + ); + println!("{:?}", data); + data.elapsed + }) + }); + } + group.finish(); + }; +} + +macro_rules! basic_single_thread_x_small_latency { + ($c:expr, $max:expr, $measure:expr) => { + let mut group = $c.benchmark_group(function_name!()); + group.warm_up_time(Duration::from_secs(10)); + for pct in &[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110] { + group.bench_with_input(BenchmarkId::from_parameter(pct), &$max, |b, &max| { + b.iter_custom(|iters| { + let mut backing_set: HashMap = HashMap::with_capacity(max); + (0..$max).for_each(|i| { + backing_set.insert(i, i); + }); + let data = run_single_thread_test( + iters, + iters / 5, + *pct, + backing_set, + Some(Duration::from_nanos(5)), + AccessPattern::Random(0, max), + ); + println!("{:?}", data); + data.elapsed + }) + }); + } + group.finish(); + }; +} + +macro_rules! basic_single_thread_x_small_pct { + ($c:expr, $max:expr, $measure:expr) => { + let mut group = $c.benchmark_group(function_name!()); + group.warm_up_time(Duration::from_secs(10)); + for pct in &[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110] { + group.bench_with_input(BenchmarkId::from_parameter(pct), &$max, |b, &max| { + b.iter_custom(|iters| { + let mut backing_set: HashMap = HashMap::with_capacity(max); + (0..$max).for_each(|i| { + backing_set.insert(i, i); + }); + let data = run_single_thread_test( + iters, + iters / 10, + *pct, + backing_set, + Some(Duration::from_nanos(5)), + AccessPattern::Random(0, max), + ); + println!("{:?}", data); + (data.hit_count, data.attempt) + }) + }); + } + group.finish(); + }; +} + +#[named] +pub fn tlocal_multi_thread_2048_small_latency(c: &mut Criterion) { + tlocal_multi_thread_x_small_latency!(c, 2048, MeasureType::Latency); +} + +#[named] +pub fn basic_multi_thread_2048_small_latency(c: &mut Criterion) { + basic_multi_thread_x_small_latency!(c, 2048, MeasureType::Latency); +} + +#[named] +pub fn basic_single_thread_2048_small_latency(c: &mut Criterion) { + basic_single_thread_x_small_latency!(c, 2048, MeasureType::Latency); +} + +#[named] +pub fn basic_single_thread_2048_small_pct(c: &mut Criterion) { + basic_single_thread_x_small_pct!(c, 2048, MeasureType::HitPct); +} + +criterion_group!( + name = latency; + config = Criterion::default() + // .measurement_time(Duration::from_secs(15)) + .with_plots(); + targets = basic_single_thread_2048_small_latency, + basic_multi_thread_2048_small_latency, + tlocal_multi_thread_2048_small_latency, + + +); + +criterion_group!( + name = hit_percent; + config = Criterion::default().with_measurement(HitPercentage); + targets = basic_single_thread_2048_small_pct +); + +criterion_main!(latency, hit_percent); diff --git a/vendor/concread/benches/hashmap_benchmark.rs b/vendor/concread/benches/hashmap_benchmark.rs new file mode 100644 index 0000000..bc0da72 --- /dev/null +++ b/vendor/concread/benches/hashmap_benchmark.rs @@ -0,0 +1,320 @@ +// The benchmarks aim to only measure times of the operations in their names. +// That's why all use Bencher::iter_batched which enables non-benchmarked +// preparation before running the measured function. +// Insert (which doesn't completely avoid updates, but makes them unlikely), +// remove and search have benchmarks with empty values and with custom structs +// of 42 64-bit integers. +// (as a sidenote, the performance really differs; in the case of remove, the +// remove function itself returns original value - the benchmark doesn't use +// this value, but performance is significantly worse - about twice on my +// machine - than the empty value remove; it might be interesting to see if a +// remove function returning void would perform better, ie. if the returns +// are optimized - omitted in this case). +// The counts of inserted/removed/searched elements are chosen at random from +// constant ranges in an attempt to avoid a single count performing better +// because of specific HW features of computers the code is benchmarked with. + +extern crate concread; +extern crate criterion; +extern crate rand; + +use concread::hashmap::*; +use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; +use rand::{thread_rng, Rng}; + +// ranges of counts for different benchmarks (MINs are inclusive, MAXes exclusive): +const INSERT_COUNT_MIN: usize = 120; +const INSERT_COUNT_MAX: usize = 140; +const INSERT_COUNT_FOR_REMOVE_MIN: usize = 340; +const INSERT_COUNT_FOR_REMOVE_MAX: usize = 360; +const REMOVE_COUNT_MIN: usize = 120; +const REMOVE_COUNT_MAX: usize = 140; +const INSERT_COUNT_FOR_SEARCH_MIN: usize = 120; +const INSERT_COUNT_FOR_SEARCH_MAX: usize = 140; +const SEARCH_COUNT_MIN: usize = 120; +const SEARCH_COUNT_MAX: usize = 140; +// In the search benches, we randomly search for elements of a range of SEARCH_SIZE_NUMERATOR / SEARCH_SIZE_DENOMINATOR +// times the number of elements contained. +const SEARCH_SIZE_NUMERATOR: usize = 4; +const SEARCH_SIZE_DENOMINATOR: usize = 3; + +pub fn insert_empty_value_rollback(c: &mut Criterion) { + c.bench_function("insert_empty_value_rollback", |b| { + b.iter_batched( + || prepare_insert(()), + |(mut map, list)| { + insert_vec(&mut map, list); + }, + BatchSize::SmallInput, + ) + }); +} + +pub fn insert_empty_value_commit(c: &mut Criterion) { + c.bench_function("insert_empty_value_commit", |b| { + b.iter_batched( + || prepare_insert(()), + |(mut map, list)| insert_vec(&mut map, list).commit(), + BatchSize::SmallInput, + ) + }); +} + +pub fn insert_struct_value_rollback(c: &mut Criterion) { + c.bench_function("insert_struct_value_rollback", |b| { + b.iter_batched( + || prepare_insert(Struct::default()), + |(mut map, list)| { + insert_vec(&mut map, list); + }, + BatchSize::SmallInput, + ) + }); +} + +pub fn insert_struct_value_commit(c: &mut Criterion) { + c.bench_function("insert_struct_value_commit", |b| { + b.iter_batched( + || prepare_insert(Struct::default()), + |(mut map, list)| insert_vec(&mut map, list).commit(), + BatchSize::SmallInput, + ) + }); +} + +pub fn remove_empty_value_rollback(c: &mut Criterion) { + c.bench_function("remove_empty_value_rollback", |b| { + b.iter_batched( + || prepare_remove(()), + |(ref mut map, ref list)| { + remove_vec(map, list); + }, + BatchSize::SmallInput, + ) + }); +} + +pub fn remove_empty_value_commit(c: &mut Criterion) { + c.bench_function("remove_empty_value_commit", |b| { + b.iter_batched( + || prepare_remove(()), + |(ref mut map, ref list)| remove_vec(map, list).commit(), + BatchSize::SmallInput, + ) + }); +} + +pub fn remove_struct_value_no_read_rollback(c: &mut Criterion) { + c.bench_function("remove_struct_value_no_read_rollback", |b| { + b.iter_batched( + || prepare_remove(Struct::default()), + |(ref mut map, ref list)| { + remove_vec(map, list); + }, + BatchSize::SmallInput, + ) + }); +} + +pub fn remove_struct_value_no_read_commit(c: &mut Criterion) { + c.bench_function("remove_struct_value_no_read_commit", |b| { + b.iter_batched( + || prepare_remove(Struct::default()), + |(ref mut map, ref list)| remove_vec(map, list).commit(), + BatchSize::SmallInput, + ) + }); +} + +pub fn search_empty_value(c: &mut Criterion) { + c.bench_function("search_empty_value", |b| { + b.iter_batched( + || prepare_search(()), + |(ref map, ref list)| search_vec(map, list), + BatchSize::SmallInput, + ) + }); +} + +pub fn search_struct_value(c: &mut Criterion) { + c.bench_function("search_struct_value", |b| { + b.iter_batched( + || prepare_search(Struct::default()), + |(ref map, ref list)| search_vec(map, list), + BatchSize::SmallInput, + ) + }); +} + +criterion_group!( + insert, + insert_empty_value_rollback, + insert_empty_value_commit, + insert_struct_value_rollback, + insert_struct_value_commit +); +criterion_group!( + remove, + remove_empty_value_rollback, + remove_empty_value_commit, + remove_struct_value_no_read_rollback, + remove_struct_value_no_read_commit +); +criterion_group!(search, search_empty_value, search_struct_value); +criterion_main!(insert, remove, search); + +// Utility functions: + +fn insert_vec<'a, V: Clone + Sync + Send + 'static>( + map: &'a mut HashMap, + list: Vec<(u32, V)>, +) -> HashMapWriteTxn<'a, u32, V> { + let mut write_txn = map.write(); + for (key, val) in list.into_iter() { + write_txn.insert(key, val); + } + write_txn +} + +fn remove_vec<'a, V: Clone + Sync + Send + 'static>( + map: &'a mut HashMap, + list: &Vec, +) -> HashMapWriteTxn<'a, u32, V> { + let mut write_txn = map.write(); + for i in list.iter() { + write_txn.remove(i); + } + write_txn +} + +fn search_vec(map: &HashMap, list: &Vec) { + let read_txn = map.read(); + for i in list.iter() { + // ! This could potentially get optimized into nothing ! + read_txn.get(&i); + } +} + +#[derive(Default, Clone)] +struct Struct { + var1: i64, + var2: i64, + var3: i64, + var4: i64, + var5: i64, + var6: i64, + var7: i64, + var8: i64, + var9: i64, + var10: i64, + var11: i64, + var12: i64, + var13: i64, + var14: i64, + var15: i64, + var16: i64, + var17: i64, + var18: i64, + var19: i64, + var20: i64, + var21: i64, + var22: i64, + var23: i64, + var24: i64, + var25: i64, + var26: i64, + var27: i64, + var28: i64, + var29: i64, + var30: i64, + var31: i64, + var32: i64, + var33: i64, + var34: i64, + var35: i64, + var36: i64, + var37: i64, + var38: i64, + var39: i64, + var40: i64, + var41: i64, + var42: i64, +} + +fn prepare_insert(value: V) -> (HashMap, Vec<(u32, V)>) { + let mut rng = thread_rng(); + let count = rng.gen_range(INSERT_COUNT_MIN..INSERT_COUNT_MAX); + let mut list = Vec::with_capacity(count); + for _ in 0..count { + list.push(( + rng.gen_range(0..INSERT_COUNT_MAX << 8) as u32, + value.clone(), + )); + } + (HashMap::new(), list) +} + +/// Prepares a remove benchmark with values in the HashMap being clones of the 'value' parameter +fn prepare_remove(value: V) -> (HashMap, Vec) { + let mut rng = thread_rng(); + let insert_count = rng.gen_range(INSERT_COUNT_FOR_REMOVE_MIN..INSERT_COUNT_FOR_REMOVE_MAX); + let remove_count = rng.gen_range(REMOVE_COUNT_MIN..REMOVE_COUNT_MAX); + let map = HashMap::new(); + let mut write_txn = map.write(); + for i in random_order(insert_count, insert_count).iter() { + // We could count on the hash function alone to make the order random, but it seems + // better to count on every possible implementation. + write_txn.insert(i.clone(), value.clone()); + } + write_txn.commit(); + (map, random_order(insert_count, remove_count)) +} + +fn prepare_search(value: V) -> (HashMap, Vec) { + let mut rng = thread_rng(); + let insert_count = rng.gen_range(INSERT_COUNT_FOR_SEARCH_MIN..INSERT_COUNT_FOR_SEARCH_MAX); + let search_limit = insert_count * SEARCH_SIZE_NUMERATOR / SEARCH_SIZE_DENOMINATOR; + let search_count = rng.gen_range(SEARCH_COUNT_MIN..SEARCH_COUNT_MAX); + + // Create a HashMap with elements 0 through insert_count(-1) + let map = HashMap::new(); + let mut write_txn = map.write(); + for k in 0..insert_count { + write_txn.insert(k as u32, value.clone()); + } + write_txn.commit(); + + // Choose 'search_count' numbers from [0,search_limit) randomly to be searched in the created map. + let mut list = Vec::with_capacity(search_count); + for _ in 0..search_count { + list.push(rng.gen_range(0..search_limit as u32)); + } + (map, list) +} + +/// Returns a Vec of n elements from the range [0,up_to) in random order without repetition +fn random_order(up_to: usize, n: usize) -> Vec { + let mut rng = thread_rng(); + let mut order = Vec::with_capacity(n); + let mut generated = vec![false; up_to]; + let mut remaining = n; + let mut remaining_elems = up_to; + while remaining > 0 { + let mut r = rng.gen_range(0..remaining_elems); + // find the r-th yet nongenerated number: + for i in 0..up_to { + if generated[i] { + continue; + } + if r == 0 { + order.push(i as u32); + generated[i] = true; + break; + } + r -= 1; + } + remaining -= 1; + remaining_elems -= 1; + } + order +} diff --git a/vendor/concread/src/arcache/ll.rs b/vendor/concread/src/arcache/ll.rs new file mode 100644 index 0000000..38ce513 --- /dev/null +++ b/vendor/concread/src/arcache/ll.rs @@ -0,0 +1,413 @@ +use std::fmt::Debug; +use std::marker::PhantomData; +use std::mem::MaybeUninit; +use std::ptr; + +pub trait LLWeight { + fn ll_weight(&self) -> usize; +} + +/* +impl LLWeight for T { + #[inline] + default fn ll_weight(&self) -> usize { + 1 + } +} +*/ + +#[derive(Clone, Debug)] +pub(crate) struct LL +where + K: LLWeight + Clone + Debug, +{ + head: *mut LLNode, + tail: *mut LLNode, + size: usize, + // tag: usize, +} + +#[derive(Debug)] +pub(crate) struct LLNode +where + K: LLWeight + Clone + Debug, +{ + pub(crate) k: MaybeUninit, + next: *mut LLNode, + prev: *mut LLNode, + // tag: usize, +} + +#[derive(Clone, Debug)] +pub(crate) struct LLIterMut<'a, K> +where + K: LLWeight + Clone + Debug, +{ + next: *mut LLNode, + end: *mut LLNode, + phantom: PhantomData<&'a K>, +} + +impl LL +where + K: LLWeight + Clone + Debug, +{ + pub(crate) fn new(// tag: usize + ) -> Self { + // assert!(tag > 0); + let (head, tail) = LLNode::create_markers(); + LL { + head, + tail, + size: 0, + // tag, + } + } + + #[allow(dead_code)] + pub(crate) fn iter_mut(&self) -> LLIterMut { + LLIterMut { + next: unsafe { (*self.head).next }, + end: self.tail, + phantom: PhantomData, + } + } + + // Append a k to the set, and return it's pointer. + pub(crate) fn append_k(&mut self, k: K) -> *mut LLNode { + let n = LLNode::new(k); + self.append_n(n); + n + } + + // Append an arbitrary node into this set. + pub(crate) fn append_n(&mut self, n: *mut LLNode) { + // Who is to the left of tail? + unsafe { + self.size += (*(*n).k.as_ptr()).ll_weight(); + // must be untagged + // assert!((*n).tag == 0); + debug_assert!((*self.tail).next.is_null()); + debug_assert!(!(*self.tail).prev.is_null()); + let pred = (*self.tail).prev; + debug_assert!(!pred.is_null()); + debug_assert!((*pred).next == self.tail); + (*n).prev = pred; + (*n).next = self.tail; + // (*n).tag = self.tag; + (*pred).next = n; + (*self.tail).prev = n; + // We should have a prev and next + debug_assert!(!(*n).prev.is_null()); + debug_assert!(!(*n).next.is_null()); + // And that prev's next is us, and next's prev is us. + debug_assert!(!(*(*n).prev).next.is_null()); + debug_assert!(!(*(*n).next).prev.is_null()); + debug_assert!((*(*n).prev).next == n); + debug_assert!((*(*n).next).prev == n); + } + } + + // Given a node ptr, extract and put it at the tail. IE hit. + pub(crate) fn touch(&mut self, n: *mut LLNode) { + debug_assert!(self.size > 0); + if n == unsafe { (*self.tail).prev } { + // Done, no-op + } else { + self.extract(n); + self.append_n(n); + } + } + + // remove this node from the ll, and return it's ptr. + pub(crate) fn pop(&mut self) -> *mut LLNode { + let n = unsafe { (*self.head).next }; + self.extract(n); + debug_assert!(!n.is_null()); + debug_assert!(n != self.head); + debug_assert!(n != self.tail); + n + } + + // Cut a node out from this list from any location. + pub(crate) fn extract(&mut self, n: *mut LLNode) { + assert!(self.size > 0); + assert!(!n.is_null()); + unsafe { + // We should have a prev and next + debug_assert!(!(*n).prev.is_null()); + debug_assert!(!(*n).next.is_null()); + // And that prev's next is us, and next's prev is us. + debug_assert!(!(*(*n).prev).next.is_null()); + debug_assert!(!(*(*n).next).prev.is_null()); + debug_assert!((*(*n).prev).next == n); + debug_assert!((*(*n).next).prev == n); + // And we belong to this set + // assert!((*n).tag == self.tag); + self.size -= (*(*n).k.as_ptr()).ll_weight(); + } + + unsafe { + let prev = (*n).prev; + let next = (*n).next; + // prev <-> n <-> next + (*next).prev = prev; + (*prev).next = next; + // Null things for paranoia. + if cfg!(test) || cfg!(debug_assertions) { + (*n).prev = ptr::null_mut(); + (*n).next = ptr::null_mut(); + } + // (*n).tag = 0; + } + } + + pub(crate) fn len(&self) -> usize { + self.size + } + + #[cfg(test)] + pub(crate) fn peek_head(&self) -> Option<&K> { + debug_assert!(!self.head.is_null()); + let next = unsafe { (*self.head).next }; + if next == self.tail { + None + } else { + let l = unsafe { + let ptr = (*next).k.as_ptr(); + &(*ptr) as &K + }; + Some(l) + } + } + + #[cfg(test)] + pub(crate) fn peek_tail(&self) -> Option<&K> { + debug_assert!(!self.tail.is_null()); + let prev = unsafe { (*self.tail).prev }; + if prev == self.head { + None + } else { + let l = unsafe { + let ptr = (*prev).k.as_ptr(); + &(*ptr) as &K + }; + Some(l) + } + } +} + +impl Drop for LL +where + K: LLWeight + Clone + Debug, +{ + fn drop(&mut self) { + let head = self.head; + let tail = self.tail; + let mut n = unsafe { (*head).next }; + while n != tail { + let next = unsafe { (*n).next }; + unsafe { ptr::drop_in_place((*n).k.as_mut_ptr()) }; + LLNode::free(n); + n = next; + } + LLNode::free(head); + LLNode::free(tail); + } +} + +impl LLNode +where + K: LLWeight + Clone + Debug, +{ + #[inline] + pub(crate) fn create_markers() -> (*mut Self, *mut Self) { + let head = Box::into_raw(Box::new(LLNode { + k: MaybeUninit::uninit(), + next: ptr::null_mut(), + prev: ptr::null_mut(), + // tag: 0, + })); + let tail = Box::into_raw(Box::new(LLNode { + k: MaybeUninit::uninit(), + next: ptr::null_mut(), + prev: head, + // tag: 0, + })); + unsafe { + (*head).next = tail; + } + (head, tail) + } + + #[inline] + pub(crate) fn new( + k: K, + // tag: usize + ) -> *mut Self { + let b = Box::new(LLNode { + k: MaybeUninit::new(k), + next: ptr::null_mut(), + prev: ptr::null_mut(), + // tag, + }); + Box::into_raw(b) + } + + #[inline] + fn free(v: *mut Self) { + debug_assert!(!v.is_null()); + let _ = unsafe { Box::from_raw(v) }; + } +} + +impl AsRef for LLNode +where + K: LLWeight + Clone + Debug, +{ + fn as_ref(&self) -> &K { + unsafe { + let ptr = self.k.as_ptr(); + &(*ptr) as &K + } + } +} + +impl AsMut for LLNode +where + K: LLWeight + Clone + Debug, +{ + fn as_mut(&mut self) -> &mut K { + unsafe { + let ptr = self.k.as_mut_ptr(); + &mut (*ptr) as &mut K + } + } +} + +impl<'a, K> Iterator for LLIterMut<'a, K> +where + K: LLWeight + Clone + Debug, +{ + type Item = &'a mut K; + + fn next(&mut self) -> Option { + debug_assert!(!self.next.is_null()); + if self.next == self.end { + None + } else { + let r = Some(unsafe { (*self.next).as_mut() }); + self.next = unsafe { (*self.next).next }; + r + } + } +} + +#[cfg(test)] +mod tests { + use crate::arcache::ll::{LLWeight, LL}; + + impl LLWeight for Box { + #[inline] + fn ll_weight(&self) -> usize { + 1 + } + } + + #[test] + fn test_cache_arc_ll_basic() { + // We test with box so that we leak on error + let mut ll: LL> = LL::new(); + assert!(ll.len() == 0); + // Allocate new nodes + let n1 = ll.append_k(Box::new(1)); + let n2 = ll.append_k(Box::new(2)); + let n3 = ll.append_k(Box::new(3)); + let n4 = ll.append_k(Box::new(4)); + // Check that n1 is the head, n3 is tail. + assert!(ll.len() == 4); + assert!(ll.peek_head().unwrap().as_ref() == &1); + assert!(ll.peek_tail().unwrap().as_ref() == &4); + + // Touch 2, it's now tail. + ll.touch(n2); + assert!(ll.len() == 4); + assert!(ll.peek_head().unwrap().as_ref() == &1); + assert!(ll.peek_tail().unwrap().as_ref() == &2); + + // Touch 1 (head), it's the tail now. + ll.touch(n1); + assert!(ll.len() == 4); + assert!(ll.peek_head().unwrap().as_ref() == &3); + assert!(ll.peek_tail().unwrap().as_ref() == &1); + + // Touch 1 (tail), it stays as tail. + ll.touch(n1); + assert!(ll.len() == 4); + assert!(ll.peek_head().unwrap().as_ref() == &3); + assert!(ll.peek_tail().unwrap().as_ref() == &1); + + // pop from head + let _n3 = ll.pop(); + assert!(ll.len() == 3); + assert!(ll.peek_head().unwrap().as_ref() == &4); + assert!(ll.peek_tail().unwrap().as_ref() == &1); + + // cut a node out from any (head, mid, tail) + ll.extract(n2); + assert!(ll.len() == 2); + assert!(ll.peek_head().unwrap().as_ref() == &4); + assert!(ll.peek_tail().unwrap().as_ref() == &1); + + ll.extract(n1); + assert!(ll.len() == 1); + assert!(ll.peek_head().unwrap().as_ref() == &4); + assert!(ll.peek_tail().unwrap().as_ref() == &4); + + // test touch on ll of size 1 + ll.touch(n4); + assert!(ll.len() == 1); + assert!(ll.peek_head().unwrap().as_ref() == &4); + assert!(ll.peek_tail().unwrap().as_ref() == &4); + // Remove last + let _n4 = ll.pop(); + assert!(ll.len() == 0); + assert!(ll.peek_head().is_none()); + assert!(ll.peek_tail().is_none()); + + // Add them all back so they are dropped. + ll.append_n(n1); + ll.append_n(n2); + ll.append_n(n3); + ll.append_n(n4); + } + + #[derive(Clone, Debug)] + struct Weighted { + _i: u64, + } + + impl LLWeight for Weighted { + fn ll_weight(&self) -> usize { + 8 + } + } + + #[test] + fn test_cache_arc_ll_weighted() { + // We test with box so that we leak on error + let mut ll: LL = LL::new(); + assert!(ll.len() == 0); + let _n1 = ll.append_k(Weighted { _i: 1 }); + assert!(ll.len() == 8); + let _n2 = ll.append_k(Weighted { _i: 2 }); + assert!(ll.len() == 16); + let n1 = ll.pop(); + assert!(ll.len() == 8); + let n2 = ll.pop(); + assert!(ll.len() == 0); + // Add back so they drop + ll.append_n(n1); + ll.append_n(n2); + } +} diff --git a/vendor/concread/src/arcache/mod.rs b/vendor/concread/src/arcache/mod.rs new file mode 100644 index 0000000..ee00531 --- /dev/null +++ b/vendor/concread/src/arcache/mod.rs @@ -0,0 +1,2795 @@ +//! ARCache - A concurrently readable adaptive replacement cache. +//! +//! An ARCache is used in place of a `RwLock` or `Mutex`. +//! This structure is transactional, meaning that readers have guaranteed +//! point-in-time views of the cache and their items, while allowing writers +//! to proceed with inclusions and cache state management in parallel. +//! +//! This means that unlike a `RwLock` which can have many readers OR one writer +//! this cache is capable of many readers, over multiple data generations AND +//! writers that are serialised. This formally means that this is an ACID +//! compliant Cache. + +mod ll; + +use self::ll::{LLNode, LLWeight, LL}; +// use self::traits::ArcWeight; +use crate::cowcell::{CowCell, CowCellReadTxn}; +use crate::hashmap::*; +use crossbeam::channel::{unbounded, Receiver, Sender}; +use crossbeam::queue::ArrayQueue; +use parking_lot::{Mutex, RwLock}; +use std::collections::HashMap as Map; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +use std::borrow::Borrow; +use std::cell::UnsafeCell; +use std::convert::TryFrom; +use std::fmt::Debug; +use std::hash::Hash; +use std::mem; +use std::ops::Deref; +use std::ops::DerefMut; +use std::time::Instant; + +// const READ_THREAD_MIN: usize = 8; +const READ_THREAD_RATIO: usize = 16; + +/// Statistics related to the Arc +#[derive(Clone, Debug, PartialEq)] +pub struct CacheStats { + /// The number of hits during all read operations on the primary cache. + pub reader_hits: usize, + /// The number of hits during all read operations on the thread local caches. + pub reader_tlocal_hits: usize, + /// The number of inclusions to the thread local storage. + pub reader_tlocal_includes: usize, + /// The number of inclusions through read operations. + pub reader_includes: usize, + /// The number of hits during all write operations. + pub write_hits: usize, + /// The number of inclusions or changes through write operations. + pub write_includes: usize, + /// The number of modifications to the cache content during a write. + pub write_modifies: usize, + /// The maximum number of items in the shared cache. + pub shared_max: usize, + /// The number of items in the frequent set + pub freq: usize, + /// The number of items in the recent set + pub recent: usize, + /// The number of items evicted from the frequent set + pub freq_evicts: usize, + /// The number of items evicted from the recent set + pub recent_evicts: usize, + /// The current cache weight between recent and frequent. + pub p_weight: usize, + /// The number of keys seen through the cache's lifetime. + pub all_seen_keys: usize, +} + +struct ReaderStatEvent { + t: Instant, + hits: usize, + tlocal_hits: usize, + tlocal_includes: usize, + reader_includes: usize, +} + +enum ThreadCacheItem { + Present(V, bool, usize), + Removed(bool), +} + +struct CacheHitEvent { + t: Instant, + k_hash: u64, +} + +struct CacheIncludeEvent { + t: Instant, + k: K, + v: V, + txid: u64, + size: usize, +} + +#[derive(Hash, Ord, PartialOrd, Eq, PartialEq, Clone, Debug)] +struct CacheItemInner +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, +{ + k: K, + txid: u64, + size: usize, +} + +impl LLWeight for CacheItemInner +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, +{ + #[inline] + fn ll_weight(&self) -> usize { + self.size + } +} + +#[derive(Clone, Debug)] +enum CacheItem +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, +{ + Freq(*mut LLNode>, V), + Rec(*mut LLNode>, V), + GhostFreq(*mut LLNode>), + GhostRec(*mut LLNode>), + Haunted(*mut LLNode>), +} + +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Send for CacheItem +{ +} +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Sync for CacheItem +{ +} + +#[cfg(test)] +#[derive(Clone, Debug, PartialEq)] +pub(crate) enum CacheState { + Freq, + Rec, + GhostFreq, + GhostRec, + Haunted, + None, +} + +#[cfg(test)] +#[derive(Debug, PartialEq)] +pub(crate) struct CStat { + max: usize, + cache: usize, + tlocal: usize, + freq: usize, + rec: usize, + ghost_freq: usize, + ghost_rec: usize, + haunted: usize, + p: usize, +} + +struct ArcInner +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + /// Weight of items between the two caches. + p: usize, + freq: LL>, + rec: LL>, + ghost_freq: LL>, + ghost_rec: LL>, + haunted: LL>, + // + stat_rx: Receiver, + // rx: Receiver>, + hit_queue: Arc>, + inc_queue: Arc>>, + min_txid: u64, +} + +struct ArcShared +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + // Max number of elements to cache. + max: usize, + // Max number of elements for a reader per thread. + read_max: usize, + // channels for readers. + stat_tx: Sender, + hit_queue: Arc>, + inc_queue: Arc>>, + /// The number of items that are present in the cache before we start to process + /// the arc sets/lists. + watermark: usize, +} + +/// A configurable builder to create new concurrent Adaptive Replacement Caches. +#[derive(Default)] +pub struct ARCacheBuilder { + stats: Option, + max: Option, + read_max: Option, + watermark: Option, +} + +/// A concurrently readable adaptive replacement cache. Operations are performed on the +/// cache via read and write operations. +pub struct ARCache +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + // Use a unified tree, allows simpler movement of items between the + // cache types. + cache: HashMap>, + // This is normally only ever taken in "read" mode, so it's effectively + // an uncontended barrier. + shared: RwLock>, + // These are only taken during a quiesce + inner: Mutex>, + stats: CowCell, + above_watermark: AtomicBool, +} + +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Send for ARCache +{ +} +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Sync for ARCache +{ +} + +#[derive(Debug, Clone)] +struct ReadCacheItem +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + k: K, + v: V, + size: usize, +} + +impl LLWeight for ReadCacheItem +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + #[inline] + fn ll_weight(&self) -> usize { + self.size + } +} + +struct ReadCache +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + // cache of our missed items to send forward. + // On drop we drain this to the channel + set: Map>>, + read_size: usize, + tlru: LL>, +} + +/// An active read transaction over the cache. The data is this cache is guaranteed to be +/// valid at the point in time the read is created. You may include items during a cache +/// miss via the "insert" function. +pub struct ARCacheReadTxn<'a, K, V> +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + caller: &'a ARCache, + // ro_txn to cache + cache: HashMapReadTxn<'a, K, CacheItem>, + tlocal: Option>, + // channel to send stat information + stat_tx: Sender, + // tx channel to send forward events. + // tx: Sender>, + hit_queue: Arc>, + inc_queue: Arc>>, + above_watermark: bool, + hits: usize, + tlocal_hits: usize, + tlocal_includes: usize, + reader_includes: usize, +} + +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Send for ARCacheReadTxn<'_, K, V> +{ +} +unsafe impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Sync for ARCacheReadTxn<'_, K, V> +{ +} + +/// An active write transaction over the cache. The data in this cache is isolated +/// from readers, and may be rolled-back if an error occurs. Changes only become +/// globally visible once you call "commit". Items may be added to the cache on +/// a miss via "insert", and you can explicitly remove items by calling "remove". +pub struct ARCacheWriteTxn<'a, K, V> +where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, +{ + caller: &'a ARCache, + // wr_txn to cache + cache: HashMapWriteTxn<'a, K, CacheItem>, + // Cache of missed items (w_ dirty/clean) + // On COMMIT we drain this to the main cache + tlocal: Map>, + hit: UnsafeCell>, + clear: UnsafeCell, + above_watermark: bool, +} + +/* +pub struct ArcReadSnapshot { + // How to communicate back to the caller the loads we did? + tlocal: &mut Map>, +} +*/ + +impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > CacheItem +{ + fn to_vref(&self) -> Option<&V> { + match &self { + CacheItem::Freq(_, v) | CacheItem::Rec(_, v) => Some(v), + _ => None, + } + } + + #[cfg(test)] + fn to_state(&self) -> CacheState { + match &self { + CacheItem::Freq(_, _v) => CacheState::Freq, + CacheItem::Rec(_, _v) => CacheState::Rec, + CacheItem::GhostFreq(_) => CacheState::GhostFreq, + CacheItem::GhostRec(_) => CacheState::GhostRec, + CacheItem::Haunted(_) => CacheState::Haunted, + } + } +} + +macro_rules! drain_ll_to_ghost { + ( + $cache:expr, + $ll:expr, + $gf:expr, + $gr:expr, + $txid:expr + ) => {{ + while $ll.len() > 0 { + let n = $ll.pop(); + debug_assert!(!n.is_null()); + unsafe { + // Set the item's evict txid. + (*n).as_mut().txid = $txid; + } + let mut r = $cache.get_mut(unsafe { &(*n).as_mut().k }); + match r { + Some(ref mut ci) => { + let mut next_state = match &ci { + CacheItem::Freq(n, _) => { + $gf.append_n(*n); + CacheItem::GhostFreq(*n) + } + CacheItem::Rec(n, _) => { + $gr.append_n(*n); + CacheItem::GhostRec(*n) + } + _ => { + // Impossible state! + unreachable!(); + } + }; + // Now change the state. + mem::swap(*ci, &mut next_state); + } + None => { + // Impossible state! + unreachable!(); + } + } + } // end while + }}; +} + +macro_rules! evict_to_len { + ( + $cache:expr, + $ll:expr, + $to_ll:expr, + $size:expr, + $txid:expr + ) => {{ + debug_assert!($ll.len() >= $size); + + while $ll.len() > $size { + let n = $ll.pop(); + debug_assert!(!n.is_null()); + let mut r = $cache.get_mut(unsafe { &(*n).as_mut().k }); + unsafe { + // Set the item's evict txid. + (*n).as_mut().txid = $txid; + } + match r { + Some(ref mut ci) => { + let mut next_state = match &ci { + CacheItem::Freq(llp, _v) => { + debug_assert!(*llp == n); + // No need to extract, already popped! + // $ll.extract(*llp); + $to_ll.append_n(*llp); + CacheItem::GhostFreq(*llp) + } + CacheItem::Rec(llp, _v) => { + debug_assert!(*llp == n); + // No need to extract, already popped! + // $ll.extract(*llp); + $to_ll.append_n(*llp); + CacheItem::GhostRec(*llp) + } + _ => { + // Impossible state! + unreachable!(); + } + }; + // Now change the state. + mem::swap(*ci, &mut next_state); + } + None => { + // Impossible state! + unreachable!(); + } + } + } + }}; +} + +macro_rules! evict_to_haunted_len { + ( + $cache:expr, + $ll:expr, + $to_ll:expr, + $size:expr, + $txid:expr + ) => {{ + debug_assert!($ll.len() >= $size); + + while $ll.len() > $size { + let n = $ll.pop(); + debug_assert!(!n.is_null()); + $to_ll.append_n(n); + let mut r = $cache.get_mut(unsafe { &(*n).as_mut().k }); + unsafe { + // Set the item's evict txid. + (*n).as_mut().txid = $txid; + } + match r { + Some(ref mut ci) => { + // Now change the state. + let mut next_state = CacheItem::Haunted(n); + mem::swap(*ci, &mut next_state); + } + None => { + // Impossible state! + unreachable!(); + } + }; + } + }}; +} + +impl ARCacheBuilder { + /// Create a new ARCache builder that you can configure before creation. + pub fn new() -> Self { + Self::default() + } + + /// Configure a new ARCache, that derives it's size based on your expected workload. + /// + /// The values are total number of items you want to have in memory, the number + /// of read threads you expect concurrently, the expected average number of cache + /// misses per read operation, and the expected average number of writes or write + /// cache misses per operation. The following formula is assumed: + /// + /// `max + (threads * (max/16))` + /// ` + (threads * avg number of misses per op)` + /// ` + avg number of writes per transaction` + /// + /// The cache may still exceed your provided total, and inaccurate tuning numbers + /// will yield a situation where you may use too-little ram, or too much. This could + /// be to your read misses exceeding your expected amount causing the queues to have + /// more items in them at a time, or your writes are larger than expected. + /// + /// If you set ex_ro_miss to zero, no read thread local cache will be configured, but + /// space will still be reserved for channel communication. + #[must_use] + pub fn set_expected_workload( + self, + total: usize, + threads: usize, + ex_ro_miss: usize, + ex_rw_miss: usize, + read_cache: bool, + ) -> Self { + let total = isize::try_from(total).unwrap(); + let threads = isize::try_from(threads).unwrap(); + let ro_miss = isize::try_from(ex_ro_miss).unwrap(); + let wr_miss = isize::try_from(ex_rw_miss).unwrap(); + let ratio = isize::try_from(READ_THREAD_RATIO).unwrap(); + // I'd like to thank wolfram alpha ... for this magic. + let max = -((ratio * ((ro_miss * threads) + wr_miss - total)) / (ratio + threads)); + let read_max = if read_cache { max / ratio } else { 0 }; + + let max = usize::try_from(max).unwrap(); + let read_max = usize::try_from(read_max).unwrap(); + + ARCacheBuilder { + stats: self.stats, + max: Some(max), + read_max: Some(read_max), + watermark: self.watermark, + } + } + + /// Configure a new ARCache, with a capacity of `max` main cache items and `read_max` + /// Note that due to the way the cache operates, the number of items can and + /// will exceed `max` on a regular basis, so you should consider using `set_expected_workload` + /// and specifying your expected workload parameters to have a better derived + /// cache size. + #[must_use] + pub fn set_size(self, max: usize, read_max: usize) -> Self { + ARCacheBuilder { + stats: self.stats, + max: Some(max), + read_max: Some(read_max), + watermark: self.watermark, + } + } + + /// See [new_size] for more information. This allows manual configuration of the data + /// tracking watermark. To disable this, set to 0. If watermark is greater than + /// max, it will be clamped to max. + #[must_use] + pub fn set_watermark(self, watermark: usize) -> Self { + ARCacheBuilder { + stats: self.stats, + max: self.max, + read_max: self.read_max, + watermark: Some(watermark), + } + } + + /// Import read/write hit stats from a previous execution of this cache. + #[must_use] + pub fn set_stats(self, stats: CacheStats) -> Self { + ARCacheBuilder { + stats: Some(stats), + max: self.max, + read_max: self.read_max, + watermark: self.watermark, + } + } + + /// Consume this builder, returning a cache if successful. If configured parameters are + /// missing or incorrect, a None will be returned. + pub fn build(self) -> Option> + where + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + { + let ARCacheBuilder { + stats, + max, + read_max, + watermark, + } = self; + + let (max, read_max) = max.zip(read_max)?; + + let mut stats = stats.unwrap_or(CacheStats { + reader_hits: 0, + reader_tlocal_hits: 0, + reader_tlocal_includes: 0, + reader_includes: 0, + write_hits: 0, + write_includes: 0, + write_modifies: 0, + shared_max: 0, + freq: 0, + recent: 0, + freq_evicts: 0, + recent_evicts: 0, + p_weight: 0, + all_seen_keys: 0, + }); + + // Reset some values to 0 because else it doesn't really max sense ... + stats.shared_max = 0; + stats.freq = 0; + stats.recent = 0; + stats.all_seen_keys = 0; + stats.p_weight = 0; + + // Ensure that p isn't too large. Could happen if the cache size was reduced from + // a previous stats invocation. + // + // NOTE: I decided not to port this over, because it may cause issues in early cache start + // up when the lists are empty. + // stats.p_weight = stats.p_weight.clamp(0, max); + + let watermark = watermark.unwrap_or(if max < 128 { 0 } else { (max / 20) * 16 }); + let watermark = watermark.clamp(0, max); + + let (stat_tx, stat_rx) = unbounded(); + + // The hit queue is reasonably cheap, so we can let this grow a bit. + /* + let chan_size = max / 20; + let chan_size = if chan_size < 16 { 16 } else { chan_size }; + let chan_size = chan_size.clamp(0, 128); + */ + let chan_size = 64; + let hit_queue = Arc::new(ArrayQueue::new(chan_size)); + + // this can oversize and take a lot of time to drain and manage, so we keep this bounded. + // let chan_size = chan_size.clamp(0, 64); + let chan_size = 32; + let inc_queue = Arc::new(ArrayQueue::new(chan_size)); + + let shared = RwLock::new(ArcShared { + max, + read_max, + stat_tx, + hit_queue: hit_queue.clone(), + inc_queue: inc_queue.clone(), + watermark, + }); + let inner = Mutex::new(ArcInner { + // We use p from the former stats. + p: 0, + freq: LL::new(), + rec: LL::new(), + ghost_freq: LL::new(), + ghost_rec: LL::new(), + haunted: LL::new(), + stat_rx, + hit_queue, + inc_queue, + min_txid: 0, + }); + + Some(ARCache { + cache: HashMap::new(), + shared, + inner, + stats: CowCell::new(stats), + above_watermark: AtomicBool::new(true), + }) + } +} + +impl< + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > ARCache +{ + /// Use ARCacheBuilder instead + #[deprecated(since = "0.2.20", note = "please use`ARCacheBuilder` instead")] + pub fn new( + total: usize, + threads: usize, + ex_ro_miss: usize, + ex_rw_miss: usize, + read_cache: bool, + ) -> Self { + ARCacheBuilder::default() + .set_expected_workload(total, threads, ex_ro_miss, ex_rw_miss, read_cache) + .build() + .expect("Invaled cache parameters!") + } + + /// Use ARCacheBuilder instead + #[deprecated(since = "0.2.20", note = "please use`ARCacheBuilder` instead")] + pub fn new_size(max: usize, read_max: usize) -> Self { + ARCacheBuilder::default() + .set_size(max, read_max) + .build() + .expect("Invaled cache parameters!") + } + + /// Use ARCacheBuilder instead + #[deprecated(since = "0.2.20", note = "please use`ARCacheBuilder` instead")] + pub fn new_size_watermark(max: usize, read_max: usize, watermark: usize) -> Self { + ARCacheBuilder::default() + .set_size(max, read_max) + .set_watermark(watermark) + .build() + .expect("Invaled cache parameters!") + } + + /// Begin a read operation on the cache. This reader has a thread-local cache for items + /// that are localled included via `insert`, and can communicate back to the main cache + /// to safely include items. + pub fn read(&self) -> ARCacheReadTxn { + let rshared = self.shared.read(); + let tlocal = if rshared.read_max > 0 { + Some(ReadCache { + set: Map::new(), + read_size: rshared.read_max, + tlru: LL::new(), + }) + } else { + None + }; + let above_watermark = self.above_watermark.load(Ordering::Relaxed); + ARCacheReadTxn { + caller: self, + cache: self.cache.read(), + tlocal, + stat_tx: rshared.stat_tx.clone(), + hit_queue: rshared.hit_queue.clone(), + inc_queue: rshared.inc_queue.clone(), + above_watermark, + hits: 0, + tlocal_hits: 0, + tlocal_includes: 0, + reader_includes: 0, + } + } + + /// Begin a write operation on the cache. This writer has a thread-local store + /// for all items that have been included or dirtied in the transactions, items + /// may be removed from this cache (ie deleted, invalidated). + pub fn write(&self) -> ARCacheWriteTxn { + let above_watermark = self.above_watermark.load(Ordering::Relaxed); + ARCacheWriteTxn { + caller: self, + cache: self.cache.write(), + tlocal: Map::new(), + hit: UnsafeCell::new(Vec::new()), + clear: UnsafeCell::new(false), + above_watermark, + } + } + + /// View the statistics for this cache. These values are a snapshot of a point in + /// time and may not be accurate at "this exact moment". + pub fn view_stats(&self) -> CowCellReadTxn { + self.stats.read() + } + + fn try_write(&self) -> Option> { + self.cache.try_write().map(|cache| { + let above_watermark = self.above_watermark.load(Ordering::Relaxed); + ARCacheWriteTxn { + caller: self, + cache, + tlocal: Map::new(), + hit: UnsafeCell::new(Vec::new()), + clear: UnsafeCell::new(false), + above_watermark, + } + }) + } + + fn try_quiesce(&self) { + if let Some(wr_txn) = self.try_write() { + wr_txn.commit() + }; + } + + fn calc_p_freq(ghost_rec_len: usize, ghost_freq_len: usize, p: &mut usize) { + let delta = if ghost_rec_len > ghost_freq_len { + ghost_rec_len / ghost_freq_len + } else { + 1 + }; + if delta < *p { + *p -= delta + } else { + *p = 0 + } + } + + fn calc_p_rec(cap: usize, ghost_rec_len: usize, ghost_freq_len: usize, p: &mut usize) { + let delta = if ghost_freq_len > ghost_rec_len { + ghost_freq_len / ghost_rec_len + } else { + 1 + }; + if delta <= cap - *p { + *p += delta + } else { + *p = cap + } + } + + fn drain_tlocal_inc<'a>( + &'a self, + cache: &mut HashMapWriteTxn<'a, K, CacheItem>, + inner: &mut ArcInner, + shared: &ArcShared, + stats: &mut CacheStats, + tlocal: Map>, + commit_txid: u64, + ) { + // drain tlocal into the main cache. + tlocal.into_iter().for_each(|(k, tcio)| { + let r = cache.get_mut(&k); + match (r, tcio) { + (None, ThreadCacheItem::Present(tci, clean, size)) => { + assert!(clean); + let llp = inner.rec.append_k(CacheItemInner { + k: k.clone(), + txid: commit_txid, + size, + }); + stats.write_includes += 1; + cache.insert(k, CacheItem::Rec(llp, tci)); + } + (None, ThreadCacheItem::Removed(clean)) => { + assert!(clean); + // Mark this as haunted + let llp = inner.haunted.append_k(CacheItemInner { + k: k.clone(), + txid: commit_txid, + size: 1, + }); + cache.insert(k, CacheItem::Haunted(llp)); + } + (Some(ref mut ci), ThreadCacheItem::Removed(clean)) => { + assert!(clean); + // From whatever set we were in, pop and move to haunted. + let mut next_state = match ci { + CacheItem::Freq(llp, _v) => { + // println!("tlocal {:?} Freq -> Freq", k); + inner.freq.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + inner.haunted.append_n(*llp); + CacheItem::Haunted(*llp) + } + CacheItem::Rec(llp, _v) => { + // println!("tlocal {:?} Rec -> Freq", k); + // Remove the node and put it into freq. + inner.rec.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + inner.haunted.append_n(*llp); + CacheItem::Haunted(*llp) + } + CacheItem::GhostFreq(llp) => { + // println!("tlocal {:?} GhostFreq -> Freq", k); + inner.ghost_freq.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + inner.haunted.append_n(*llp); + CacheItem::Haunted(*llp) + } + CacheItem::GhostRec(llp) => { + // println!("tlocal {:?} GhostRec -> Rec", k); + inner.ghost_rec.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + inner.haunted.append_n(*llp); + CacheItem::Haunted(*llp) + } + CacheItem::Haunted(llp) => { + // println!("tlocal {:?} Haunted -> Rec", k); + unsafe { (**llp).as_mut().txid = commit_txid }; + CacheItem::Haunted(*llp) + } + }; + // Now change the state. + mem::swap(*ci, &mut next_state); + } + // TODO: https://github.com/rust-lang/rust/issues/68354 will stabilise + // in 1.44 so we can prevent a need for a clone. + (Some(ref mut ci), ThreadCacheItem::Present(ref tci, clean, size)) => { + assert!(clean); + // * as we include each item, what state was it in before? + // It's in the cache - what action must we take? + let mut next_state = match ci { + CacheItem::Freq(llp, _v) => { + stats.write_modifies += 1; + // println!("tlocal {:?} Freq -> Freq", k); + inner.freq.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + unsafe { (**llp).as_mut().size = size }; + // Move the list item to it's head. + inner.freq.append_n(*llp); + // Update v. + CacheItem::Freq(*llp, (*tci).clone()) + } + CacheItem::Rec(llp, _v) => { + stats.write_modifies += 1; + // println!("tlocal {:?} Rec -> Freq", k); + // Remove the node and put it into freq. + inner.rec.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + unsafe { (**llp).as_mut().size = size }; + inner.freq.append_n(*llp); + CacheItem::Freq(*llp, (*tci).clone()) + } + CacheItem::GhostFreq(llp) => { + stats.write_includes += 1; + // println!("tlocal {:?} GhostFreq -> Freq", k); + // Ajdust p + Self::calc_p_freq( + inner.ghost_rec.len(), + inner.ghost_freq.len(), + &mut inner.p, + ); + inner.ghost_freq.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + unsafe { (**llp).as_mut().size = size }; + inner.freq.append_n(*llp); + CacheItem::Freq(*llp, (*tci).clone()) + } + CacheItem::GhostRec(llp) => { + stats.write_includes += 1; + // println!("tlocal {:?} GhostRec -> Rec", k); + // Ajdust p + Self::calc_p_rec( + shared.max, + inner.ghost_rec.len(), + inner.ghost_freq.len(), + &mut inner.p, + ); + inner.ghost_rec.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + unsafe { (**llp).as_mut().size = size }; + inner.rec.append_n(*llp); + CacheItem::Rec(*llp, (*tci).clone()) + } + CacheItem::Haunted(llp) => { + stats.write_includes += 1; + // println!("tlocal {:?} Haunted -> Rec", k); + inner.haunted.extract(*llp); + unsafe { (**llp).as_mut().txid = commit_txid }; + unsafe { (**llp).as_mut().size = size }; + inner.rec.append_n(*llp); + CacheItem::Rec(*llp, (*tci).clone()) + } + }; + // Now change the state. + mem::swap(*ci, &mut next_state); + } + } + }); + } + + fn drain_hit_rx<'a>( + &'a self, + cache: &mut HashMapWriteTxn<'a, K, CacheItem>, + inner: &mut ArcInner, + commit_ts: Instant, + ) { + // * for each item + // while let Ok(ce) = inner.rx.try_recv() { + while let Some(ce) = inner.hit_queue.pop() { + let CacheHitEvent { t, k_hash } = ce; + if let Some(ref mut ci_slots) = unsafe { cache.get_slot_mut(k_hash) } { + for ref mut ci in ci_slots.iter_mut() { + let mut next_state = match &ci.v { + CacheItem::Freq(llp, v) => { + // println!("rxhit {:?} Freq -> Freq", k); + inner.freq.touch(*llp); + CacheItem::Freq(*llp, v.clone()) + } + CacheItem::Rec(llp, v) => { + // println!("rxhit {:?} Rec -> Freq", k); + inner.rec.extract(*llp); + inner.freq.append_n(*llp); + CacheItem::Freq(*llp, v.clone()) + } + // While we can't add this from nothing, we can + // at least keep it in the ghost sets. + CacheItem::GhostFreq(llp) => { + // println!("rxhit {:?} GhostFreq -> GhostFreq", k); + inner.ghost_freq.touch(*llp); + CacheItem::GhostFreq(*llp) + } + CacheItem::GhostRec(llp) => { + // println!("rxhit {:?} GhostRec -> GhostRec", k); + inner.ghost_rec.touch(*llp); + CacheItem::GhostRec(*llp) + } + CacheItem::Haunted(llp) => { + // println!("rxhit {:?} Haunted -> Haunted", k); + // We can't do anything about this ... + CacheItem::Haunted(*llp) + } + }; + mem::swap(&mut (*ci).v, &mut next_state); + } // for each item in the bucket. + } + // Do nothing, it must have been evicted. + + // Stop processing the queue, we are up to "now". + if t >= commit_ts { + break; + } + } + } + + fn drain_inc_rx<'a>( + &'a self, + cache: &mut HashMapWriteTxn<'a, K, CacheItem>, + inner: &mut ArcInner, + shared: &ArcShared, + commit_ts: Instant, + ) { + while let Some(ce) = inner.inc_queue.pop() { + // Update if it was inc + let CacheIncludeEvent { + t, + k, + v: iv, + txid, + size, + } = ce; + let mut r = cache.get_mut(&k); + match r { + Some(ref mut ci) => { + let mut next_state = match &ci { + CacheItem::Freq(llp, _v) => { + if unsafe { (**llp).as_ref().txid >= txid } || inner.min_txid > txid { + // println!("rxinc {:?} Freq -> Freq (touch only)", k); + // Our cache already has a newer value, keep it. + inner.freq.touch(*llp); + None + } else { + // println!("rxinc {:?} Freq -> Freq (update)", k); + // The value is newer, update. + inner.freq.extract(*llp); + unsafe { (**llp).as_mut().txid = txid }; + unsafe { (**llp).as_mut().size = size }; + inner.freq.append_n(*llp); + Some(CacheItem::Freq(*llp, iv)) + } + } + CacheItem::Rec(llp, v) => { + inner.rec.extract(*llp); + if unsafe { (**llp).as_ref().txid >= txid } || inner.min_txid > txid { + // println!("rxinc {:?} Rec -> Freq (touch only)", k); + inner.freq.append_n(*llp); + Some(CacheItem::Freq(*llp, v.clone())) + } else { + // println!("rxinc {:?} Rec -> Freq (update)", k); + unsafe { (**llp).as_mut().txid = txid }; + unsafe { (**llp).as_mut().size = size }; + inner.freq.append_n(*llp); + Some(CacheItem::Freq(*llp, iv)) + } + } + CacheItem::GhostFreq(llp) => { + // Adjust p + Self::calc_p_freq( + inner.ghost_rec.len(), + inner.ghost_freq.len(), + &mut inner.p, + ); + if unsafe { (**llp).as_ref().txid > txid } || inner.min_txid > txid { + // println!("rxinc {:?} GhostFreq -> GhostFreq", k); + // The cache version is newer, this is just a hit. + inner.ghost_freq.touch(*llp); + None + } else { + // This item is newer, so we can include it. + // println!("rxinc {:?} GhostFreq -> Rec", k); + inner.ghost_freq.extract(*llp); + unsafe { (**llp).as_mut().txid = txid }; + unsafe { (**llp).as_mut().size = size }; + inner.freq.append_n(*llp); + Some(CacheItem::Freq(*llp, iv)) + } + } + CacheItem::GhostRec(llp) => { + // Adjust p + Self::calc_p_rec( + shared.max, + inner.ghost_rec.len(), + inner.ghost_freq.len(), + &mut inner.p, + ); + if unsafe { (**llp).as_ref().txid > txid } || inner.min_txid > txid { + // println!("rxinc {:?} GhostRec -> GhostRec", k); + inner.ghost_rec.touch(*llp); + None + } else { + // println!("rxinc {:?} GhostRec -> Rec", k); + inner.ghost_rec.extract(*llp); + unsafe { (**llp).as_mut().txid = txid }; + unsafe { (**llp).as_mut().size = size }; + inner.rec.append_n(*llp); + Some(CacheItem::Rec(*llp, iv)) + } + } + CacheItem::Haunted(llp) => { + if unsafe { (**llp).as_ref().txid > txid } || inner.min_txid > txid { + // println!("rxinc {:?} Haunted -> Haunted", k); + None + } else { + // println!("rxinc {:?} Haunted -> Rec", k); + inner.haunted.extract(*llp); + unsafe { (**llp).as_mut().txid = txid }; + unsafe { (**llp).as_mut().size = size }; + inner.rec.append_n(*llp); + Some(CacheItem::Rec(*llp, iv)) + } + } + }; + if let Some(ref mut next_state) = next_state { + mem::swap(*ci, next_state); + } + } + None => { + // It's not present - include it! + // println!("rxinc {:?} None -> Rec", k); + if txid >= inner.min_txid { + let llp = inner.rec.append_k(CacheItemInner { + k: k.clone(), + txid, + size, + }); + cache.insert(k, CacheItem::Rec(llp, iv)); + } + } + }; + + // Stop processing the queue, we are up to "now". + if t >= commit_ts { + break; + } + } + } + + fn drain_tlocal_hits<'a>( + &'a self, + cache: &mut HashMapWriteTxn<'a, K, CacheItem>, + inner: &mut ArcInner, + // shared: &ArcShared, + commit_txid: u64, + hit: Vec, + ) { + // Stats updated by caller + hit.into_iter().for_each(|k_hash| { + // * everything hit must be in main cache now, so bring these + // all to the relevant item heads. + // * Why do this last? Because the write is the "latest" we want all the fresh + // written items in the cache over the "read" hits, it gives us some aprox + // of time ordering, but not perfect. + + // Find the item in the cache. + // * based on it's type, promote it in the correct list, or move it. + // How does this prevent incorrect promotion from rec to freq? txid? + // println!("Checking Hit ... {:?}", k); + let mut r = unsafe { cache.get_slot_mut(k_hash) }; + match r { + Some(ref mut ci_slots) => { + for ref mut ci in ci_slots.iter_mut() { + // This differs from above - we skip if we don't touch anything + // that was added in this txn. This is to prevent double touching + // anything that was included in a write. + let mut next_state = match &ci.v { + CacheItem::Freq(llp, v) => { + if unsafe { (**llp).as_ref().txid != commit_txid } { + // println!("hit {:?} Freq -> Freq", k); + inner.freq.touch(*llp); + Some(CacheItem::Freq(*llp, v.clone())) + } else { + None + } + } + CacheItem::Rec(llp, v) => { + if unsafe { (**llp).as_ref().txid != commit_txid } { + // println!("hit {:?} Rec -> Freq", k); + inner.rec.extract(*llp); + inner.freq.append_n(*llp); + Some(CacheItem::Freq(*llp, v.clone())) + } else { + None + } + } + _ => { + // Ignore hits on items that may have been cleared. + None + } + }; + // Now change the state. + if let Some(ref mut next_state) = next_state { + mem::swap(&mut (*ci).v, next_state); + } + } // for each ci in slots + } + None => { + // Impossible state! + unreachable!(); + } + } + }); + } + + fn drain_stat_rx<'a>( + &'a self, + inner: &mut ArcInner, + stats: &mut CacheStats, + commit_ts: Instant, + ) { + while let Ok(ce) = inner.stat_rx.try_recv() { + let ReaderStatEvent { + t, + hits, + tlocal_hits, + tlocal_includes, + reader_includes, + } = ce; + + stats.reader_tlocal_hits += tlocal_hits; + stats.reader_tlocal_includes += tlocal_includes; + stats.reader_hits += hits; + stats.reader_includes += reader_includes; + + // Stop processing the queue, we are up to "now". + if t >= commit_ts { + break; + } + } + } + + #[allow(clippy::cognitive_complexity)] + fn evict<'a>( + &'a self, + cache: &mut HashMapWriteTxn<'a, K, CacheItem>, + inner: &mut ArcInner, + shared: &ArcShared, + stats: &mut CacheStats, + commit_txid: u64, + ) { + debug_assert!(inner.p <= shared.max); + // Convince the compiler copying is okay. + let p = inner.p; + stats.p_weight = p; + + if inner.rec.len() + inner.freq.len() > shared.max { + // println!("Checking cache evict"); + /* + println!( + "from -> rec {:?}, freq {:?}", + inner.rec.len(), + inner.freq.len() + ); + */ + let delta = (inner.rec.len() + inner.freq.len()) - shared.max; + // We have overflowed by delta. As we are not "evicting as we go" we have to work out + // what we should have evicted up to now. + // + // keep removing from rec until == p OR delta == 0, and if delta remains, then remove from freq. + + let rec_to_len = if inner.p == 0 { + // println!("p == 0 => {:?}", inner.rec.len()); + debug_assert!(delta <= inner.rec.len()); + // We are fully weight to freq, so only remove in rec. + inner.rec.len() - delta + } else if inner.rec.len() > inner.p { + // There is a partial weighting, how much do we need to move? + let rec_delta = inner.rec.len() - inner.p; + if rec_delta > delta { + /* + println!( + "p ({:?}) <= rec ({:?}), rec_delta ({:?}) > delta ({:?})", + inner.p, + inner.rec.len(), + rec_delta, + delta + ); + */ + // We will have removed enough through delta alone in rec. + inner.rec.len() - delta + } else { + /* + println!( + "p ({:?}) <= rec ({:?}), rec_delta ({:?}) <= delta ({:?})", + inner.p, + inner.rec.len(), + rec_delta, + delta + ); + */ + // Remove the full delta, and excess will be removed from freq. + inner.rec.len() - rec_delta + } + } else { + // rec is already below p, therefore we must need to remove in freq, and + // we need to consider how much is in rec. + // println!("p ({:?}) > rec ({:?})", inner.p, inner.rec.len()); + inner.rec.len() + }; + + // Now we can get the expected sizes; + debug_assert!(shared.max >= rec_to_len); + let freq_to_len = shared.max - rec_to_len; + // println!("move to -> rec {:?}, freq {:?}", rec_to_len, freq_to_len); + debug_assert!(freq_to_len + rec_to_len <= shared.max); + + stats.freq_evicts += inner.freq.len() - freq_to_len; + stats.recent_evicts += inner.rec.len() - rec_to_len; + + evict_to_len!( + cache, + inner.rec, + &mut inner.ghost_rec, + rec_to_len, + commit_txid + ); + evict_to_len!( + cache, + inner.freq, + &mut inner.ghost_freq, + freq_to_len, + commit_txid + ); + + // Finally, do an evict of the ghost sets if they are too long - these are weighted + // inverse to the above sets. Note the freq to len in ghost rec, and rec to len in + // ghost freq! + if inner.ghost_rec.len() > (shared.max - p) { + evict_to_haunted_len!( + cache, + inner.ghost_rec, + &mut inner.haunted, + freq_to_len, + commit_txid + ); + } + + if inner.ghost_freq.len() > p { + evict_to_haunted_len!( + cache, + inner.ghost_freq, + &mut inner.haunted, + rec_to_len, + commit_txid + ); + } + } + } + + #[allow(clippy::unnecessary_mut_passed)] + fn commit<'a>( + &'a self, + mut cache: HashMapWriteTxn<'a, K, CacheItem>, + tlocal: Map>, + hit: Vec, + clear: bool, + init_above_watermark: bool, + ) { + // What is the time? + let commit_ts = Instant::now(); + let commit_txid = cache.get_txid(); + // Copy p + init cache sizes for adjustment. + let mut inner = self.inner.lock(); + let shared = self.shared.read(); + let mut stat_guard = self.stats.write(); + let stats = stat_guard.get_mut(); + + // Did we request to be cleared? If so, we move everything to a ghost set + // that was live. + // + // we also set the min_txid watermark which prevents any inclusion of + // any item that existed before this point in time. + if clear { + // Set the watermark of this txn. + inner.min_txid = commit_txid; + + // Indicate that we evicted all to ghost/freq + stats.freq_evicts += inner.freq.len(); + stats.recent_evicts += inner.rec.len(); + + // Move everything active into ghost sets. + drain_ll_to_ghost!( + &mut cache, + inner.freq, + inner.ghost_freq, + inner.ghost_rec, + commit_txid + ); + drain_ll_to_ghost!( + &mut cache, + inner.rec, + inner.ghost_freq, + inner.ghost_rec, + commit_txid + ); + } + + // Why is it okay to drain the rx/tlocal and create the cache in a temporary + // oversize? Because these values in the queue/tlocal are already in memory + // and we are moving them to the cache, we are not actually using any more + // memory (well, not significantly more). By adding everything, then evicting + // we also get better and more accurate hit patterns over the cache based on what + // was used. This gives us an advantage over other cache types - we can see + // patterns based on temporal usage that other caches can't, at the expense that + // it may take some moments for that cache pattern to sync to the main thread. + + // stats.write_inc_or_mod += tlocal.len(); + + self.drain_tlocal_inc( + &mut cache, + inner.deref_mut(), + shared.deref(), + stats, + tlocal, + commit_txid, + ); + + // drain rx until empty or time >= time. + self.drain_inc_rx(&mut cache, inner.deref_mut(), shared.deref(), commit_ts); + + self.drain_hit_rx(&mut cache, inner.deref_mut(), commit_ts); + + // drain the tlocal hits into the main cache. + stats.write_hits += hit.len(); + self.drain_tlocal_hits(&mut cache, inner.deref_mut(), commit_txid, hit); + + // now clean the space for each of the primary caches, evicting into the ghost sets. + // * It's possible that both caches are now over-sized if rx was empty + // but wr inc many items. + // * p has possibly changed width, causing a balance shift + // * and ghost items have been included changing ghost list sizes. + // so we need to do a clean up/balance of all the list lengths. + self.evict( + &mut cache, + inner.deref_mut(), + shared.deref(), + stats, + commit_txid, + ); + + self.drain_stat_rx(inner.deref_mut(), stats, commit_ts); + + stats.shared_max = shared.max; + stats.freq = inner.freq.len(); + stats.recent = inner.rec.len(); + stats.all_seen_keys = cache.len(); + + // Indicate if we are at/above watermark, so that read/writers begin to indicate their + // hit events so we can start to setup/order our arc sets correctly. + // + // If we drop below this again, they'll go back to just insert/remove content only mode. + if init_above_watermark { + if (inner.freq.len() + inner.rec.len()) < shared.watermark { + self.above_watermark.store(false, Ordering::Relaxed); + } + } else if (inner.freq.len() + inner.rec.len()) >= shared.watermark { + self.above_watermark.store(true, Ordering::Relaxed); + } + + // Commit the stats + stat_guard.commit(); + // commit on the wr txn. + cache.commit(); + // done! + // eprintln!("quiesce took - {:?}", commit_ts.elapsed()); + } +} + +impl< + 'a, + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > ARCacheWriteTxn<'a, K, V> +{ + /// Commit the changes of this writer, making them globally visible. This causes + /// all items written to this thread's local store to become visible in the main + /// cache. + /// + /// To rollback (abort) and operation, just do not call commit (consider std::mem::drop + /// on the write transaction) + pub fn commit(self) { + self.caller.commit( + self.cache, + self.tlocal, + self.hit.into_inner(), + self.clear.into_inner(), + self.above_watermark, + ) + } + + /// Clear all items of the cache. This operation does not take effect until you commit. + /// After calling "clear", you may then include new items which will be stored thread + /// locally until you commit. + pub fn clear(&mut self) { + // Mark that we have been requested to clear the cache. + unsafe { + let clear_ptr = self.clear.get(); + *clear_ptr = true; + } + // Dump the hit log. + unsafe { + let hit_ptr = self.hit.get(); + (*hit_ptr).clear(); + } + // Dump the thread local state. + self.tlocal.clear(); + // From this point any get will miss on the main cache. + // Inserts are accepted. + } + + /// Attempt to retieve a k-v pair from the cache. If it is present in the main cache OR + /// the thread local cache, a `Some` is returned, else you will recieve a `None`. On a + /// `None`, you must then consult the external data source that this structure is acting + /// as a cache for. + pub fn get<'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq + Ord, + { + let k_hash: u64 = self.cache.prehash(k); + + let r: Option<&V> = if let Some(tci) = self.tlocal.get(k) { + match tci { + ThreadCacheItem::Present(v, _clean, _size) => { + let v = v as *const _; + unsafe { Some(&(*v)) } + } + ThreadCacheItem::Removed(_clean) => { + return None; + } + } + } else { + // If we have been requested to clear, the main cache is "empty" + // but we can't do that until a commit, so we just flag it and avoid. + let is_cleared = unsafe { + let clear_ptr = self.clear.get(); + *clear_ptr + }; + if !is_cleared { + if let Some(v) = self.cache.get_prehashed(k, k_hash) { + (*v).to_vref() + } else { + None + } + } else { + None + } + }; + // How do we track this was a hit? + // Remember, we don't track misses - they are *implied* by the fact they'll trigger + // an inclusion from the external system. Subsequent, any further re-hit on an + // included value WILL be tracked, allowing arc to adjust appropriately. + if self.above_watermark && r.is_some() { + unsafe { + let hit_ptr = self.hit.get(); + (*hit_ptr).push(k_hash); + } + } + r + } + + /// Determine if this cache contains the following key. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq + Ord, + { + self.get(k).is_some() + } + + /// Add a value to the cache. This may be because you have had a cache miss and + /// now wish to include in the thread local storage, or because you have written + /// a new value and want it to be submitted for caching. This item is marked as + /// clean, IE you have synced it to whatever associated store exists. + pub fn insert(&mut self, k: K, v: V) { + self.tlocal.insert(k, ThreadCacheItem::Present(v, true, 1)); + } + + /// Insert an item to the cache, with an associated weight/size factor. See also [insert] + pub fn insert_sized(&mut self, k: K, v: V, size: usize) { + self.tlocal + .insert(k, ThreadCacheItem::Present(v, true, size)); + } + + /// Remove this value from the thread local cache IE mask from from being + /// returned until this thread performs an insert. This item is marked as clean + /// IE you have synced it to whatever associated store exists. + pub fn remove(&mut self, k: K) { + self.tlocal.insert(k, ThreadCacheItem::Removed(true)); + } + + /// Add a value to the cache. This may be because you have had a cache miss and + /// now wish to include in the thread local storage, or because you have written + /// a new value and want it to be submitted for caching. This item is marked as + /// dirty, because you have *not* synced it. You MUST call iter_mut_mark_clean before calling + /// `commit` on this transaction, or a panic will occur. + pub fn insert_dirty(&mut self, k: K, v: V) { + self.tlocal.insert(k, ThreadCacheItem::Present(v, false, 1)); + } + + /// Insert a dirty item to the cache, with an associated weight/size factor. See also [insert_dirty] + pub fn insert_dirty_sized(&mut self, k: K, v: V, size: usize) { + self.tlocal + .insert(k, ThreadCacheItem::Present(v, false, size)); + } + + /// Remove this value from the thread local cache IE mask from from being + /// returned until this thread performs an insert. This item is marked as + /// dirty, because you have *not* synced it. You MUST call iter_mut_mark_clean before calling + /// `commit` on this transaction, or a panic will occur. + pub fn remove_dirty(&mut self, k: K) { + self.tlocal.insert(k, ThreadCacheItem::Removed(false)); + } + + /// Determines if dirty elements exist in this cache or not. + pub fn is_dirty(&self) -> bool { + self.iter_dirty().take(1).next().is_some() + } + + /// Yields an iterator over all values that are currently dirty. As the iterator + /// progresses, items will NOT be marked clean. This allows you to examine + /// any currently dirty items in the cache. + pub fn iter_dirty(&self) -> impl Iterator)> { + self.tlocal + .iter() + .filter(|(_k, v)| match v { + ThreadCacheItem::Present(_v, c, _size) => !c, + ThreadCacheItem::Removed(c) => !c, + }) + .map(|(k, v)| { + // Get the data. + let data = match v { + ThreadCacheItem::Present(v, _c, _size) => Some(v), + ThreadCacheItem::Removed(_c) => None, + }; + (k, data) + }) + } + + /// Yields a mutable iterator over all values that are currently dirty. As the iterator + /// progresses, items will NOT be marked clean. This allows you to modify and + /// change any currently dirty items as required. + pub fn iter_mut_dirty(&mut self) -> impl Iterator)> { + self.tlocal + .iter_mut() + .filter(|(_k, v)| match v { + ThreadCacheItem::Present(_v, c, _size) => !c, + ThreadCacheItem::Removed(c) => !c, + }) + .map(|(k, v)| { + // Get the data. + let data = match v { + ThreadCacheItem::Present(v, _c, _size) => Some(v), + ThreadCacheItem::Removed(_c) => None, + }; + (k, data) + }) + } + + /// Yields an iterator over all values that are currently dirty. As the iterator + /// progresses, items will be marked clean. This is where you should sync dirty + /// cache content to your associated store. The iterator is K, Option, where + /// the Option indicates if the item has been remove (None) or is updated (Some). + pub fn iter_mut_mark_clean(&mut self) -> impl Iterator)> { + self.tlocal + .iter_mut() + .filter(|(_k, v)| match v { + ThreadCacheItem::Present(_v, c, _size) => !c, + ThreadCacheItem::Removed(c) => !c, + }) + .map(|(k, v)| { + // Mark it clean. + match v { + ThreadCacheItem::Present(_v, c, _size) => *c = true, + ThreadCacheItem::Removed(c) => *c = true, + } + // Get the data. + let data = match v { + ThreadCacheItem::Present(v, _c, _size) => Some(v), + ThreadCacheItem::Removed(_c) => None, + }; + (k, data) + }) + } + + #[cfg(test)] + pub(crate) fn iter_rec(&self) -> impl Iterator { + self.cache.values().filter_map(|ci| match &ci { + CacheItem::Rec(lln, _) => unsafe { + let cii = &*((**lln).k.as_ptr()); + Some(&cii.k) + }, + _ => None, + }) + } + + #[cfg(test)] + pub(crate) fn iter_ghost_rec(&self) -> impl Iterator { + self.cache.values().filter_map(|ci| match &ci { + CacheItem::GhostRec(lln) => unsafe { + let cii = &*((**lln).k.as_ptr()); + Some(&cii.k) + }, + _ => None, + }) + } + + #[cfg(test)] + pub(crate) fn iter_ghost_freq(&self) -> impl Iterator { + self.cache.values().filter_map(|ci| match &ci { + CacheItem::GhostFreq(lln) => unsafe { + let cii = &*((**lln).k.as_ptr()); + Some(&cii.k) + }, + _ => None, + }) + } + + #[cfg(test)] + pub(crate) fn peek_hit(&self) -> &[u64] { + let hit_ptr = self.hit.get(); + unsafe { &(*hit_ptr) } + } + + #[cfg(test)] + pub(crate) fn peek_cache<'b, Q: ?Sized>(&'a self, k: &'b Q) -> CacheState + where + K: Borrow, + Q: Hash + Eq + Ord, + { + if let Some(v) = self.cache.get(k) { + (*v).to_state() + } else { + CacheState::None + } + } + + #[cfg(test)] + pub(crate) fn peek_stat(&self) -> CStat { + let inner = self.caller.inner.lock(); + let shared = self.caller.shared.read(); + CStat { + max: shared.max, + cache: self.cache.len(), + tlocal: self.tlocal.len(), + freq: inner.freq.len(), + rec: inner.rec.len(), + ghost_freq: inner.ghost_freq.len(), + ghost_rec: inner.ghost_rec.len(), + haunted: inner.haunted.len(), + p: inner.p, + } + } + + // get_mut + // If it's in tlocal, return that as get_mut + // if it's in the cache, clone to tlocal, then get_mut to tlock + // if not, return none. + + // to_snapshot +} + +impl< + 'a, + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > ARCacheReadTxn<'a, K, V> +{ + /// Attempt to retieve a k-v pair from the cache. If it is present in the main cache OR + /// the thread local cache, a `Some` is returned, else you will recieve a `None`. On a + /// `None`, you must then consult the external data source that this structure is acting + /// as a cache for. + pub fn get<'b, Q: ?Sized>(&'b mut self, k: &'b Q) -> Option<&'b V> + where + K: Borrow, + Q: Hash + Eq + Ord, + { + let k_hash: u64 = self.cache.prehash(k); + + let r: Option<&V> = self + .tlocal + .as_ref() + .and_then(|cache| { + cache.set.get(k).map(|v| unsafe { + // Indicate a hit on the tlocal cache. + #[allow(mutable_transmutes)] + let tlocal_hits = std::mem::transmute::<&usize, &mut usize>(&self.tlocal_hits); + *tlocal_hits += 1; + + if self.above_watermark { + let _ = self.hit_queue.push(CacheHitEvent { + t: Instant::now(), + k_hash, + }); + } + let v = &(**v).as_ref().v as *const _; + // This discards the lifetime and repins it to &'b. + &(*v) + }) + }) + .or_else(|| { + self.cache.get_prehashed(k, k_hash).and_then(|v| { + (*v).to_vref().map(|vin| unsafe { + // Indicate a hit on the main cache. + #[allow(mutable_transmutes)] + let hits = std::mem::transmute::<&usize, &mut usize>(&self.hits); + *hits += 1; + + if self.above_watermark { + let _ = self.hit_queue.push(CacheHitEvent { + t: Instant::now(), + k_hash, + }); + } + + let vin = vin as *const _; + &(*vin) + }) + }) + }); + + r + } + + /// Determine if this cache contains the following key. + pub fn contains_key<'b, Q: ?Sized>(&mut self, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq + Ord, + { + self.get(k).is_some() + } + + /// Insert an item to the cache, with an associated weight/size factor. See also [insert] + pub fn insert_sized(&mut self, k: K, v: V, size: usize) { + let mut v = v; + // Send a copy forward through time and space. + // let _ = self.tx.try_send( + self.reader_includes += 1; + let _ = self.inc_queue.push(CacheIncludeEvent { + t: Instant::now(), + k: k.clone(), + v: v.clone(), + txid: self.cache.get_txid(), + size, + }); + + // We have a cache, so lets update it. + if let Some(ref mut cache) = self.tlocal { + self.tlocal_includes += 1; + let n = if cache.tlru.len() >= cache.read_size { + let n = cache.tlru.pop(); + // swap the old_key/old_val out + let mut k_clone = k.clone(); + unsafe { + mem::swap(&mut k_clone, &mut (*n).as_mut().k); + mem::swap(&mut v, &mut (*n).as_mut().v); + } + // remove old K from the tree: + cache.set.remove(&k_clone); + n + } else { + // Just add it! + cache.tlru.append_k(ReadCacheItem { + k: k.clone(), + v, + size, + }) + }; + let r = cache.set.insert(k, n); + // There should never be a previous value. + assert!(r.is_none()); + } + } + + /// Add a value to the cache. This may be because you have had a cache miss and + /// now wish to include in the thread local storage. + /// + /// Note that is invalid to insert an item who's key already exists in this thread local cache, + /// and this is asserted IE will panic if you attempt this. It is also invalid for you to insert + /// a value that does not match the source-of-truth state, IE inserting a different + /// value than another thread may percieve. This is a *read* thread, so you should only be adding + /// values that are relevant to this read transaction and this point in time. If you do not + /// heed this warning, you may alter the fabric of time and space and have some interesting + /// distortions in your data over time. + pub fn insert(&mut self, k: K, v: V) { + self.insert_sized(k, v, 1) + } +} + +impl< + 'a, + K: Hash + Eq + Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Debug + Sync + Send + 'static, + > Drop for ARCacheReadTxn<'a, K, V> +{ + fn drop(&mut self) { + let _ = self.stat_tx.send(ReaderStatEvent { + t: Instant::now(), + hits: self.hits, + tlocal_hits: self.tlocal_hits, + tlocal_includes: self.tlocal_includes, + reader_includes: self.reader_includes, + }); + self.caller.try_quiesce(); + } +} + +#[cfg(test)] +mod tests { + use crate::arcache::ARCache as Arc; + use crate::arcache::ARCacheBuilder; + use crate::arcache::CStat; + use crate::arcache::CacheState; + + #[test] + fn test_cache_arc_basic() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + let mut wr_txn = arc.write(); + + assert!(wr_txn.get(&1) == None); + assert!(wr_txn.peek_hit().len() == 0); + wr_txn.insert(1, 1); + assert!(wr_txn.get(&1) == Some(&1)); + assert!(wr_txn.peek_hit().len() == 1); + + wr_txn.commit(); + + // Now we start the second txn, and see if it's in there. + let wr_txn = arc.write(); + assert!(wr_txn.peek_cache(&1) == CacheState::Rec); + assert!(wr_txn.get(&1) == Some(&1)); + assert!(wr_txn.peek_hit().len() == 1); + wr_txn.commit(); + // And now check it's moved to Freq due to the extra + let wr_txn = arc.write(); + assert!(wr_txn.peek_cache(&1) == CacheState::Freq); + println!("{:?}", wr_txn.peek_stat()); + } + + #[test] + fn test_cache_evict() { + println!("== 1"); + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + let mut wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 0, + tlocal: 0, + freq: 0, + rec: 0, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + // In the first txn we insert 4 items. + wr_txn.insert(1, 1); + wr_txn.insert(2, 2); + wr_txn.insert(3, 3); + wr_txn.insert(4, 4); + + assert!( + CStat { + max: 4, + cache: 0, + tlocal: 4, + freq: 0, + rec: 0, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + wr_txn.commit(); + + // Now we start the second txn, and check the stats. + println!("== 2"); + let wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 4, + tlocal: 0, + freq: 0, + rec: 4, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + // Now touch two items, this promote to the freq set. + // Remember, a double hit doesn't weight any more than 1 hit. + assert!(wr_txn.get(&1) == Some(&1)); + assert!(wr_txn.get(&1) == Some(&1)); + assert!(wr_txn.get(&2) == Some(&2)); + + wr_txn.commit(); + + // Now we start the third txn, and check the stats. + println!("== 3"); + let mut wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 4, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + // Add one more item, this will trigger an evict. + wr_txn.insert(5, 5); + wr_txn.commit(); + + // Now we start the fourth txn, and check the stats. + println!("== 4"); + let wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 5, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 1, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + // And assert what's in the sets to be sure of what went where. + // 🚨 Can no longer peek these with hashmap backing as the keys may + // be evicted out-of-order, but the stats are correct! + + // Now touch the two recent items to bring them also to freq + + let rec_set: Vec = wr_txn.iter_rec().take(2).copied().collect(); + assert!(wr_txn.get(&rec_set[0]) == Some(&rec_set[0])); + assert!(wr_txn.get(&rec_set[1]) == Some(&rec_set[1])); + + wr_txn.commit(); + + // Now we start the fifth txn, and check the stats. + println!("== 5"); + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 5, + tlocal: 0, + freq: 4, + rec: 0, + ghost_freq: 0, + ghost_rec: 1, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + // And assert what's in the sets to be sure of what went where. + // 🚨 Can no longer peek these with hashmap backing as the keys may + // be evicted out-of-order, but the stats are correct! + + // Now touch the one item that's in ghost rec - this will trigger + // an evict from ghost freq + let grec: usize = wr_txn.iter_ghost_rec().take(1).copied().next().unwrap(); + wr_txn.insert(grec, grec); + assert!(wr_txn.get(&grec) == Some(&grec)); + // When we add 3, we are basically issuing a demand that the rec set should be + // allowed to grow as we had a potential cache miss here. + wr_txn.commit(); + + // Now we start the sixth txn, and check the stats. + println!("== 6"); + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 5, + tlocal: 0, + freq: 3, + rec: 1, + ghost_freq: 1, + ghost_rec: 0, + haunted: 0, + p: 1 + } == wr_txn.peek_stat() + ); + // And assert what's in the sets to be sure of what went where. + // 🚨 Can no longer peek these with hashmap backing as the keys may + // be evicted out-of-order, but the stats are correct! + assert!(wr_txn.peek_cache(&grec) == CacheState::Rec); + + // Right, seventh txn - we show how a cache scan doesn't cause p shifting or evict. + // tl;dr - attempt to include a bunch in a scan, and it will be ignored as p is low, + // and any miss on rec won't shift p unless it's in the ghost rec. + wr_txn.insert(10, 10); + wr_txn.insert(11, 11); + wr_txn.insert(12, 12); + wr_txn.commit(); + + println!("== 7"); + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 3, + rec: 1, + ghost_freq: 1, + ghost_rec: 3, + haunted: 0, + p: 1 + } == wr_txn.peek_stat() + ); + // 🚨 Can no longer peek these with hashmap backing as the keys may + // be evicted out-of-order, but the stats are correct! + + // Eight txn - now that we had a demand for items before, we re-demand them - this will trigger + // a shift in p, causing some more to be in the rec cache. + let grec_set: Vec = wr_txn.iter_ghost_rec().take(3).copied().collect(); + + grec_set.iter().for_each(|i| wr_txn.insert(*i, *i)); + wr_txn.commit(); + + println!("== 8"); + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 0, + rec: 4, + ghost_freq: 4, + ghost_rec: 0, + haunted: 0, + p: 4 + } == wr_txn.peek_stat() + ); + + grec_set + .iter() + .for_each(|i| assert!(wr_txn.peek_cache(i) == CacheState::Rec)); + + // Now lets go back the other way - we want freq items to come back. + let gfreq_set: Vec = wr_txn.iter_ghost_freq().take(4).copied().collect(); + + gfreq_set.iter().for_each(|i| wr_txn.insert(*i, *i)); + wr_txn.commit(); + + println!("== 9"); + let wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 4, + rec: 0, + ghost_freq: 0, + ghost_rec: 4, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + // 🚨 Can no longer peek these with hashmap backing as the keys may + // be evicted out-of-order, but the stats are correct! + gfreq_set + .iter() + .for_each(|i| assert!(wr_txn.peek_cache(i) == CacheState::Freq)); + + // And done! + wr_txn.commit(); + // See what stats did + let stats = arc.view_stats(); + println!("{:?}", *stats); + } + + #[test] + fn test_cache_concurrent_basic() { + // Now we want to check some basic interactions of read and write together. + + // Setup the cache. + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + // start a rd + { + let mut rd_txn = arc.read(); + // add items to the rd + rd_txn.insert(1, 1); + rd_txn.insert(2, 2); + rd_txn.insert(3, 3); + rd_txn.insert(4, 4); + // Should be in the tlocal + // assert!(rd_txn.get(&1).is_some()); + // assert!(rd_txn.get(&2).is_some()); + // assert!(rd_txn.get(&3).is_some()); + // assert!(rd_txn.get(&4).is_some()); + // end the rd + } + arc.try_quiesce(); + // What state is the cache now in? + println!("== 2"); + let wr_txn = arc.write(); + println!("{:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 4, + tlocal: 0, + freq: 0, + rec: 4, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + assert!(wr_txn.peek_cache(&1) == CacheState::Rec); + assert!(wr_txn.peek_cache(&2) == CacheState::Rec); + assert!(wr_txn.peek_cache(&3) == CacheState::Rec); + assert!(wr_txn.peek_cache(&4) == CacheState::Rec); + // Magic! Without a single write op we included items! + // Lets have the read touch two items, and then add two new. + // This will trigger evict on 1/2 + { + let mut rd_txn = arc.read(); + // add items to the rd + assert!(rd_txn.get(&3) == Some(&3)); + assert!(rd_txn.get(&4) == Some(&4)); + rd_txn.insert(5, 5); + rd_txn.insert(6, 6); + // end the rd + } + // Now commit and check the state. + wr_txn.commit(); + println!("== 3"); + let wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 6, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 2, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + assert!(wr_txn.peek_cache(&1) == CacheState::GhostRec); + assert!(wr_txn.peek_cache(&2) == CacheState::GhostRec); + assert!(wr_txn.peek_cache(&3) == CacheState::Freq); + assert!(wr_txn.peek_cache(&4) == CacheState::Freq); + assert!(wr_txn.peek_cache(&5) == CacheState::Rec); + assert!(wr_txn.peek_cache(&6) == CacheState::Rec); + + // Now trigger hits on 1/2 which will cause a shift in P. + { + let mut rd_txn = arc.read(); + // add items to the rd + rd_txn.insert(1, 1); + rd_txn.insert(2, 2); + // end the rd + } + + wr_txn.commit(); + println!("== 4"); + let wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 6, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 2, + haunted: 0, + p: 2 + } == wr_txn.peek_stat() + ); + assert!(wr_txn.peek_cache(&1) == CacheState::Rec); + assert!(wr_txn.peek_cache(&2) == CacheState::Rec); + assert!(wr_txn.peek_cache(&3) == CacheState::Freq); + assert!(wr_txn.peek_cache(&4) == CacheState::Freq); + assert!(wr_txn.peek_cache(&5) == CacheState::GhostRec); + assert!(wr_txn.peek_cache(&6) == CacheState::GhostRec); + // See what stats did + let stats = arc.view_stats(); + println!("stats 1: {:?}", *stats); + assert!(stats.reader_hits == 2); + assert!(stats.reader_includes == 8); + assert!(stats.reader_tlocal_includes == 8); + assert!(stats.reader_tlocal_hits == 0); + } + + // Test edge cases that are horrifying and could destroy peoples lives + // and sanity. + #[test] + fn test_cache_concurrent_cursed_1() { + // Case 1 - It's possible for a read transaction to last for a long time, + // and then have a cache include, which may cause an attempt to include + // an outdated value into the cache. To handle this the haunted set exists + // so that all keys and their eviction ids are always tracked for all of time + // to ensure that we never incorrectly include a value that may have been updated + // more recently. + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + + // Start a wr + let mut wr_txn = arc.write(); + // Start a rd + let mut rd_txn = arc.read(); + // Add the value 1,1 via the wr. + wr_txn.insert(1, 1); + + // assert 1 is not in rd. + assert!(rd_txn.get(&1) == None); + + // Commit wr + wr_txn.commit(); + // Even after the commit, it's not in rd. + assert!(rd_txn.get(&1) == None); + // begin wr + let mut wr_txn = arc.write(); + // We now need to flood the cache, to cause ghost rec eviction. + wr_txn.insert(10, 1); + wr_txn.insert(11, 1); + wr_txn.insert(12, 1); + wr_txn.insert(13, 1); + wr_txn.insert(14, 1); + wr_txn.insert(15, 1); + wr_txn.insert(16, 1); + wr_txn.insert(17, 1); + // commit wr + wr_txn.commit(); + + // begin wr + let wr_txn = arc.write(); + // assert that 1 is haunted. + assert!(wr_txn.peek_cache(&1) == CacheState::Haunted); + // assert 1 is not in rd. + assert!(rd_txn.get(&1) == None); + // now that 1 is hanuted, in rd attempt to insert 1, X + rd_txn.insert(1, 100); + // commit wr + wr_txn.commit(); + + // start wr + let wr_txn = arc.write(); + // assert that 1 is still haunted. + assert!(wr_txn.peek_cache(&1) == CacheState::Haunted); + // assert that 1, x is in rd. + assert!(rd_txn.get(&1) == Some(&100)); + // done! + } + + #[test] + fn test_cache_clear() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + + // Start a wr + let mut wr_txn = arc.write(); + // Add a bunch of values, and touch some twice. + wr_txn.insert(10, 10); + wr_txn.insert(11, 11); + wr_txn.insert(12, 12); + wr_txn.insert(13, 13); + wr_txn.insert(14, 14); + wr_txn.insert(15, 15); + wr_txn.insert(16, 16); + wr_txn.insert(17, 17); + wr_txn.commit(); + // Begin a new write. + let wr_txn = arc.write(); + + // Touch two values that are in the rec set. + let rec_set: Vec = wr_txn.iter_rec().take(2).copied().collect(); + println!("{:?}", rec_set); + assert!(wr_txn.get(&rec_set[0]) == Some(&rec_set[0])); + assert!(wr_txn.get(&rec_set[1]) == Some(&rec_set[1])); + + // commit wr + wr_txn.commit(); + // Begin a new write. + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 4, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + // Clear + wr_txn.clear(); + // Now commit + wr_txn.commit(); + // Now check their states. + let wr_txn = arc.write(); + // See what stats did + println!("stat -> {:?}", wr_txn.peek_stat()); + // stat -> CStat { max: 4, cache: 8, tlocal: 0, freq: 0, rec: 0, ghost_freq: 2, ghost_rec: 6, haunted: 0, p: 0 } + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 0, + rec: 0, + ghost_freq: 2, + ghost_rec: 6, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + let stats = arc.view_stats(); + println!("{:?}", *stats); + } + + #[test] + fn test_cache_clear_rollback() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + + // Start a wr + let mut wr_txn = arc.write(); + // Add a bunch of values, and touch some twice. + wr_txn.insert(10, 10); + wr_txn.insert(11, 11); + wr_txn.insert(12, 12); + wr_txn.insert(13, 13); + wr_txn.insert(14, 14); + wr_txn.insert(15, 15); + wr_txn.insert(16, 16); + wr_txn.insert(17, 17); + wr_txn.commit(); + // Begin a new write. + let wr_txn = arc.write(); + let rec_set: Vec = wr_txn.iter_rec().take(2).copied().collect(); + println!("{:?}", rec_set); + let r = wr_txn.get(&rec_set[0]); + println!("{:?}", r); + assert!(r == Some(&rec_set[0])); + assert!(wr_txn.get(&rec_set[1]) == Some(&rec_set[1])); + + // commit wr + wr_txn.commit(); + // Begin a new write. + let mut wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 4, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + // Clear + wr_txn.clear(); + // Now abort the clear - should do nothing! + drop(wr_txn); + // Check the states, should not have changed + let wr_txn = arc.write(); + println!("stat -> {:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 8, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 4, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + } + + #[test] + fn test_cache_clear_cursed() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + // Setup for the test + // -- + let mut wr_txn = arc.write(); + wr_txn.insert(10, 1); + wr_txn.commit(); + // -- + let wr_txn = arc.write(); + assert!(wr_txn.peek_cache(&10) == CacheState::Rec); + wr_txn.commit(); + // -- + // Okay, now the test starts. First, we begin a read + let mut rd_txn = arc.read(); + // Then while that read exists, we open a write, and conduct + // a cache clear. + let mut wr_txn = arc.write(); + wr_txn.clear(); + // Commit the clear write. + wr_txn.commit(); + + // Now on the read, we perform a touch of an item, and we include + // something that was not yet in the cache. + assert!(rd_txn.get(&10) == Some(&1)); + rd_txn.insert(11, 1); + // Complete the read + std::mem::drop(rd_txn); + // Perform a cache quiesce + arc.try_quiesce(); + // -- + + // Assert that the items that we provided were NOT included, and are + // in the correct states. + let wr_txn = arc.write(); + assert!(wr_txn.peek_cache(&10) == CacheState::GhostRec); + println!("--> {:?}", wr_txn.peek_cache(&11)); + assert!(wr_txn.peek_cache(&11) == CacheState::None); + } + + #[test] + fn test_cache_dirty_write() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 4) + .build() + .expect("Invaled cache parameters!"); + let mut wr_txn = arc.write(); + wr_txn.insert_dirty(10, 1); + wr_txn.iter_mut_mark_clean().for_each(|(_k, _v)| {}); + wr_txn.commit(); + } + + #[test] + fn test_cache_read_no_tlocal() { + // Check a cache with no read local thread capacity + // Setup the cache. + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 0) + .build() + .expect("Invaled cache parameters!"); + // start a rd + { + let mut rd_txn = arc.read(); + // add items to the rd + rd_txn.insert(1, 1); + rd_txn.insert(2, 2); + rd_txn.insert(3, 3); + rd_txn.insert(4, 4); + // end the rd + // Everything should be missing frm the tlocal. + assert!(rd_txn.get(&1).is_none()); + assert!(rd_txn.get(&2).is_none()); + assert!(rd_txn.get(&3).is_none()); + assert!(rd_txn.get(&4).is_none()); + } + arc.try_quiesce(); + // What state is the cache now in? + println!("== 2"); + let wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 4, + tlocal: 0, + freq: 0, + rec: 4, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + assert!(wr_txn.peek_cache(&1) == CacheState::Rec); + assert!(wr_txn.peek_cache(&2) == CacheState::Rec); + assert!(wr_txn.peek_cache(&3) == CacheState::Rec); + assert!(wr_txn.peek_cache(&4) == CacheState::Rec); + let stats = arc.view_stats(); + println!("stats 1: {:?}", *stats); + assert!(stats.reader_includes == 4); + assert!(stats.reader_tlocal_includes == 0); + assert!(stats.reader_tlocal_hits == 0); + } + + #[derive(Clone, Debug)] + struct Weighted { + _i: u64, + } + + #[test] + fn test_cache_weighted() { + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 0) + .build() + .expect("Invaled cache parameters!"); + let mut wr_txn = arc.write(); + + assert!( + CStat { + max: 4, + cache: 0, + tlocal: 0, + freq: 0, + rec: 0, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + // In the first txn we insert 2 weight 2 items. + wr_txn.insert_sized(1, Weighted { _i: 1 }, 2); + wr_txn.insert_sized(2, Weighted { _i: 2 }, 2); + + assert!( + CStat { + max: 4, + cache: 0, + tlocal: 2, + freq: 0, + rec: 0, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + wr_txn.commit(); + + // Now once commited, the proper sizes kick in. + + let wr_txn = arc.write(); + // eprintln!("{:?}", wr_txn.peek_stat()); + assert!( + CStat { + max: 4, + cache: 2, + tlocal: 0, + freq: 0, + rec: 4, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + wr_txn.commit(); + + // Check the numbers move properly. + let wr_txn = arc.write(); + wr_txn.get(&1); + wr_txn.commit(); + + let mut wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 2, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 0, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + + wr_txn.insert_sized(3, Weighted { _i: 3 }, 2); + wr_txn.insert_sized(4, Weighted { _i: 4 }, 2); + wr_txn.commit(); + + // Check the evicts + let wr_txn = arc.write(); + assert!( + CStat { + max: 4, + cache: 4, + tlocal: 0, + freq: 2, + rec: 2, + ghost_freq: 0, + ghost_rec: 4, + haunted: 0, + p: 0 + } == wr_txn.peek_stat() + ); + wr_txn.commit(); + + let stats = arc.view_stats(); + println!("{:?}", *stats); + } + + #[test] + fn test_cache_stats_reload() { + // Make a cache + let arc: Arc = ARCacheBuilder::default() + .set_size(4, 0) + .build() + .expect("Invaled cache parameters!"); + let mut wr_txn = arc.write(); + wr_txn.insert(1, 1); + wr_txn.commit(); + + let stats = arc.view_stats(); + println!("stats 1: {:?}", *stats); + + let arc2: Arc = ARCacheBuilder::default() + .set_size(4, 0) + .set_stats((*stats).clone()) + .build() + .expect("Failed to build"); + + let stats2 = arc2.view_stats(); + println!("stats 2: {:?}", *stats2); + assert!(stats.write_includes == stats2.write_includes); + assert!(stats.write_modifies == stats2.write_modifies); + } +} diff --git a/vendor/concread/src/arcache/traits.rs b/vendor/concread/src/arcache/traits.rs new file mode 100644 index 0000000..0d2f466 --- /dev/null +++ b/vendor/concread/src/arcache/traits.rs @@ -0,0 +1,15 @@ +//! Traits for allowing custom sizing/weights to items in the ARC + +/// A trait that allows custom weighting of items in the arc. +pub trait ArcWeight { + /// Return the weight of this item. This value MAY be dynamic + /// as the cache copies this for it's internal tracking purposes + fn arc_weight(&self) -> usize; +} + +impl ArcWeight for T { + #[inline] + default fn arc_weight(&self) -> usize { + 1 + } +} diff --git a/vendor/concread/src/bptree/asynch.rs b/vendor/concread/src/bptree/asynch.rs new file mode 100644 index 0000000..c82242c --- /dev/null +++ b/vendor/concread/src/bptree/asynch.rs @@ -0,0 +1,280 @@ +//! Async `BptreeMap` - See the documentation for the sync `BptreeMap` + +use crate::internals::lincowcell_async::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; + +include!("impl.rs"); + +impl + BptreeMap +{ + /// Initiate a read transaction for the tree, concurrent to any + /// other readers or writers. + pub async fn read<'x>(&'x self) -> BptreeMapReadTxn<'x, K, V> { + let inner = self.inner.read().await; + BptreeMapReadTxn { inner } + } + + /// Initiate a write transaction for the tree, exclusive to this + /// writer, and concurrently to all existing reads. + pub async fn write<'x>(&'x self) -> BptreeMapWriteTxn<'x, K, V> { + let inner = self.inner.write().await; + BptreeMapWriteTxn { inner } + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + BptreeMapWriteTxn<'a, K, V> +{ + /// Commit the changes from this write transaction. Readers after this point + /// will be able to percieve these changes. + /// + /// To abort (unstage changes), just do not call this function. + pub async fn commit(self) { + self.inner.commit().await; + } +} + +#[cfg(test)] +mod tests { + use super::BptreeMap; + use crate::internals::bptree::node::{assert_released, L_CAPACITY}; + // use rand::prelude::*; + use rand::seq::SliceRandom; + use std::iter::FromIterator; + + #[tokio::test] + async fn test_bptree2_map_basic_write() { + let bptree: BptreeMap = BptreeMap::new(); + { + let mut bpwrite = bptree.write().await; + // We should be able to insert. + bpwrite.insert(0, 0); + bpwrite.insert(1, 1); + assert!(bpwrite.get(&0) == Some(&0)); + assert!(bpwrite.get(&1) == Some(&1)); + bpwrite.insert(2, 2); + bpwrite.commit().await; + // println!("commit"); + } + { + // Do a clear, but roll it back. + let mut bpwrite = bptree.write().await; + bpwrite.clear(); + // DO NOT commit, this triggers the rollback. + // println!("post clear"); + } + { + let bpwrite = bptree.write().await; + assert!(bpwrite.get(&0) == Some(&0)); + assert!(bpwrite.get(&1) == Some(&1)); + // println!("fin write"); + } + std::mem::drop(bptree); + assert_released(); + } + + #[tokio::test] + async fn test_bptree2_map_cursed_get_mut() { + let bptree: BptreeMap = BptreeMap::new(); + { + let mut w = bptree.write().await; + w.insert(0, 0); + w.commit().await; + } + let r1 = bptree.read().await; + { + let mut w = bptree.write().await; + let cursed_zone = w.get_mut(&0).unwrap(); + *cursed_zone = 1; + // Correctly fails to work as it's a second borrow, which isn't + // possible once w.remove occurs + // w.remove(&0); + // *cursed_zone = 2; + w.commit().await; + } + let r2 = bptree.read().await; + assert!(r1.get(&0) == Some(&0)); + assert!(r2.get(&0) == Some(&1)); + + /* + // Correctly fails to compile. PHEW! + let fail = { + let mut w = bptree.write(); + w.get_mut(&0).unwrap() + }; + */ + std::mem::drop(r1); + std::mem::drop(r2); + std::mem::drop(bptree); + assert_released(); + } + + #[tokio::test] + async fn test_bptree2_map_from_iter_1() { + let ins: Vec = (0..(L_CAPACITY << 4)).collect(); + + let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + + { + let w = map.write().await; + assert!(w.verify()); + println!("{:?}", w.tree_density()); + } + // assert!(w.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + std::mem::drop(map); + assert_released(); + } + + #[tokio::test] + async fn test_bptree2_map_from_iter_2() { + let mut rng = rand::thread_rng(); + let mut ins: Vec = (0..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + + { + let w = map.write().await; + assert!(w.verify()); + // w.compact_force(); + assert!(w.verify()); + // assert!(w.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + } + + std::mem::drop(map); + assert_released(); + } + + async fn bptree_map_basic_concurrency(lower: usize, upper: usize) { + // Create a map + let map = BptreeMap::new(); + + // add values + { + let mut w = map.write().await; + w.extend((0..lower).map(|v| (v, v))); + w.commit().await; + } + + // read + let r = map.read().await; + assert!(r.len() == lower); + for i in 0..lower { + assert!(r.contains_key(&i)) + } + + // Check a second write doesn't interfere + { + let mut w = map.write().await; + w.extend((lower..upper).map(|v| (v, v))); + w.commit().await; + } + + assert!(r.len() == lower); + + // But a new write can see + let r2 = map.read().await; + assert!(r2.len() == upper); + for i in 0..upper { + assert!(r2.contains_key(&i)) + } + + // Now drain the tree, and the reader should be unaffected. + { + let mut w = map.write().await; + for i in 0..upper { + assert!(w.remove(&i).is_some()) + } + w.commit().await; + } + + // All consistent! + assert!(r.len() == lower); + assert!(r2.len() == upper); + for i in 0..upper { + assert!(r2.contains_key(&i)) + } + + let r3 = map.read().await; + // println!("{:?}", r3.len()); + assert!(r3.len() == 0); + + std::mem::drop(r); + std::mem::drop(r2); + std::mem::drop(r3); + + std::mem::drop(map); + assert_released(); + } + + #[tokio::test] + async fn test_bptree2_map_acb_order() { + // Need to ensure that txns are dropped in order. + + // Add data, enouugh to cause a split. All data should be *2 + let map = BptreeMap::new(); + // add values + { + let mut w = map.write().await; + w.extend((0..(L_CAPACITY * 2)).map(|v| (v * 2, v * 2))); + w.commit().await; + } + let ro_txn_a = map.read().await; + + // New write, add 1 val + { + let mut w = map.write().await; + w.insert(1, 1); + w.commit().await; + } + + let ro_txn_b = map.read().await; + // ro_txn_b now owns nodes from a + + // New write, update a value + { + let mut w = map.write().await; + w.insert(1, 10001); + w.commit().await; + } + + let ro_txn_c = map.read().await; + // ro_txn_c + // Drop ro_txn_b + assert!(ro_txn_b.verify()); + std::mem::drop(ro_txn_b); + // Are both still valid? + assert!(ro_txn_a.verify()); + assert!(ro_txn_c.verify()); + // Drop remaining + std::mem::drop(ro_txn_a); + std::mem::drop(ro_txn_c); + std::mem::drop(map); + assert_released(); + } + + #[tokio::test] + async fn test_bptree2_map_weird_txn_behaviour() { + let map: BptreeMap = BptreeMap::new(); + + let mut wr = map.write().await; + let rd = map.read().await; + + wr.insert(1, 1); + assert!(rd.get(&1) == None); + wr.commit().await; + assert!(rd.get(&1) == None); + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] + async fn test_bptree2_map_basic_concurrency_small() { + bptree_map_basic_concurrency(100, 200).await + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] + async fn test_bptree2_map_basic_concurrency_large() { + bptree_map_basic_concurrency(10_000, 20_000).await + } +} diff --git a/vendor/concread/src/bptree/impl.rs b/vendor/concread/src/bptree/impl.rs new file mode 100644 index 0000000..d4faa3e --- /dev/null +++ b/vendor/concread/src/bptree/impl.rs @@ -0,0 +1,411 @@ +use crate::internals::bptree::cursor::CursorReadOps; +use crate::internals::bptree::cursor::{CursorRead, CursorWrite, SuperBlock}; +use crate::internals::bptree::iter::{Iter, KeyIter, ValueIter}; +use crate::internals::lincowcell::LinCowCellCapable; +use std::borrow::Borrow; +use std::fmt::Debug; +use std::iter::FromIterator; + +/// A concurrently readable map based on a modified B+Tree structure. +/// +/// This structure can be used in locations where you would otherwise us +/// `RwLock` or `Mutex`. +/// +/// Generally, the concurrent HashMap is a better choice unless you require +/// ordered key storage. +/// +/// This is a concurrently readable structure, meaning it has transactional +/// properties. Writers are serialised (one after the other), and readers +/// can exist in parallel with stable views of the structure at a point +/// in time. +/// +/// This is achieved through the use of COW or MVCC. As a write occurs +/// subsets of the tree are cloned into the writer thread and then commited +/// later. This may cause memory usage to increase in exchange for a gain +/// in concurrent behaviour. +/// +/// Transactions can be rolled-back (aborted) without penalty by dropping +/// the `BptreeMapWriteTxn` without calling `commit()`. +pub struct BptreeMap +where + K: Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCell, CursorRead, CursorWrite>, +} + +unsafe impl Send + for BptreeMap +{ +} +unsafe impl Sync + for BptreeMap +{ +} + +/// An active read transaction over a `BptreeMap`. The data in this tree +/// is guaranteed to not change and will remain consistent for the life +/// of this transaction. +pub struct BptreeMapReadTxn<'a, K, V> +where + K: Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite>, +} + +/// An active write transaction for a `BptreeMap`. The data in this tree +/// may be modified exclusively through this transaction without affecting +/// readers. The write may be rolledback/aborted by dropping this guard +/// without calling `commit()`. Once `commit()` is called, readers will be +/// able to access and percieve changes in new transactions. +pub struct BptreeMapWriteTxn<'a, K, V> +where + K: Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite>, +} + +enum SnapshotType<'a, K, V> +where + K: Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + R(&'a CursorRead), + W(&'a CursorWrite), +} + +/// A point-in-time snapshot of the tree from within a read OR write. This is +/// useful for building other transactional types ontop of this structure, as +/// you need a way to downcast both BptreeMapReadTxn or BptreeMapWriteTxn to +/// a singular reader type for a number of get_inner() style patterns. +/// +/// This snapshot IS safe within the read thread due to the nature of the +/// implementation borrowing the inner tree to prevent mutations within the +/// same thread while the read snapshot is open. +pub struct BptreeMapReadSnapshot<'a, K, V> +where + K: Ord + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: SnapshotType<'a, K, V>, +} + +impl Default + for BptreeMap +{ + fn default() -> Self { + Self::new() + } +} + +impl + BptreeMap +{ + /// Construct a new concurrent tree + pub fn new() -> Self { + // I acknowledge I understand what is required to make this safe. + BptreeMap { + inner: LinCowCell::new(unsafe { SuperBlock::new() }), + } + } + + /// Attempt to create a new write, returns None if another writer + /// already exists. + pub fn try_write(&self) -> Option> { + self.inner + .try_write() + .map(|inner| BptreeMapWriteTxn { inner }) + } +} + +impl + FromIterator<(K, V)> for BptreeMap +{ + fn from_iter>(iter: I) -> Self { + let mut new_sblock = unsafe { SuperBlock::new() }; + let prev = new_sblock.create_reader(); + let mut cursor = new_sblock.create_writer(); + + cursor.extend(iter); + + let _ = new_sblock.pre_commit(cursor, &prev); + + BptreeMap { + inner: LinCowCell::new(new_sblock), + } + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + Extend<(K, V)> for BptreeMapWriteTxn<'a, K, V> +{ + fn extend>(&mut self, iter: I) { + self.inner.as_mut().extend(iter); + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + BptreeMapWriteTxn<'a, K, V> +{ + // == RO methods + + /// Retrieve a value from the tree. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get<'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Ord, + { + self.inner.as_ref().search(k) + } + + /// Assert if a key exists in the tree. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Ord, + { + self.inner.as_ref().contains_key(k) + } + + /// returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + self.inner.as_ref().len() + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.inner.as_ref().len() == 0 + } + + // (adv) range + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + self.inner.as_ref().kv_iter() + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + self.inner.as_ref().v_iter() + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + self.inner.as_ref().k_iter() + } + + // (adv) keys + + // (adv) values + + #[allow(unused)] + pub(crate) fn get_txid(&self) -> u64 { + self.inner.as_ref().get_txid() + } + + // == RW methods + + /// Reset this tree to an empty state. As this is within the transaction this + /// change only takes effect once commited. + pub fn clear(&mut self) { + self.inner.as_mut().clear() + } + + /// Insert or update a value by key. If the value previously existed it is returned + /// as `Some(V)`. If the value did not previously exist this returns `None`. + pub fn insert(&mut self, k: K, v: V) -> Option { + self.inner.as_mut().insert(k, v) + } + + /// Remove a key if it exists in the tree. If the value exists, we return it as `Some(V)`, + /// and if it did not exist, we return `None` + pub fn remove(&mut self, k: &K) -> Option { + self.inner.as_mut().remove(k) + } + + // split_off + /* + pub fn split_off_gte(&mut self, key: &K) -> BptreeMap { + unimplemented!(); + } + */ + + /// Remove all values less than (but not including) key from the map. + pub fn split_off_lt(&mut self, key: &K) { + self.inner.as_mut().split_off_lt(key) + } + + // ADVANCED + // append (join two sets) + + /// Get a mutable reference to a value in the tree. This is correctly, and + /// safely cloned before you attempt to mutate the value, isolating it from + /// other transactions. + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.inner.as_mut().get_mut_ref(key) + } + + // range_mut + + // entry + + // iter_mut + + #[cfg(test)] + pub(crate) fn tree_density(&self) -> (usize, usize) { + self.inner.as_ref().tree_density() + } + + #[cfg(test)] + pub(crate) fn verify(&self) -> bool { + self.inner.as_ref().verify() + } + + /// Create a read-snapshot of the current tree. This does NOT guarantee the tree may + /// not be mutated during the read, so you MUST guarantee that no functions of the + /// write txn are called while this snapshot is active. + pub fn to_snapshot(&'a self) -> BptreeMapReadSnapshot { + BptreeMapReadSnapshot { + inner: SnapshotType::W(self.inner.as_ref()), + } + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + BptreeMapReadTxn<'a, K, V> +{ + /// Retrieve a value from the tree. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get(&'a self, k: &'a Q) -> Option<&'a V> + where + K: Borrow, + Q: Ord, + { + self.inner.as_ref().search(k) + } + + /// Assert if a key exists in the tree. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Ord, + { + self.inner.as_ref().contains_key(k) + } + + /// Returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + self.inner.as_ref().len() + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.inner.as_ref().len() == 0 + } + + // (adv) range + #[allow(unused)] + pub(crate) fn get_txid(&self) -> u64 { + self.inner.as_ref().get_txid() + } + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + self.inner.as_ref().kv_iter() + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + self.inner.as_ref().v_iter() + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + self.inner.as_ref().k_iter() + } + + /// Create a read-snapshot of the current tree. + /// As this is the read variant, it IS safe, and guaranteed the tree will not change. + pub fn to_snapshot(&'a self) -> BptreeMapReadSnapshot { + BptreeMapReadSnapshot { + inner: SnapshotType::R(self.inner.as_ref()), + } + } + + #[cfg(test)] + #[allow(dead_code)] + pub(crate) fn verify(&self) -> bool { + self.inner.as_ref().verify() + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + BptreeMapReadSnapshot<'a, K, V> +{ + /// Retrieve a value from the tree. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get(&'a self, k: &'a Q) -> Option<&'a V> + where + K: Borrow, + Q: Ord, + { + match self.inner { + SnapshotType::R(inner) => inner.search(k), + SnapshotType::W(inner) => inner.search(k), + } + } + + /// Assert if a key exists in the tree. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Ord, + { + match self.inner { + SnapshotType::R(inner) => inner.contains_key(k), + SnapshotType::W(inner) => inner.contains_key(k), + } + } + + /// Returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + match self.inner { + SnapshotType::R(inner) => inner.len(), + SnapshotType::W(inner) => inner.len(), + } + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + // (adv) range + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + match self.inner { + SnapshotType::R(inner) => inner.kv_iter(), + SnapshotType::W(inner) => inner.kv_iter(), + } + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + match self.inner { + SnapshotType::R(inner) => inner.v_iter(), + SnapshotType::W(inner) => inner.v_iter(), + } + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + match self.inner { + SnapshotType::R(inner) => inner.k_iter(), + SnapshotType::W(inner) => inner.k_iter(), + } + } +} diff --git a/vendor/concread/src/bptree/mod.rs b/vendor/concread/src/bptree/mod.rs new file mode 100644 index 0000000..78bec64 --- /dev/null +++ b/vendor/concread/src/bptree/mod.rs @@ -0,0 +1,588 @@ +//! See the documentation for `BptreeMap` + +#[cfg(feature = "asynch")] +pub mod asynch; + +use crate::internals::lincowcell::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; + +include!("impl.rs"); + +impl + BptreeMap +{ + /// Initiate a read transaction for the tree, concurrent to any + /// other readers or writers. + pub fn read(&self) -> BptreeMapReadTxn { + let inner = self.inner.read(); + BptreeMapReadTxn { inner } + } + + /// Initiate a write transaction for the tree, exclusive to this + /// writer, and concurrently to all existing reads. + pub fn write(&self) -> BptreeMapWriteTxn { + let inner = self.inner.write(); + BptreeMapWriteTxn { inner } + } +} + +impl<'a, K: Clone + Ord + Debug + Sync + Send + 'static, V: Clone + Sync + Send + 'static> + BptreeMapWriteTxn<'a, K, V> +{ + /// Commit the changes from this write transaction. Readers after this point + /// will be able to percieve these changes. + /// + /// To abort (unstage changes), just do not call this function. + pub fn commit(self) { + self.inner.commit(); + } +} + +#[cfg(test)] +mod tests { + use super::BptreeMap; + use crate::internals::bptree::node::{assert_released, L_CAPACITY}; + // use rand::prelude::*; + use rand::seq::SliceRandom; + use std::iter::FromIterator; + + #[test] + fn test_bptree2_map_basic_write() { + let bptree: BptreeMap = BptreeMap::new(); + { + let mut bpwrite = bptree.write(); + // We should be able to insert. + bpwrite.insert(0, 0); + bpwrite.insert(1, 1); + assert!(bpwrite.get(&0) == Some(&0)); + assert!(bpwrite.get(&1) == Some(&1)); + bpwrite.insert(2, 2); + bpwrite.commit(); + // println!("commit"); + } + { + // Do a clear, but roll it back. + let mut bpwrite = bptree.write(); + bpwrite.clear(); + // DO NOT commit, this triggers the rollback. + // println!("post clear"); + } + { + let bpwrite = bptree.write(); + assert!(bpwrite.get(&0) == Some(&0)); + assert!(bpwrite.get(&1) == Some(&1)); + // println!("fin write"); + } + std::mem::drop(bptree); + assert_released(); + } + + #[test] + fn test_bptree2_map_cursed_get_mut() { + let bptree: BptreeMap = BptreeMap::new(); + { + let mut w = bptree.write(); + w.insert(0, 0); + w.commit(); + } + let r1 = bptree.read(); + { + let mut w = bptree.write(); + let cursed_zone = w.get_mut(&0).unwrap(); + *cursed_zone = 1; + // Correctly fails to work as it's a second borrow, which isn't + // possible once w.remove occurs + // w.remove(&0); + // *cursed_zone = 2; + w.commit(); + } + let r2 = bptree.read(); + assert!(r1.get(&0) == Some(&0)); + assert!(r2.get(&0) == Some(&1)); + + /* + // Correctly fails to compile. PHEW! + let fail = { + let mut w = bptree.write(); + w.get_mut(&0).unwrap() + }; + */ + std::mem::drop(r1); + std::mem::drop(r2); + std::mem::drop(bptree); + assert_released(); + } + + #[test] + fn test_bptree2_map_from_iter_1() { + let ins: Vec = (0..(L_CAPACITY << 4)).collect(); + + let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + + { + let w = map.write(); + assert!(w.verify()); + println!("{:?}", w.tree_density()); + } + // assert!(w.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + std::mem::drop(map); + assert_released(); + } + + #[test] + fn test_bptree2_map_from_iter_2() { + let mut rng = rand::thread_rng(); + let mut ins: Vec = (0..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let map = BptreeMap::from_iter(ins.into_iter().map(|v| (v, v))); + + { + let w = map.write(); + assert!(w.verify()); + // w.compact_force(); + assert!(w.verify()); + // assert!(w.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + } + + std::mem::drop(map); + assert_released(); + } + + fn bptree_map_basic_concurrency(lower: usize, upper: usize) { + // Create a map + let map = BptreeMap::new(); + + // add values + { + let mut w = map.write(); + w.extend((0..lower).map(|v| (v, v))); + w.commit(); + } + + // read + let r = map.read(); + assert!(r.len() == lower); + for i in 0..lower { + assert!(r.contains_key(&i)) + } + + // Check a second write doesn't interfere + { + let mut w = map.write(); + w.extend((lower..upper).map(|v| (v, v))); + w.commit(); + } + + assert!(r.len() == lower); + + // But a new write can see + let r2 = map.read(); + assert!(r2.len() == upper); + for i in 0..upper { + assert!(r2.contains_key(&i)) + } + + // Now drain the tree, and the reader should be unaffected. + { + let mut w = map.write(); + for i in 0..upper { + assert!(w.remove(&i).is_some()) + } + w.commit(); + } + + // All consistent! + assert!(r.len() == lower); + assert!(r2.len() == upper); + for i in 0..upper { + assert!(r2.contains_key(&i)) + } + + let r3 = map.read(); + // println!("{:?}", r3.len()); + assert!(r3.len() == 0); + + std::mem::drop(r); + std::mem::drop(r2); + std::mem::drop(r3); + + std::mem::drop(map); + assert_released(); + } + + #[test] + fn test_bptree2_map_acb_order() { + // Need to ensure that txns are dropped in order. + + // Add data, enouugh to cause a split. All data should be *2 + let map = BptreeMap::new(); + // add values + { + let mut w = map.write(); + w.extend((0..(L_CAPACITY * 2)).map(|v| (v * 2, v * 2))); + w.commit(); + } + let ro_txn_a = map.read(); + + // New write, add 1 val + { + let mut w = map.write(); + w.insert(1, 1); + w.commit(); + } + + let ro_txn_b = map.read(); + // ro_txn_b now owns nodes from a + + // New write, update a value + { + let mut w = map.write(); + w.insert(1, 10001); + w.commit(); + } + + let ro_txn_c = map.read(); + // ro_txn_c + // Drop ro_txn_b + assert!(ro_txn_b.verify()); + std::mem::drop(ro_txn_b); + // Are both still valid? + assert!(ro_txn_a.verify()); + assert!(ro_txn_c.verify()); + // Drop remaining + std::mem::drop(ro_txn_a); + std::mem::drop(ro_txn_c); + std::mem::drop(map); + assert_released(); + } + + #[test] + fn test_bptree2_map_weird_txn_behaviour() { + let map: BptreeMap = BptreeMap::new(); + + let mut wr = map.write(); + let rd = map.read(); + + wr.insert(1, 1); + assert!(rd.get(&1) == None); + wr.commit(); + assert!(rd.get(&1) == None); + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_bptree2_map_basic_concurrency_small() { + bptree_map_basic_concurrency(100, 200) + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_bptree2_map_basic_concurrency_large() { + bptree_map_basic_concurrency(10_000, 20_000) + } + + /* + #[test] + fn test_bptree2_map_write_compact() { + let mut rng = rand::thread_rng(); + let insa: Vec = (0..(L_CAPACITY << 4)).collect(); + + let map = BptreeMap::from_iter(insa.into_iter().map(|v| (v, v))); + + let mut w = map.write(); + // Created linearly, should not need compact + assert!(w.compact() == false); + assert!(w.verify()); + assert!(w.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + + // Even in reverse, we shouldn't need it ... + let insb: Vec = (0..(L_CAPACITY << 4)).collect(); + let bmap = BptreeMap::from_iter(insb.into_iter().rev().map(|v| (v, v))); + let mut bw = bmap.write(); + assert!(bw.compact() == false); + assert!(bw.verify()); + // Assert the density is "best" + assert!(bw.tree_density() == ((L_CAPACITY << 4), (L_CAPACITY << 4))); + + // Random however, may. + let mut insc: Vec = (0..(L_CAPACITY << 4)).collect(); + insc.shuffle(&mut rng); + let cmap = BptreeMap::from_iter(insc.into_iter().map(|v| (v, v))); + let mut cw = cmap.write(); + let (_n, d1) = cw.tree_density(); + cw.compact_force(); + assert!(cw.verify()); + let (_n, d2) = cw.tree_density(); + assert!(d2 <= d1); + } + */ + + /* + use std::sync::atomic::{AtomicUsize, Ordering}; + use crossbeam_utils::thread::scope; + use rand::Rng; + const MAX_TARGET: usize = 210_000; + + #[test] + fn test_bptree2_map_thread_stress() { + let start = time::now(); + let reader_completions = AtomicUsize::new(0); + // Setup a tree with some initial data. + let map: BptreeMap = BptreeMap::from_iter( + (0..10_000).map(|v| (v, v)) + ); + // now setup the threads. + scope(|scope| { + let mref = ↦ + let rref = &reader_completions; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move || { + println!("Started reader ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let m_read = mref.read(); + proceed = ! m_read.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + for i in v1..r1 { + m_read.get(&i); + } + assert!(m_read.verify()); + rref.fetch_add(1, Ordering::Relaxed); + } + println!("Closing reader ..."); + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move || { + println!("Started writer ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let mut m_write = mref.write(); + proceed = ! m_write.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + let v2 = rng.gen_range(1, 19) * 10_000; + let r2 = v2 + 10_000; + + for i in v1..r1 { + m_write.insert(i, i); + } + for i in v2..r2 { + m_write.remove(&i); + } + m_write.commit(); + } + println!("Closing writer ..."); + }) + }) + .collect(); + + let _complete = scope.spawn(move || { + let mut last_value = 200_000; + while last_value < MAX_TARGET { + let mut m_write = mref.write(); + last_value += 1; + if last_value % 1000 == 0 { + println!("{:?}", last_value); + } + m_write.insert(last_value, last_value); + assert!(m_write.verify()); + m_write.commit(); + } + }); + + }); + let end = time::now(); + print!("BptreeMap MT create :{} reader completions :{}", end - start, reader_completions.load(Ordering::Relaxed)); + // Done! + } + + #[test] + fn test_std_mutex_btreemap_thread_stress() { + use std::collections::BTreeMap; + use std::sync::Mutex; + + let start = time::now(); + let reader_completions = AtomicUsize::new(0); + // Setup a tree with some initial data. + let map: Mutex> = Mutex::new(BTreeMap::from_iter( + (0..10_000).map(|v| (v, v)) + )); + // now setup the threads. + scope(|scope| { + let mref = ↦ + let rref = &reader_completions; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move || { + println!("Started reader ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let m_read = mref.lock().unwrap(); + proceed = ! m_read.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + for i in v1..r1 { + m_read.get(&i); + } + rref.fetch_add(1, Ordering::Relaxed); + } + println!("Closing reader ..."); + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move || { + println!("Started writer ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let mut m_write = mref.lock().unwrap(); + proceed = ! m_write.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + let v2 = rng.gen_range(1, 19) * 10_000; + let r2 = v2 + 10_000; + + for i in v1..r1 { + m_write.insert(i, i); + } + for i in v2..r2 { + m_write.remove(&i); + } + } + println!("Closing writer ..."); + }) + }) + .collect(); + + let _complete = scope.spawn(move || { + let mut last_value = 200_000; + while last_value < MAX_TARGET { + let mut m_write = mref.lock().unwrap(); + last_value += 1; + if last_value % 1000 == 0 { + println!("{:?}", last_value); + } + m_write.insert(last_value, last_value); + } + }); + + }); + let end = time::now(); + print!("Mutex MT create :{} reader completions :{}", end - start, reader_completions.load(Ordering::Relaxed)); + // Done! + } + + #[test] + fn test_std_rwlock_btreemap_thread_stress() { + use std::collections::BTreeMap; + use std::sync::RwLock; + + let start = time::now(); + let reader_completions = AtomicUsize::new(0); + // Setup a tree with some initial data. + let map: RwLock> = RwLock::new(BTreeMap::from_iter( + (0..10_000).map(|v| (v, v)) + )); + // now setup the threads. + scope(|scope| { + let mref = ↦ + let rref = &reader_completions; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move || { + println!("Started reader ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let m_read = mref.read().unwrap(); + proceed = ! m_read.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + for i in v1..r1 { + m_read.get(&i); + } + rref.fetch_add(1, Ordering::Relaxed); + } + println!("Closing reader ..."); + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move || { + println!("Started writer ..."); + let mut rng = rand::thread_rng(); + let mut proceed = true; + while proceed { + let mut m_write = mref.write().unwrap(); + proceed = ! m_write.contains_key(&MAX_TARGET); + // Get a random number. + // Add 10_000 * random + // Remove 10_000 * random + let v1 = rng.gen_range(1, 18) * 10_000; + let r1 = v1 + 10_000; + let v2 = rng.gen_range(1, 19) * 10_000; + let r2 = v2 + 10_000; + + for i in v1..r1 { + m_write.insert(i, i); + } + for i in v2..r2 { + m_write.remove(&i); + } + } + println!("Closing writer ..."); + }) + }) + .collect(); + + let _complete = scope.spawn(move || { + let mut last_value = 200_000; + while last_value < MAX_TARGET { + let mut m_write = mref.write().unwrap(); + last_value += 1; + if last_value % 1000 == 0 { + println!("{:?}", last_value); + } + m_write.insert(last_value, last_value); + } + }); + + }); + let end = time::now(); + print!("RwLock MT create :{} reader completions :{}", end - start, reader_completions.load(Ordering::Relaxed)); + // Done! + } + */ +} diff --git a/vendor/concread/src/cowcell/asynch.rs b/vendor/concread/src/cowcell/asynch.rs new file mode 100644 index 0000000..b8d1233 --- /dev/null +++ b/vendor/concread/src/cowcell/asynch.rs @@ -0,0 +1,311 @@ +//! Async CowCell - A concurrently readable cell with Arc +//! +//! See `CowCell` for more details. + +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; +use tokio::sync::{Mutex, MutexGuard}; + +/// A conncurrently readable async cell. +/// +/// This structure behaves in a similar manner to a `RwLock`. However unlike +/// a `RwLock`, writes and parallel reads can be performed at the same time. This +/// means readers and writers do no block either other. Writers are serialised. +/// +/// To achieve this a form of "copy-on-write" (or for Rust, clone on write) is +/// used. As a write transaction begins, we clone the existing data to a new +/// location that is capable of being mutated. +/// +/// Readers are guaranteed that the content of the `CowCell` will live as long +/// as the read transaction is open, and will be consistent for the duration +/// of the transaction. There can be an "unlimited" number of readers in parallel +/// accessing different generations of data of the `CowCell`. +/// +/// Writers are serialised and are guaranteed they have exclusive write access +/// to the structure. +#[derive(Debug)] +pub struct CowCell { + write: Mutex<()>, + active: Mutex>, +} + +/// A `CowCell` Write Transaction handle. +/// +/// This allows mutation of the content of the `CowCell` without blocking or +/// affecting current readers. +/// +/// Changes are only stored in this structure until you call commit. To abort/ +/// rollback a change, don't call commit and allow the write transaction to +/// be dropped. This causes the `CowCell` to unlock allowing the next writer +/// to proceed. +pub struct CowCellWriteTxn<'a, T: 'a> { + // Hold open the guard, and initiate the copy to here. + work: Option, + read: Arc, + // This way we know who to contact for updating our data .... + caller: &'a CowCell, + _guard: MutexGuard<'a, ()>, +} + +/// A `CowCell` Read Transaction handle. +/// +/// This allows safe reading of the value within the `CowCell`, that allows +/// no mutation of the value, and without blocking writers. +#[derive(Debug)] +pub struct CowCellReadTxn(Arc); + +impl Clone for CowCellReadTxn { + fn clone(&self) -> Self { + CowCellReadTxn(self.0.clone()) + } +} + +impl CowCell +where + T: Clone, +{ + /// Create a new `CowCell` for storing type `T`. `T` must implement `Clone` + /// to enable clone-on-write. + pub fn new(data: T) -> Self { + CowCell { + write: Mutex::new(()), + active: Mutex::new(Arc::new(data)), + } + } + + /// Begin a read transaction, returning a read guard. The content of + /// the read guard is guaranteed to be consistent for the life time of the + /// read - even if writers commit during. + pub async fn read<'x>(&'x self) -> CowCellReadTxn { + let rwguard = self.active.lock().await; + CowCellReadTxn(rwguard.clone()) + // rwguard ends here + } + + /// Begin a write transaction, returning a write guard. The content of the + /// write is only visible to this thread, and is not visible to any reader + /// until `commit()` is called. + pub async fn write<'x>(&'x self) -> CowCellWriteTxn<'x, T> { + /* Take the exclusive write lock first */ + let mguard = self.write.lock().await; + // We delay copying until the first get_mut. + let read = { + let rwguard = self.active.lock().await; + rwguard.clone() + }; + /* Now build the write struct */ + CowCellWriteTxn { + work: None, + read, + caller: self, + _guard: mguard, + } + } + + /// Attempt to create a write transaction. If it fails, and err + /// is returned. On success the `Ok(guard)` is returned. See also + /// `write(&self)` + pub async fn try_write<'x>(&'x self) -> Option> { + /* Take the exclusive write lock first */ + if let Ok(mguard) = self.write.try_lock() { + // We delay copying until the first get_mut. + let read: Arc<_> = { + let rwguard = self.active.lock().await; + rwguard.clone() + }; + /* Now build the write struct */ + Some(CowCellWriteTxn { + work: None, + read, + caller: self, + _guard: mguard, + }) + } else { + None + } + } + + async fn commit(&self, newdata: Option) { + if let Some(nd) = newdata { + let mut rwguard = self.active.lock().await; + let new_inner = Arc::new(nd); + // now over-write the last value in the mutex. + *rwguard = new_inner; + } + // If not some, we do nothing. + // Done + } +} + +impl Deref for CowCellReadTxn { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.0 + } +} + +impl<'a, T> CowCellWriteTxn<'a, T> +where + T: Clone, +{ + /// Access a mutable pointer of the data in the `CowCell`. This data is only + /// visible to the write transaction object in this thread, until you call + /// `commit()`. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + if self.work.is_none() { + let mut data: Option = Some((*self.read).clone()); + std::mem::swap(&mut data, &mut self.work); + // Should be the none we previously had. + debug_assert!(data.is_none()) + } + self.work.as_mut().expect("can not fail") + } + + /// Commit the changes made in this write transactions to the `CowCell`. + /// This will consume the transaction so no further changes can be made + /// after this is called. Not calling this in a block, is equivalent to + /// an abort/rollback of the transaction. + pub async fn commit(self) { + /* Write our data back to the CowCell */ + self.caller.commit(self.work).await; + } +} + +impl<'a, T> Deref for CowCellWriteTxn<'a, T> +where + T: Clone, +{ + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + match &self.work { + Some(v) => v, + None => &(*self.read), + } + } +} + +impl<'a, T> DerefMut for CowCellWriteTxn<'a, T> +where + T: Clone, +{ + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +#[cfg(test)] +mod tests { + use super::CowCell; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + + #[tokio::test] + async fn test_deref_mut() { + let data: i64 = 0; + let cc = CowCell::new(data); + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write().await; + *cc_wrtxn = 1; + cc_wrtxn.commit().await; + } + let cc_rotxn = cc.read().await; + assert_eq!(*cc_rotxn, 1); + } + + #[tokio::test] + async fn test_try_write() { + let data: i64 = 0; + let cc = CowCell::new(data); + /* Take a write txn */ + let cc_wrtxn_a = cc.try_write().await; + assert!(cc_wrtxn_a.is_some()); + /* Because we already hold the writ, the second is guaranteed to fail */ + let cc_wrtxn_a = cc.try_write().await; + assert!(cc_wrtxn_a.is_none()); + } + + #[tokio::test] + async fn test_simple_create() { + let data: i64 = 0; + let cc = CowCell::new(data); + + let cc_rotxn_a = cc.read().await; + assert_eq!(*cc_rotxn_a, 0); + + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write().await; + /* Get the data ... */ + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(*mut_ptr, 0); + *mut_ptr = 1; + assert_eq!(*mut_ptr, 1); + } + assert_eq!(*cc_rotxn_a, 0); + + let cc_rotxn_b = cc.read().await; + assert_eq!(*cc_rotxn_b, 0); + /* The write txn and it's lock is dropped here */ + cc_wrtxn.commit().await; + } + + /* Start a new txn and see it's still good */ + let cc_rotxn_c = cc.read().await; + assert_eq!(*cc_rotxn_c, 1); + assert_eq!(*cc_rotxn_a, 0); + } + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + impl Drop for TestGcWrapper { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + async fn test_gc_operation_thread(cc: Arc>>) { + while GC_COUNT.load(Ordering::Acquire) < 50 { + // thread::sleep(std::time::Duration::from_millis(200)); + { + let mut cc_wrtxn = cc.write().await; + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit().await; + } + } + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] + async fn test_gc_operation() { + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = Arc::new(CowCell::new(data)); + + let _ = tokio::join!( + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + ); + + assert!(GC_COUNT.load(Ordering::Acquire) >= 50); + } +} diff --git a/vendor/concread/src/cowcell/mod.rs b/vendor/concread/src/cowcell/mod.rs new file mode 100644 index 0000000..539e76a --- /dev/null +++ b/vendor/concread/src/cowcell/mod.rs @@ -0,0 +1,401 @@ +//! CowCell - A concurrently readable cell with Arc +//! +//! A CowCell can be used in place of a `RwLock`. Readers are guaranteed that +//! the data will not change during the lifetime of the read. Readers do +//! not block writers, and writers do not block readers. Writers are serialised +//! same as the write in a RwLock. +//! +//! This is the `Arc` collected implementation. `Arc` is slightly slower than `EbrCell` +//! but has better behaviour with very long running read operations, and more +//! accurate memory reclaim behaviour. + +#[cfg(feature = "asynch")] +pub mod asynch; + +use parking_lot::{Mutex, MutexGuard}; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +/// A conncurrently readable cell. +/// +/// This structure behaves in a similar manner to a `RwLock`. However unlike +/// a `RwLock`, writes and parallel reads can be performed at the same time. This +/// means readers and writers do no block either other. Writers are serialised. +/// +/// To achieve this a form of "copy-on-write" (or for Rust, clone on write) is +/// used. As a write transaction begins, we clone the existing data to a new +/// location that is capable of being mutated. +/// +/// Readers are guaranteed that the content of the `CowCell` will live as long +/// as the read transaction is open, and will be consistent for the duration +/// of the transaction. There can be an "unlimited" number of readers in parallel +/// accessing different generations of data of the `CowCell`. +/// +/// Writers are serialised and are guaranteed they have exclusive write access +/// to the structure. +/// +/// # Examples +/// ``` +/// use concread::cowcell::CowCell; +/// +/// let data: i64 = 0; +/// let cowcell = CowCell::new(data); +/// +/// // Begin a read transaction +/// let read_txn = cowcell.read(); +/// assert_eq!(*read_txn, 0); +/// { +/// // Now create a write, and commit it. +/// let mut write_txn = cowcell.write(); +/// *write_txn = 1; +/// // Commit the change +/// write_txn.commit(); +/// } +/// // Show the previous generation still reads '0' +/// assert_eq!(*read_txn, 0); +/// let new_read_txn = cowcell.read(); +/// // And a new read transaction has '1' +/// assert_eq!(*new_read_txn, 1); +/// ``` +#[derive(Debug)] +pub struct CowCell { + write: Mutex<()>, + active: Mutex>, +} + +/// A `CowCell` Write Transaction handle. +/// +/// This allows mutation of the content of the `CowCell` without blocking or +/// affecting current readers. +/// +/// Changes are only stored in this structure until you call commit. To abort/ +/// rollback a change, don't call commit and allow the write transaction to +/// be dropped. This causes the `CowCell` to unlock allowing the next writer +/// to proceed. +pub struct CowCellWriteTxn<'a, T: 'a> { + // Hold open the guard, and initiate the copy to here. + work: Option, + read: Arc, + // This way we know who to contact for updating our data .... + caller: &'a CowCell, + _guard: MutexGuard<'a, ()>, +} + +/// A `CowCell` Read Transaction handle. +/// +/// This allows safe reading of the value within the `CowCell`, that allows +/// no mutation of the value, and without blocking writers. +#[derive(Debug)] +pub struct CowCellReadTxn(Arc); + +impl Clone for CowCellReadTxn { + fn clone(&self) -> Self { + CowCellReadTxn(self.0.clone()) + } +} + +impl CowCell +where + T: Clone, +{ + /// Create a new `CowCell` for storing type `T`. `T` must implement `Clone` + /// to enable clone-on-write. + pub fn new(data: T) -> Self { + CowCell { + write: Mutex::new(()), + active: Mutex::new(Arc::new(data)), + } + } + + /// Begin a read transaction, returning a read guard. The content of + /// the read guard is guaranteed to be consistent for the life time of the + /// read - even if writers commit during. + pub fn read(&self) -> CowCellReadTxn { + let rwguard = self.active.lock(); + CowCellReadTxn(rwguard.clone()) + // rwguard ends here + } + + /// Begin a write transaction, returning a write guard. The content of the + /// write is only visible to this thread, and is not visible to any reader + /// until `commit()` is called. + pub fn write(&self) -> CowCellWriteTxn { + /* Take the exclusive write lock first */ + let mguard = self.write.lock(); + // We delay copying until the first get_mut. + let read = { + let rwguard = self.active.lock(); + rwguard.clone() + }; + /* Now build the write struct */ + CowCellWriteTxn { + work: None, + read, + caller: self, + _guard: mguard, + } + } + + /// Attempt to create a write transaction. If it fails, and err + /// is returned. On success the `Ok(guard)` is returned. See also + /// `write(&self)` + pub fn try_write(&self) -> Option> { + /* Take the exclusive write lock first */ + self.write.try_lock().map(|mguard| { + // We delay copying until the first get_mut. + let read = { + let rwguard = self.active.lock(); + rwguard.clone() + }; + /* Now build the write struct */ + CowCellWriteTxn { + work: None, + read, + caller: self, + _guard: mguard, + } + }) + } + + fn commit(&self, newdata: Option) { + if let Some(nd) = newdata { + let mut rwguard = self.active.lock(); + let new_inner = Arc::new(nd); + // now over-write the last value in the mutex. + *rwguard = new_inner; + } + // If not some, we do nothing. + // Done + } +} + +impl Deref for CowCellReadTxn { + type Target = T; + + #[inline] + fn deref(&self) -> &T { + &self.0 + } +} + +impl<'a, T> CowCellWriteTxn<'a, T> +where + T: Clone, +{ + /// Access a mutable pointer of the data in the `CowCell`. This data is only + /// visible to the write transaction object in this thread, until you call + /// `commit()`. + #[inline(always)] + pub fn get_mut(&mut self) -> &mut T { + if self.work.is_none() { + let mut data: Option = Some((*self.read).clone()); + std::mem::swap(&mut data, &mut self.work); + // Should be the none we previously had. + debug_assert!(data.is_none()) + } + self.work.as_mut().expect("can not fail") + } + + /// Commit the changes made in this write transactions to the `CowCell`. + /// This will consume the transaction so no further changes can be made + /// after this is called. Not calling this in a block, is equivalent to + /// an abort/rollback of the transaction. + pub fn commit(self) { + /* Write our data back to the CowCell */ + self.caller.commit(self.work); + } +} + +impl<'a, T> Deref for CowCellWriteTxn<'a, T> +where + T: Clone, +{ + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + match &self.work { + Some(v) => v, + None => &(*self.read), + } + } +} + +impl<'a, T> DerefMut for CowCellWriteTxn<'a, T> +where + T: Clone, +{ + #[inline(always)] + fn deref_mut(&mut self) -> &mut T { + self.get_mut() + } +} + +#[cfg(test)] +mod tests { + use super::CowCell; + use std::sync::atomic::{AtomicUsize, Ordering}; + + use crossbeam_utils::thread::scope; + + #[test] + fn test_deref_mut() { + let data: i64 = 0; + let cc = CowCell::new(data); + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write(); + *cc_wrtxn = 1; + cc_wrtxn.commit(); + } + let cc_rotxn = cc.read(); + assert_eq!(*cc_rotxn, 1); + } + + #[test] + fn test_try_write() { + let data: i64 = 0; + let cc = CowCell::new(data); + /* Take a write txn */ + let cc_wrtxn_a = cc.try_write(); + assert!(cc_wrtxn_a.is_some()); + /* Because we already hold the writ, the second is guaranteed to fail */ + let cc_wrtxn_a = cc.try_write(); + assert!(cc_wrtxn_a.is_none()); + } + + #[test] + fn test_simple_create() { + let data: i64 = 0; + let cc = CowCell::new(data); + + let cc_rotxn_a = cc.read(); + assert_eq!(*cc_rotxn_a, 0); + + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write(); + /* Get the data ... */ + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(*mut_ptr, 0); + *mut_ptr = 1; + assert_eq!(*mut_ptr, 1); + } + assert_eq!(*cc_rotxn_a, 0); + + let cc_rotxn_b = cc.read(); + assert_eq!(*cc_rotxn_b, 0); + /* The write txn and it's lock is dropped here */ + cc_wrtxn.commit(); + } + + /* Start a new txn and see it's still good */ + let cc_rotxn_c = cc.read(); + assert_eq!(*cc_rotxn_c, 1); + assert_eq!(*cc_rotxn_a, 0); + } + + const MAX_TARGET: i64 = 2000; + + #[test] + #[cfg_attr(miri, ignore)] + fn test_multithread_create() { + let start = time::Instant::now(); + // Create the new cowcell. + let data: i64 = 0; + let cc = CowCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move |_| { + let mut last_value: i64 = 0; + while last_value < MAX_TARGET { + let cc_rotxn = cc_ref.read(); + { + assert!(*cc_rotxn >= last_value); + last_value = *cc_rotxn; + } + } + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + let mut last_value: i64 = 0; + while last_value < MAX_TARGET { + let mut cc_wrtxn = cc_ref.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + assert!(*mut_ptr >= last_value); + last_value = *mut_ptr; + *mut_ptr = *mut_ptr + 1; + } + cc_wrtxn.commit(); + } + }) + }) + .collect(); + }) + .is_ok()); + + let end = time::Instant::now(); + print!("Arc MT create :{:?} ", end - start); + } + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + impl Drop for TestGcWrapper { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + fn test_gc_operation_thread(cc: &CowCell>) { + while GC_COUNT.load(Ordering::Acquire) < 50 { + // thread::sleep(std::time::Duration::from_millis(200)); + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_gc_operation() { + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = CowCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + test_gc_operation_thread(cc_ref); + }) + }) + .collect(); + }) + .is_ok()); + + assert!(GC_COUNT.load(Ordering::Acquire) >= 50); + } +} diff --git a/vendor/concread/src/ebrcell/mod.rs b/vendor/concread/src/ebrcell/mod.rs new file mode 100644 index 0000000..b938987 --- /dev/null +++ b/vendor/concread/src/ebrcell/mod.rs @@ -0,0 +1,524 @@ +//! EbrCell - A concurrently readable cell with Ebr +//! +//! An `EbrCell` can be used in place of a `RwLock`. Readers are guaranteed that +//! the data will not change during the lifetime of the read. Readers do +//! not block writers, and writers do not block readers. Writers are serialised +//! same as the write in a `RwLock`. +//! +//! This is the Ebr collected implementation. +//! Ebr is the crossbeam-epoch based reclaim system for async memory +//! garbage collection. Ebr is faster than `Arc`, +//! but long transactions can cause the memory usage to grow very quickly +//! before a garbage reclaim. This is a space time trade, where you gain +//! performance at the expense of delaying garbage collection. Holding Ebr +//! reads for too long may impact garbage collection of other epoch structures +//! or crossbeam library components. +//! If you need accurate memory reclaim, use the Arc (`CowCell`) implementation. + +use crossbeam_epoch as epoch; +use crossbeam_epoch::{Atomic, Guard, Owned}; +use std::sync::atomic::Ordering::{Acquire, Relaxed, Release}; + +use parking_lot::{Mutex, MutexGuard}; +use std::marker::Send; +use std::mem; +use std::ops::{Deref, DerefMut}; + +/// An `EbrCell` Write Transaction handle. +/// +/// This allows mutation of the content of the `EbrCell` without blocking or +/// affecting current readers. +/// +/// Changes are only stored in the structure until you call commit: to +/// abort a change, don't call commit and allow the write transaction to +/// go out of scope. This causes the `EbrCell` to unlock allowing other +/// writes to proceed. +pub struct EbrCellWriteTxn<'a, T: 'static + Clone + Send + Sync> { + data: Option, + // This way we know who to contact for updating our data .... + caller: &'a EbrCell, + _guard: MutexGuard<'a, ()>, +} + +impl<'a, T> EbrCellWriteTxn<'a, T> +where + T: Clone + Sync + Send + 'static, +{ + /// Access a mutable pointer of the data in the `EbrCell`. This data is only + /// visible to this write transaction object in this thread until you call + /// 'commit'. + pub fn get_mut(&mut self) -> &mut T { + self.data.as_mut().unwrap() + } + + /// Commit the changes in this write transaction to the `EbrCell`. This will + /// consume the transaction so that further changes can not be made to it + /// after this function is called. + pub fn commit(mut self) { + /* Write our data back to the EbrCell */ + // Now make a new dummy element, and swap it into the mutex + // This fixes up ownership of some values for lifetimes. + let mut element: Option = None; + mem::swap(&mut element, &mut self.data); + self.caller.commit(element); + } +} + +impl<'a, T> Deref for EbrCellWriteTxn<'a, T> +where + T: Clone + Sync + Send, +{ + type Target = T; + + #[inline] + fn deref(&self) -> &T { + self.data.as_ref().unwrap() + } +} + +impl<'a, T> DerefMut for EbrCellWriteTxn<'a, T> +where + T: Clone + Sync + Send, +{ + fn deref_mut(&mut self) -> &mut T { + self.data.as_mut().unwrap() + } +} + +/// A concurrently readable cell. +/// +/// This structure behaves in a similar manner to a `RwLock>`. However +/// unlike a read-write lock, writes and parallel reads can be performed +/// simultaneously. This means writes do not block reads or reads do not +/// block writes. +/// +/// To achieve this a form of "copy-on-write" (or for Rust, clone on write) is +/// used. As a write transaction begins, we clone the existing data to a new +/// location that is capable of being mutated. +/// +/// Readers are guaranteed that the content of the `EbrCell` will live as long +/// as the read transaction is open, and will be consistent for the duration +/// of the transaction. There can be an "unlimited" number of readers in parallel +/// accessing different generations of data of the `EbrCell`. +/// +/// Data that is copied is garbage collected using the crossbeam-epoch library. +/// +/// Writers are serialised and are guaranteed they have exclusive write access +/// to the structure. +/// +/// # Examples +/// ``` +/// use concread::ebrcell::EbrCell; +/// +/// let data: i64 = 0; +/// let ebrcell = EbrCell::new(data); +/// +/// // Begin a read transaction +/// let read_txn = ebrcell.read(); +/// assert_eq!(*read_txn, 0); +/// { +/// // Now create a write, and commit it. +/// let mut write_txn = ebrcell.write(); +/// *write_txn = 1; +/// // Commit the change +/// write_txn.commit(); +/// } +/// // Show the previous generation still reads '0' +/// assert_eq!(*read_txn, 0); +/// let new_read_txn = ebrcell.read(); +/// // And a new read transaction has '1' +/// assert_eq!(*new_read_txn, 1); +/// ``` +#[derive(Debug)] +pub struct EbrCell { + write: Mutex<()>, + active: Atomic, +} + +impl EbrCell +where + T: Clone + Sync + Send + 'static, +{ + /// Create a new `EbrCell` storing type `T`. `T` must implement `Clone`. + pub fn new(data: T) -> Self { + EbrCell { + write: Mutex::new(()), + active: Atomic::new(data), + } + } + + /// Begin a write transaction, returning a write guard. + pub fn write(&self) -> EbrCellWriteTxn { + /* Take the exclusive write lock first */ + let mguard = self.write.lock(); + /* Do an atomic load of the current value */ + let guard = epoch::pin(); + let cur_shared = self.active.load(Acquire, &guard); + /* Now build the write struct, we'll discard the pin shortly! */ + EbrCellWriteTxn { + /* This is the 'copy' of the copy on write! */ + data: Some(unsafe { cur_shared.deref().clone() }), + caller: self, + _guard: mguard, + } + } + + /// Attempt to begin a write transaction. If it's already held, + /// `None` is returned. + pub fn try_write(&self) -> Option> { + self.write.try_lock().map(|mguard| { + let guard = epoch::pin(); + let cur_shared = self.active.load(Acquire, &guard); + /* Now build the write struct, we'll discard the pin shortly! */ + EbrCellWriteTxn { + /* This is the 'copy' of the copy on write! */ + data: Some(unsafe { cur_shared.deref().clone() }), + caller: self, + _guard: mguard, + } + }) + } + + /// This is an internal compontent of the commit cycle. It takes ownership + /// of the value stored in the writetxn, and commits it to the main EbrCell + /// safely. + /// + /// In theory you could use this as a "lock free" version, but you don't + /// know if you are trampling a previous change, so it's private and we + /// let the writetxn struct serialise and protect this interface. + fn commit(&self, element: Option) { + // Yield a read txn? + let guard = epoch::pin(); + + // Load the previous data ready for unlinking + let prev_data = self.active.load(Acquire, &guard); + // Make the data Owned, and set it in the active. + let owned_data: Owned = Owned::new(element.unwrap()); + let _shared_data = self + .active + .compare_exchange(prev_data, owned_data, Release, Relaxed, &guard); + // Finally, set our previous data for cleanup. + unsafe { guard.defer_destroy(prev_data) }; + // Then return the current data with a readtxn. Do we need a new guard scope? + } + + /// Begin a read transaction. The returned [`EbrCellReadTxn'] guarantees + /// the data lives long enough via crossbeam's Epoch type. When this is + /// dropped the data *may* be freed at some point in the future. + pub fn read(&self) -> EbrCellReadTxn { + let guard = epoch::pin(); + + // This option returns None on null pointer, but we can never be null + // as we have to init with data, and all replacement ALWAYS gives us + // a ptr, so unwrap? + let cur = { + let c = self.active.load(Acquire, &guard); + c.as_raw() + }; + + EbrCellReadTxn { + _guard: guard, + data: cur, + } + } +} + +impl Drop for EbrCell +where + T: Clone + Sync + Send + 'static, +{ + fn drop(&mut self) { + // Right, we are dropping! Everything is okay here *except* + // that we need to tell our active data to be unlinked, else it may + // be dropped "unsafely". + let guard = epoch::pin(); + + let prev_data = self.active.load(Acquire, &guard); + unsafe { guard.defer_destroy(prev_data) }; + } +} + +/// A read transaction. This stores a reference to the data from the main +/// `EbrCell`, and guarantees it is alive for the duration of the read. +// #[derive(Debug)] +pub struct EbrCellReadTxn { + _guard: Guard, + data: *const T, +} + +impl Deref for EbrCellReadTxn { + type Target = T; + + /// Derference and access the value within the read transaction. + fn deref(&self) -> &T { + unsafe { &(*self.data) } + } +} + +#[cfg(test)] +mod tests { + use std::sync::atomic::{AtomicUsize, Ordering}; + + use super::EbrCell; + use crossbeam_utils::thread::scope; + + #[test] + fn test_deref_mut() { + let data: i64 = 0; + let cc = EbrCell::new(data); + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write(); + *cc_wrtxn = 1; + cc_wrtxn.commit(); + } + let cc_rotxn = cc.read(); + assert_eq!(*cc_rotxn, 1); + } + + #[test] + fn test_try_write() { + let data: i64 = 0; + let cc = EbrCell::new(data); + /* Take a write txn */ + let cc_wrtxn_a = cc.try_write(); + assert!(cc_wrtxn_a.is_some()); + /* Because we already hold the writ, the second is guaranteed to fail */ + let cc_wrtxn_a = cc.try_write(); + assert!(cc_wrtxn_a.is_none()); + } + + #[test] + fn test_simple_create() { + let data: i64 = 0; + let cc = EbrCell::new(data); + + let cc_rotxn_a = cc.read(); + assert_eq!(*cc_rotxn_a, 0); + + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write(); + /* Get the data ... */ + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(*mut_ptr, 0); + *mut_ptr = 1; + assert_eq!(*mut_ptr, 1); + } + assert_eq!(*cc_rotxn_a, 0); + + let cc_rotxn_b = cc.read(); + assert_eq!(*cc_rotxn_b, 0); + /* The write txn and it's lock is dropped here */ + cc_wrtxn.commit(); + } + + /* Start a new txn and see it's still good */ + let cc_rotxn_c = cc.read(); + assert_eq!(*cc_rotxn_c, 1); + assert_eq!(*cc_rotxn_a, 0); + } + + const MAX_TARGET: i64 = 2000; + + #[test] + #[cfg_attr(miri, ignore)] + fn test_multithread_create() { + let start = time::Instant::now(); + // Create the new ebrcell. + let data: i64 = 0; + let cc = EbrCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move |_| { + let mut last_value: i64 = 0; + while last_value < MAX_TARGET { + let cc_rotxn = cc_ref.read(); + { + assert!(*cc_rotxn >= last_value); + last_value = *cc_rotxn; + } + } + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + let mut last_value: i64 = 0; + while last_value < MAX_TARGET { + let mut cc_wrtxn = cc_ref.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + assert!(*mut_ptr >= last_value); + last_value = *mut_ptr; + *mut_ptr = *mut_ptr + 1; + } + cc_wrtxn.commit(); + } + }) + }) + .collect(); + }) + .is_ok()); + + let end = time::Instant::now(); + print!("Ebr MT create :{:?} ", end - start); + } + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + impl Drop for TestGcWrapper { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + fn test_gc_operation_thread(cc: &EbrCell>) { + while GC_COUNT.load(Ordering::Acquire) < 50 { + // thread::sleep(std::time::Duration::from_millis(200)); + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_gc_operation() { + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = EbrCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + test_gc_operation_thread(cc_ref); + }) + }) + .collect(); + }) + .is_ok()); + + assert!(GC_COUNT.load(Ordering::Acquire) >= 50); + } +} + +#[cfg(test)] +mod tests_linear { + use std::sync::atomic::{AtomicUsize, Ordering}; + + use super::EbrCell; + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + impl Drop for TestGcWrapper { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + #[test] + fn test_gc_operation_linear() { + /* + * Test if epoch drops in order (or ordered enough). + * A property required for b+tree with cow is that txn's + * are dropped in order so that tree states are not invalidated. + * + * A -> B -> C + * + * If B is dropped, it invalidates nodes copied from A + * causing the tree to corrupt txn A (and maybe C). + * + * EBR due to it's design while it won't drop in order, + * it drops generationally, in blocks. This is probably + * good enough. This means that: + * + * A -> B -> C .. -> X -> Y + * + * EBR will drop in blocks such as: + * + * | g1 | g2 | live | + * A -> B -> C .. -> X -> Y + * + * This test is "small" but asserts a basic sanity of drop + * ordering, but it's not conclusive for b+tree. More testing + * (likely multi-thread strees test) is needed, or analysis from + * other EBR developers. + */ + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = EbrCell::new(data); + + // Open a read A. + let cc_rotxn_a = cc.read(); + // open a write, change and commit + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + // open a read B. + let cc_rotxn_b = cc.read(); + // open a write, change and commit + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + // open a read C + let cc_rotxn_c = cc.read(); + + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop B + drop(cc_rotxn_b); + + // gc count should be 0. + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop C + drop(cc_rotxn_c); + + // gc count should be 0 + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop A + drop(cc_rotxn_a); + + // gc count should be 2 (A + B, C is still live) + assert!(GC_COUNT.load(Ordering::Acquire) <= 2); + } +} diff --git a/vendor/concread/src/hashmap/asynch.rs b/vendor/concread/src/hashmap/asynch.rs new file mode 100644 index 0000000..bca512c --- /dev/null +++ b/vendor/concread/src/hashmap/asynch.rs @@ -0,0 +1,155 @@ +//! HashMap - A async locked concurrently readable HashMap +//! +//! For more, see [`HashMap`] + +#![allow(clippy::implicit_hasher)] + +use crate::internals::lincowcell_async::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; + +include!("impl.rs"); + +impl + HashMap +{ + /// Construct a new concurrent hashmap + pub fn new() -> Self { + // I acknowledge I understand what is required to make this safe. + HashMap { + inner: LinCowCell::new(unsafe { SuperBlock::new() }), + } + } + + /// Initiate a read transaction for the Hashmap, concurrent to any + /// other readers or writers. + pub async fn read<'x>(&'x self) -> HashMapReadTxn<'x, K, V> { + let inner = self.inner.read().await; + HashMapReadTxn { inner } + } + + /// Initiate a write transaction for the map, exclusive to this + /// writer, and concurrently to all existing reads. + pub async fn write<'x>(&'x self) -> HashMapWriteTxn<'x, K, V> { + let inner = self.inner.write().await; + HashMapWriteTxn { inner } + } + + /// Attempt to create a new write, returns None if another writer + /// already exists. + pub fn try_write(&self) -> Option> { + self.inner + .try_write() + .map(|inner| HashMapWriteTxn { inner }) + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapWriteTxn<'a, K, V> +{ + /// Commit the changes from this write transaction. Readers after this point + /// will be able to percieve these changes. + /// + /// To abort (unstage changes), just do not call this function. + pub async fn commit(self) { + self.inner.commit().await; + } +} + +#[cfg(test)] +mod tests { + use super::HashMap; + + #[tokio::test] + async fn test_hashmap_basic_write() { + let hmap: HashMap = HashMap::new(); + let mut hmap_write = hmap.write().await; + + hmap_write.insert(10, 10); + hmap_write.insert(15, 15); + + assert!(hmap_write.contains_key(&10)); + assert!(hmap_write.contains_key(&15)); + assert!(!hmap_write.contains_key(&20)); + + assert!(hmap_write.get(&10) == Some(&10)); + { + let v = hmap_write.get_mut(&10).unwrap(); + *v = 11; + } + assert!(hmap_write.get(&10) == Some(&11)); + + assert!(hmap_write.remove(&10).is_some()); + assert!(!hmap_write.contains_key(&10)); + assert!(hmap_write.contains_key(&15)); + + assert!(hmap_write.remove(&30).is_none()); + + hmap_write.clear(); + assert!(!hmap_write.contains_key(&10)); + assert!(!hmap_write.contains_key(&15)); + hmap_write.commit().await; + } + + #[tokio::test] + async fn test_hashmap_basic_read_write() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write().await; + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + hmap_w1.commit().await; + + let hmap_r1 = hmap.read().await; + assert!(hmap_r1.contains_key(&10)); + assert!(hmap_r1.contains_key(&15)); + assert!(!hmap_r1.contains_key(&20)); + + let mut hmap_w2 = hmap.write().await; + hmap_w2.insert(20, 20); + hmap_w2.commit().await; + + assert!(hmap_r1.contains_key(&10)); + assert!(hmap_r1.contains_key(&15)); + assert!(!hmap_r1.contains_key(&20)); + + let hmap_r2 = hmap.read().await; + assert!(hmap_r2.contains_key(&10)); + assert!(hmap_r2.contains_key(&15)); + assert!(hmap_r2.contains_key(&20)); + } + + #[tokio::test] + async fn test_hashmap_basic_read_snapshot() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write().await; + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + + let snap = hmap_w1.to_snapshot(); + assert!(snap.contains_key(&10)); + assert!(snap.contains_key(&15)); + assert!(!snap.contains_key(&20)); + } + + #[tokio::test] + async fn test_hashmap_basic_iter() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write().await; + assert!(hmap_w1.iter().count() == 0); + + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + + assert!(hmap_w1.iter().count() == 2); + } + + #[tokio::test] + async fn test_hashmap_from_iter() { + let hmap: HashMap = vec![(10, 10), (15, 15), (20, 20)].into_iter().collect(); + let hmap_r2 = hmap.read().await; + assert!(hmap_r2.contains_key(&10)); + assert!(hmap_r2.contains_key(&15)); + assert!(hmap_r2.contains_key(&20)); + } +} diff --git a/vendor/concread/src/hashmap/impl.rs b/vendor/concread/src/hashmap/impl.rs new file mode 100644 index 0000000..0f0cecd --- /dev/null +++ b/vendor/concread/src/hashmap/impl.rs @@ -0,0 +1,382 @@ +use crate::internals::hashmap::cursor::CursorReadOps; +use crate::internals::hashmap::cursor::{CursorRead, CursorWrite, SuperBlock}; +use crate::internals::hashmap::iter::*; +use std::borrow::Borrow; + +use crate::internals::lincowcell::LinCowCellCapable; + +use std::fmt::Debug; +use std::hash::Hash; +use std::iter::FromIterator; + +/// A concurrently readable map based on a modified B+Tree structured with fast +/// parallel hashed key lookup. +/// +/// This structure can be used in locations where you would otherwise us +/// `RwLock` or `Mutex`. +/// +/// This is a concurrently readable structure, meaning it has transactional +/// properties. Writers are serialised (one after the other), and readers +/// can exist in parallel with stable views of the structure at a point +/// in time. +/// +/// This is achieved through the use of COW or MVCC. As a write occurs +/// subsets of the tree are cloned into the writer thread and then commited +/// later. This may cause memory usage to increase in exchange for a gain +/// in concurrent behaviour. +/// +/// Transactions can be rolled-back (aborted) without penalty by dropping +/// the `HashMapWriteTxn` without calling `commit()`. +pub struct HashMap +where + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCell, CursorRead, CursorWrite>, +} + +unsafe impl + Send for HashMap +{ +} +unsafe impl + Sync for HashMap +{ +} + +/// An active read transaction over a `HashMap`. The data in this tree +/// is guaranteed to not change and will remain consistent for the life +/// of this transaction. +pub struct HashMapReadTxn<'a, K, V> +where + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCellReadTxn<'a, SuperBlock, CursorRead, CursorWrite>, +} + +/// An active write transaction for a `HashMap`. The data in this tree +/// may be modified exclusively through this transaction without affecting +/// readers. The write may be rolledback/aborted by dropping this guard +/// without calling `commit()`. Once `commit()` is called, readers will be +/// able to access and percieve changes in new transactions. +pub struct HashMapWriteTxn<'a, K, V> +where + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: LinCowCellWriteTxn<'a, SuperBlock, CursorRead, CursorWrite>, +} + +enum SnapshotType<'a, K, V> +where + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + R(&'a CursorRead), + W(&'a CursorWrite), +} + +/// A point-in-time snapshot of the tree from within a read OR write. This is +/// useful for building other transactional types ontop of this structure, as +/// you need a way to downcast both HashMapReadTxn or HashMapWriteTxn to +/// a singular reader type for a number of get_inner() style patterns. +/// +/// This snapshot IS safe within the read thread due to the nature of the +/// implementation borrowing the inner tree to prevent mutations within the +/// same thread while the read snapshot is open. +pub struct HashMapReadSnapshot<'a, K, V> +where + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, +{ + inner: SnapshotType<'a, K, V>, +} + +impl Default + for HashMap +{ + fn default() -> Self { + Self::new() + } +} + +impl + FromIterator<(K, V)> for HashMap +{ + fn from_iter>(iter: I) -> Self { + let mut new_sblock = unsafe { SuperBlock::new() }; + let prev = new_sblock.create_reader(); + let mut cursor = new_sblock.create_writer(); + cursor.extend(iter); + + let _ = new_sblock.pre_commit(cursor, &prev); + + HashMap { + inner: LinCowCell::new(new_sblock), + } + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > Extend<(K, V)> for HashMapWriteTxn<'a, K, V> +{ + fn extend>(&mut self, iter: I) { + self.inner.as_mut().extend(iter); + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapWriteTxn<'a, K, V> +{ + /* + pub(crate) fn prehash<'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.as_ref().hash_key(k) + } + */ + + pub(crate) fn get_prehashed<'b, Q: ?Sized>(&'a self, k: &'b Q, k_hash: u64) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.as_ref().search(k_hash, k) + } + + /// Retrieve a value from the map. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get<'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + let k_hash = self.inner.as_ref().hash_key(k); + self.get_prehashed(k, k_hash) + } + + /// Assert if a key exists in the map. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.get(k).is_some() + } + + /// returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + self.inner.as_ref().len() + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.inner.as_ref().len() == 0 + } + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + self.inner.as_ref().kv_iter() + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + self.inner.as_ref().v_iter() + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + self.inner.as_ref().k_iter() + } + + /// Reset this map to an empty state. As this is within the transaction this + /// change only takes effect once commited. Once cleared, you can begin adding + /// new writes and changes, again, that will only be visible once commited. + pub fn clear(&mut self) { + self.inner.as_mut().clear() + } + + /// Insert or update a value by key. If the value previously existed it is returned + /// as `Some(V)`. If the value did not previously exist this returns `None`. + pub fn insert(&mut self, k: K, v: V) -> Option { + // Hash the key. + let k_hash = self.inner.as_ref().hash_key(&k); + self.inner.as_mut().insert(k_hash, k, v) + } + + /// Remove a key if it exists in the tree. If the value exists, we return it as `Some(V)`, + /// and if it did not exist, we return `None` + pub fn remove(&mut self, k: &K) -> Option { + let k_hash = self.inner.as_ref().hash_key(k); + self.inner.as_mut().remove(k_hash, k) + } + + /// Get a mutable reference to a value in the tree. This is correctly, and + /// safely cloned before you attempt to mutate the value, isolating it from + /// other transactions. + pub fn get_mut(&mut self, k: &K) -> Option<&mut V> { + let k_hash = self.inner.as_ref().hash_key(k); + self.inner.as_mut().get_mut_ref(k_hash, k) + } + + /// Create a read-snapshot of the current map. This does NOT guarantee the map may + /// not be mutated during the read, so you MUST guarantee that no functions of the + /// write txn are called while this snapshot is active. + pub fn to_snapshot(&'a self) -> HashMapReadSnapshot { + HashMapReadSnapshot { + inner: SnapshotType::W(self.inner.as_ref()), + } + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapReadTxn<'a, K, V> +{ + pub(crate) fn get_prehashed<'b, Q: ?Sized>(&'a self, k: &'b Q, k_hash: u64) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.search(k_hash, k) + } + + /// Retrieve a value from the tree. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get<'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + let k_hash = self.inner.as_ref().hash_key(k); + self.get_prehashed(k, k_hash) + } + + /// Assert if a key exists in the tree. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.get(k).is_some() + } + + /// Returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + self.inner.as_ref().len() + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.inner.as_ref().len() == 0 + } + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + self.inner.as_ref().kv_iter() + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + self.inner.as_ref().v_iter() + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + self.inner.as_ref().k_iter() + } + + /// Create a read-snapshot of the current tree. + /// As this is the read variant, it IS safe, and guaranteed the tree will not change. + pub fn to_snapshot(&'a self) -> HashMapReadSnapshot<'a, K, V> { + HashMapReadSnapshot { + inner: SnapshotType::R(self.inner.as_ref()), + } + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapReadSnapshot<'a, K, V> +{ + /// Retrieve a value from the tree. If the value exists, a reference is returned + /// as `Some(&V)`, otherwise if not present `None` is returned. + pub fn get<'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + match self.inner { + SnapshotType::R(inner) => { + let k_hash = inner.hash_key(k); + inner.search(k_hash, k) + } + SnapshotType::W(inner) => { + let k_hash = inner.hash_key(k); + inner.search(k_hash, k) + } + } + } + + /// Assert if a key exists in the tree. + pub fn contains_key<'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.get(k).is_some() + } + + /// Returns the current number of k:v pairs in the tree + pub fn len(&self) -> usize { + match self.inner { + SnapshotType::R(inner) => inner.len(), + SnapshotType::W(inner) => inner.len(), + } + } + + /// Determine if the set is currently empty + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + // (adv) range + + /// Iterator over `(&K, &V)` of the set + pub fn iter(&self) -> Iter { + match self.inner { + SnapshotType::R(inner) => inner.kv_iter(), + SnapshotType::W(inner) => inner.kv_iter(), + } + } + + /// Iterator over &K + pub fn values(&self) -> ValueIter { + match self.inner { + SnapshotType::R(inner) => inner.v_iter(), + SnapshotType::W(inner) => inner.v_iter(), + } + } + + /// Iterator over &V + pub fn keys(&self) -> KeyIter { + match self.inner { + SnapshotType::R(inner) => inner.k_iter(), + SnapshotType::W(inner) => inner.k_iter(), + } + } +} + diff --git a/vendor/concread/src/hashmap/mod.rs b/vendor/concread/src/hashmap/mod.rs new file mode 100644 index 0000000..0aa5fa2 --- /dev/null +++ b/vendor/concread/src/hashmap/mod.rs @@ -0,0 +1,213 @@ +//! HashMap - A concurrently readable HashMap +//! +//! This is a specialisation of the `BptreeMap`, allowing a concurrently readable +//! HashMap. Unlike a traditional hashmap it does *not* have `O(1)` lookup, as it +//! internally uses a tree-like structure to store a series of buckets. However +//! if you do not need key-ordering, due to the storage of the hashes as `u64` +//! the operations in the tree to seek the bucket is much faster than the use of +//! the same key in the `BptreeMap`. +//! +//! For more details. see the `BptreeMap` +//! +//! This structure is very different to the `im` crate. The `im` crate is +//! sync + send over individual operations. This means that multiple writes can +//! be interleaved atomicly and safely, and the readers always see the latest +//! data. While this is potentially useful to a set of problems, transactional +//! structures are suited to problems where readers have to maintain consistent +//! data views for a duration of time, cpu cache friendly behaviours and +//! database like transaction properties (ACID). + +#![allow(clippy::implicit_hasher)] + +#[cfg(feature = "asynch")] +pub mod asynch; + +use crate::internals::hashmap::cursor::Datum; +use crate::internals::lincowcell::{LinCowCell, LinCowCellReadTxn, LinCowCellWriteTxn}; + +include!("impl.rs"); + +impl + HashMap +{ + /// Construct a new concurrent hashmap + pub fn new() -> Self { + // I acknowledge I understand what is required to make this safe. + HashMap { + inner: LinCowCell::new(unsafe { SuperBlock::new() }), + } + } + + /// Initiate a read transaction for the Hashmap, concurrent to any + /// other readers or writers. + pub fn read(&self) -> HashMapReadTxn { + let inner = self.inner.read(); + HashMapReadTxn { inner } + } + + /// Initiate a write transaction for the map, exclusive to this + /// writer, and concurrently to all existing reads. + pub fn write(&self) -> HashMapWriteTxn { + let inner = self.inner.write(); + HashMapWriteTxn { inner } + } + + /// Attempt to create a new write, returns None if another writer + /// already exists. + pub fn try_write(&self) -> Option> { + self.inner + .try_write() + .map(|inner| HashMapWriteTxn { inner }) + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapWriteTxn<'a, K, V> +{ + pub(crate) fn get_txid(&self) -> u64 { + self.inner.as_ref().get_txid() + } + + pub(crate) fn prehash<'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.as_ref().hash_key(k) + } + + /// This is *unsafe* because changing the key CAN and WILL break hashing, which can + /// have serious consequences. This API only exists to allow arcache to access the inner + /// content of the slot to simplify it's API. You should basically never touch this + /// function as it's the HashMap equivalent of a the demon sphere. + pub(crate) unsafe fn get_slot_mut(&mut self, k_hash: u64) -> Option<&mut [Datum]> { + self.inner.as_mut().get_slot_mut_ref(k_hash) + } + + /// Commit the changes from this write transaction. Readers after this point + /// will be able to percieve these changes. + /// + /// To abort (unstage changes), just do not call this function. + pub fn commit(self) { + self.inner.commit(); + } +} + +impl< + 'a, + K: Hash + Eq + Clone + Debug + Sync + Send + 'static, + V: Clone + Sync + Send + 'static, + > HashMapReadTxn<'a, K, V> +{ + pub(crate) fn get_txid(&self) -> u64 { + self.inner.as_ref().get_txid() + } + + pub(crate) fn prehash<'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq, + { + self.inner.as_ref().hash_key(k) + } +} + +#[cfg(test)] +mod tests { + use super::HashMap; + + #[test] + fn test_hashmap_basic_write() { + let hmap: HashMap = HashMap::new(); + let mut hmap_write = hmap.write(); + + hmap_write.insert(10, 10); + hmap_write.insert(15, 15); + + assert!(hmap_write.contains_key(&10)); + assert!(hmap_write.contains_key(&15)); + assert!(!hmap_write.contains_key(&20)); + + assert!(hmap_write.get(&10) == Some(&10)); + { + let v = hmap_write.get_mut(&10).unwrap(); + *v = 11; + } + assert!(hmap_write.get(&10) == Some(&11)); + + assert!(hmap_write.remove(&10).is_some()); + assert!(!hmap_write.contains_key(&10)); + assert!(hmap_write.contains_key(&15)); + + assert!(hmap_write.remove(&30).is_none()); + + hmap_write.clear(); + assert!(!hmap_write.contains_key(&10)); + assert!(!hmap_write.contains_key(&15)); + hmap_write.commit(); + } + + #[test] + fn test_hashmap_basic_read_write() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write(); + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + hmap_w1.commit(); + + let hmap_r1 = hmap.read(); + assert!(hmap_r1.contains_key(&10)); + assert!(hmap_r1.contains_key(&15)); + assert!(!hmap_r1.contains_key(&20)); + + let mut hmap_w2 = hmap.write(); + hmap_w2.insert(20, 20); + hmap_w2.commit(); + + assert!(hmap_r1.contains_key(&10)); + assert!(hmap_r1.contains_key(&15)); + assert!(!hmap_r1.contains_key(&20)); + + let hmap_r2 = hmap.read(); + assert!(hmap_r2.contains_key(&10)); + assert!(hmap_r2.contains_key(&15)); + assert!(hmap_r2.contains_key(&20)); + } + + #[test] + fn test_hashmap_basic_read_snapshot() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write(); + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + + let snap = hmap_w1.to_snapshot(); + assert!(snap.contains_key(&10)); + assert!(snap.contains_key(&15)); + assert!(!snap.contains_key(&20)); + } + + #[test] + fn test_hashmap_basic_iter() { + let hmap: HashMap = HashMap::new(); + let mut hmap_w1 = hmap.write(); + assert!(hmap_w1.iter().count() == 0); + + hmap_w1.insert(10, 10); + hmap_w1.insert(15, 15); + + assert!(hmap_w1.iter().count() == 2); + } + + #[test] + fn test_hashmap_from_iter() { + let hmap: HashMap = vec![(10, 10), (15, 15), (20, 20)].into_iter().collect(); + let hmap_r2 = hmap.read(); + assert!(hmap_r2.contains_key(&10)); + assert!(hmap_r2.contains_key(&15)); + assert!(hmap_r2.contains_key(&20)); + } +} diff --git a/vendor/concread/src/internals/bptree/cursor.rs b/vendor/concread/src/internals/bptree/cursor.rs new file mode 100644 index 0000000..5bfe282 --- /dev/null +++ b/vendor/concread/src/internals/bptree/cursor.rs @@ -0,0 +1,2649 @@ +// The cursor is what actually knits a tree together from the parts +// we have, and has an important role to keep the system consistent. +// +// Additionally, the cursor also is responsible for general movement +// throughout the structure and how to handle that effectively + +use super::node::*; +use crate::internals::lincowcell::LinCowCellCapable; +use std::borrow::Borrow; +use std::fmt::Debug; +use std::mem; + +use super::iter::{Iter, KeyIter, ValueIter}; +use super::states::*; +use std::iter::Extend; + +use parking_lot::Mutex; + +/// The internal root of the tree, with associated garbage lists etc. +#[derive(Debug)] +pub(crate) struct SuperBlock +where + K: Ord + Clone + Debug, + V: Clone, +{ + root: *mut Node, + size: usize, + txid: u64, +} + +impl LinCowCellCapable, CursorWrite> + for SuperBlock +{ + fn create_reader(&self) -> CursorRead { + // This sets up the first reader. + CursorRead::new(self) + } + + fn create_writer(&self) -> CursorWrite { + // Create a writer. + CursorWrite::new(self) + } + + fn pre_commit( + &mut self, + mut new: CursorWrite, + prev: &CursorRead, + ) -> CursorRead { + let mut prev_last_seen = prev.last_seen.lock(); + debug_assert!((*prev_last_seen).is_empty()); + + let new_last_seen = &mut new.last_seen; + // swap the two lists. We should now have "empty" + std::mem::swap(&mut (*prev_last_seen), &mut (*new_last_seen)); + debug_assert!((*new_last_seen).is_empty()); + // Now when the lock is dropped, both sides see the correct info and garbage for drops. + + // We are done, time to seal everything. + new.first_seen.iter().for_each(|n| unsafe { + (**n).make_ro(); + }); + // Clear first seen, we won't be dropping them from here. + new.first_seen.clear(); + + // == Push data into our sb. == + self.root = new.root; + self.size = new.length; + self.txid = new.txid; + + // Create the new reader. + CursorRead::new(self) + } +} + +impl SuperBlock { + /// This is UNSAFE because you *MUST* understand how to manage the transactions + /// of this type and to give a correct linearised transaction manager the ability + /// to control this. + /// + /// More than likely, you WILL NOT do this so you should RUN AWAY and try to forget + /// you ever saw this function at all. + pub unsafe fn new() -> Self { + let leaf: *mut Leaf = Node::new_leaf(1); + SuperBlock { + root: leaf as *mut Node, + size: 0, + txid: 1, + } + } + + #[cfg(test)] + pub(crate) fn new_test(txid: u64, root: *mut Node) -> Self { + assert!(txid < (TXID_MASK >> TXID_SHF)); + assert!(txid > 0); + // let last_seen: Vec<*mut Node> = Vec::with_capacity(16); + let mut first_seen = Vec::with_capacity(16); + // Do a pre-verify to be sure it's sane. + assert!(unsafe { (*root).verify() }); + // Collect anythinng from root into this txid if needed. + // Set txid to txid on all tree nodes from the root. + first_seen.push(root); + unsafe { (*root).sblock_collect(&mut first_seen) }; + + // Lock them all + first_seen.iter().for_each(|n| unsafe { + (**n).make_ro(); + }); + + // Determine our count internally. + let (length, _) = unsafe { (*root).tree_density() }; + + // Good to go! + SuperBlock { + txid, + size: length, + root, + } + } +} + +#[derive(Debug)] +pub(crate) struct CursorRead +where + K: Ord + Clone + Debug, + V: Clone, +{ + txid: u64, + length: usize, + root: *mut Node, + last_seen: Mutex>>, +} + +#[derive(Debug)] +pub(crate) struct CursorWrite +where + K: Ord + Clone + Debug, + V: Clone, +{ + txid: u64, + length: usize, + root: *mut Node, + last_seen: Vec<*mut Node>, + first_seen: Vec<*mut Node>, +} + +pub(crate) trait CursorReadOps { + fn get_root_ref(&self) -> &Node; + + fn get_root(&self) -> *mut Node; + + fn len(&self) -> usize; + + fn get_txid(&self) -> u64; + + #[cfg(test)] + fn get_tree_density(&self) -> (usize, usize) { + // Walk the tree and calculate the packing effeciency. + let rref = self.get_root_ref(); + rref.tree_density() + } + + fn search<'a, 'b, Q: ?Sized>(&'a self, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Ord, + { + let mut node = self.get_root(); + for _i in 0..65536 { + if unsafe { (*node).is_leaf() } { + let lref = leaf_ref!(node, K, V); + return lref.get_ref(k).map(|v| unsafe { + // Strip the lifetime and rebind to the 'a self. + // This is safe because we know that these nodes will NOT + // be altered during the lifetime of this txn, so the references + // will remain stable. + let x = v as *const V; + &*x as &V + }); + } else { + let bref = branch_ref!(node, K, V); + let idx = bref.locate_node(k); + node = bref.get_idx_unchecked(idx); + } + } + panic!("Tree depth exceeded max limit (65536). This may indicate memory corruption."); + } + + #[allow(clippy::needless_lifetimes)] + fn contains_key<'a, 'b, Q: ?Sized>(&'a self, k: &'b Q) -> bool + where + K: Borrow, + Q: Ord, + { + self.search(k).is_some() + } + + fn kv_iter(&self) -> Iter { + Iter::new(self.get_root(), self.len()) + } + + fn k_iter(&self) -> KeyIter { + KeyIter::new(self.get_root(), self.len()) + } + + fn v_iter(&self) -> ValueIter { + ValueIter::new(self.get_root(), self.len()) + } + + #[cfg(test)] + fn verify(&self) -> bool { + self.get_root_ref().no_cycles() && self.get_root_ref().verify() && { + let (l, _) = self.get_tree_density(); + l == self.len() + } + } +} + +impl CursorWrite { + pub(crate) fn new(sblock: &SuperBlock) -> Self { + let txid = sblock.txid + 1; + assert!(txid < (TXID_MASK >> TXID_SHF)); + // println!("starting wr txid -> {:?}", txid); + let length = sblock.size; + let root = sblock.root; + // TODO: Could optimise how big these are based + // on past trends? Or based on % tree size? + let last_seen = Vec::with_capacity(16); + let first_seen = Vec::with_capacity(16); + + CursorWrite { + txid, + length, + root, + last_seen, + first_seen, + } + } + + pub(crate) fn clear(&mut self) { + // Reset the values in this tree. + // We need to mark everything as disposable, and create a new root! + self.last_seen.push(self.root); + unsafe { (*self.root).sblock_collect(&mut self.last_seen) }; + let nroot: *mut Leaf = Node::new_leaf(self.txid); + let mut nroot = nroot as *mut Node; + self.first_seen.push(nroot); + mem::swap(&mut self.root, &mut nroot); + self.length = 0; + } + + // Functions as insert_or_update + pub(crate) fn insert(&mut self, k: K, v: V) -> Option { + let r = match clone_and_insert( + self.root, + self.txid, + k, + v, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRInsertState::NoClone(res) => res, + CRInsertState::Clone(res, mut nnode) => { + // We have a new root node, swap it in. + // !!! It's already been cloned and marked for cleaning by the clone_and_insert + // call. + // eprintln!("swap: {:?}, {:?}", self.root, nnode); + mem::swap(&mut self.root, &mut nnode); + // Return the insert result + res + } + CRInsertState::CloneSplit(lnode, rnode) => { + // The previous root had to split - make a new + // root now and put it inplace. + let mut nroot = Node::new_branch(self.txid, lnode, rnode) as *mut Node; + self.first_seen.push(nroot); + // The root was cloned as part of clone split + // This swaps the POINTERS not the content! + mem::swap(&mut self.root, &mut nroot); + // As we split, there must NOT have been an existing + // key to overwrite. + None + } + CRInsertState::Split(rnode) => { + // The previous root was already part of this txn, but has now + // split. We need to construct a new root and swap them. + // + // Note, that we have to briefly take an extra RC on the root so + // that we can get it into the branch. + let mut nroot = Node::new_branch(self.txid, self.root, rnode) as *mut Node; + self.first_seen.push(nroot); + // println!("ls push 2"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + // As we split, there must NOT have been an existing + // key to overwrite. + None + } + CRInsertState::RevSplit(lnode) => { + let mut nroot = Node::new_branch(self.txid, lnode, self.root) as *mut Node; + self.first_seen.push(nroot); + // println!("ls push 3"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + None + } + CRInsertState::CloneRevSplit(rnode, lnode) => { + let mut nroot = Node::new_branch(self.txid, lnode, rnode) as *mut Node; + self.first_seen.push(nroot); + // root was cloned in the rev split + // println!("ls push 4"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + None + } + }; + // If this is none, it means a new slot is now occupied. + if r.is_none() { + self.length += 1; + } + r + } + + pub(crate) fn remove(&mut self, k: &K) -> Option { + let r = match clone_and_remove( + self.root, + self.txid, + k, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRRemoveState::NoClone(res) => res, + CRRemoveState::Clone(res, mut nnode) => { + mem::swap(&mut self.root, &mut nnode); + res + } + CRRemoveState::Shrink(res) => { + if self_meta!(self.root).is_leaf() { + // No action - we have an empty tree. + res + } else { + // Root is being demoted, get the last branch and + // promote it to the root. + self.last_seen.push(self.root); + let rmut = branch_ref!(self.root, K, V); + let mut pnode = rmut.extract_last_node(); + mem::swap(&mut self.root, &mut pnode); + res + } + } + CRRemoveState::CloneShrink(res, mut nnode) => { + if self_meta!(nnode).is_leaf() { + // The tree is empty, but we cloned the root to get here. + mem::swap(&mut self.root, &mut nnode); + res + } else { + // Our root is getting demoted here, get the remaining branch + self.last_seen.push(nnode); + let rmut = branch_ref!(nnode, K, V); + let mut pnode = rmut.extract_last_node(); + // Promote it to the new root + mem::swap(&mut self.root, &mut pnode); + res + } + } + }; + if r.is_some() { + self.length -= 1; + } + r + } + + #[cfg(test)] + pub(crate) fn path_clone(&mut self, k: &K) { + match path_clone( + self.root, + self.txid, + k, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRCloneState::Clone(mut nroot) => { + // We cloned the root, so swap it. + mem::swap(&mut self.root, &mut nroot); + } + CRCloneState::NoClone => {} + }; + } + + pub(crate) fn get_mut_ref(&mut self, k: &K) -> Option<&mut V> { + match path_clone( + self.root, + self.txid, + k, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRCloneState::Clone(mut nroot) => { + // We cloned the root, so swap it. + mem::swap(&mut self.root, &mut nroot); + } + CRCloneState::NoClone => {} + }; + // Now get the ref. + path_get_mut_ref(self.root, k) + } + + pub(crate) fn split_off_lt(&mut self, k: &K) { + /* + // Remove all the values less than from the top of the tree. + loop { + let result = clone_and_split_off_trim_lt( + self.root, + self.txid, + k, + &mut self.last_seen, + &mut self.first_seen, + ); + // println!("clone_and_split_off_trim_lt -> {:?}", result); + match result { + CRTrimState::Complete => break, + CRTrimState::Clone(mut nroot) => { + // We cloned the root as we changed it, but don't need + // to recurse so we break the loop. + mem::swap(&mut self.root, &mut nroot); + break; + } + CRTrimState::Promote(mut nroot) => { + mem::swap(&mut self.root, &mut nroot); + // This will continue and try again. + } + } + } + */ + + /* + // Now work up the tree and clean up the remaining path inbetween + let result = clone_and_split_off_prune_lt(&mut self.root, self.txid, k); + // println!("clone_and_split_off_prune_lt -> {:?}", result); + match result { + CRPruneState::OkNoClone => {} + CRPruneState::OkClone(mut nroot) => { + mem::swap(&mut self.root, &mut nroot); + } + CRPruneState::Prune => { + if self.root.is_leaf() { + // No action, the tree is now empty. + } else { + // Root is being demoted, get the last branch and + // promote it to the root. + let rmut = Arc::get_mut(&mut self.root).unwrap().as_mut_branch(); + let mut pnode = rmut.extract_last_node(); + mem::swap(&mut self.root, &mut pnode); + } + } + CRPruneState::ClonePrune(mut clone) => { + if self.root.is_leaf() { + mem::swap(&mut self.root, &mut clone); + } else { + let rmut = Arc::get_mut(&mut clone).unwrap().as_mut_branch(); + let mut pnode = rmut.extract_last_node(); + mem::swap(&mut self.root, &mut pnode); + } + } + }; + */ + + // Get rid of anything else dangling + let mut rmkeys: Vec = Vec::new(); + for ki in self.k_iter() { + if ki >= k { + break; + } + rmkeys.push(ki.clone()); + } + + for kr in rmkeys.into_iter() { + let _ = self.remove(&kr); + } + + // Iterate over the remaining kv's to fix our k,v count. + let newsize = self.kv_iter().count(); + self.length = newsize; + } + + #[cfg(test)] + pub(crate) fn root_txid(&self) -> u64 { + self.get_root_ref().get_txid() + } + + #[cfg(test)] + pub(crate) fn tree_density(&self) -> (usize, usize) { + self.get_root_ref().tree_density() + } +} + +impl Extend<(K, V)> for CursorWrite { + fn extend>(&mut self, iter: I) { + iter.into_iter().for_each(|(k, v)| { + let _ = self.insert(k, v); + }); + } +} + +impl Drop for CursorWrite { + fn drop(&mut self) { + // If there is content in first_seen, this means we aborted and must rollback + // of these items! + // println!("Releasing CW FS -> {:?}", self.first_seen); + self.first_seen.iter().for_each(|n| Node::free(*n)) + } +} + +impl Drop for CursorRead { + fn drop(&mut self) { + // If there is content in last_seen, a future generation wants us to remove it! + let last_seen_guard = self + .last_seen + .try_lock() + .expect("Unable to lock, something is horridly wrong!"); + last_seen_guard.iter().for_each(|n| Node::free(*n)); + std::mem::drop(last_seen_guard); + } +} + +impl Drop for SuperBlock { + fn drop(&mut self) { + // eprintln!("Releasing SuperBlock ..."); + // We must be the last SB and no txns exist. Drop the tree now. + // TODO: Calc this based on size. + let mut first_seen = Vec::with_capacity(16); + // eprintln!("{:?}", self.root); + first_seen.push(self.root); + unsafe { (*self.root).sblock_collect(&mut first_seen) }; + first_seen.iter().for_each(|n| Node::free(*n)); + } +} + +impl CursorRead { + pub(crate) fn new(sblock: &SuperBlock) -> Self { + // println!("starting rd txid -> {:?}", sblock.txid); + CursorRead { + txid: sblock.txid, + length: sblock.size, + root: sblock.root, + last_seen: Mutex::new(Vec::with_capacity(0)), + } + } +} + +impl CursorReadOps for CursorRead { + fn get_root_ref(&self) -> &Node { + unsafe { &*(self.root) } + } + + fn get_root(&self) -> *mut Node { + self.root + } + + fn len(&self) -> usize { + self.length + } + + fn get_txid(&self) -> u64 { + self.txid + } +} + +impl CursorReadOps for CursorWrite { + fn get_root_ref(&self) -> &Node { + unsafe { &*(self.root) } + } + + fn get_root(&self) -> *mut Node { + self.root + } + + fn len(&self) -> usize { + self.length + } + + fn get_txid(&self) -> u64 { + self.txid + } +} + +fn clone_and_insert( + node: *mut Node, + txid: u64, + k: K, + v: V, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRInsertState { + /* + * Let's talk about the magic of this function. Come, join + * me around the [🔥🔥🔥] + * + * This function is the heart and soul of a copy on write + * structure - as we progress to the leaf location where we + * wish to perform an alteration, we clone (if required) all + * nodes on the path. This way an abort (rollback) of the + * commit simply is to drop the cursor, where the "new" + * cloned values are only referenced. To commit, we only need + * to replace the tree root in the parent structures as + * the cloned path must by definition include the root, and + * will contain references to nodes that did not need cloning, + * thus keeping them alive. + */ + + if self_meta!(node).is_leaf() { + // NOTE: We have to match, rather than map here, as rust tries to + // move k:v into both closures! + + // Leaf path + match leaf_ref!(node, K, V).req_clone(txid) { + Some(cnode) => { + // println!(); + first_seen.push(cnode); + // println!("ls push 5"); + last_seen.push(node); + // Clone was required. + let mref = leaf_ref!(cnode, K, V); + // insert to the new node. + match mref.insert_or_update(k, v) { + LeafInsertState::Ok(res) => CRInsertState::Clone(res, cnode), + LeafInsertState::Split(rnode) => { + first_seen.push(rnode as *mut Node); + // let rnode = Node::new_leaf_ins(txid, sk, sv); + CRInsertState::CloneSplit(cnode, rnode as *mut Node) + } + LeafInsertState::RevSplit(lnode) => { + first_seen.push(lnode as *mut Node); + CRInsertState::CloneRevSplit(cnode, lnode as *mut Node) + } + } + } + None => { + // No clone required. + // simply do the insert. + let mref = leaf_ref!(node, K, V); + match mref.insert_or_update(k, v) { + LeafInsertState::Ok(res) => CRInsertState::NoClone(res), + LeafInsertState::Split(rnode) => { + // We split, but left is already part of the txn group, so lets + // just return what's new. + // let rnode = Node::new_leaf_ins(txid, sk, sv); + first_seen.push(rnode as *mut Node); + CRInsertState::Split(rnode as *mut Node) + } + LeafInsertState::RevSplit(lnode) => { + first_seen.push(lnode as *mut Node); + CRInsertState::RevSplit(lnode as *mut Node) + } + } + } + } // end match + } else { + // Branch path + // Decide if we need to clone - we do this as we descend due to a quirk in Arc + // get_mut, because we don't have access to get_mut_unchecked (and this api may + // never be stabilised anyway). When we change this to *mut + garbage lists we + // could consider restoring the reactive behaviour that clones up, rather than + // cloning down the path. + // + // NOTE: We have to match, rather than map here, as rust tries to + // move k:v into both closures! + match branch_ref!(node, K, V).req_clone(txid) { + Some(cnode) => { + // + first_seen.push(cnode as *mut Node); + // println!("ls push 6"); + last_seen.push(node as *mut Node); + // Not same txn, clone instead. + let nmref = branch_ref!(cnode, K, V); + let anode_idx = nmref.locate_node(&k); + let anode = nmref.get_idx_unchecked(anode_idx); + + match clone_and_insert(anode, txid, k, v, last_seen, first_seen) { + CRInsertState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + // Pass back up that we cloned. + CRInsertState::Clone(res, cnode) + } + CRInsertState::CloneSplit(lnode, rnode) => { + // CloneSplit here, would have already updated lnode/rnode into the + // gc lists. + // Second, we update anode_idx node with our lnode as the new clone. + nmref.replace_by_idx(anode_idx, lnode); + + // Third we insert rnode - perfect world it's at anode_idx + 1, but + // we use the normal insert routine for now. + match nmref.add_node(rnode) { + BranchInsertState::Ok => CRInsertState::Clone(None, cnode), + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::CloneSplit(cnode, nrnode as *mut Node) + } + } + } + CRInsertState::CloneRevSplit(nnode, lnode) => { + nmref.replace_by_idx(anode_idx, nnode); + match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::Clone(None, cnode), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::CloneSplit(cnode, nrnode as *mut Node) + } + } + } + CRInsertState::NoClone(_res) => { + // If our descendant did not clone, then we don't have to either. + unreachable!("Shoud never be possible."); + // CRInsertState::NoClone(res) + } + CRInsertState::Split(_rnode) => { + // I think + unreachable!("This represents a corrupt tree state"); + } + CRInsertState::RevSplit(_lnode) => { + unreachable!("This represents a corrupt tree state"); + } + } // end match + } // end Some, + None => { + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(&k); + let anode = nmref.get_idx_unchecked(anode_idx); + + match clone_and_insert(anode, txid, k, v, last_seen, first_seen) { + CRInsertState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + // We did not clone, and no further work needed. + CRInsertState::NoClone(res) + } + CRInsertState::NoClone(res) => { + // If our descendant did not clone, then we don't have to do any adjustments + // or further work. + CRInsertState::NoClone(res) + } + CRInsertState::Split(rnode) => { + match nmref.add_node(rnode) { + // Similar to CloneSplit - we are either okay, and the insert was happy. + BranchInsertState::Ok => CRInsertState::NoClone(None), + // Or *we* split as well, and need to return a new sibling branch. + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::Split(nrnode as *mut Node) + } + } + } + CRInsertState::CloneSplit(lnode, rnode) => { + // work inplace. + // Second, we update anode_idx node with our lnode as the new clone. + nmref.replace_by_idx(anode_idx, lnode); + + // Third we insert rnode - perfect world it's at anode_idx + 1, but + // we use the normal insert routine for now. + match nmref.add_node(rnode) { + // Similar to CloneSplit - we are either okay, and the insert was happy. + BranchInsertState::Ok => CRInsertState::NoClone(None), + // Or *we* split as well, and need to return a new sibling branch. + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::Split(nrnode as *mut Node) + } + } + } + CRInsertState::RevSplit(lnode) => match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::NoClone(None), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::Split(nrnode as *mut Node) + } + }, + CRInsertState::CloneRevSplit(nnode, lnode) => { + nmref.replace_by_idx(anode_idx, nnode); + match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::NoClone(None), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::Split(nrnode as *mut Node) + } + } + } + } // end match + } + } // end match branch ref clone + } // end if leaf +} + +fn path_clone( + node: *mut Node, + txid: u64, + k: &K, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRCloneState { + if unsafe { (*node).is_leaf() } { + unsafe { + (*(node as *mut Leaf)) + .req_clone(txid) + .map(|cnode| { + // Track memory + last_seen.push(node); + // println!("ls push 7 {:?}", node); + first_seen.push(cnode); + CRCloneState::Clone(cnode) + }) + .unwrap_or(CRCloneState::NoClone) + } + } else { + // We are in a branch, so locate our descendent and prepare + // to clone if needed. + // println!("txid -> {:?} {:?}", node_txid, txid); + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(k); + let anode = nmref.get_idx_unchecked(anode_idx); + match path_clone(anode, txid, k, last_seen, first_seen) { + CRCloneState::Clone(cnode) => { + // Do we need to clone? + nmref + .req_clone(txid) + .map(|acnode| { + // We require to be cloned. + last_seen.push(node); + // println!("ls push 8"); + first_seen.push(acnode); + let nmref = branch_ref!(acnode, K, V); + nmref.replace_by_idx(anode_idx, cnode); + CRCloneState::Clone(acnode) + }) + .unwrap_or_else(|| { + // Nope, just insert and unwind. + nmref.replace_by_idx(anode_idx, cnode); + CRCloneState::NoClone + }) + } + CRCloneState::NoClone => { + // Did not clone, unwind. + CRCloneState::NoClone + } + } + } +} + +fn clone_and_remove( + node: *mut Node, + txid: u64, + k: &K, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRRemoveState { + if self_meta!(node).is_leaf() { + leaf_ref!(node, K, V) + .req_clone(txid) + .map(|cnode| { + first_seen.push(cnode); + // println!("ls push 10 {:?}", node); + last_seen.push(node); + let mref = leaf_ref!(cnode, K, V); + match mref.remove(k) { + LeafRemoveState::Ok(res) => CRRemoveState::Clone(res, cnode), + LeafRemoveState::Shrink(res) => CRRemoveState::CloneShrink(res, cnode), + } + }) + .unwrap_or_else(|| { + let mref = leaf_ref!(node, K, V); + match mref.remove(k) { + LeafRemoveState::Ok(res) => CRRemoveState::NoClone(res), + LeafRemoveState::Shrink(res) => CRRemoveState::Shrink(res), + } + }) + } else { + // Locate the node we need to work on and then react if it + // requests a shrink. + branch_ref!(node, K, V) + .req_clone(txid) + .map(|cnode| { + first_seen.push(cnode); + // println!("ls push 11 {:?}", node); + last_seen.push(node); + // Done mm + let nmref = branch_ref!(cnode, K, V); + let anode_idx = nmref.locate_node(k); + let anode = nmref.get_idx_unchecked(anode_idx); + match clone_and_remove(anode, txid, k, last_seen, first_seen) { + CRRemoveState::NoClone(_res) => { + unreachable!("Should never occur"); + // CRRemoveState::NoClone(res) + } + CRRemoveState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + CRRemoveState::Clone(res, cnode) + } + CRRemoveState::Shrink(_res) => { + unreachable!("This represents a corrupt tree state"); + } + CRRemoveState::CloneShrink(res, nnode) => { + // Put our cloned child into the tree at the correct location, don't worry, + // the shrink_decision will deal with it. + nmref.replace_by_idx(anode_idx, nnode); + + // Now setup the sibling, to the left *or* right. + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + // Okay, now work out what we need to do. + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::Clone(res, cnode) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // println!("ls push 20 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Clone(res, cnode) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values. + // println!("ls push 21 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::CloneShrink(res, cnode) + } + } + } + } + }) + .unwrap_or_else(|| { + // We are already part of this txn + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(k); + let anode = nmref.get_idx_unchecked(anode_idx); + match clone_and_remove(anode, txid, k, last_seen, first_seen) { + CRRemoveState::NoClone(res) => CRRemoveState::NoClone(res), + CRRemoveState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + CRRemoveState::NoClone(res) + } + CRRemoveState::Shrink(res) => { + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::NoClone(res) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // + // A quirk here is based on how clone_sibling_idx works. We may actually + // start with anode_idx of 0, which triggers a right clone, so it's + // *already* in the mm lists. But here right is "last seen" now if + // + // println!("ls push 22 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::NoClone(res) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values, so we begin to shrink up. + // println!("ls push 23 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Shrink(res) + } + } + } + CRRemoveState::CloneShrink(res, nnode) => { + // We don't need to clone, just work on the nmref we have. + // + // Swap in the cloned node to the correct location. + nmref.replace_by_idx(anode_idx, nnode); + // Now setup the sibling, to the left *or* right. + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::NoClone(res) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // println!("ls push 24 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::NoClone(res) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values. + // println!("ls push 25 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Shrink(res) + } + } + } + } + }) // end unwrap_or_else + } +} + +fn path_get_mut_ref<'a, K: Clone + Ord + Debug, V: Clone>( + node: *mut Node, + k: &K, +) -> Option<&'a mut V> +where + K: 'a, +{ + if self_meta!(node).is_leaf() { + leaf_ref!(node, K, V).get_mut_ref(k) + } else { + // This nmref binds the life of the reference ... + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(k); + let anode = nmref.get_idx_unchecked(anode_idx); + // That we get here. So we can't just return it, and we need to 'strip' the + // lifetime so that it's bound to the lifetime of the outer node + // rather than the nmref. + let r: Option<*mut V> = path_get_mut_ref(anode, k).map(|v| v as *mut V); + + // I solemly swear I am up to no good. + r.map(|v| unsafe { &mut *v as &mut V }) + } +} + +/* +fn clone_and_split_off_trim_lt<'a, K: Clone + Ord + Debug, V: Clone>( + node: *mut Node, + txid: u64, + k: &K, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRTrimState { + if self_meta!(node).is_leaf() { + // No action, it's a leaf. Prune will do it. + CRTrimState::Complete + } else { + branch_ref!(node, K, V) + .req_clone(txid) + .map(|cnode| { + let nmref = branch_ref!(cnode, K, V); + first_seen.push(cnode as *mut Node); + last_seen.push(node as *mut Node); + match nmref.trim_lt_key(k, last_seen, first_seen) { + BranchTrimState::Complete => CRTrimState::Clone(cnode), + BranchTrimState::Promote(pnode) => { + // We just cloned it but oh well, away you go ... + last_seen.push(cnode as *mut Node); + CRTrimState::Promote(pnode) + } + } + }) + .unwrap_or_else(|| { + let nmref = branch_ref!(node, K, V); + + match nmref.trim_lt_key(k, last_seen, first_seen) { + BranchTrimState::Complete => CRTrimState::Complete, + BranchTrimState::Promote(pnode) => { + // We are about to remove our node, so mark it as the last time. + last_seen.push(node); + CRTrimState::Promote(pnode) + } + } + }) + } +} +*/ + +/* +fn clone_and_split_off_prune_lt<'a, K: Clone + Ord + Debug, V: Clone>( + node: &'a mut ABNode, + txid: usize, + k: &K, +) -> CRPruneState { + if node.is_leaf() { + // I think this should be do nothing, the up walk will clean. + if node.txid == txid { + let nmref = Arc::get_mut(node).unwrap().as_mut_leaf(); + match nmref.remove_lt(k) { + LeafPruneState::Ok => CRPruneState::OkNoClone, + LeafPruneState::Prune => CRPruneState::Prune, + } + } else { + let mut cnode = node.req_clone(txid); + let nmref = Arc::get_mut(&mut cnode).unwrap().as_mut_leaf(); + match nmref.remove_lt(k) { + LeafPruneState::Ok => CRPruneState::OkClone(cnode), + LeafPruneState::Prune => CRPruneState::ClonePrune(cnode), + } + } + } else { + if node.txid == txid { + let nmref = Arc::get_mut(node).unwrap().as_mut_branch(); + let anode_idx = nmref.locate_node(&k); + let anode = nmref.get_idx_unchecked(anode_idx); + let result = clone_and_split_off_prune_lt(anode, txid, k); + // println!("== clone_and_split_off_prune_lt --> {:?}", result); + match result { + CRPruneState::OkNoClone => { + match nmref.prune(anode_idx) { + Ok(_) => { + // Okay, the branch remains valid, return that we are okay, and + // no clone is needed. + CRPruneState::OkNoClone + } + Err(_) => CRPruneState::Prune, + } + } + CRPruneState::OkClone(clone) => { + // Our child cloned, so replace it. + nmref.replace_by_idx(anode_idx, clone); + // Check our node for anything else to be removed. + match nmref.prune(anode_idx) { + Ok(_) => { + // Okay, the branch remains valid, return that we are okay, and + // no clone is needed. + CRPruneState::OkNoClone + } + Err(_) => CRPruneState::Prune, + } + } + CRPruneState::Prune => { + match nmref.prune_decision(txid, anode_idx) { + Ok(_) => { + // Okay, the branch remains valid. Now we need to trim any + // excess if possible. + CRPruneState::OkNoClone + } + Err(_) => CRPruneState::Prune, + } + } + CRPruneState::ClonePrune(clone) => { + // Our child cloned, and intends to be removed. + nmref.replace_by_idx(anode_idx, clone); + // Now make the prune decision. + match nmref.prune_decision(txid, anode_idx) { + Ok(_) => { + // Okay, the branch remains valid. Now we need to trim any + // excess if possible. + CRPruneState::OkNoClone + } + Err(_) => CRPruneState::Prune, + } + } + } + } else { + let mut cnode = node.req_clone(txid); + let nmref = Arc::get_mut(&mut cnode).unwrap().as_mut_branch(); + let anode_idx = nmref.locate_node(&k); + let anode = nmref.get_idx_unchecked(anode_idx); + let result = clone_and_split_off_prune_lt(anode, txid, k); + // println!("!= clone_and_split_off_prune_lt --> {:?}", result); + match result { + CRPruneState::OkNoClone => { + // I think this is an impossible state - how can a child be in the + // txid if we are not? + unreachable!("Impossible tree state") + } + CRPruneState::OkClone(clone) => { + // Our child cloned, so replace it. + nmref.replace_by_idx(anode_idx, clone); + // Check our node for anything else to be removed. + match nmref.prune(anode_idx) { + Ok(_) => { + // Okay, the branch remains valid, return that we are okay. + CRPruneState::OkClone(cnode) + } + Err(_) => CRPruneState::ClonePrune(cnode), + } + } + CRPruneState::Prune => { + unimplemented!(); + } + CRPruneState::ClonePrune(clone) => { + // Our child cloned, and intends to be removed. + nmref.replace_by_idx(anode_idx, clone); + // Now make the prune decision. + match nmref.prune_decision(txid, anode_idx) { + Ok(_) => { + // Okay, the branch remains valid. Now we need to trim any + // excess if possible. + CRPruneState::OkClone(cnode) + } + Err(_) => CRPruneState::ClonePrune(cnode), + } + } // end clone prune + } // end match result + } + } +} +*/ + +#[cfg(test)] +mod tests { + use super::super::node::*; + use super::super::states::*; + use super::SuperBlock; + use super::{CursorRead, CursorReadOps}; + use crate::internals::lincowcell::LinCowCellCapable; + use rand::seq::SliceRandom; + use std::mem; + + fn create_leaf_node(v: usize) -> *mut Node { + let node = Node::new_leaf(1); + { + let nmut: &mut Leaf<_, _> = leaf_ref!(node, usize, usize); + nmut.insert_or_update(v, v); + } + node as *mut Node + } + + fn create_leaf_node_full(vbase: usize) -> *mut Node { + assert!(vbase % 10 == 0); + let node = Node::new_leaf(1); + { + let nmut = leaf_ref!(node, usize, usize); + for idx in 0..L_CAPACITY { + let v = vbase + idx; + nmut.insert_or_update(v, v); + } + // println!("lnode full {:?} -> {:?}", vbase, nmut); + } + node as *mut Node + } + + fn create_branch_node_full(vbase: usize) -> *mut Node { + let l1 = create_leaf_node(vbase); + let l2 = create_leaf_node(vbase + 10); + let lbranch = Node::new_branch(1, l1, l2); + let bref = branch_ref!(lbranch, usize, usize); + for i in 2..BV_CAPACITY { + let l = create_leaf_node(vbase + (10 * i)); + let r = bref.add_node(l); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + } + } + assert!(bref.count() == L_CAPACITY); + lbranch as *mut Node + } + + #[test] + fn test_bptree2_cursor_insert_leaf() { + // First create the node + cursor + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + eprintln!("{:?}", wcurs); + + let prev_txid = wcurs.root_txid(); + eprintln!("prev_txid {:?}", prev_txid); + + // Now insert - the txid should be different. + let r = wcurs.insert(1, 1); + assert!(r.is_none()); + eprintln!("get_root_ref {:?}", wcurs.get_root_ref().meta.get_txid()); + let r1_txid = wcurs.root_txid(); + assert!(r1_txid == prev_txid + 1); + + // Now insert again - the txid should be the same. + let r = wcurs.insert(2, 2); + assert!(r.is_none()); + let r2_txid = wcurs.root_txid(); + assert!(r2_txid == r1_txid); + // The clones worked as we wanted! + assert!(wcurs.verify()); + } + + #[test] + fn test_bptree2_cursor_insert_split_1() { + // Given a leaf at max, insert such that: + // + // leaf + // + // leaf -> split leaf + // + // + // root + // / \ + // leaf split leaf + // + // It's worth noting that this is testing the CloneSplit path + // as leaf needs a clone AND to split to achieve the new root. + + let node = create_leaf_node_full(10); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + let prev_txid = wcurs.root_txid(); + + let r = wcurs.insert(1, 1); + assert!(r.is_none()); + let r1_txid = wcurs.root_txid(); + assert!(r1_txid == prev_txid + 1); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_2() { + // Similar to split_1, but test the Split only path. This means + // leaf needs to be below max to start, and we insert enough in-txn + // to trigger a clone of leaf AND THEN to cause the split. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(L_CAPACITY + 1) { + // println!("ITER v {}", v); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_3() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split inbetween l/sl (new txn) + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + let r = wcurs.insert(19, 19); + assert!(r.is_none()); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_4() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split of sl (new txn) + // + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(29, 29); + assert!(r.is_none()); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_5() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split inbetween l/sl (same txn) + // + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Now insert to trigger the needed actions. + // Remember, we only need L_CAPACITY because there is already a + // value in the leaf. + for idx in 0..(L_CAPACITY) { + let v = 10 + 1 + idx; + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_6() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split of sl (same txn) + // + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Now insert to trigger the needed actions. + // Remember, we only need L_CAPACITY because there is already a + // value in the leaf. + for idx in 0..(L_CAPACITY) { + let v = 20 + 1 + idx; + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_7() { + // root + // / \ + // leaf split leaf + // Insert to leaf then split leaf such that root has cloned + // in step 1, but doesn't need clone in 2. + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(11, 11); + assert!(r.is_none()); + assert!(wcurs.verify()); + + let r = wcurs.insert(21, 21); + assert!(r.is_none()); + assert!(wcurs.verify()); + + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_split_8() { + // root + // / \ + // leaf split leaf + // ^ ^ + // \---- nnode 1 \----- nnode 2 + // + // Check double leaf split of sl (same txn). This is to + // take the clonesplit path in the branch case where branch already + // cloned. + // + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(19, 19); + assert!(r.is_none()); + assert!(wcurs.verify()); + + let r = wcurs.insert(29, 29); + assert!(r.is_none()); + assert!(wcurs.verify()); + + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_stress_1() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(L_CAPACITY << 4) { + // println!("ITER v {}", v); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_stress_2() { + // Insert descending + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in (1..(L_CAPACITY << 4)).rev() { + // println!("ITER v {}", v); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_stress_3() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + // Add transaction-ised versions. + #[test] + fn test_bptree2_cursor_insert_stress_4() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in 1..(L_CAPACITY << 4) { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_stress_5() { + // Insert descending + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in (1..(L_CAPACITY << 4)).rev() { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_insert_stress_6() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in ins.into_iter() { + let mut wcurs = sb.create_writer(); + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_search_1() { + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(L_CAPACITY << 4) { + let r = wcurs.insert(v, v); + assert!(r.is_none()); + let r = wcurs.search(&v); + assert!(r.unwrap() == &v); + } + + for v in 1..(L_CAPACITY << 4) { + let r = wcurs.search(&v); + assert!(r.unwrap() == &v); + } + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_length_1() { + // Check the length is consistent on operations. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(L_CAPACITY << 4) { + let r = wcurs.insert(v, v); + assert!(r.is_none()); + } + // println!("{} == {}", wcurs.len(), L_CAPACITY << 4); + assert!(wcurs.len() == L_CAPACITY << 4); + } + + #[test] + fn test_bptree2_cursor_remove_01_p0() { + // Check that a single value can be removed correctly without change. + // Check that a missing value is removed as "None". + // Check that emptying the root is ok. + // BOTH of these need new txns to check clone, and then re-use txns. + // + // + let lnode = create_leaf_node_full(0); + let sb = SuperBlock::new_test(1, lnode); + let mut wcurs = sb.create_writer(); + // println!("{:?}", wcurs); + + for v in 0..L_CAPACITY { + let x = wcurs.remove(&v); + // println!("{:?}", wcurs); + assert!(x == Some(v)); + } + + for v in 0..L_CAPACITY { + let x = wcurs.remove(&v); + assert!(x == None); + } + + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_01_p1() { + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + let _ = wcurs.remove(&0); + // println!("{:?}", wcurs); + + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_02() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (new txn) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + // println!("{:?}", wcurs); + assert!(wcurs.verify()); + + wcurs.remove(&20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_03() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "leaf" and merge right (really left, but you know ...). (new txn) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(30); + let root = Node::new_branch(0, lnode, rnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(znode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&10); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_04p0() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup sibling leaf to already be cloned. + wcurs.path_clone(&10); + assert!(wcurs.verify()); + + wcurs.remove(&20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_04p1() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup leaf to already be cloned. + wcurs.path_clone(&20); + assert!(wcurs.verify()); + + wcurs.remove(&20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_05() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "leaf" and merge 'right'. (split leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(30); + let root = Node::new_branch(0, lnode, rnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(znode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup leaf to already be cloned. + wcurs.path_clone(&20); + + wcurs.remove(&10); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_06() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - new + // + // when remove from rbranch, mergc left to lbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + + wcurs.remove(&30); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_07() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - new + // + // when remove from lbranch, merge right to rbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_08() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - full + // rbranch - 2node + // txn - new + // + // when remove from rbranch, borrow from lbranch + // will NOT reduce height + let lbranch = create_branch_node_full(0); + + let r1 = create_leaf_node(80); + let r2 = create_leaf_node(90); + let rbranch = Node::new_branch(0, r1, r2); + + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&80); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_09() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - full + // txn - new + // + // when remove from lbranch, borrow from rbranch + // will NOT reduce height + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let lbranch = Node::new_branch(0, l1, l2); + + let rbranch = create_branch_node_full(100); + + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_10() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - touch lbranch + // + // when remove from rbranch, mergc left to lbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + + wcurs.path_clone(&0); + wcurs.path_clone(&10); + + wcurs.remove(&30); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_11() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - touch rbranch + // + // when remove from lbranch, merge right to rbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.path_clone(&20); + wcurs.path_clone(&30); + + wcurs.remove(&0); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_12() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - full + // rbranch - 2node + // txn - touch lbranch + // + // when remove from rbranch, borrow from lbranch + // will NOT reduce height + let lbranch = create_branch_node_full(0); + + let r1 = create_leaf_node(80); + let r2 = create_leaf_node(90); + let rbranch = Node::new_branch(0, r1, r2); + + let root = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + // let count = BV_CAPACITY + 2; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.path_clone(&0); + wcurs.path_clone(&10); + wcurs.path_clone(&20); + + wcurs.remove(&90); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_13() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - full + // txn - touch rbranch + // + // when remove from lbranch, borrow from rbranch + // will NOT reduce height + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let lbranch = Node::new_branch(0, l1, l2); + + let rbranch = create_branch_node_full(100); + + let root = Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + for i in 0..BV_CAPACITY { + let k = 100 + (10 * i); + wcurs.path_clone(&k); + } + assert!(wcurs.verify()); + + wcurs.remove(&10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_14() { + // Test leaf borrow left + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&20); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_15() { + // Test leaf borrow right. + let lnode = create_leaf_node(10) as *mut Node; + let rnode = create_leaf_node_full(20) as *mut Node; + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(&10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + fn tree_create_rand() -> (SuperBlock, CursorRead) { + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let mut sb = unsafe { SuperBlock::new() }; + let rdr = sb.create_reader(); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.insert(v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + let rdr = sb.pre_commit(wcurs, &rdr); + (sb, rdr) + } + + #[test] + fn test_bptree2_cursor_remove_stress_1() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in 1..(L_CAPACITY << 4) { + // println!("-- ITER v {}", v); + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + let rdr2 = sb.pre_commit(wcurs, &rdr); + // On shutdown, check we dropped all as needed. + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_stress_2() { + // Insert descending + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in (1..(L_CAPACITY << 4)).rev() { + // println!("ITER v {}", v); + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + let rdr2 = sb.pre_commit(wcurs, &rdr); + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_stress_3() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + let rdr2 = sb.pre_commit(wcurs, &rdr); + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + // Add transaction-ised versions. + #[test] + fn test_bptree2_cursor_remove_stress_4() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let (mut sb, mut rdr) = tree_create_rand(); + + for v in 1..(L_CAPACITY << 4) { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_stress_5() { + // Insert descending + let (mut sb, mut rdr) = tree_create_rand(); + + for v in (1..(L_CAPACITY << 4)).rev() { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_remove_stress_6() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(L_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let (mut sb, mut rdr) = tree_create_rand(); + + for v in ins.into_iter() { + let mut wcurs = sb.create_writer(); + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + /* + #[test] + #[cfg_attr(miri, ignore)] + fn test_bptree2_cursor_remove_stress_7() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..10240).collect(); + + let node: *mut Leaf = Node::new_leaf(0); + let mut wcurs = CursorWrite::new_test(1, node as *mut _); + wcurs.extend(ins.iter().map(|v| (*v, *v))); + + ins.shuffle(&mut rng); + + let compacts = 0; + + for v in ins.into_iter() { + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + // let (l, m) = wcurs.tree_density(); + // if l > 0 && (m / l) > 1 { + // compacts += 1; + // } + } + println!("compacts {:?}", compacts); + } + */ + + // This is for setting up trees that are specialised for the split off tests. + // This is because we can exercise a LOT of complex edge cases by bracketing + // within this tree. It also works on both node sizes. + // + // This is a 16 node tree, with 4 branches and a root. We have 2 values per leaf to + // allow some cases to be explored. We also need "gaps" between the values to allow other + // cases. + // + // Effectively this means we can test by splitoff on the values: + // for i in [0,100,200,300]: + // for j in [0, 10, 20, 30]: + // t1 = i + j + // t2 = i + j + 1 + // t3 = i + j + 2 + // t4 = i + j + 3 + // + fn create_split_off_leaf(base: usize) -> *mut Node { + let l = Node::new_leaf(0); + let lref = leaf_ref!(l, usize, usize); + lref.insert_or_update(base + 1, base + 1); + lref.insert_or_update(base + 2, base + 2); + l as *mut _ + } + + fn create_split_off_branch(base: usize) -> *mut Node { + // This is a helper for create_split_off_tree to make the sub-branches based + // on a base. + let l1 = create_split_off_leaf(base); + let l2 = create_split_off_leaf(base + 10); + let l3 = create_split_off_leaf(base + 20); + let l4 = create_split_off_leaf(base + 30); + + let branch = Node::new_branch(0, l1, l2); + let nref = branch_ref!(branch, usize, usize); + nref.add_node(l3); + nref.add_node(l4); + + branch as *mut _ + } + + fn create_split_off_tree() -> *mut Node { + let b1 = create_split_off_branch(0); + let b2 = create_split_off_branch(100); + let b3 = create_split_off_branch(200); + let b4 = create_split_off_branch(300); + let root = Node::new_branch(0, b1, b2); + let nref = branch_ref!(root, usize, usize); + nref.add_node(b3); + nref.add_node(b4); + + root as *mut _ + } + + #[test] + fn test_bptree2_cursor_split_off_lt_01() { + // Make a tree witth just a leaf + // Do a split_off_lt. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + wcurs.split_off_lt(&5); + + // Remember, all the cases of the remove_lte are already tested on + // leaf. + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_split_off_lt_02() { + // Make a tree witth just a leaf + // Do a split_off_lt. + let node = create_leaf_node_full(10); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + wcurs.split_off_lt(&11); + + // Remember, all the cases of the remove_lte are already tested on + // leaf. + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_split_off_lt_03() { + // Make a tree witth just a leaf + // Do a split_off_lt. + let node = create_leaf_node_full(10); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + wcurs.path_clone(&11); + wcurs.split_off_lt(&11); + + // Remember, all the cases of the remove_lte are already tested on + // leaf. + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + fn run_split_off_test_clone(v: usize, _exp: usize) { + // println!("RUNNING -> {:?}", v); + let tree = create_split_off_tree(); + + let sb = SuperBlock::new_test(1, tree); + let mut wcurs = sb.create_writer(); + // 0 is min, and not present, will cause no change. + // clone everything + let outer: [usize; 4] = [0, 100, 200, 300]; + let inner: [usize; 4] = [0, 10, 20, 30]; + for i in outer.iter() { + for j in inner.iter() { + wcurs.path_clone(&(i + j + 1)); + } + } + + wcurs.split_off_lt(&v); + assert!(wcurs.verify()); + if v > 0 { + assert!(!wcurs.contains_key(&(v - 1))); + } + // assert!(wcurs.len() == exp); + + // println!("{:?}", wcurs); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + fn run_split_off_test(v: usize, _exp: usize) { + // println!("RUNNING -> {:?}", v); + let tree = create_split_off_tree(); + // println!("START -> {:?}", tree); + + let sb = SuperBlock::new_test(1, tree); + let mut wcurs = sb.create_writer(); + // 0 is min, and not present, will cause no change. + wcurs.split_off_lt(&v); + assert!(wcurs.verify()); + if v > 0 { + assert!(!wcurs.contains_key(&(v - 1))); + } + // assert!(wcurs.len() == exp); + + // println!("{:?}", wcurs); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_bptree2_cursor_split_off_lt_clone_stress() { + // Can't proceed as the "fake" tree we make is invalid. + debug_assert!(L_CAPACITY >= 4); + let outer: [usize; 4] = [0, 100, 200, 300]; + let inner: [usize; 4] = [0, 10, 20, 30]; + for i in outer.iter() { + for j in inner.iter() { + run_split_off_test_clone(i + j, 32); + run_split_off_test_clone(i + j + 1, 32); + run_split_off_test_clone(i + j + 2, 32); + run_split_off_test_clone(i + j + 3, 32); + } + } + } + + #[test] + fn test_bptree2_cursor_split_off_lt_stress() { + debug_assert!(L_CAPACITY >= 4); + let outer: [usize; 4] = [0, 100, 200, 300]; + let inner: [usize; 4] = [0, 10, 20, 30]; + for i in outer.iter() { + for j in inner.iter() { + run_split_off_test(i + j, 32); + run_split_off_test(i + j + 1, 32); + run_split_off_test(i + j + 2, 32); + run_split_off_test(i + j + 3, 32); + } + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_bptree2_cursor_split_off_lt_random_stress() { + let data: Vec = (0..1024).collect(); + + for v in data.iter() { + let node: *mut Leaf = Node::new_leaf(0) as *mut _; + let sb = SuperBlock::new_test(1, node as *mut Node); + let mut wcurs = sb.create_writer(); + wcurs.extend(data.iter().map(|v| (*v, *v))); + + if v > &0 { + assert!(wcurs.contains_key(&(v - 1))); + } + + wcurs.split_off_lt(&v); + assert!(!wcurs.contains_key(&(v - 1))); + if v < &1024 { + assert!(wcurs.contains_key(&v)); + } + assert!(wcurs.verify()); + let contents: Vec<_> = wcurs.k_iter().collect(); + assert!(contents[0] == v); + assert!(contents.len() as isize == (1024 - v)); + } + } + + /* + #[test] + fn test_bptree_cursor_get_mut_ref_1() { + // Test that we can clone a path (new txn) + // Test that we don't re-clone. + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let mut wcurs = CursorWrite::new(root, 0); + assert!(wcurs.verify()); + + let r1 = wcurs.get_mut_ref(&10); + std::mem::drop(r1); + let r1 = wcurs.get_mut_ref(&10); + std::mem::drop(r1); + } + */ +} diff --git a/vendor/concread/src/internals/bptree/iter.rs b/vendor/concread/src/internals/bptree/iter.rs new file mode 100644 index 0000000..b50f84a --- /dev/null +++ b/vendor/concread/src/internals/bptree/iter.rs @@ -0,0 +1,367 @@ +//! Iterators for the map. + +// Iterators for the bptree +use super::node::{Branch, Leaf, Meta, Node}; +use std::collections::VecDeque; +use std::fmt::Debug; +use std::marker::PhantomData; + +pub(crate) struct LeafIter<'a, K, V> +where + K: Ord + Clone + Debug, + V: Clone, +{ + length: Option, + // idx: usize, + stack: VecDeque<(*mut Node, usize)>, + phantom_k: PhantomData<&'a K>, + phantom_v: PhantomData<&'a V>, +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> LeafIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, size_hint: bool) -> Self { + let length = if size_hint { + Some(unsafe { (*root).leaf_count() }) + } else { + None + }; + + // We probably need to position the VecDeque here. + let mut stack = VecDeque::new(); + + let mut work_node = root; + loop { + stack.push_back((work_node, 0)); + if self_meta!(work_node).is_leaf() { + break; + } else { + work_node = branch_ref!(work_node, K, V).get_idx_unchecked(0); + } + } + + LeafIter { + length, + // idx: 0, + stack, + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + #[cfg(test)] + pub(crate) fn new_base() -> Self { + LeafIter { + length: None, + // idx: 0, + stack: VecDeque::new(), + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + pub(crate) fn stack_position(&mut self, idx: usize) { + // Get the current branch, it must the the back. + if let Some((bref, bpidx)) = self.stack.back() { + let wbranch = branch_ref!(*bref, K, V); + if let Some(node) = wbranch.get_idx_checked(idx) { + // Insert as much as possible now. First insert + // our current idx, then all the 0, idxs. + let mut work_node = node; + let mut work_idx = idx; + loop { + self.stack.push_back((work_node, work_idx)); + if self_meta!(work_node).is_leaf() { + break; + } else { + work_idx = 0; + work_node = branch_ref!(work_node, K, V).get_idx_unchecked(work_idx); + } + } + } else { + // Unwind further. + let bpidx = *bpidx + 1; + let _ = self.stack.pop_back(); + self.stack_position(bpidx) + } + } + // Must have been none, so we are exhausted. This means + // the stack is empty, so return. + } + + /* + fn peek(&'a mut self) -> Option<&'a Leaf> { + // I have no idea how peekable works, yolo. + self.stack.back().map(|t| t.0.as_leaf()) + } + */ +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> Iterator for LeafIter<'a, K, V> { + type Item = &'a Leaf; + + fn next(&mut self) -> Option { + // base case is the vecdeque is empty + let (leafref, parent_idx) = match self.stack.pop_back() { + Some(lr) => lr, + None => return None, + }; + + // Setup the veqdeque for the next iteration. + self.stack_position(parent_idx + 1); + + // Return the leaf as we found at the start, regardless of the + // stack operations. + Some(leaf_ref!(leafref, K, V)) + } + + fn size_hint(&self) -> (usize, Option) { + match self.length { + Some(l) => (l, Some(l)), + // We aren't (shouldn't) be estimating + None => (0, None), + } + } +} + +/// Iterator over references to Key Value pairs stored in the map. +pub struct Iter<'a, K, V> +where + K: Ord + Clone + Debug, + V: Clone, +{ + length: usize, + idx: usize, + curleaf: Option<&'a Leaf>, + leafiter: LeafIter<'a, K, V>, +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> Iter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + let mut liter = LeafIter::new(root, false); + let leaf = liter.next(); + // We probably need to position the VecDeque here. + Iter { + length, + idx: 0, + curleaf: leaf, + leafiter: liter, + } + } +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + if let Some(leaf) = self.curleaf { + if let Some(r) = leaf.get_kv_idx_checked(self.idx) { + self.idx += 1; + Some(r) + } else { + self.curleaf = self.leafiter.next(); + self.idx = 0; + self.next() + } + } else { + None + } + } + + /// Provide a hint as to the number of items this iterator will yield. + fn size_hint(&self) -> (usize, Option) { + (self.length, Some(self.length)) + } +} + +/// Iterater over references to Keys stored in the map. +pub struct KeyIter<'a, K, V> +where + K: Ord + Clone + Debug, + V: Clone, +{ + iter: Iter<'a, K, V>, +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> KeyIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + KeyIter { + iter: Iter::new(root, length), + } + } +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> Iterator for KeyIter<'a, K, V> { + type Item = &'a K; + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + self.iter.next().map(|(k, _)| k) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// Iterater over references to Values stored in the map. +pub struct ValueIter<'a, K, V> +where + K: Ord + Clone + Debug, + V: Clone, +{ + iter: Iter<'a, K, V>, +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> ValueIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + ValueIter { + iter: Iter::new(root, length), + } + } +} + +impl<'a, K: Clone + Ord + Debug, V: Clone> Iterator for ValueIter<'a, K, V> { + type Item = &'a V; + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + self.iter.next().map(|(_, v)| v) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +#[cfg(test)] +mod tests { + use super::super::cursor::SuperBlock; + use super::super::node::{Branch, Leaf, Node, L_CAPACITY}; + use super::{Iter, LeafIter}; + + fn create_leaf_node_full(vbase: usize) -> *mut Node { + assert!(vbase % 10 == 0); + let node = Node::new_leaf(0); + { + let nmut = leaf_ref!(node, usize, usize); + for idx in 0..L_CAPACITY { + let v = vbase + idx; + nmut.insert_or_update(v, v); + } + } + node as *mut _ + } + + #[test] + fn test_bptree2_iter_leafiter_1() { + let test_iter: LeafIter = LeafIter::new_base(); + assert!(test_iter.count() == 0); + } + + #[test] + fn test_bptree2_iter_leafiter_2() { + let lnode = create_leaf_node_full(10); + let mut test_iter = LeafIter::new(lnode, true); + + assert!(test_iter.size_hint() == (1, Some(1))); + + let lref = test_iter.next().unwrap(); + assert!(lref.min() == &10); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, lnode as *mut _); + } + + #[test] + fn test_bptree2_iter_leafiter_3() { + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let mut test_iter: LeafIter = LeafIter::new(root as *mut _, true); + + assert!(test_iter.size_hint() == (2, Some(2))); + let lref = test_iter.next().unwrap(); + let rref = test_iter.next().unwrap(); + assert!(lref.min() == &10); + assert!(rref.min() == &20); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_bptree2_iter_leafiter_4() { + let l1node = create_leaf_node_full(10); + let r1node = create_leaf_node_full(20); + let l2node = create_leaf_node_full(30); + let r2node = create_leaf_node_full(40); + let b1node = Node::new_branch(0, l1node, r1node); + let b2node = Node::new_branch(0, l2node, r2node); + let root: *mut Branch = + Node::new_branch(0, b1node as *mut _, b2node as *mut _); + let mut test_iter: LeafIter = LeafIter::new(root as *mut _, true); + + assert!(test_iter.size_hint() == (4, Some(4))); + let l1ref = test_iter.next().unwrap(); + let r1ref = test_iter.next().unwrap(); + let l2ref = test_iter.next().unwrap(); + let r2ref = test_iter.next().unwrap(); + assert!(l1ref.min() == &10); + assert!(r1ref.min() == &20); + assert!(l2ref.min() == &30); + assert!(r2ref.min() == &40); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_bptree2_iter_leafiter_5() { + let lnode = create_leaf_node_full(10); + let mut test_iter = LeafIter::new(lnode, true); + + assert!(test_iter.size_hint() == (1, Some(1))); + + let lref = test_iter.next().unwrap(); + assert!(lref.min() == &10); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, lnode as *mut _); + } + + #[test] + fn test_bptree2_iter_iter_1() { + // Make a tree + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let test_iter: Iter = Iter::new(root as *mut _, L_CAPACITY * 2); + + assert!(test_iter.size_hint() == (L_CAPACITY * 2, Some(L_CAPACITY * 2))); + assert!(test_iter.count() == L_CAPACITY * 2); + // Iterate! + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_bptree2_iter_iter_2() { + let l1node = create_leaf_node_full(10); + let r1node = create_leaf_node_full(20); + let l2node = create_leaf_node_full(30); + let r2node = create_leaf_node_full(40); + let b1node = Node::new_branch(0, l1node, r1node); + let b2node = Node::new_branch(0, l2node, r2node); + let root: *mut Branch = + Node::new_branch(0, b1node as *mut _, b2node as *mut _); + let test_iter: Iter = Iter::new(root as *mut _, L_CAPACITY * 4); + + // println!("{:?}", test_iter.size_hint()); + + assert!(test_iter.size_hint() == (L_CAPACITY * 4, Some(L_CAPACITY * 4))); + assert!(test_iter.count() == L_CAPACITY * 4); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } +} diff --git a/vendor/concread/src/internals/bptree/macros.rs b/vendor/concread/src/internals/bptree/macros.rs new file mode 100644 index 0000000..b776e28 --- /dev/null +++ b/vendor/concread/src/internals/bptree/macros.rs @@ -0,0 +1,39 @@ +macro_rules! debug_assert_leaf { + ($x:expr) => {{ + debug_assert!($x.meta.is_leaf()); + }}; +} + +macro_rules! debug_assert_branch { + ($x:expr) => {{ + debug_assert!($x.meta.is_branch()); + }}; +} + +macro_rules! self_meta { + ($x:expr) => {{ + unsafe { &mut *($x as *mut Meta) } + }}; +} + +macro_rules! branch_ref { + ($x:expr, $k:ty, $v:ty) => {{ + debug_assert!(unsafe { (*$x).meta.is_branch() }); + unsafe { &mut *($x as *mut Branch<$k, $v>) } + }}; +} + +macro_rules! leaf_ref { + ($x:expr, $k:ty, $v:ty) => {{ + debug_assert!(unsafe { (*$x).meta.is_leaf() }); + unsafe { &mut *($x as *mut Leaf<$k, $v>) } + }}; +} + +macro_rules! key_search { + ($self:expr, $k:expr) => {{ + let (left, _) = $self.key.split_at($self.count()); + let inited: &[K] = unsafe { slice::from_raw_parts(left.as_ptr() as *const K, left.len()) }; + slice_search_linear(inited, $k) + }}; +} diff --git a/vendor/concread/src/internals/bptree/mod.rs b/vendor/concread/src/internals/bptree/mod.rs new file mode 100644 index 0000000..34c3485 --- /dev/null +++ b/vendor/concread/src/internals/bptree/mod.rs @@ -0,0 +1,8 @@ +//! Nothing to see here. + +#[macro_use] +pub(crate) mod macros; +pub(crate) mod cursor; +pub mod iter; +pub(crate) mod node; +pub(crate) mod states; diff --git a/vendor/concread/src/internals/bptree/node.rs b/vendor/concread/src/internals/bptree/node.rs new file mode 100644 index 0000000..499b13f --- /dev/null +++ b/vendor/concread/src/internals/bptree/node.rs @@ -0,0 +1,2513 @@ +use super::states::*; +use crate::utils::*; +// use libc::{c_void, mprotect, PROT_READ, PROT_WRITE}; +use crossbeam::utils::CachePadded; +use std::borrow::Borrow; +use std::fmt::{self, Debug, Error}; +use std::marker::PhantomData; +use std::mem::MaybeUninit; +use std::ptr; +use std::slice; + +#[cfg(all(test, not(miri)))] +use parking_lot::Mutex; +#[cfg(test)] +use std::collections::BTreeSet; +#[cfg(all(test, not(miri)))] +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub(crate) const TXID_MASK: u64 = 0x0fff_ffff_ffff_fff0; +const FLAG_MASK: u64 = 0xf000_0000_0000_0000; +const COUNT_MASK: u64 = 0x0000_0000_0000_000f; +pub(crate) const TXID_SHF: usize = 4; +const FLAG_BRANCH: u64 = 0x1000_0000_0000_0000; +const FLAG_LEAF: u64 = 0x2000_0000_0000_0000; +const FLAG_INVALID: u64 = 0x4000_0000_0000_0000; +// const FLAG_HASH: u64 = 0x4000_0000_0000_0000; +// const FLAG_BUCKET: u64 = 0x8000_0000_0000_0000; +const FLAG_DROPPED: u64 = 0xaaaa_bbbb_cccc_dddd; + +#[cfg(feature = "skinny")] +pub(crate) const L_CAPACITY: usize = 3; +#[cfg(feature = "skinny")] +const L_CAPACITY_N1: usize = L_CAPACITY - 1; +#[cfg(feature = "skinny")] +pub(crate) const BV_CAPACITY: usize = L_CAPACITY + 1; + +#[cfg(not(feature = "skinny"))] +pub(crate) const L_CAPACITY: usize = 7; +#[cfg(not(feature = "skinny"))] +const L_CAPACITY_N1: usize = L_CAPACITY - 1; +#[cfg(not(feature = "skinny"))] +pub(crate) const BV_CAPACITY: usize = L_CAPACITY + 1; + +#[cfg(all(test, not(miri)))] +thread_local!(static NODE_COUNTER: AtomicUsize = AtomicUsize::new(1)); +#[cfg(all(test, not(miri)))] +thread_local!(static ALLOC_LIST: Mutex> = Mutex::new(BTreeSet::new())); + +#[cfg(all(test, not(miri)))] +fn alloc_nid() -> usize { + let nid: usize = NODE_COUNTER.with(|nc| nc.fetch_add(1, Ordering::AcqRel)); + #[cfg(all(test, not(miri)))] + { + ALLOC_LIST.with(|llist| llist.lock().insert(nid)); + } + // eprintln!("Allocate -> {:?}", nid); + nid +} + +#[cfg(all(test, not(miri)))] +fn release_nid(nid: usize) { + // println!("Release -> {:?}", nid); + // debug_assert!(nid != 3); + let r = ALLOC_LIST.with(|llist| llist.lock().remove(&nid)); + assert!(r == true); +} + +#[cfg(test)] +pub(crate) fn assert_released() { + #[cfg(not(miri))] + { + let is_empt = ALLOC_LIST.with(|llist| { + let x = llist.lock(); + eprintln!("Remaining -> {:?}", x); + x.is_empty() + }); + assert!(is_empt); + } +} + +#[repr(C)] +pub(crate) struct Meta(u64); + +#[repr(C)] +pub(crate) struct Branch +where + K: Ord + Clone + Debug, + V: Clone, +{ + pub(crate) meta: Meta, + key: [MaybeUninit; L_CAPACITY], + nodes: [*mut Node; BV_CAPACITY], + #[cfg(all(test, not(miri)))] + pub(crate) nid: usize, +} + +#[repr(C)] +pub(crate) struct Leaf +where + K: Ord + Clone + Debug, + V: Clone, +{ + pub(crate) meta: Meta, + key: [MaybeUninit; L_CAPACITY], + values: [MaybeUninit; L_CAPACITY], + #[cfg(all(test, not(miri)))] + pub(crate) nid: usize, +} + +#[repr(C)] +pub(crate) struct Node { + pub(crate) meta: Meta, + k: PhantomData, + v: PhantomData, +} + +/* +pub(crate) union NodeX +where + K: Ord + Clone + Debug, + V: Clone, +{ + meta: Meta, + leaf: Leaf, + branch: Branch, +} +*/ + +impl Node { + pub(crate) fn new_leaf(txid: u64) -> *mut Leaf { + // println!("Req new leaf"); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + let x: Box>> = Box::new(CachePadded::new(Leaf { + meta: Meta((txid << TXID_SHF) | FLAG_LEAF), + key: unsafe { MaybeUninit::uninit().assume_init() }, + values: unsafe { MaybeUninit::uninit().assume_init() }, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + Box::into_raw(x) as *mut Leaf + } + + fn new_leaf_ins(flags: u64, k: K, v: V) -> *mut Leaf { + // println!("Req new leaf ins"); + // debug_assert!(false); + debug_assert!((flags & FLAG_MASK) == FLAG_LEAF); + // Let the flag, txid and the count of value 1 through. + let txid = flags & (TXID_MASK | FLAG_MASK | 1); + let x: Box>> = Box::new(CachePadded::new(Leaf { + meta: Meta(txid), + #[cfg(feature = "skinny")] + key: [ + MaybeUninit::new(k), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(not(feature = "skinny"))] + key: [ + MaybeUninit::new(k), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(feature = "skinny")] + values: [ + MaybeUninit::new(v), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(not(feature = "skinny"))] + values: [ + MaybeUninit::new(v), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + Box::into_raw(x) as *mut Leaf + } + + pub(crate) fn new_branch( + txid: u64, + l: *mut Node, + r: *mut Node, + ) -> *mut Branch { + // println!("Req new branch"); + debug_assert!(!l.is_null()); + debug_assert!(!r.is_null()); + debug_assert!(unsafe { (*l).verify() }); + debug_assert!(unsafe { (*r).verify() }); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + let x: Box>> = Box::new(CachePadded::new(Branch { + // This sets the default (key) count to 1, since we take an l/r + meta: Meta((txid << TXID_SHF) | FLAG_BRANCH | 1), + #[cfg(feature = "skinny")] + key: [ + MaybeUninit::new(unsafe { (*r).min().clone() }), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(not(feature = "skinny"))] + key: [ + MaybeUninit::new(unsafe { (*r).min().clone() }), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(feature = "skinny")] + nodes: [l, r, ptr::null_mut(), ptr::null_mut()], + #[cfg(not(feature = "skinny"))] + nodes: [ + l, + r, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ], + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + debug_assert!(x.verify()); + Box::into_raw(x) as *mut Branch + } + + #[inline(always)] + pub(crate) fn make_ro(&self) { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.make_ro() + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.make_ro() + } + _ => unreachable!(), + } + } + + #[inline(always)] + #[cfg(test)] + pub(crate) fn get_txid(&self) -> u64 { + self.meta.get_txid() + } + + #[inline(always)] + pub(crate) fn is_leaf(&self) -> bool { + self.meta.is_leaf() + } + + #[allow(unused)] + #[inline(always)] + pub(crate) fn is_branch(&self) -> bool { + self.meta.is_branch() + } + + #[cfg(test)] + pub(crate) fn tree_density(&self) -> (usize, usize) { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + (lref.count(), L_CAPACITY) + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + let mut lcount = 0; // leaf populated + let mut mcount = 0; // leaf max possible + for idx in 0..(bref.count() + 1) { + let n = bref.nodes[idx] as *mut Node; + let (l, m) = unsafe { (*n).tree_density() }; + lcount += l; + mcount += m; + } + (lcount, mcount) + } + _ => unreachable!(), + } + } + + pub(crate) fn leaf_count(&self) -> usize { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => 1, + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + let mut lcount = 0; // leaf count + for idx in 0..(bref.count() + 1) { + let n = bref.nodes[idx] as *mut Node; + lcount += unsafe { (*n).leaf_count() }; + } + lcount + } + _ => unreachable!(), + } + } + + #[cfg(test)] + #[inline(always)] + pub(crate) fn get_ref(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord, + { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.get_ref(k) + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.get_ref(k) + } + _ => { + // println!("FLAGS: {:x}", self.meta.0); + unreachable!() + } + } + } + + #[inline(always)] + pub(crate) fn min(&self) -> &K { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.min() + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.min() + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn max(&self) -> &K { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.max() + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.max() + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn verify(&self) -> bool { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.verify() + } + FLAG_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.verify() + } + _ => unreachable!(), + } + } + + #[cfg(test)] + fn no_cycles_inner(&self, track: &mut BTreeSet<*const Self>) -> bool { + match self.meta.0 & FLAG_MASK { + FLAG_LEAF => { + // check if we are in the set? + track.insert(self as *const Self) + } + FLAG_BRANCH => { + if track.insert(self as *const Self) { + // check + let bref = unsafe { &*(self as *const _ as *const Branch) }; + for i in 0..(bref.count() + 1) { + let n = bref.nodes[i]; + let r = unsafe { (*n).no_cycles_inner(track) }; + if r == false { + // panic!(); + return false; + } + } + true + } else { + // panic!(); + false + } + } + _ => { + // println!("FLAGS: {:x}", self.meta.0); + unreachable!() + } + } + } + + #[cfg(test)] + pub(crate) fn no_cycles(&self) -> bool { + let mut track = BTreeSet::new(); + self.no_cycles_inner(&mut track) + } + + pub(crate) fn sblock_collect(&mut self, alloc: &mut Vec<*mut Node>) { + // Reset our txid. + // self.meta.0 &= FLAG_MASK | COUNT_MASK; + // self.meta.0 |= txid << TXID_SHF; + + if (self.meta.0 & FLAG_MASK) == FLAG_BRANCH { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + for idx in 0..(bref.count() + 1) { + alloc.push(bref.nodes[idx]); + let n = bref.nodes[idx] as *mut Node; + unsafe { (*n).sblock_collect(alloc) }; + } + } + } + + pub(crate) fn free(node: *mut Node) { + let self_meta = self_meta!(node); + match self_meta.0 & FLAG_MASK { + FLAG_LEAF => Leaf::free(node as *mut Leaf), + FLAG_BRANCH => Branch::free(node as *mut Branch), + _ => unreachable!(), + } + } +} + +impl Meta { + #[inline(always)] + fn set_count(&mut self, c: usize) { + debug_assert!(c < 16); + // Zero the bits in the flag from the count. + self.0 &= FLAG_MASK | TXID_MASK; + // Assign them. + self.0 |= c as u8 as u64; + } + + #[inline(always)] + pub(crate) fn count(&self) -> usize { + (self.0 & COUNT_MASK) as usize + } + + #[inline(always)] + fn add_count(&mut self, x: usize) { + self.set_count(self.count() + x); + } + + #[inline(always)] + fn inc_count(&mut self) { + debug_assert!(self.count() < 15); + // Since count is the lowest bits, we can just inc + // dec this as normal. + self.0 += 1; + } + + #[inline(always)] + fn dec_count(&mut self) { + debug_assert!(self.count() > 0); + self.0 -= 1; + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + (self.0 & TXID_MASK) >> TXID_SHF + } + + #[inline(always)] + pub(crate) fn is_leaf(&self) -> bool { + (self.0 & FLAG_MASK) == FLAG_LEAF + } + + #[inline(always)] + pub(crate) fn is_branch(&self) -> bool { + (self.0 & FLAG_MASK) == FLAG_BRANCH + } +} + +impl Leaf { + #[inline(always)] + #[cfg(test)] + fn set_count(&mut self, c: usize) { + debug_assert_leaf!(self); + self.meta.set_count(c) + } + + #[inline(always)] + pub(crate) fn count(&self) -> usize { + debug_assert_leaf!(self); + self.meta.count() + } + + #[inline(always)] + fn inc_count(&mut self) { + debug_assert_leaf!(self); + self.meta.inc_count() + } + + #[inline(always)] + fn dec_count(&mut self) { + debug_assert_leaf!(self); + self.meta.dec_count() + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + debug_assert_leaf!(self); + self.meta.get_txid() + } + + pub(crate) fn get_ref(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord, + { + debug_assert_leaf!(self); + key_search!(self, k) + .ok() + .map(|idx| unsafe { &*self.values[idx].as_ptr() }) + } + + pub(crate) fn get_mut_ref(&mut self, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Ord, + { + debug_assert_leaf!(self); + key_search!(self, k) + .ok() + .map(|idx| unsafe { &mut *self.values[idx].as_mut_ptr() }) + } + + #[inline(always)] + pub(crate) fn get_kv_idx_checked(&self, idx: usize) -> Option<(&K, &V)> { + debug_assert_leaf!(self); + if idx < self.count() { + Some((unsafe { &*self.key[idx].as_ptr() }, unsafe { + &*self.values[idx].as_ptr() + })) + } else { + None + } + } + + pub(crate) fn min(&self) -> &K { + debug_assert!(self.count() > 0); + unsafe { &*self.key[0].as_ptr() } + } + + pub(crate) fn max(&self) -> &K { + debug_assert!(self.count() > 0); + unsafe { &*self.key[self.count() - 1].as_ptr() } + } + + pub(crate) fn req_clone(&self, txid: u64) -> Option<*mut Node> { + debug_assert_leaf!(self); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + if self.get_txid() == txid { + // Same txn, no action needed. + None + } else { + debug_assert!(txid > self.get_txid()); + // eprintln!("Req clone leaf"); + // debug_assert!(false); + // Diff txn, must clone. + // # https://github.com/kanidm/concread/issues/55 + // We flag the node as unable to drop it's internals. + let new_txid = + (self.meta.0 & (FLAG_MASK | COUNT_MASK)) | (txid << TXID_SHF) | FLAG_INVALID; + let mut x: Box>> = Box::new(CachePadded::new(Leaf { + // Need to preserve count. + meta: Meta(new_txid), + key: unsafe { MaybeUninit::uninit().assume_init() }, + values: unsafe { MaybeUninit::uninit().assume_init() }, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + + debug_assert!((x.meta.0 & FLAG_INVALID) != 0); + + // Copy in the values to the correct location. + for idx in 0..self.count() { + unsafe { + let lkey = (*self.key[idx].as_ptr()).clone(); + x.key[idx].as_mut_ptr().write(lkey); + let lvalue = (*self.values[idx].as_ptr()).clone(); + x.values[idx].as_mut_ptr().write(lvalue); + } + } + // Finally undo the invalid flag to allow drop to proceed. + x.meta.0 &= !FLAG_INVALID; + + debug_assert!((x.meta.0 & FLAG_INVALID) == 0); + + Some(Box::into_raw(x) as *mut Node) + } + } + + pub(crate) fn insert_or_update(&mut self, k: K, v: V) -> LeafInsertState { + debug_assert_leaf!(self); + // Find the location we need to update + let r = key_search!(self, &k); + match r { + Ok(idx) => { + // It exists at idx, replace + let prev = unsafe { self.values[idx].as_mut_ptr().replace(v) }; + // Prev now contains the original value, return it! + LeafInsertState::Ok(Some(prev)) + } + Err(idx) => { + if self.count() >= L_CAPACITY { + // Overflow to a new node + if idx >= self.count() { + // Greate than all else, split right + let rnode = Node::new_leaf_ins(self.meta.0, k, v); + LeafInsertState::Split(rnode) + } else if idx == 0 { + // Lower than all else, split left. + // let lnode = ...; + let lnode = Node::new_leaf_ins(self.meta.0, k, v); + LeafInsertState::RevSplit(lnode) + } else { + // Within our range, pop max, insert, and split + // right. + let pk = + unsafe { slice_remove(&mut self.key, L_CAPACITY - 1).assume_init() }; + let pv = + unsafe { slice_remove(&mut self.values, L_CAPACITY - 1).assume_init() }; + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), idx); + slice_insert(&mut self.values, MaybeUninit::new(v), idx); + } + + let rnode = Node::new_leaf_ins(self.meta.0, pk, pv); + LeafInsertState::Split(rnode) + } + } else { + // We have space. + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), idx); + slice_insert(&mut self.values, MaybeUninit::new(v), idx); + } + self.inc_count(); + LeafInsertState::Ok(None) + } + } + } + } + + pub(crate) fn remove(&mut self, k: &Q) -> LeafRemoveState + where + K: Borrow, + Q: Ord, + { + debug_assert_leaf!(self); + if self.count() == 0 { + return LeafRemoveState::Shrink(None); + } + // We must have a value - where are you .... + match key_search!(self, k).ok() { + // Count still greater than 0, so Ok and None, + None => LeafRemoveState::Ok(None), + Some(idx) => { + // Get the kv out + let _pk = unsafe { slice_remove(&mut self.key, idx).assume_init() }; + let pv = unsafe { slice_remove(&mut self.values, idx).assume_init() }; + self.dec_count(); + if self.count() == 0 { + LeafRemoveState::Shrink(Some(pv)) + } else { + LeafRemoveState::Ok(Some(pv)) + } + } + } + } + + pub(crate) fn take_from_l_to_r(&mut self, right: &mut Self) { + debug_assert!(right.count() == 0); + let count = self.count() / 2; + let start_idx = self.count() - count; + + //move key and values + unsafe { + slice_move(&mut right.key, 0, &mut self.key, start_idx, count); + slice_move(&mut right.values, 0, &mut self.values, start_idx, count); + } + + // update the counts + self.meta.set_count(start_idx); + right.meta.set_count(count); + } + + pub(crate) fn take_from_r_to_l(&mut self, right: &mut Self) { + debug_assert!(self.count() == 0); + let count = right.count() / 2; + let start_idx = right.count() - count; + + // Move values from right to left. + unsafe { + slice_move(&mut self.key, 0, &mut right.key, 0, count); + slice_move(&mut self.values, 0, &mut right.values, 0, count); + } + // Shift the values in right down. + unsafe { + ptr::copy( + right.key.as_ptr().add(count), + right.key.as_mut_ptr(), + start_idx, + ); + ptr::copy( + right.values.as_ptr().add(count), + right.values.as_mut_ptr(), + start_idx, + ); + } + + // Fix the counts. + self.meta.set_count(count); + right.meta.set_count(start_idx); + } + + /* + pub(crate) fn remove_lt(&mut self, k: &Q) -> LeafPruneState + where + K: Borrow, + Q: Ord, + { + unimplemented!(); + } + */ + + #[inline(always)] + pub(crate) fn make_ro(&self) { + debug_assert_leaf!(self); + /* + let r = unsafe { + mprotect( + self as *const Leaf as *mut c_void, + size_of::>(), + PROT_READ + ) + }; + assert!(r == 0); + */ + } + + #[inline(always)] + pub(crate) fn merge(&mut self, right: &mut Self) { + debug_assert_leaf!(self); + debug_assert_leaf!(right); + let sc = self.count(); + let rc = right.count(); + unsafe { + slice_merge(&mut self.key, sc, &mut right.key, rc); + slice_merge(&mut self.values, sc, &mut right.values, rc); + } + self.meta.add_count(right.count()); + right.meta.set_count(0); + } + + pub(crate) fn verify(&self) -> bool { + debug_assert_leaf!(self); + // println!("verify leaf -> {:?}", self); + // Check key sorting + if self.meta.count() == 0 { + return true; + } + let mut lk: &K = unsafe { &*self.key[0].as_ptr() }; + for work_idx in 1..self.meta.count() { + let rk: &K = unsafe { &*self.key[work_idx].as_ptr() }; + if lk >= rk { + // println!("{:?}", self); + if cfg!(test) { + return false; + } else { + debug_assert!(false); + } + } + lk = rk; + } + true + } + + fn free(node: *mut Self) { + unsafe { + let _x: Box>> = + Box::from_raw(node as *mut CachePadded>); + } + } +} + +impl Debug for Leaf { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), Error> { + debug_assert_leaf!(self); + write!(f, "Leaf -> {}", self.count())?; + #[cfg(all(test, not(miri)))] + write!(f, " nid: {}", self.nid)?; + write!(f, " \\-> [ ")?; + for idx in 0..self.count() { + write!(f, "{:?}, ", unsafe { &*self.key[idx].as_ptr() })?; + } + write!(f, " ]") + } +} + +impl Drop for Leaf { + fn drop(&mut self) { + debug_assert_leaf!(self); + #[cfg(all(test, not(miri)))] + release_nid(self.nid); + // Due to the use of maybe uninit we have to drop any contained values. + // https://github.com/kanidm/concread/issues/55 + // if we are invalid, do NOT drop our internals as they MAY be inconsistent. + // this WILL leak memory, but it's better than crashing. + if self.meta.0 & FLAG_INVALID == 0 { + unsafe { + for idx in 0..self.count() { + ptr::drop_in_place(self.key[idx].as_mut_ptr()); + ptr::drop_in_place(self.values[idx].as_mut_ptr()); + } + } + } + // Done + self.meta.0 = FLAG_DROPPED; + debug_assert!(self.meta.0 & FLAG_MASK != FLAG_LEAF); + // #[cfg(test)] + // println!("set leaf {:?} to {:x}", self.nid, self.meta.0); + } +} + +impl Branch { + #[allow(unused)] + #[inline(always)] + fn set_count(&mut self, c: usize) { + debug_assert_branch!(self); + self.meta.set_count(c) + } + + #[inline(always)] + pub(crate) fn count(&self) -> usize { + debug_assert_branch!(self); + self.meta.count() + } + + #[inline(always)] + fn inc_count(&mut self) { + debug_assert_branch!(self); + self.meta.inc_count() + } + + #[inline(always)] + fn dec_count(&mut self) { + debug_assert_branch!(self); + self.meta.dec_count() + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + debug_assert_branch!(self); + self.meta.get_txid() + } + + // Can't inline as this is recursive! + pub(crate) fn min(&self) -> &K { + debug_assert_branch!(self); + unsafe { (*self.nodes[0]).min() } + } + + // Can't inline as this is recursive! + pub(crate) fn max(&self) -> &K { + debug_assert_branch!(self); + // Remember, self.count() is + 1 offset, so this gets + // the max node + unsafe { (*self.nodes[self.count()]).max() } + } + + pub(crate) fn req_clone(&self, txid: u64) -> Option<*mut Node> { + debug_assert_branch!(self); + if self.get_txid() == txid { + // Same txn, no action needed. + None + } else { + // println!("Req clone branch"); + // Diff txn, must clone. + // # https://github.com/kanidm/concread/issues/55 + // We flag the node as unable to drop it's internals. + let new_txid = + (self.meta.0 & (FLAG_MASK | COUNT_MASK)) | (txid << TXID_SHF) | FLAG_INVALID; + let mut x: Box>> = Box::new(CachePadded::new(Branch { + // Need to preserve count. + meta: Meta(new_txid), + key: unsafe { MaybeUninit::uninit().assume_init() }, + // We can simply clone the pointers. + nodes: self.nodes, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + + debug_assert!((x.meta.0 & FLAG_INVALID) != 0); + + // Copy in the keys to the correct location. + for idx in 0..self.count() { + unsafe { + let lkey = (*self.key[idx].as_ptr()).clone(); + x.key[idx].as_mut_ptr().write(lkey); + } + } + // Finally undo the invalid flag to allow drop to proceed. + x.meta.0 &= !FLAG_INVALID; + + debug_assert!((x.meta.0 & FLAG_INVALID) == 0); + + Some(Box::into_raw(x) as *mut Node) + } + } + + #[inline(always)] + pub(crate) fn locate_node(&self, k: &Q) -> usize + where + K: Borrow, + Q: Ord, + { + debug_assert_branch!(self); + match key_search!(self, k) { + Err(idx) => idx, + Ok(idx) => idx + 1, + } + } + + #[inline(always)] + pub(crate) fn get_idx_unchecked(&self, idx: usize) -> *mut Node { + debug_assert_branch!(self); + debug_assert!(idx <= self.count()); + debug_assert!(!self.nodes[idx].is_null()); + self.nodes[idx] + } + + #[inline(always)] + pub(crate) fn get_idx_checked(&self, idx: usize) -> Option<*mut Node> { + debug_assert_branch!(self); + // Remember, that nodes can have +1 to count which is why <= here, not <. + if idx <= self.count() { + debug_assert!(!self.nodes[idx].is_null()); + Some(self.nodes[idx]) + } else { + None + } + } + + #[cfg(test)] + pub(crate) fn get_ref(&self, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Ord, + { + debug_assert_branch!(self); + // If the value is Ok(idx), then that means + // we were located to the right node. This is because we + // exactly hit and located on the key. + // + // If the value is Err(idx), then we have the exact index already. + // as branches is of-by-one. + let idx = self.locate_node(k); + unsafe { (*self.nodes[idx]).get_ref(k) } + } + + pub(crate) fn add_node(&mut self, node: *mut Node) -> BranchInsertState { + debug_assert_branch!(self); + // do we have space? + if self.count() == L_CAPACITY { + // if no space -> + // split and send two nodes back for new branch + // There are three possible states that this causes. + // 1 * The inserted node is the greater than all current values, causing l(max, node) + // to be returned. + // 2 * The inserted node is between max - 1 and max, causing l(node, max) to be returned. + // 3 * The inserted node is a low/middle value, causing max and max -1 to be returned. + // + let kr = unsafe { (*node).min() }; + let r = key_search!(self, kr); + let ins_idx = r.unwrap_err(); + // Everything will pop max. + let max = unsafe { *(self.nodes.get_unchecked(BV_CAPACITY - 1)) }; + let res = match ins_idx { + // Case 1 + L_CAPACITY => { + // println!("case 1"); + // Greater than all current values, so we'll just return max and node. + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + // Now setup the ret val NOTICE compared to case 2 that we swap node and max? + BranchInsertState::Split(max, node) + } + // Case 2 + L_CAPACITY_N1 => { + // println!("case 2"); + // Greater than all but max, so we return max and node in the correct order. + // Drop the key between them. + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + // Now setup the ret val NOTICE compared to case 1 that we swap node and max? + BranchInsertState::Split(node, max) + } + // Case 3 + ins_idx => { + // Get the max - 1 and max nodes out. + let maxn1 = unsafe { *(self.nodes.get_unchecked(BV_CAPACITY - 2)) }; + // Drop the key between them. + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + // Drop the key before us that we are about to replace. + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 2)).assume_init() }; + // Add node and it's key to the correct location. + let k: K = kr.clone(); + let leaf_ins_idx = ins_idx + 1; + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + + BranchInsertState::Split(maxn1, max) + } + }; + // Dec count as we always reduce branch by one as we split return + // two. + self.dec_count(); + res + } else { + // if space -> + // Get the nodes min-key - we clone it because we'll certainly be inserting it! + let k: K = unsafe { (*node).min().clone() }; + // bst and find when min-key < key[idx] + let r = key_search!(self, &k); + // if r is ever found, I think this is a bug, because we should never be able to + // add a node with an existing min. + // + // [ 5 ] + // / \ + // [0,] [5,] + // + // So if we added here to [0, ], and it had to overflow to split, then everything + // must be < 5. Why? Because to get to [0,] as your insert target, you must be < 5. + // if we added to [5,] then a split must be greater than, or the insert would replace 5. + // + // if we consider + // + // [ 5 ] + // / \ + // [0,] [7,] + // + // Now we insert 5, and 7, splits. 5 would remain in the tree and we'd split 7 to the right + // + // As a result, any "Ok(idx)" must represent a corruption of the tree. + // debug_assert!(r.is_err()); + let ins_idx = r.unwrap_err(); + let leaf_ins_idx = ins_idx + 1; + // So why do we only need to insert right? Because the left-most + // leaf when it grows, it splits to the right. That importantly + // means that we only need to insert to replace the min and it's + // right leaf, or anything higher. As a result, we are always + // targetting ins_idx and leaf_ins_idx = ins_idx + 1. + // + // We have a situation like: + // + // [1, 3, 9, 18] + // + // and ins_idx is 2. IE: + // + // [1, 3, 9, 18] + // ^-- k=6 + // + // So this we need to shift those r-> and insert. + // + // [1, 3, x, 9, 18] + // ^-- k=6 + // + // [1, 3, 6, 9, 18] + // + // Now we need to consider the leaves too: + // + // [1, 3, 9, 18] + // | | | | | + // v v v v v + // 0 1 3 9 18 + // + // So that means we need to move leaf_ins_idx = (ins_idx + 1) + // right also + // + // [1, 3, x, 9, 18] + // | | | | | | + // v v v v v v + // 0 1 3 x 9 18 + // ^-- leaf for k=6 will go here. + // + // Now to talk about the right expand issue - lets say 0 conducted + // a split, it returns the new right node - which would push + // 3 to the right to insert a new right hand side as required. So we + // really never need to consider the left most leaf to have to be + // replaced in any conditions. + // + // Magic! + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + // finally update the count + self.inc_count(); + // Return that we are okay to go! + BranchInsertState::Ok + } + } + + pub(crate) fn add_node_left( + &mut self, + lnode: *mut Node, + sibidx: usize, + ) -> BranchInsertState { + debug_assert_branch!(self); + if self.count() == L_CAPACITY { + if sibidx == self.count() { + // If sibidx == self.count, then we must be going into max - 1. + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, k5, xx ] + // [ v1, v2, v3, v4, v5, v6, xx ] + // + // [ k1, k2, k3, k4, k5, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, v6, xx ] -> [ ln, v7 ] + // + // So in this case we drop k6, and return a split. + let max = self.nodes[BV_CAPACITY - 1]; + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + self.dec_count(); + BranchInsertState::Split(lnode, max) + } else if sibidx == (self.count() - 1) { + // If sibidx == (self.count - 1), then we must be going into max - 2 + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, dd, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // + // + // This means that we need to return v6,v7 in a split, and + // just append node after v5. + let maxn1 = self.nodes[BV_CAPACITY - 2]; + let max = self.nodes[BV_CAPACITY - 1]; + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 2)).assume_init() }; + self.dec_count(); + self.dec_count(); + // [ k1, k2, k3, k4, dd, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, xx, xx ] -> [ v6, v7 ] + let k: K = unsafe { (*lnode).min().clone() }; + + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), sibidx - 1); + slice_insert(&mut self.nodes, lnode, sibidx); + // slice_insert(&mut self.node, MaybeUninit::new(node), sibidx); + } + self.inc_count(); + // + // [ k1, k2, k3, k4, nk, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, ln, xx ] -> [ v6, v7 ] + + BranchInsertState::Split(maxn1, max) + } else { + // All other cases; + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, dd, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // + // [ k1, k2, k3, nk, k4, dd ] [ k6 ] + // [ v1, v2, v3, ln, v4, v5, xx ] -> [ v6, v7 ] + // + // This means that we need to return v6,v7 in a split,, drop k5, + // then insert + + // Setup the nodes we intend to split away. + let maxn1 = self.nodes[BV_CAPACITY - 2]; + let max = self.nodes[BV_CAPACITY - 1]; + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 1)).assume_init() }; + let _kdrop = + unsafe { ptr::read(self.key.get_unchecked(L_CAPACITY - 2)).assume_init() }; + self.dec_count(); + self.dec_count(); + + // println!("pre-fixup -> {:?}", self); + + let sibnode = self.nodes[sibidx]; + let nkey: K = unsafe { (*sibnode).min().clone() }; + + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(nkey), sibidx); + slice_insert(&mut self.nodes, lnode, sibidx); + } + + self.inc_count(); + // println!("post fixup -> {:?}", self); + + BranchInsertState::Split(maxn1, max) + } + } else { + // We have space, so just put it in! + // [ k1, k2, k3, k4, xx, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, xx, xx ] + // [ v1, v2, v3, ln, v4, v5, xx ] + // + // [ k1, k2, k3, nk, k4, xx ] + // [ v1, v2, v3, ln, v4, v5, xx ] + // + + let sibnode = self.nodes[sibidx]; + let nkey: K = unsafe { (*sibnode).min().clone() }; + + unsafe { + slice_insert(&mut self.nodes, lnode, sibidx); + slice_insert(&mut self.key, MaybeUninit::new(nkey), sibidx); + } + + self.inc_count(); + // println!("post fixup -> {:?}", self); + BranchInsertState::Ok + } + } + + fn remove_by_idx(&mut self, idx: usize) -> *mut Node { + debug_assert_branch!(self); + debug_assert!(idx <= self.count()); + debug_assert!(idx > 0); + // remove by idx. + let _pk = unsafe { slice_remove(&mut self.key, idx - 1).assume_init() }; + let pn = unsafe { slice_remove(&mut self.nodes, idx) }; + self.dec_count(); + pn + } + + pub(crate) fn shrink_decision(&mut self, ridx: usize) -> BranchShrinkState { + // Given two nodes, we need to decide what to do with them! + // + // Remember, this isn't happening in a vacuum. This is really a manipulation of + // the following structure: + // + // parent (self) + // / \ + // left right + // + // We also need to consider the following situation too: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // Imagine we have exhausted r2, so we need to merge: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 <<-- r2 + // + // This leaves us with a partial state of + // + // root + // / \ + // lbranch rbranch (invalid!) + // / \ / + // l1 l2 r1 + // + // This means rbranch issues a cloneshrink to root. clone shrink must contain the remainer + // so that it can be reparented: + // + // root + // / + // lbranch -- + // / \ \ + // l1 l2 r1 + // + // Now root has to shrink too. + // + // root -- + // / \ \ + // l1 l2 r1 + // + // So, we have to analyse the situation. + // * Have left or right been emptied? (how to handle when branches) + // * Is left or right belowe a reasonable threshold? + // * Does the opposite have capacity to remain valid? + + debug_assert_branch!(self); + debug_assert!(ridx > 0 && ridx <= self.count()); + let left = self.nodes[ridx - 1]; + let right = self.nodes[ridx]; + debug_assert!(!left.is_null()); + debug_assert!(!right.is_null()); + + match unsafe { (*left).meta.0 & FLAG_MASK } { + FLAG_LEAF => { + let lmut = leaf_ref!(left, K, V); + let rmut = leaf_ref!(right, K, V); + + if lmut.count() + rmut.count() <= L_CAPACITY { + lmut.merge(rmut); + // remove the right node from parent + let dnode = self.remove_by_idx(ridx); + debug_assert!(dnode == right); + if self.count() == 0 { + // We now need to be merged across as we only contain a single + // value now. + BranchShrinkState::Shrink(dnode) + } else { + // We are complete! + // #[cfg(test)] + // println!("🔥 {:?}", rmut.nid); + BranchShrinkState::Merge(dnode) + } + } else if rmut.count() > (L_CAPACITY / 2) { + lmut.take_from_r_to_l(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else if lmut.count() > (L_CAPACITY / 2) { + lmut.take_from_l_to_r(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else { + // Do nothing + BranchShrinkState::Balanced + } + } + FLAG_BRANCH => { + // right or left is now in a "corrupt" state with a single value that we need to relocate + // to left - or we need to borrow from left and fix it! + let lmut = branch_ref!(left, K, V); + let rmut = branch_ref!(right, K, V); + + debug_assert!(rmut.count() == 0 || lmut.count() == 0); + debug_assert!(rmut.count() <= L_CAPACITY || lmut.count() <= L_CAPACITY); + // println!("{:?} {:?}", lmut.count(), rmut.count()); + if lmut.count() == L_CAPACITY { + lmut.take_from_l_to_r(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else if rmut.count() == L_CAPACITY { + lmut.take_from_r_to_l(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else { + // merge the right to tail of left. + // println!("BL {:?}", lmut); + // println!("BR {:?}", rmut); + lmut.merge(rmut); + // println!("AL {:?}", lmut); + // println!("AR {:?}", rmut); + // Reduce our count + let dnode = self.remove_by_idx(ridx); + debug_assert!(dnode == right); + if self.count() == 0 { + // We now need to be merged across as we also only contain a single + // value now. + BranchShrinkState::Shrink(dnode) + } else { + // We are complete! + // #[cfg(test)] + // println!("🚨 {:?}", rmut.nid); + BranchShrinkState::Merge(dnode) + } + } + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn extract_last_node(&self) -> *mut Node { + debug_assert_branch!(self); + self.nodes[0] + } + + pub(crate) fn rekey_by_idx(&mut self, idx: usize) { + debug_assert_branch!(self); + debug_assert!(idx <= self.count()); + debug_assert!(idx > 0); + // For the node listed, rekey it. + let nref = self.nodes[idx]; + let nkey = unsafe { ((*nref).min()).clone() }; + unsafe { + self.key[idx - 1].as_mut_ptr().write(nkey); + } + } + + #[inline(always)] + pub(crate) fn merge(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + let sc = self.count(); + let rc = right.count(); + if rc == 0 { + let node = right.nodes[0]; + debug_assert!(!node.is_null()); + let k: K = unsafe { (*node).min().clone() }; + let ins_idx = self.count(); + let leaf_ins_idx = ins_idx + 1; + unsafe { + slice_insert(&mut self.key, MaybeUninit::new(k), ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + self.inc_count(); + } else { + debug_assert!(sc == 0); + unsafe { + // Move all the nodes from right. + slice_merge(&mut self.nodes, 1, &mut right.nodes, rc + 1); + // Move the related keys. + slice_merge(&mut self.key, 1, &mut right.key, rc); + } + // Set our count correctly. + self.meta.set_count(rc + 1); + // Set right len to 0 + right.meta.set_count(0); + // rekey the lowest pointer. + unsafe { + let nptr = self.nodes[1]; + let k: K = (*nptr).min().clone(); + self.key[0].as_mut_ptr().write(k); + } + // done! + } + } + + pub(crate) fn take_from_l_to_r(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + debug_assert!(self.count() > right.count()); + // Starting index of where we move from. We work normally from a branch + // with only zero (but the base) branch item, but we do the math anyway + // to be sure incase we change later. + // + // So, self.len must be larger, so let's give a few examples here. + // 4 = 7 - (7 + 0) / 2 (will move 4, 5, 6) + // 3 = 6 - (6 + 0) / 2 (will move 3, 4, 5) + // 3 = 5 - (5 + 0) / 2 (will move 3, 4) + // 2 = 4 .... (will move 2, 3) + // + let count = (self.count() + right.count()) / 2; + let start_idx = self.count() - count; + // Move the remaining element from r to the correct location. + // + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] -> [ v8, ------- ] + // + // To: + // + // [ k1, k2, k3, k4, k5, k6 ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, v5, v6, v7 ] -> [ --, --, --, v8, --, ... + // + unsafe { + ptr::swap( + right.nodes.get_unchecked_mut(0), + right.nodes.get_unchecked_mut(count), + ) + } + // Move our values from the tail. + // We would move 3 now to: + // + // [ k1, k2, k3, k4, k5, k6 ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, --, --, -- ] -> [ v5, v6, v7, v8, --, ... + // + unsafe { + slice_move(&mut right.nodes, 0, &mut self.nodes, start_idx + 1, count); + } + // Remove the keys from left. + // So we need to remove the corresponding keys. so that we get. + // + // [ k1, k2, k3, --, --, -- ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, --, --, -- ] -> [ v5, v6, v7, v8, --, ... + // + // This means it's start_idx - 1 up to BK cap + + for kidx in (start_idx - 1)..L_CAPACITY { + let _pk = unsafe { ptr::read(self.key.get_unchecked(kidx)).assume_init() }; + // They are dropped now. + } + // Adjust both counts - we do this before rekey to ensure that the safety + // checks hold in debugging. + right.meta.set_count(count); + self.meta.set_count(start_idx); + // Rekey right + for kidx in 1..(count + 1) { + right.rekey_by_idx(kidx); + } + // Done! + } + + pub(crate) fn take_from_r_to_l(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + debug_assert!(right.count() >= self.count()); + + let count = (self.count() + right.count()) / 2; + let start_idx = right.count() - count; + + // We move count from right to left. + unsafe { + slice_move(&mut self.nodes, 1, &mut right.nodes, 0, count); + } + + // Pop the excess keys in right + // So say we had 6/7 in right, and 0/1 in left. + // + // We have a start_idx of 4, and count of 3. + // + // We moved 3 values from right, leaving 4. That means we need to remove + // keys 0, 1, 2. The remaining keys are moved down. + for kidx in 0..count { + let _pk = unsafe { ptr::read(right.key.get_unchecked(kidx)).assume_init() }; + // They are dropped now. + } + + // move keys down in right + unsafe { + ptr::copy( + right.key.as_ptr().add(count), + right.key.as_mut_ptr(), + start_idx, + ); + } + // move nodes down in right + unsafe { + ptr::copy( + right.nodes.as_ptr().add(count), + right.nodes.as_mut_ptr(), + start_idx + 1, + ); + } + + // update counts + right.meta.set_count(start_idx); + self.meta.set_count(count); + // Rekey left + for kidx in 1..(count + 1) { + self.rekey_by_idx(kidx); + } + // Done! + } + + #[inline(always)] + pub(crate) fn replace_by_idx(&mut self, idx: usize, node: *mut Node) { + debug_assert_branch!(self); + debug_assert!(idx <= self.count()); + debug_assert!(!self.nodes[idx].is_null()); + self.nodes[idx] = node; + } + + pub(crate) fn clone_sibling_idx( + &mut self, + txid: u64, + idx: usize, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, + ) -> usize { + debug_assert_branch!(self); + // if we clone, return Some new ptr. if not, None. + let (ridx, idx) = if idx == 0 { + // println!("clone_sibling_idx clone right"); + // If we are 0 we clone our right sibling, + // and return thet right idx as 1. + (1, 1) + } else { + // println!("clone_sibling_idx clone left"); + // Else we clone the left, and leave our index unchanged + // as we are the right node. + (idx, idx - 1) + }; + // Now clone the item at idx. + debug_assert!(idx <= self.count()); + let sib_ptr = self.nodes[idx]; + debug_assert!(!sib_ptr.is_null()); + // Do we need to clone? + let res = match unsafe { (*sib_ptr).meta.0 & FLAG_MASK } { + FLAG_LEAF => { + let lref = unsafe { &*(sib_ptr as *const _ as *const Leaf) }; + lref.req_clone(txid) + } + FLAG_BRANCH => { + let bref = unsafe { &*(sib_ptr as *const _ as *const Branch) }; + bref.req_clone(txid) + } + _ => unreachable!(), + }; + + // If it did clone, it's a some, so we map that to have the from and new ptrs for + // the memory management. + if let Some(n_ptr) = res { + // println!("ls push 101 {:?}", sib_ptr); + first_seen.push(n_ptr); + last_seen.push(sib_ptr); + // Put the pointer in place. + self.nodes[idx] = n_ptr; + }; + // Now return the right index + ridx + } + + /* + pub(crate) fn trim_lt_key( + &mut self, + k: &Q, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, + ) -> BranchTrimState + where + K: Borrow, + Q: Ord, + { + debug_assert_branch!(self); + // The possible states of a branch are + // + // [ 0, 4, 8, 12 ] + // [n1, n2, n3, n4, n5] + // + let r = key_search!(self, k); + + let sc = self.count(); + + match r { + Ok(idx) => { + debug_assert!(idx < sc); + // * A key matches exactly a value. IE k is 4. This means we can remove + // n1 and n2 because we know 4 must be in n3 as the min. + + // NEED MM + debug_assert!(false); + + unsafe { + slice_slide_and_drop(&mut self.key, idx, sc - (idx + 1)); + slice_slide(&mut self.nodes.as_mut(), idx, sc - idx); + } + self.meta.set_count(sc - (idx + 1)); + + if self.count() == 0 { + let rnode = self.extract_last_node(); + BranchTrimState::Promote(rnode) + } else { + BranchTrimState::Complete + } + } + Err(idx) => { + if idx == 0 { + // * The key is less than min. IE it wants to remove the lowest value. + // Check the "max" value of the subtree to know if we can proceed. + let tnode: *mut Node = self.nodes[0]; + let branch_k: &K = unsafe { (*tnode).max() }; + if branch_k.borrow() < k { + // Everything is smaller, let's remove it that subtree. + // NEED MM + debug_assert!(false); + let _pk = unsafe { slice_remove(&mut self.key, 0).assume_init() }; + let _pn = unsafe { slice_remove(self.nodes.as_mut(), 0) }; + self.dec_count(); + BranchTrimState::Complete + } else { + BranchTrimState::Complete + } + } else if idx >= self.count() { + // remove everything except max. + unsafe { + // NEED MM + debug_assert!(false); + // We just drop all the keys. + for kidx in 0..self.count() { + ptr::drop_in_place(self.key[kidx].as_mut_ptr()); + // ptr::drop_in_place(self.nodes[kidx].as_mut_ptr()); + } + // Move the last node to the bottom. + self.nodes[0] = self.nodes[sc]; + } + self.meta.set_count(0); + let rnode = self.extract_last_node(); + // Something may still be valid, hand it on. + BranchTrimState::Promote(rnode) + } else { + // * A key is between two values. We can remove everything less, but not + // the assocated. For example, remove 6 would cause n1, n2 to be removed, but + // the prune/walk will have to examine n3 to know about further changes. + debug_assert!(idx > 0); + + let tnode: *mut Node = self.nodes[0]; + let branch_k: &K = unsafe { (*tnode).max() }; + + if branch_k.borrow() < k { + // NEED MM + debug_assert!(false); + // Remove including idx. + unsafe { + slice_slide_and_drop(&mut self.key, idx, sc - (idx + 1)); + slice_slide(self.nodes.as_mut(), idx, sc - idx); + } + self.meta.set_count(sc - (idx + 1)); + } else { + // NEED MM + debug_assert!(false); + unsafe { + slice_slide_and_drop(&mut self.key, idx - 1, sc - idx); + slice_slide(self.nodes.as_mut(), idx - 1, sc - (idx - 1)); + } + self.meta.set_count(sc - idx); + } + + if self.count() == 0 { + // NEED MM + debug_assert!(false); + let rnode = self.extract_last_node(); + BranchTrimState::Promote(rnode) + } else { + BranchTrimState::Complete + } + } + } + } + } + */ + + #[inline(always)] + pub(crate) fn make_ro(&self) { + debug_assert_branch!(self); + /* + let r = unsafe { + mprotect( + self as *const Branch as *mut c_void, + size_of::>(), + PROT_READ + ) + }; + assert!(r == 0); + */ + } + + pub(crate) fn verify(&self) -> bool { + debug_assert_branch!(self); + if self.count() == 0 { + // Not possible to be valid! + debug_assert!(false); + return false; + } + // println!("verify branch -> {:?}", self); + // Check we are sorted. + let mut lk: &K = unsafe { &*self.key[0].as_ptr() }; + for work_idx in 1..self.count() { + let rk: &K = unsafe { &*self.key[work_idx].as_ptr() }; + // println!("{:?} >= {:?}", lk, rk); + if lk >= rk { + debug_assert!(false); + return false; + } + lk = rk; + } + // Recursively call verify + for work_idx in 0..self.count() { + let node = unsafe { &*self.nodes[work_idx] }; + if !node.verify() { + for work_idx in 0..(self.count() + 1) { + let nref = unsafe { &*self.nodes[work_idx] }; + if !nref.verify() { + // println!("Failed children"); + debug_assert!(false); + return false; + } + } + } + } + // Check descendants are validly ordered. + // V-- remember, there are count + 1 nodes. + for work_idx in 0..self.count() { + // get left max and right min + let lnode = unsafe { &*self.nodes[work_idx] }; + let rnode = unsafe { &*self.nodes[work_idx + 1] }; + + let pkey = unsafe { &*self.key[work_idx].as_ptr() }; + let lkey = lnode.max(); + let rkey = rnode.min(); + if lkey >= pkey || pkey > rkey { + // println!("++++++"); + // println!("{:?} >= {:?}, {:?} > {:?}", lkey, pkey, pkey, rkey); + // println!("out of order key found {}", work_idx); + // println!("left --> {:?}", lnode); + // println!("right -> {:?}", rnode); + // println!("prnt -> {:?}", self); + debug_assert!(false); + return false; + } + } + // All good! + true + } + + fn free(node: *mut Self) { + unsafe { + let mut _x: Box>> = + Box::from_raw(node as *mut CachePadded>); + } + } +} + +impl Debug for Branch { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), Error> { + debug_assert_branch!(self); + write!(f, "Branch -> {}", self.count())?; + #[cfg(all(test, not(miri)))] + write!(f, " nid: {}", self.nid)?; + write!(f, " \\-> [ ")?; + for idx in 0..self.count() { + write!(f, "{:?}, ", unsafe { &*self.key[idx].as_ptr() })?; + } + write!(f, " ]") + } +} + +impl Drop for Branch { + fn drop(&mut self) { + debug_assert_branch!(self); + #[cfg(all(test, not(miri)))] + release_nid(self.nid); + // Due to the use of maybe uninit we have to drop any contained values. + // https://github.com/kanidm/concread/issues/55 + // if we are invalid, do NOT drop our internals as they MAY be inconsistent. + // this WILL leak memory, but it's better than crashing. + if self.meta.0 & FLAG_INVALID == 0 { + unsafe { + for idx in 0..self.count() { + ptr::drop_in_place(self.key[idx].as_mut_ptr()); + } + } + } + // Done + self.meta.0 = FLAG_DROPPED; + debug_assert!(self.meta.0 & FLAG_MASK != FLAG_BRANCH); + // println!("set branch {:?} to {:x}", self.nid, self.meta.0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bptree2_node_cache_size() { + let ls = std::mem::size_of::>() - std::mem::size_of::(); + let bs = std::mem::size_of::>() - std::mem::size_of::(); + #[cfg(feature = "skinny")] + { + assert!(ls <= 64); + assert!(bs <= 64); + } + #[cfg(not(feature = "skinny"))] + { + assert!(ls <= 128); + assert!(bs <= 128); + } + } + + #[test] + fn test_bptree2_node_test_weird_basics() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + + assert!(leaf.get_txid() == 1); + // println!("{:?}", leaf); + + leaf.set_count(1); + assert!(leaf.count() == 1); + leaf.set_count(0); + assert!(leaf.count() == 0); + + leaf.inc_count(); + leaf.inc_count(); + leaf.inc_count(); + assert!(leaf.count() == 3); + leaf.dec_count(); + leaf.dec_count(); + leaf.dec_count(); + assert!(leaf.count() == 0); + + /* + let branch: *mut Branch = Node::new_branch(1, ptr::null_mut(), ptr::null_mut()); + let branch = unsafe { &mut *branch }; + assert!(branch.get_txid() == 1); + // println!("{:?}", branch); + + branch.set_count(3); + assert!(branch.count() == 3); + branch.set_count(0); + assert!(branch.count() == 0); + Branch::free(branch as *mut _); + */ + + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_in_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for kv in 0..L_CAPACITY { + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(&kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + // Check update to capacity + for kv in 0..L_CAPACITY { + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(&kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_out_of_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + + assert!(L_CAPACITY <= 8); + let kvs = [7, 5, 1, 6, 2, 3, 0, 8]; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for idx in 0..L_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(&kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == L_CAPACITY); + // Check update to capacity + for idx in 0..L_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(&kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == L_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_min() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(L_CAPACITY <= 8); + + let kvs = [3, 2, 6, 4, 5, 1, 9, 0]; + let min = [3, 2, 2, 2, 2, 1, 1, 0]; + + for idx in 0..L_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(&kv) == Some(&kv)); + assert!(leaf.min() == &min[idx]); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == L_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_max() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(L_CAPACITY <= 8); + + let kvs = [1, 3, 2, 6, 4, 5, 9, 0]; + let max: [usize; 8] = [1, 3, 3, 6, 6, 6, 9, 9]; + + for idx in 0..L_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(&kv) == Some(&kv)); + assert!(leaf.max() == &max[idx]); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == L_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_remove_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + leaf.insert_or_update(kv, kv); + } + // Remove all but one. + for kv in 0..(L_CAPACITY - 1) { + let r = leaf.remove(&kv); + if let LeafRemoveState::Ok(Some(rkv)) = r { + assert!(rkv == kv); + } else { + assert!(false); + } + } + assert!(leaf.count() == 1); + assert!(leaf.max() == &(L_CAPACITY - 1)); + // Remove a non-existant value. + let r = leaf.remove(&(L_CAPACITY + 20)); + if let LeafRemoveState::Ok(None) = r { + // Ok! + } else { + assert!(false); + } + // Finally clear the node, should request a shrink. + let kv = L_CAPACITY - 1; + let r = leaf.remove(&kv); + if let LeafRemoveState::Shrink(Some(rkv)) = r { + assert!(rkv == kv); + } else { + assert!(false); + } + assert!(leaf.count() == 0); + // Remove non-existant post shrink. Should never happen + // but safety first! + let r = leaf.remove(&0); + if let LeafRemoveState::Shrink(None) = r { + // Ok! + } else { + assert!(false); + } + + assert!(leaf.count() == 0); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_remove_out_of_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + leaf.insert_or_update(kv, kv); + } + let mid = L_CAPACITY / 2; + // This test removes all BUT one node to keep the states simple. + for kv in mid..(L_CAPACITY - 1) { + let r = leaf.remove(&kv); + match r { + LeafRemoveState::Ok(_) => {} + _ => panic!(), + } + } + + for kv in 0..(L_CAPACITY / 2) { + let r = leaf.remove(&kv); + match r { + LeafRemoveState::Ok(_) => {} + _ => panic!(), + } + } + + assert!(leaf.count() == 1); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_bptree2_node_leaf_insert_split() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + leaf.insert_or_update(kv + 10, kv + 10); + } + + // Split right + let r = leaf.insert_or_update(L_CAPACITY + 10, L_CAPACITY + 10); + if let LeafInsertState::Split(rleaf) = r { + unsafe { + assert!((&*rleaf).count() == 1); + } + Leaf::free(rleaf); + } else { + panic!(); + } + + // Split left + let r = leaf.insert_or_update(0, 0); + if let LeafInsertState::RevSplit(lleaf) = r { + unsafe { + assert!((&*lleaf).count() == 1); + } + Leaf::free(lleaf); + } else { + panic!(); + } + + assert!(leaf.count() == L_CAPACITY); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + /* + #[test] + fn test_bptree_leaf_remove_lt() { + // This is used in split off. + // Remove none + let leaf1: *mut Leaf = Node::new_leaf(1); + let leaf1 = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + let _ = leaf1.insert_or_update(kv + 10, kv); + } + leaf1.remove_lt(&5); + assert!(leaf1.count() == L_CAPACITY); + Leaf::free(leaf1 as *mut _); + + // Remove all + let leaf2: *mut Leaf = Node::new_leaf(1); + let leaf2 = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + let _ = leaf2.insert_or_update(kv + 10, kv); + } + leaf2.remove_lt(&(L_CAPACITY + 10)); + assert!(leaf2.count() == 0); + Leaf::free(leaf2 as *mut _); + + // Remove from middle + let leaf3: *mut Leaf = Node::new_leaf(1); + let leaf3 = unsafe { &mut *leaf }; + for kv in 0..L_CAPACITY { + let _ = leaf3.insert_or_update(kv + 10, kv); + } + leaf3.remove_lt(&((L_CAPACITY / 2) + 10)); + assert!(leaf3.count() == (L_CAPACITY / 2)); + Leaf::free(leaf3 as *mut _); + + // Remove less than not in leaf. + let leaf4: *mut Leaf = Node::new_leaf(1); + let leaf4 = unsafe { &mut *leaf }; + let _ = leaf4.insert_or_update(5, 5); + let _ = leaf4.insert_or_update(15, 15); + leaf4.remove_lt(&10); + assert!(leaf4.count() == 1); + + // Add another and remove all. + let _ = leaf4.insert_or_update(20, 20); + leaf4.remove_lt(&25); + assert!(leaf4.count() == 0); + Leaf::free(leaf4 as *mut _); + // Done! + assert_released(); + } + */ + + /* ============================================ */ + // Branch tests here! + + #[test] + fn test_bptree2_node_branch_new() { + // Create a new branch, and test it. + let left: *mut Leaf = Node::new_leaf(1); + let left_ref = unsafe { &mut *left }; + let right: *mut Leaf = Node::new_leaf(1); + let right_ref = unsafe { &mut *right }; + + // add kvs to l and r + for kv in 0..L_CAPACITY { + left_ref.insert_or_update(kv + 10, kv + 10); + right_ref.insert_or_update(kv + 20, kv + 20); + } + // create branch + let branch: *mut Branch = Node::new_branch( + 1, + left as *mut Node, + right as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + // Test .min works on our descendants + assert!(branch_ref.min() == &10); + // Test .max works on our descendats. + assert!(branch_ref.max() == &(20 + L_CAPACITY - 1)); + // Get some k within the leaves. + assert!(branch_ref.get_ref(&11) == Some(&11)); + assert!(branch_ref.get_ref(&21) == Some(&21)); + // get some k that is out of bounds. + assert!(branch_ref.get_ref(&1) == None); + assert!(branch_ref.get_ref(&100) == None); + + Leaf::free(left as *mut _); + Leaf::free(right as *mut _); + Branch::free(branch as *mut _); + assert_released(); + } + + // Helpers + macro_rules! test_3_leaf { + ($fun:expr) => {{ + let a: *mut Leaf = Node::new_leaf(1); + let b: *mut Leaf = Node::new_leaf(1); + let c: *mut Leaf = Node::new_leaf(1); + + unsafe { + (*a).insert_or_update(10, 10); + (*b).insert_or_update(20, 20); + (*c).insert_or_update(30, 30); + } + + $fun(a, b, c); + + Leaf::free(a as *mut _); + Leaf::free(b as *mut _); + Leaf::free(c as *mut _); + assert_released(); + }}; + } + + #[test] + fn test_bptree2_node_branch_add_min() { + // This pattern occurs with "revsplit" to help with reverse + // ordered inserts. + test_3_leaf!(|a, b, c| { + // Add the max two to the branch + let branch: *mut Branch = Node::new_branch( + 1, + b as *mut Node, + c as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + // Now min node (uses a diff function!) + let r = branch_ref.add_node_left(a as *mut Node, 0); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + #[test] + fn test_bptree2_node_branch_add_mid() { + test_3_leaf!(|a, b, c| { + // Add the outer two to the branch + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + c as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + let r = branch_ref.add_node(b as *mut Node); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + #[test] + fn test_bptree2_node_branch_add_max() { + test_3_leaf!(|a, b, c| { + // add the bottom two + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + b as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + let r = branch_ref.add_node(c as *mut Node); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + // Helpers + macro_rules! test_max_leaf { + ($fun:expr) => {{ + let a: *mut Leaf = Node::new_leaf(1); + let b: *mut Leaf = Node::new_leaf(1); + let c: *mut Leaf = Node::new_leaf(1); + let d: *mut Leaf = Node::new_leaf(1); + + #[cfg(not(feature = "skinny"))] + let e: *mut Leaf = Node::new_leaf(1); + #[cfg(not(feature = "skinny"))] + let f: *mut Leaf = Node::new_leaf(1); + #[cfg(not(feature = "skinny"))] + let g: *mut Leaf = Node::new_leaf(1); + #[cfg(not(feature = "skinny"))] + let h: *mut Leaf = Node::new_leaf(1); + + unsafe { + (*a).insert_or_update(10, 10); + (*b).insert_or_update(20, 20); + (*c).insert_or_update(30, 30); + (*d).insert_or_update(40, 40); + #[cfg(not(feature = "skinny"))] + { + (*e).insert_or_update(50, 50); + (*f).insert_or_update(60, 60); + (*g).insert_or_update(70, 70); + (*h).insert_or_update(80, 80); + } + } + + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + b as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + branch_ref.add_node(c as *mut Node); + branch_ref.add_node(d as *mut Node); + + #[cfg(not(feature = "skinny"))] + { + branch_ref.add_node(e as *mut Node); + branch_ref.add_node(f as *mut Node); + branch_ref.add_node(g as *mut Node); + branch_ref.add_node(h as *mut Node); + } + + assert!(branch_ref.count() == L_CAPACITY); + + #[cfg(feature = "skinny")] + $fun(branch_ref, 40); + #[cfg(not(feature = "skinny"))] + $fun(branch_ref, 80); + + // MUST NOT verify here, as it's a use after free of the tests inserted node! + Branch::free(branch as *mut _); + Leaf::free(a as *mut _); + Leaf::free(b as *mut _); + Leaf::free(c as *mut _); + Leaf::free(d as *mut _); + #[cfg(not(feature = "skinny"))] + { + Leaf::free(e as *mut _); + Leaf::free(f as *mut _); + Leaf::free(g as *mut _); + Leaf::free(h as *mut _); + } + assert_released(); + }}; + } + + #[test] + fn test_bptree2_node_branch_add_split_min() { + // Used in rev split + } + + #[test] + fn test_bptree2_node_branch_add_split_mid() { + test_max_leaf!(|branch_ref: &mut Branch, max: usize| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to L_CAPACITY, incs of 10 + unsafe { + (*node).insert_or_update(15, 15); + }; + + // Add in the middle + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(x, y) => { + unsafe { + assert!((*x).min() == &(max - 10)); + assert!((*y).min() == &max); + } + // X, Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } + + #[test] + fn test_bptree2_node_branch_add_split_max() { + test_max_leaf!(|branch_ref: &mut Branch, max: usize| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to L_CAPACITY, incs of 10 + unsafe { + (*node).insert_or_update(200, 200); + }; + + // Add in at the end. + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(y, mynode) => { + unsafe { + // println!("{:?}", (*y).min()); + // println!("{:?}", (*mynode).min()); + assert!((*y).min() == &max); + assert!((*mynode).min() == &200); + } + // Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } + + #[test] + fn test_bptree2_node_branch_add_split_n1max() { + // Add one before the end! + test_max_leaf!(|branch_ref: &mut Branch, max: usize| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to L_CAPACITY, incs of 10 + unsafe { + (*node).insert_or_update(max - 5, max - 5); + }; + + // Add in one before the end. + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(mynode, y) => { + unsafe { + assert!((*mynode).min() == &(max - 5)); + assert!((*y).min() == &max); + } + // Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } +} diff --git a/vendor/concread/src/internals/bptree/states.rs b/vendor/concread/src/internals/bptree/states.rs new file mode 100644 index 0000000..68eee43 --- /dev/null +++ b/vendor/concread/src/internals/bptree/states.rs @@ -0,0 +1,117 @@ +use super::node::{Leaf, Node}; +use std::fmt::Debug; + +#[derive(Debug)] +pub(crate) enum LeafInsertState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Ok(Option), + // Split(K, V), + Split(*mut Leaf), + // We split in the reverse direction. + RevSplit(*mut Leaf), +} + +#[derive(Debug)] +pub(crate) enum LeafRemoveState +where + V: Clone, +{ + Ok(Option), + // Indicate that we found the associated value, but this + // removal means we no longer exist so should be removed. + Shrink(Option), +} + +#[derive(Debug)] +pub(crate) enum BranchInsertState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Ok, + // Two nodes that need addition to a new branch? + Split(*mut Node, *mut Node), +} + +#[derive(Debug)] +pub(crate) enum BranchShrinkState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Balanced, + Merge(*mut Node), + Shrink(*mut Node), +} + +/* +#[derive(Debug)] +pub(crate) enum BranchTrimState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Complete, + Promote(*mut Node), +} + +pub(crate) enum CRTrimState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Complete, + Clone(*mut Node), + Promote(*mut Node), +} +*/ + +#[derive(Debug)] +pub(crate) enum CRInsertState +where + K: Ord + Clone + Debug, + V: Clone, +{ + // We did not need to clone, here is the result. + NoClone(Option), + // We had to clone the referenced node provided. + Clone(Option, *mut Node), + // We had to split, but did not need a clone. + // REMEMBER: In all split cases it means the key MUST NOT have + // previously existed, so it implies return none to the + // caller. + Split(*mut Node), + RevSplit(*mut Node), + // We had to clone and split. + CloneSplit(*mut Node, *mut Node), + CloneRevSplit(*mut Node, *mut Node), +} + +#[derive(Debug)] +pub(crate) enum CRCloneState +where + K: Ord + Clone + Debug, + V: Clone, +{ + Clone(*mut Node), + NoClone, +} + +#[derive(Debug)] +pub(crate) enum CRRemoveState +where + K: Ord + Clone + Debug, + V: Clone, +{ + // We did not need to clone, here is the result. + NoClone(Option), + // We had to clone the referenced node provided. + Clone(Option, *mut Node), + // + Shrink(Option), + // + CloneShrink(Option, *mut Node), +} diff --git a/vendor/concread/src/internals/hashmap/cursor.rs b/vendor/concread/src/internals/hashmap/cursor.rs new file mode 100644 index 0000000..e321a5a --- /dev/null +++ b/vendor/concread/src/internals/hashmap/cursor.rs @@ -0,0 +1,2299 @@ +//! The cursor is what actually knits a tree together from the parts +//! we have, and has an important role to keep the system consistent. +//! +//! Additionally, the cursor also is responsible for general movement +//! throughout the structure and how to handle that effectively + +use super::node::*; +use rand::Rng; +use std::borrow::Borrow; +use std::fmt::Debug; +use std::mem; + +use ahash::AHasher; +use std::hash::{Hash, Hasher}; + +use super::iter::{Iter, KeyIter, ValueIter}; +use super::states::*; +use parking_lot::Mutex; + +use crate::internals::lincowcell::LinCowCellCapable; + +/// A stored K/V in the hash bucket. +#[derive(Clone)] +pub struct Datum +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + /// The K in K:V. + pub k: K, + /// The V in K:V. + pub v: V, +} + +/// The internal root of the tree, with associated garbage lists etc. +#[derive(Debug)] +pub(crate) struct SuperBlock +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + root: *mut Node, + size: usize, + txid: u64, + key1: u128, + key2: u128, +} + +impl LinCowCellCapable, CursorWrite> + for SuperBlock +{ + fn create_reader(&self) -> CursorRead { + CursorRead::new(self) + } + + fn create_writer(&self) -> CursorWrite { + CursorWrite::new(self) + } + + fn pre_commit( + &mut self, + mut new: CursorWrite, + prev: &CursorRead, + ) -> CursorRead { + let mut prev_last_seen = prev.last_seen.lock(); + debug_assert!((*prev_last_seen).is_empty()); + + let new_last_seen = &mut new.last_seen; + std::mem::swap(&mut (*prev_last_seen), &mut (*new_last_seen)); + debug_assert!((*new_last_seen).is_empty()); + + // Now when the lock is dropped, both sides see the correct info and garbage for drops. + // Clear first seen, we won't be dropping them from here. + new.first_seen.clear(); + + self.root = new.root; + self.size = new.length; + self.txid = new.txid; + + // Create the new reader. + CursorRead::new(self) + } +} + +impl SuperBlock { + /// 🔥 🔥 🔥 + pub unsafe fn new() -> Self { + let leaf: *mut Leaf = Node::new_leaf(1); + SuperBlock { + root: leaf as *mut Node, + size: 0, + txid: 1, + key1: rand::thread_rng().gen::(), + key2: rand::thread_rng().gen::(), + } + } + + #[cfg(test)] + pub(crate) fn new_test(txid: u64, root: *mut Node) -> Self { + assert!(txid < (TXID_MASK >> TXID_SHF)); + assert!(txid > 0); + // Do a pre-verify to be sure it's sane. + assert!(unsafe { (*root).verify() }); + // Collect anythinng from root into this txid if needed. + // Set txid to txid on all tree nodes from the root. + // first_seen.push(root); + // unsafe { (*root).sblock_collect(&mut first_seen) }; + // Lock them all + /* + first_seen.iter().for_each(|n| unsafe { + (**n).make_ro(); + }); + */ + // Determine our count internally. + let (size, _, _) = unsafe { (*root).tree_density() }; + // Gen keys. + let key1 = rand::thread_rng().gen::(); + let key2 = rand::thread_rng().gen::(); + // Good to go! + SuperBlock { + txid, + size, + root, + key1, + key2, + } + } +} + +#[derive(Debug)] +pub(crate) struct CursorRead +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + key1: u128, + key2: u128, + txid: u64, + length: usize, + root: *mut Node, + last_seen: Mutex>>, +} + +#[derive(Debug)] +pub(crate) struct CursorWrite +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + // Need to build a stack as we go - of what, I'm not sure ... + txid: u64, + length: usize, + root: *mut Node, + key1: u128, + key2: u128, + last_seen: Vec<*mut Node>, + first_seen: Vec<*mut Node>, +} + +pub(crate) trait CursorReadOps { + fn get_root_ref(&self) -> &Node; + + fn get_root(&self) -> *mut Node; + + fn len(&self) -> usize; + + fn get_txid(&self) -> u64; + + fn hash_key<'a, 'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq; + + #[cfg(test)] + fn get_tree_density(&self) -> (usize, usize, usize) { + // Walk the tree and calculate the packing effeciency. + let rref = self.get_root_ref(); + rref.tree_density() + } + + fn search<'a, 'b, Q: ?Sized>(&'a self, h: u64, k: &'b Q) -> Option<&'a V> + where + K: Borrow, + Q: Hash + Eq, + { + let mut node = self.get_root(); + for _i in 0..65536 { + if unsafe { (*node).is_leaf() } { + let lref = leaf_ref!(node, K, V); + return lref.get_ref(h, k).map(|v| unsafe { + // Strip the lifetime and rebind to the 'a self. + // This is safe because we know that these nodes will NOT + // be altered during the lifetime of this txn, so the references + // will remain stable. + let x = v as *const V; + &*x as &V + }); + } else { + let bref = branch_ref!(node, K, V); + let idx = bref.locate_node(h); + node = bref.get_idx_unchecked(idx); + } + } + panic!("Tree depth exceeded max limit (65536). This may indicate memory corruption."); + } + + #[allow(clippy::needless_lifetimes)] + fn contains_key<'a, 'b, Q: ?Sized>(&'a self, h: u64, k: &'b Q) -> bool + where + K: Borrow, + Q: Hash + Eq, + { + self.search(h, k).is_some() + } + + fn kv_iter(&self) -> Iter { + Iter::new(self.get_root(), self.len()) + } + + fn k_iter(&self) -> KeyIter { + KeyIter::new(self.get_root(), self.len()) + } + + fn v_iter(&self) -> ValueIter { + ValueIter::new(self.get_root(), self.len()) + } + + #[cfg(test)] + fn verify(&self) -> bool { + self.get_root_ref().no_cycles() && self.get_root_ref().verify() && { + let (l, _, _) = self.get_tree_density(); + l == self.len() + } + } +} + +impl CursorWrite { + pub(crate) fn new(sblock: &SuperBlock) -> Self { + let txid = sblock.txid + 1; + assert!(txid < (TXID_MASK >> TXID_SHF)); + // println!("starting wr txid -> {:?}", txid); + let length = sblock.size; + let root = sblock.root; + // TODO: Could optimise how big these are based + // on past trends? Or based on % tree size? + let last_seen = Vec::with_capacity(16); + let first_seen = Vec::with_capacity(16); + + CursorWrite { + txid, + length, + root, + last_seen, + first_seen, + key1: sblock.key1, + key2: sblock.key2, + } + } + + pub(crate) fn clear(&mut self) { + // Reset the values in this tree. + // We need to mark everything as disposable, and create a new root! + self.last_seen.push(self.root); + unsafe { (*self.root).sblock_collect(&mut self.last_seen) }; + let nroot: *mut Leaf = Node::new_leaf(self.txid); + let mut nroot = nroot as *mut Node; + self.first_seen.push(nroot); + mem::swap(&mut self.root, &mut nroot); + self.length = 0; + } + + // Functions as insert_or_update + pub(crate) fn insert(&mut self, h: u64, k: K, v: V) -> Option { + let r = match clone_and_insert( + self.root, + self.txid, + h, + k, + v, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRInsertState::NoClone(res) => res, + CRInsertState::Clone(res, mut nnode) => { + // We have a new root node, swap it in. + // !!! It's already been cloned and marked for cleaning by the clone_and_insert + // call. + mem::swap(&mut self.root, &mut nnode); + // Return the insert result + res + } + CRInsertState::CloneSplit(lnode, rnode) => { + // The previous root had to split - make a new + // root now and put it inplace. + let mut nroot = Node::new_branch(self.txid, lnode, rnode) as *mut Node; + self.first_seen.push(nroot); + // The root was cloned as part of clone split + // This swaps the POINTERS not the content! + mem::swap(&mut self.root, &mut nroot); + // As we split, there must NOT have been an existing + // key to overwrite. + None + } + CRInsertState::Split(rnode) => { + // The previous root was already part of this txn, but has now + // split. We need to construct a new root and swap them. + // + // Note, that we have to briefly take an extra RC on the root so + // that we can get it into the branch. + let mut nroot = Node::new_branch(self.txid, self.root, rnode) as *mut Node; + self.first_seen.push(nroot); + // println!("ls push 2"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + // As we split, there must NOT have been an existing + // key to overwrite. + None + } + CRInsertState::RevSplit(lnode) => { + let mut nroot = Node::new_branch(self.txid, lnode, self.root) as *mut Node; + self.first_seen.push(nroot); + // println!("ls push 3"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + None + } + CRInsertState::CloneRevSplit(rnode, lnode) => { + let mut nroot = Node::new_branch(self.txid, lnode, rnode) as *mut Node; + self.first_seen.push(nroot); + // root was cloned in the rev split + // println!("ls push 4"); + // self.last_seen.push(self.root); + mem::swap(&mut self.root, &mut nroot); + None + } + }; + // If this is none, it means a new slot is now occupied. + if r.is_none() { + self.length += 1; + } + r + } + + pub(crate) fn remove(&mut self, h: u64, k: &K) -> Option { + let r = match clone_and_remove( + self.root, + self.txid, + h, + k, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRRemoveState::NoClone(res) => res, + CRRemoveState::Clone(res, mut nnode) => { + mem::swap(&mut self.root, &mut nnode); + res + } + CRRemoveState::Shrink(res) => { + if self_meta!(self.root).is_leaf() { + // No action - we have an empty tree. + res + } else { + // Root is being demoted, get the last branch and + // promote it to the root. + self.last_seen.push(self.root); + let rmut = branch_ref!(self.root, K, V); + let mut pnode = rmut.extract_last_node(); + mem::swap(&mut self.root, &mut pnode); + res + } + } + CRRemoveState::CloneShrink(res, mut nnode) => { + if self_meta!(nnode).is_leaf() { + // The tree is empty, but we cloned the root to get here. + mem::swap(&mut self.root, &mut nnode); + res + } else { + // Our root is getting demoted here, get the remaining branch + self.last_seen.push(nnode); + let rmut = branch_ref!(nnode, K, V); + let mut pnode = rmut.extract_last_node(); + // Promote it to the new root + mem::swap(&mut self.root, &mut pnode); + res + } + } + }; + if r.is_some() { + self.length -= 1; + } + r + } + + #[cfg(test)] + pub(crate) fn path_clone(&mut self, h: u64) { + match path_clone( + self.root, + self.txid, + h, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRCloneState::Clone(mut nroot) => { + // We cloned the root, so swap it. + mem::swap(&mut self.root, &mut nroot); + } + CRCloneState::NoClone => {} + }; + } + + pub(crate) fn get_mut_ref(&mut self, h: u64, k: &K) -> Option<&mut V> { + match path_clone( + self.root, + self.txid, + h, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRCloneState::Clone(mut nroot) => { + // We cloned the root, so swap it. + mem::swap(&mut self.root, &mut nroot); + } + CRCloneState::NoClone => {} + }; + // Now get the ref. + path_get_mut_ref(self.root, h, k) + } + + pub(crate) unsafe fn get_slot_mut_ref(&mut self, h: u64) -> Option<&mut [Datum]> { + match path_clone( + self.root, + self.txid, + h, + &mut self.last_seen, + &mut self.first_seen, + ) { + CRCloneState::Clone(mut nroot) => { + // We cloned the root, so swap it. + mem::swap(&mut self.root, &mut nroot); + } + CRCloneState::NoClone => {} + }; + // Now get the ref. + path_get_slot_mut_ref(self.root, h) + } + + #[cfg(test)] + pub(crate) fn root_txid(&self) -> u64 { + self.get_root_ref().get_txid() + } + + /* + #[cfg(test)] + pub(crate) fn tree_density(&self) -> (usize, usize, usize) { + self.get_root_ref().tree_density() + } + */ +} + +impl Extend<(K, V)> for CursorWrite { + fn extend>(&mut self, iter: I) { + iter.into_iter().for_each(|(k, v)| { + let h = self.hash_key(&k); + let _ = self.insert(h, k, v); + }); + } +} + +impl Drop for CursorWrite { + fn drop(&mut self) { + // If there is content in first_seen, this means we aborted and must rollback + // of these items! + // println!("Releasing CW FS -> {:?}", self.first_seen); + self.first_seen.iter().for_each(|n| Node::free(*n)) + } +} + +impl Drop for CursorRead { + fn drop(&mut self) { + // If there is content in last_seen, a future generation wants us to remove it! + let last_seen_guard = self + .last_seen + .try_lock() + .expect("Unable to lock, something is horridly wrong!"); + last_seen_guard.iter().for_each(|n| Node::free(*n)); + std::mem::drop(last_seen_guard); + } +} + +impl Drop for SuperBlock { + fn drop(&mut self) { + // eprintln!("Releasing SuperBlock ..."); + // We must be the last SB and no txns exist. Drop the tree now. + // TODO: Calc this based on size. + let mut first_seen = Vec::with_capacity(16); + // eprintln!("{:?}", self.root); + first_seen.push(self.root); + unsafe { (*self.root).sblock_collect(&mut first_seen) }; + first_seen.iter().for_each(|n| Node::free(*n)); + } +} + +impl CursorRead { + pub(crate) fn new(sblock: &SuperBlock) -> Self { + // println!("starting rd txid -> {:?}", sblock.txid); + CursorRead { + txid: sblock.txid, + length: sblock.size, + root: sblock.root, + last_seen: Mutex::new(Vec::with_capacity(0)), + key1: sblock.key1, + key2: sblock.key2, + } + } +} + +/* +impl Drop for CursorRead { + fn drop(&mut self) { + unimplemented!(); + } +} +*/ + +impl CursorReadOps for CursorRead { + fn get_root_ref(&self) -> &Node { + unsafe { &*(self.root) } + } + + fn get_root(&self) -> *mut Node { + self.root + } + + fn len(&self) -> usize { + self.length + } + + fn get_txid(&self) -> u64 { + self.txid + } + + fn hash_key<'a, 'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq, + { + hash_key!(self, k) + } +} + +impl CursorReadOps for CursorWrite { + fn get_root_ref(&self) -> &Node { + unsafe { &*(self.root) } + } + + fn get_root(&self) -> *mut Node { + self.root + } + + fn len(&self) -> usize { + self.length + } + + fn get_txid(&self) -> u64 { + self.txid + } + + fn hash_key<'a, 'b, Q: ?Sized>(&'a self, k: &'b Q) -> u64 + where + K: Borrow, + Q: Hash + Eq, + { + hash_key!(self, k) + } +} + +fn clone_and_insert( + node: *mut Node, + txid: u64, + h: u64, + k: K, + v: V, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRInsertState { + /* + * Let's talk about the magic of this function. Come, join + * me around the [🔥🔥🔥] + * + * This function is the heart and soul of a copy on write + * structure - as we progress to the leaf location where we + * wish to perform an alteration, we clone (if required) all + * nodes on the path. This way an abort (rollback) of the + * commit simply is to drop the cursor, where the "new" + * cloned values are only referenced. To commit, we only need + * to replace the tree root in the parent structures as + * the cloned path must by definition include the root, and + * will contain references to nodes that did not need cloning, + * thus keeping them alive. + */ + + if self_meta!(node).is_leaf() { + // NOTE: We have to match, rather than map here, as rust tries to + // move k:v into both closures! + + // Leaf path + match leaf_ref!(node, K, V).req_clone(txid) { + Some(cnode) => { + // println!(); + first_seen.push(cnode); + // println!("ls push 5"); + last_seen.push(node); + // Clone was required. + let mref = leaf_ref!(cnode, K, V); + // insert to the new node. + match mref.insert_or_update(h, k, v) { + LeafInsertState::Ok(res) => CRInsertState::Clone(res, cnode), + LeafInsertState::Split(rnode) => { + first_seen.push(rnode as *mut Node); + // let rnode = Node::new_leaf_ins(txid, sk, sv); + CRInsertState::CloneSplit(cnode, rnode as *mut Node) + } + LeafInsertState::RevSplit(lnode) => { + first_seen.push(lnode as *mut Node); + CRInsertState::CloneRevSplit(cnode, lnode as *mut Node) + } + } + } + None => { + // No clone required. + // simply do the insert. + let mref = leaf_ref!(node, K, V); + match mref.insert_or_update(h, k, v) { + LeafInsertState::Ok(res) => CRInsertState::NoClone(res), + LeafInsertState::Split(rnode) => { + // We split, but left is already part of the txn group, so lets + // just return what's new. + // let rnode = Node::new_leaf_ins(txid, sk, sv); + first_seen.push(rnode as *mut Node); + CRInsertState::Split(rnode as *mut Node) + } + LeafInsertState::RevSplit(lnode) => { + first_seen.push(lnode as *mut Node); + CRInsertState::RevSplit(lnode as *mut Node) + } + } + } + } // end match + } else { + // Branch path + // Decide if we need to clone - we do this as we descend due to a quirk in Arc + // get_mut, because we don't have access to get_mut_unchecked (and this api may + // never be stabilised anyway). When we change this to *mut + garbage lists we + // could consider restoring the reactive behaviour that clones up, rather than + // cloning down the path. + // + // NOTE: We have to match, rather than map here, as rust tries to + // move k:v into both closures! + match branch_ref!(node, K, V).req_clone(txid) { + Some(cnode) => { + // + first_seen.push(cnode as *mut Node); + // println!("ls push 6"); + last_seen.push(node as *mut Node); + // Not same txn, clone instead. + let nmref = branch_ref!(cnode, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + + match clone_and_insert(anode, txid, h, k, v, last_seen, first_seen) { + CRInsertState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + // Pass back up that we cloned. + CRInsertState::Clone(res, cnode) + } + CRInsertState::CloneSplit(lnode, rnode) => { + // CloneSplit here, would have already updated lnode/rnode into the + // gc lists. + // Second, we update anode_idx node with our lnode as the new clone. + nmref.replace_by_idx(anode_idx, lnode); + + // Third we insert rnode - perfect world it's at anode_idx + 1, but + // we use the normal insert routine for now. + match nmref.add_node(rnode) { + BranchInsertState::Ok => CRInsertState::Clone(None, cnode), + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::CloneSplit(cnode, nrnode as *mut Node) + } + } + } + CRInsertState::CloneRevSplit(nnode, lnode) => { + nmref.replace_by_idx(anode_idx, nnode); + match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::Clone(None, cnode), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::CloneSplit(cnode, nrnode as *mut Node) + } + } + } + CRInsertState::NoClone(_res) => { + // If our descendant did not clone, then we don't have to either. + unreachable!("Shoud never be possible."); + // CRInsertState::NoClone(res) + } + CRInsertState::Split(_rnode) => { + // I think + unreachable!("This represents a corrupt tree state"); + } + CRInsertState::RevSplit(_lnode) => { + unreachable!("This represents a corrupt tree state"); + } + } // end match + } // end Some, + None => { + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + + match clone_and_insert(anode, txid, h, k, v, last_seen, first_seen) { + CRInsertState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + // We did not clone, and no further work needed. + CRInsertState::NoClone(res) + } + CRInsertState::NoClone(res) => { + // If our descendant did not clone, then we don't have to do any adjustments + // or further work. + CRInsertState::NoClone(res) + } + CRInsertState::Split(rnode) => { + match nmref.add_node(rnode) { + // Similar to CloneSplit - we are either okay, and the insert was happy. + BranchInsertState::Ok => CRInsertState::NoClone(None), + // Or *we* split as well, and need to return a new sibling branch. + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::Split(nrnode as *mut Node) + } + } + } + CRInsertState::CloneSplit(lnode, rnode) => { + // work inplace. + // Second, we update anode_idx node with our lnode as the new clone. + nmref.replace_by_idx(anode_idx, lnode); + + // Third we insert rnode - perfect world it's at anode_idx + 1, but + // we use the normal insert routine for now. + match nmref.add_node(rnode) { + // Similar to CloneSplit - we are either okay, and the insert was happy. + BranchInsertState::Ok => CRInsertState::NoClone(None), + // Or *we* split as well, and need to return a new sibling branch. + BranchInsertState::Split(clnode, crnode) => { + // Create a new branch to hold these children. + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + // Return it + CRInsertState::Split(nrnode as *mut Node) + } + } + } + CRInsertState::RevSplit(lnode) => match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::NoClone(None), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::Split(nrnode as *mut Node) + } + }, + CRInsertState::CloneRevSplit(nnode, lnode) => { + nmref.replace_by_idx(anode_idx, nnode); + match nmref.add_node_left(lnode, anode_idx) { + BranchInsertState::Ok => CRInsertState::NoClone(None), + BranchInsertState::Split(clnode, crnode) => { + let nrnode = Node::new_branch(txid, clnode, crnode); + first_seen.push(nrnode as *mut Node); + CRInsertState::Split(nrnode as *mut Node) + } + } + } + } // end match + } + } // end match branch ref clone + } // end if leaf +} + +fn path_clone( + node: *mut Node, + txid: u64, + h: u64, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRCloneState { + if unsafe { (*node).is_leaf() } { + unsafe { + (*(node as *mut Leaf)) + .req_clone(txid) + .map(|cnode| { + // Track memory + last_seen.push(node); + // println!("ls push 7 {:?}", node); + first_seen.push(cnode); + CRCloneState::Clone(cnode) + }) + .unwrap_or(CRCloneState::NoClone) + } + } else { + // We are in a branch, so locate our descendent and prepare + // to clone if needed. + // println!("txid -> {:?} {:?}", node_txid, txid); + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + match path_clone(anode, txid, h, last_seen, first_seen) { + CRCloneState::Clone(cnode) => { + // Do we need to clone? + nmref + .req_clone(txid) + .map(|acnode| { + // We require to be cloned. + last_seen.push(node); + // println!("ls push 8"); + first_seen.push(acnode); + let nmref = branch_ref!(acnode, K, V); + nmref.replace_by_idx(anode_idx, cnode); + CRCloneState::Clone(acnode) + }) + .unwrap_or_else(|| { + // Nope, just insert and unwind. + nmref.replace_by_idx(anode_idx, cnode); + CRCloneState::NoClone + }) + } + CRCloneState::NoClone => { + // Did not clone, unwind. + CRCloneState::NoClone + } + } + } +} + +fn clone_and_remove( + node: *mut Node, + txid: u64, + h: u64, + k: &K, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, +) -> CRRemoveState { + if self_meta!(node).is_leaf() { + leaf_ref!(node, K, V) + .req_clone(txid) + .map(|cnode| { + first_seen.push(cnode); + // println!("ls push 10 {:?}", node); + last_seen.push(node); + let mref = leaf_ref!(cnode, K, V); + match mref.remove(h, k) { + LeafRemoveState::Ok(res) => CRRemoveState::Clone(res, cnode), + LeafRemoveState::Shrink(res) => CRRemoveState::CloneShrink(res, cnode), + } + }) + .unwrap_or_else(|| { + let mref = leaf_ref!(node, K, V); + match mref.remove(h, k) { + LeafRemoveState::Ok(res) => CRRemoveState::NoClone(res), + LeafRemoveState::Shrink(res) => CRRemoveState::Shrink(res), + } + }) + } else { + // Locate the node we need to work on and then react if it + // requests a shrink. + branch_ref!(node, K, V) + .req_clone(txid) + .map(|cnode| { + first_seen.push(cnode); + // println!("ls push 11 {:?}", node); + last_seen.push(node); + // Done mm + let nmref = branch_ref!(cnode, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + match clone_and_remove(anode, txid, h, k, last_seen, first_seen) { + CRRemoveState::NoClone(_res) => { + unreachable!("Should never occur"); + // CRRemoveState::NoClone(res) + } + CRRemoveState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + CRRemoveState::Clone(res, cnode) + } + CRRemoveState::Shrink(_res) => { + unreachable!("This represents a corrupt tree state"); + } + CRRemoveState::CloneShrink(res, nnode) => { + // Put our cloned child into the tree at the correct location, don't worry, + // the shrink_decision will deal with it. + nmref.replace_by_idx(anode_idx, nnode); + + // Now setup the sibling, to the left *or* right. + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + // Okay, now work out what we need to do. + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::Clone(res, cnode) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // println!("ls push 20 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Clone(res, cnode) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values. + // println!("ls push 21 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::CloneShrink(res, cnode) + } + } + } + } + }) + .unwrap_or_else(|| { + // We are already part of this txn + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + match clone_and_remove(anode, txid, h, k, last_seen, first_seen) { + CRRemoveState::NoClone(res) => CRRemoveState::NoClone(res), + CRRemoveState::Clone(res, lnode) => { + nmref.replace_by_idx(anode_idx, lnode); + CRRemoveState::NoClone(res) + } + CRRemoveState::Shrink(res) => { + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::NoClone(res) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // + // A quirk here is based on how clone_sibling_idx works. We may actually + // start with anode_idx of 0, which triggers a right clone, so it's + // *already* in the mm lists. But here right is "last seen" now if + // + // println!("ls push 22 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::NoClone(res) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values, so we begin to shrink up. + // println!("ls push 23 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Shrink(res) + } + } + } + CRRemoveState::CloneShrink(res, nnode) => { + // We don't need to clone, just work on the nmref we have. + // + // Swap in the cloned node to the correct location. + nmref.replace_by_idx(anode_idx, nnode); + // Now setup the sibling, to the left *or* right. + let right_idx = + nmref.clone_sibling_idx(txid, anode_idx, last_seen, first_seen); + match nmref.shrink_decision(right_idx) { + BranchShrinkState::Balanced => { + // K:V were distributed through left and right, + // so no further action needed. + CRRemoveState::NoClone(res) + } + BranchShrinkState::Merge(dnode) => { + // Right was merged to left, and we remain + // valid + // println!("ls push 24 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::NoClone(res) + } + BranchShrinkState::Shrink(dnode) => { + // Right was merged to left, but we have now falled under the needed + // amount of values. + // println!("ls push 25 {:?}", dnode); + debug_assert!(!last_seen.contains(&dnode)); + last_seen.push(dnode); + CRRemoveState::Shrink(res) + } + } + } + } + }) // end unwrap_or_else + } +} + +fn path_get_mut_ref<'a, K: Clone + Hash + Eq + Debug, V: Clone>( + node: *mut Node, + h: u64, + k: &K, +) -> Option<&'a mut V> +where + K: 'a, +{ + if self_meta!(node).is_leaf() { + leaf_ref!(node, K, V).get_mut_ref(h, k) + } else { + // This nmref binds the life of the reference ... + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + // That we get here. So we can't just return it, and we need to 'strip' the + // lifetime so that it's bound to the lifetime of the outer node + // rather than the nmref. + let r: Option<*mut V> = path_get_mut_ref(anode, h, k).map(|v| v as *mut V); + + // I solemly swear I am up to no good. + r.map(|v| unsafe { &mut *v as &mut V }) + } +} + +unsafe fn path_get_slot_mut_ref<'a, K: Clone + Hash + Eq + Debug, V: Clone>( + node: *mut Node, + h: u64, +) -> Option<&'a mut [Datum]> +where + K: 'a, +{ + if self_meta!(node).is_leaf() { + leaf_ref!(node, K, V).get_slot_mut_ref(h) + } else { + // This nmref binds the life of the reference ... + let nmref = branch_ref!(node, K, V); + let anode_idx = nmref.locate_node(h); + let anode = nmref.get_idx_unchecked(anode_idx); + // That we get here. So we can't just return it, and we need to 'strip' the + // lifetime so that it's bound to the lifetime of the outer node + // rather than the nmref. + let r: Option<*mut [Datum]> = + path_get_slot_mut_ref(anode, h).map(|v| v as *mut [Datum]); + + // I solemly swear I am up to no good. + r.map(|v| &mut *v as &mut [Datum]) + } +} + +#[cfg(test)] +mod tests { + use super::super::node::*; + use super::super::states::*; + use super::SuperBlock; + use super::{CursorRead, CursorReadOps}; + use crate::internals::lincowcell::LinCowCellCapable; + use rand::seq::SliceRandom; + use std::mem; + + fn create_leaf_node(v: usize) -> *mut Node { + let node = Node::new_leaf(1); + { + let nmut: &mut Leaf<_, _> = leaf_ref!(node, usize, usize); + nmut.insert_or_update(v as u64, v, v); + } + node as *mut Node + } + + fn create_leaf_node_full(vbase: usize) -> *mut Node { + assert!(vbase % 10 == 0); + let node = Node::new_leaf(1); + { + let nmut = leaf_ref!(node, usize, usize); + for idx in 0..H_CAPACITY { + let v = vbase + idx; + nmut.insert_or_update(v as u64, v, v); + } + // println!("lnode full {:?} -> {:?}", vbase, nmut); + } + node as *mut Node + } + + fn create_branch_node_full(vbase: usize) -> *mut Node { + let l1 = create_leaf_node(vbase); + let l2 = create_leaf_node(vbase + 10); + let lbranch = Node::new_branch(1, l1, l2); + let bref = branch_ref!(lbranch, usize, usize); + for i in 2..HBV_CAPACITY { + let l = create_leaf_node(vbase + (10 * i)); + let r = bref.add_node(l); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + } + } + assert!(bref.slots() == H_CAPACITY); + lbranch as *mut Node + } + + #[test] + fn test_hashmap2_cursor_insert_leaf() { + // First create the node + cursor + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + let prev_txid = wcurs.root_txid(); + + // Now insert - the txid should be different. + let r = wcurs.insert(1, 1, 1); + assert!(r.is_none()); + let r1_txid = wcurs.root_txid(); + assert!(r1_txid == prev_txid + 1); + + // Now insert again - the txid should be the same. + let r = wcurs.insert(2, 2, 2); + assert!(r.is_none()); + let r2_txid = wcurs.root_txid(); + assert!(r2_txid == r1_txid); + // The clones worked as we wanted! + assert!(wcurs.verify()); + } + + #[test] + fn test_hashmap2_cursor_insert_split_1() { + // Given a leaf at max, insert such that: + // + // leaf + // + // leaf -> split leaf + // + // + // root + // / \ + // leaf split leaf + // + // It's worth noting that this is testing the CloneSplit path + // as leaf needs a clone AND to split to achieve the new root. + + let node = create_leaf_node_full(10); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + let prev_txid = wcurs.root_txid(); + + let r = wcurs.insert(1, 1, 1); + assert!(r.is_none()); + let r1_txid = wcurs.root_txid(); + assert!(r1_txid == prev_txid + 1); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_2() { + // Similar to split_1, but test the Split only path. This means + // leaf needs to be below max to start, and we insert enough in-txn + // to trigger a clone of leaf AND THEN to cause the split. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(H_CAPACITY + 1) { + // println!("ITER v {}", v); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_3() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split inbetween l/sl (new txn) + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + let r = wcurs.insert(19, 19, 19); + assert!(r.is_none()); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_4() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split of sl (new txn) + // + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(29, 29, 29); + assert!(r.is_none()); + assert!(wcurs.verify()); + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_5() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split inbetween l/sl (same txn) + // + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Now insert to trigger the needed actions. + // Remember, we only need H_CAPACITY because there is already a + // value in the leaf. + for idx in 0..(H_CAPACITY) { + let v = 10 + 1 + idx; + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_6() { + // root + // / \ + // leaf split leaf + // ^ + // \----- nnode + // + // Check leaf split of sl (same txn) + // + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Now insert to trigger the needed actions. + // Remember, we only need H_CAPACITY because there is already a + // value in the leaf. + for idx in 0..(H_CAPACITY) { + let v = 20 + 1 + idx; + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_7() { + // root + // / \ + // leaf split leaf + // Insert to leaf then split leaf such that root has cloned + // in step 1, but doesn't need clone in 2. + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(11, 11, 11); + assert!(r.is_none()); + assert!(wcurs.verify()); + + let r = wcurs.insert(21, 21, 21); + assert!(r.is_none()); + assert!(wcurs.verify()); + + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_split_8() { + // root + // / \ + // leaf split leaf + // ^ ^ + // \---- nnode 1 \----- nnode 2 + // + // Check double leaf split of sl (same txn). This is to + // take the clonesplit path in the branch case where branch already + // cloned. + // + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + let r = wcurs.insert(19, 19, 19); + assert!(r.is_none()); + assert!(wcurs.verify()); + + let r = wcurs.insert(29, 29, 29); + assert!(r.is_none()); + assert!(wcurs.verify()); + + // println!("{:?}", wcurs); + + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_stress_1() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(H_CAPACITY << 4) { + // println!("ITER v {}", v); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_stress_2() { + // Insert descending + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in (1..(H_CAPACITY << 4)).rev() { + // println!("ITER v {}", v); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_stress_3() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + // Add transaction-ised versions. + #[test] + fn test_hashmap2_cursor_insert_stress_4() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in 1..(H_CAPACITY << 4) { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_stress_5() { + // Insert descending + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in (1..(H_CAPACITY << 4)).rev() { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_insert_stress_6() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let mut sb = unsafe { SuperBlock::new() }; + let mut rdr = sb.create_reader(); + + for v in ins.into_iter() { + let mut wcurs = sb.create_writer(); + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + mem::drop(rdr); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_search_1() { + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(H_CAPACITY << 4) { + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + let r = wcurs.search(v as u64, &v); + assert!(r.unwrap() == &v); + } + + for v in 1..(H_CAPACITY << 4) { + let r = wcurs.search(v as u64, &v); + assert!(r.unwrap() == &v); + } + // On shutdown, check we dropped all as needed. + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_length_1() { + // Check the length is consistent on operations. + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + for v in 1..(H_CAPACITY << 4) { + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + } + // println!("{} == {}", wcurs.len(), H_CAPACITY << 4); + assert!(wcurs.len() == H_CAPACITY << 4); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_01_p0() { + // Check that a single value can be removed correctly without change. + // Check that a missing value is removed as "None". + // Check that emptying the root is ok. + // BOTH of these need new txns to check clone, and then re-use txns. + // + // + let lnode = create_leaf_node_full(0); + let sb = SuperBlock::new_test(1, lnode); + let mut wcurs = sb.create_writer(); + // println!("{:?}", wcurs); + + for v in 0..H_CAPACITY { + let x = wcurs.remove(v as u64, &v); + // println!("{:?}", wcurs); + assert!(x == Some(v)); + } + + for v in 0..H_CAPACITY { + let x = wcurs.remove(v as u64, &v); + assert!(x == None); + } + + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_01_p1() { + let node = create_leaf_node(0); + let sb = SuperBlock::new_test(1, node); + let mut wcurs = sb.create_writer(); + + let _ = wcurs.remove(0, &0); + // println!("{:?}", wcurs); + + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_02() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (new txn) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + println!("{:?}", wcurs); + assert!(wcurs.verify()); + wcurs.remove(20, &20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_03() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "leaf" and merge right (really left, but you know ...). (new txn) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(30); + let root = Node::new_branch(0, lnode, rnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(znode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(10, &10); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_04p0() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup sibling leaf to already be cloned. + wcurs.path_clone(10); + assert!(wcurs.verify()); + + wcurs.remove(20, &20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_04p1() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "split leaf" and merge left. (leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(0); + let root = Node::new_branch(0, znode, lnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(rnode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup leaf to already be cloned. + wcurs.path_clone(20); + assert!(wcurs.verify()); + + wcurs.remove(20, &20); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_05() { + // Given the tree: + // + // root + // / \ + // leaf split leaf + // + // Remove from "leaf" and merge 'right'. (split leaf cloned already) + let lnode = create_leaf_node(10); + let rnode = create_leaf_node(20); + let znode = create_leaf_node(30); + let root = Node::new_branch(0, lnode, rnode); + // Prevent the tree shrinking. + unsafe { (*root).add_node(znode) }; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + // Setup leaf to already be cloned. + wcurs.path_clone(20); + + wcurs.remove(10, &10); + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_06() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - new + // + // when remove from rbranch, mergc left to lbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + + wcurs.remove(30, &30); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_07() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - new + // + // when remove from lbranch, merge right to rbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(10, &10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_08() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - full + // rbranch - 2node + // txn - new + // + // when remove from rbranch, borrow from lbranch + // will NOT reduce height + let lbranch = create_branch_node_full(0); + + let r1 = create_leaf_node(80); + let r2 = create_leaf_node(90); + let rbranch = Node::new_branch(0, r1, r2); + + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(80, &80); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_09() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - full + // txn - new + // + // when remove from lbranch, borrow from rbranch + // will NOT reduce height + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let lbranch = Node::new_branch(0, l1, l2); + + let rbranch = create_branch_node_full(100); + + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(10, &10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_10() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - touch lbranch + // + // when remove from rbranch, mergc left to lbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + + assert!(wcurs.verify()); + + wcurs.path_clone(0); + wcurs.path_clone(10); + + wcurs.remove(30, &30); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_11() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - 2node + // txn - touch rbranch + // + // when remove from lbranch, merge right to rbranch. + // should cause tree height reduction. + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let r1 = create_leaf_node(20); + let r2 = create_leaf_node(30); + let lbranch = Node::new_branch(0, l1, l2); + let rbranch = Node::new_branch(0, r1, r2); + let root: *mut Branch = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _); + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.path_clone(20); + wcurs.path_clone(30); + + wcurs.remove(0, &0); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_12() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - full + // rbranch - 2node + // txn - touch lbranch + // + // when remove from rbranch, borrow from lbranch + // will NOT reduce height + let lbranch = create_branch_node_full(0); + + let r1 = create_leaf_node(80); + let r2 = create_leaf_node(90); + let rbranch = Node::new_branch(0, r1, r2); + + let root = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _) as *mut Node; + // let count = HBV_CAPACITY + 2; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.path_clone(0); + wcurs.path_clone(10); + wcurs.path_clone(20); + + wcurs.remove(90, &90); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_13() { + // Given the tree: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // conditions: + // lbranch - 2node + // rbranch - full + // txn - touch rbranch + // + // when remove from lbranch, borrow from rbranch + // will NOT reduce height + let l1 = create_leaf_node(0); + let l2 = create_leaf_node(10); + let lbranch = Node::new_branch(0, l1, l2); + + let rbranch = create_branch_node_full(100); + + let root = + Node::new_branch(0, lbranch as *mut _, rbranch as *mut _) as *mut Node; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + for i in 0..HBV_CAPACITY { + let k = 100 + (10 * i); + wcurs.path_clone(k as u64); + } + assert!(wcurs.verify()); + + wcurs.remove(10, &10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_14() { + // Test leaf borrow left + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node(20); + let root = Node::new_branch(0, lnode, rnode) as *mut Node; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(20, &20); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_15() { + // Test leaf borrow right. + let lnode = create_leaf_node(10) as *mut Node; + let rnode = create_leaf_node_full(20) as *mut Node; + let root = Node::new_branch(0, lnode, rnode) as *mut Node; + let sb = SuperBlock::new_test(1, root as *mut Node); + let mut wcurs = sb.create_writer(); + assert!(wcurs.verify()); + + wcurs.remove(10, &10); + + assert!(wcurs.verify()); + mem::drop(wcurs); + mem::drop(sb); + assert_released(); + } + + fn tree_create_rand() -> (SuperBlock, CursorRead) { + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let mut sb = unsafe { SuperBlock::new() }; + let rdr = sb.create_reader(); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.insert(v as u64, v, v); + assert!(r.is_none()); + assert!(wcurs.verify()); + } + + let rdr = sb.pre_commit(wcurs, &rdr); + (sb, rdr) + } + + #[test] + fn test_hashmap2_cursor_remove_stress_1() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in 1..(H_CAPACITY << 4) { + // println!("-- ITER v {}", v); + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // On shutdown, check we dropped all as needed. + let rdr2 = sb.pre_commit(wcurs, &rdr); + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_stress_2() { + // Insert descending + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in (1..(H_CAPACITY << 4)).rev() { + // println!("ITER v {}", v); + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + let rdr2 = sb.pre_commit(wcurs, &rdr); + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_stress_3() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let (mut sb, rdr) = tree_create_rand(); + let mut wcurs = sb.create_writer(); + + for v in ins.into_iter() { + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + } + // println!("{:?}", wcurs); + // println!("DENSITY -> {:?}", wcurs.get_tree_density()); + // On shutdown, check we dropped all as needed. + let rdr2 = sb.pre_commit(wcurs, &rdr); + std::mem::drop(rdr2); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + // Add transaction-ised versions. + #[test] + fn test_hashmap2_cursor_remove_stress_4() { + // Insert ascending - we want to ensure the tree is a few levels deep + // so we do this to a reasonable number. + let (mut sb, mut rdr) = tree_create_rand(); + + for v in 1..(H_CAPACITY << 4) { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_stress_5() { + // Insert descending + let (mut sb, mut rdr) = tree_create_rand(); + + for v in (1..(H_CAPACITY << 4)).rev() { + let mut wcurs = sb.create_writer(); + // println!("ITER v {}", v); + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + // mem::drop(node); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + #[test] + fn test_hashmap2_cursor_remove_stress_6() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..(H_CAPACITY << 4)).collect(); + ins.shuffle(&mut rng); + + let (mut sb, mut rdr) = tree_create_rand(); + + for v in ins.into_iter() { + let mut wcurs = sb.create_writer(); + let r = wcurs.remove(v as u64, &v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + rdr = sb.pre_commit(wcurs, &rdr); + } + // println!("{:?}", node); + // On shutdown, check we dropped all as needed. + // mem::drop(node); + std::mem::drop(rdr); + std::mem::drop(sb); + assert_released(); + } + + /* + #[test] + #[cfg_attr(miri, ignore)] + fn test_hashmap2_cursor_remove_stress_7() { + // Insert random + let mut rng = rand::thread_rng(); + let mut ins: Vec = (1..10240).collect(); + + let node: *mut Leaf = Node::new_leaf(0); + let mut wcurs = CursorWrite::new_test(1, node as *mut _); + wcurs.extend(ins.iter().map(|v| (*v, *v))); + + ins.shuffle(&mut rng); + + let compacts = 0; + + for v in ins.into_iter() { + let r = wcurs.remove(&v); + assert!(r == Some(v)); + assert!(wcurs.verify()); + // let (l, m) = wcurs.tree_density(); + // if l > 0 && (m / l) > 1 { + // compacts += 1; + // } + } + println!("compacts {:?}", compacts); + } + */ + + /* + #[test] + fn test_bptree_cursor_get_mut_ref_1() { + // Test that we can clone a path (new txn) + // Test that we don't re-clone. + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let mut wcurs = CursorWrite::new(root, 0); + assert!(wcurs.verify()); + + let r1 = wcurs.get_mut_ref(&10); + std::mem::drop(r1); + let r1 = wcurs.get_mut_ref(&10); + std::mem::drop(r1); + } + */ +} diff --git a/vendor/concread/src/internals/hashmap/iter.rs b/vendor/concread/src/internals/hashmap/iter.rs new file mode 100644 index 0000000..43321f5 --- /dev/null +++ b/vendor/concread/src/internals/hashmap/iter.rs @@ -0,0 +1,380 @@ +//! Iterators for the map. + +// Iterators for the bptree +use super::node::{Branch, Leaf, Meta, Node}; +use std::collections::VecDeque; +use std::fmt::Debug; +use std::hash::Hash; +use std::marker::PhantomData; + +pub(crate) struct LeafIter<'a, K, V> +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + length: Option, + // idx: usize, + stack: VecDeque<(*mut Node, usize)>, + phantom_k: PhantomData<&'a K>, + phantom_v: PhantomData<&'a V>, +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> LeafIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, size_hint: bool) -> Self { + let length = if size_hint { + Some(unsafe { (*root).leaf_count() }) + } else { + None + }; + + // We probably need to position the VecDeque here. + let mut stack = VecDeque::new(); + + let mut work_node = root; + loop { + stack.push_back((work_node, 0)); + if self_meta!(work_node).is_leaf() { + break; + } else { + work_node = branch_ref!(work_node, K, V).get_idx_unchecked(0); + } + } + + LeafIter { + length, + // idx: 0, + stack, + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + #[cfg(test)] + pub(crate) fn new_base() -> Self { + LeafIter { + length: None, + // idx: 0, + stack: VecDeque::new(), + phantom_k: PhantomData, + phantom_v: PhantomData, + } + } + + pub(crate) fn stack_position(&mut self, idx: usize) { + // Get the current branch, it must the the back. + if let Some((bref, bpidx)) = self.stack.back() { + let wbranch = branch_ref!(*bref, K, V); + if let Some(node) = wbranch.get_idx_checked(idx) { + // Insert as much as possible now. First insert + // our current idx, then all the 0, idxs. + let mut work_node = node; + let mut work_idx = idx; + loop { + self.stack.push_back((work_node, work_idx)); + if self_meta!(work_node).is_leaf() { + break; + } else { + work_idx = 0; + work_node = branch_ref!(work_node, K, V).get_idx_unchecked(work_idx); + } + } + } else { + // Unwind further. + let bpidx = *bpidx + 1; + let _ = self.stack.pop_back(); + self.stack_position(bpidx) + } + } + // Must have been none, so we are exhausted. This means + // the stack is empty, so return. + } + + /* + fn peek(&'a mut self) -> Option<&'a Leaf> { + // I have no idea how peekable works, yolo. + self.stack.back().map(|t| t.0.as_leaf()) + } + */ +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> Iterator for LeafIter<'a, K, V> { + type Item = &'a Leaf; + + fn next(&mut self) -> Option { + // base case is the vecdeque is empty + let (leafref, parent_idx) = match self.stack.pop_back() { + Some(lr) => lr, + None => return None, + }; + + // Setup the veqdeque for the next iteration. + self.stack_position(parent_idx + 1); + + // Return the leaf as we found at the start, regardless of the + // stack operations. + Some(leaf_ref!(leafref, K, V)) + } + + fn size_hint(&self) -> (usize, Option) { + match self.length { + Some(l) => (l, Some(l)), + // We aren't (shouldn't) be estimating + None => (0, None), + } + } +} + +/// Iterator over references to Key Value pairs stored in the map. +pub struct Iter<'a, K, V> +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + length: usize, + slot_idx: usize, + bk_idx: usize, + curleaf: Option<&'a Leaf>, + leafiter: LeafIter<'a, K, V>, +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> Iter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + let mut liter = LeafIter::new(root, false); + let leaf = liter.next(); + // We probably need to position the VecDeque here. + Iter { + length, + slot_idx: 0, + bk_idx: 0, + curleaf: leaf, + leafiter: liter, + } + } +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> Iterator for Iter<'a, K, V> { + type Item = (&'a K, &'a V); + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + if let Some(leaf) = self.curleaf { + if let Some(r) = leaf.get_kv_idx_checked(self.slot_idx, self.bk_idx) { + self.bk_idx += 1; + Some(r) + } else { + // Are we partway in a bucket? + if self.bk_idx > 0 { + // It's probably ended, next slot. + self.slot_idx += 1; + self.bk_idx = 0; + self.next() + } else { + // We've exhasuted the slots sink bk_idx == 0 was empty. + self.curleaf = self.leafiter.next(); + self.slot_idx = 0; + self.bk_idx = 0; + self.next() + } + } + } else { + None + } + } + + /// Provide a hint as to the number of items this iterator will yield. + fn size_hint(&self) -> (usize, Option) { + (self.length, Some(self.length)) + } +} + +/// Iterater over references to Keys stored in the map. +pub struct KeyIter<'a, K, V> +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + iter: Iter<'a, K, V>, +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> KeyIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + KeyIter { + iter: Iter::new(root, length), + } + } +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> Iterator for KeyIter<'a, K, V> { + type Item = &'a K; + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + self.iter.next().map(|(k, _)| k) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +/// Iterater over references to Values stored in the map. +pub struct ValueIter<'a, K, V> +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + iter: Iter<'a, K, V>, +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> ValueIter<'a, K, V> { + pub(crate) fn new(root: *mut Node, length: usize) -> Self { + ValueIter { + iter: Iter::new(root, length), + } + } +} + +impl<'a, K: Clone + Hash + Eq + Debug, V: Clone> Iterator for ValueIter<'a, K, V> { + type Item = &'a V; + + /// Yield the next key value reference, or `None` if exhausted. + fn next(&mut self) -> Option { + self.iter.next().map(|(_, v)| v) + } + + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } +} + +#[cfg(test)] +mod tests { + use super::super::cursor::SuperBlock; + use super::super::node::{Branch, Leaf, Node, H_CAPACITY}; + use super::{Iter, LeafIter}; + + fn create_leaf_node_full(vbase: usize) -> *mut Node { + assert!(vbase % 10 == 0); + let node = Node::new_leaf(0); + { + let nmut = leaf_ref!(node, usize, usize); + for idx in 0..H_CAPACITY { + let v = vbase + idx; + nmut.insert_or_update(v as u64, v, v); + } + } + node as *mut _ + } + + #[test] + fn test_hashmap2_iter_leafiter_1() { + let test_iter: LeafIter = LeafIter::new_base(); + assert!(test_iter.count() == 0); + } + + #[test] + fn test_hashmap2_iter_leafiter_2() { + let lnode = create_leaf_node_full(10); + let mut test_iter = LeafIter::new(lnode, true); + + assert!(test_iter.size_hint() == (1, Some(1))); + + let lref = test_iter.next().unwrap(); + assert!(lref.min() == 10); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, lnode as *mut _); + } + + #[test] + fn test_hashmap2_iter_leafiter_3() { + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let mut test_iter: LeafIter = LeafIter::new(root as *mut _, true); + + assert!(test_iter.size_hint() == (2, Some(2))); + let lref = test_iter.next().unwrap(); + let rref = test_iter.next().unwrap(); + assert!(lref.min() == 10); + assert!(rref.min() == 20); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_hashmap2_iter_leafiter_4() { + let l1node = create_leaf_node_full(10); + let r1node = create_leaf_node_full(20); + let l2node = create_leaf_node_full(30); + let r2node = create_leaf_node_full(40); + let b1node = Node::new_branch(0, l1node, r1node); + let b2node = Node::new_branch(0, l2node, r2node); + let root: *mut Branch = + Node::new_branch(0, b1node as *mut _, b2node as *mut _); + let mut test_iter: LeafIter = LeafIter::new(root as *mut _, true); + + assert!(test_iter.size_hint() == (4, Some(4))); + let l1ref = test_iter.next().unwrap(); + let r1ref = test_iter.next().unwrap(); + let l2ref = test_iter.next().unwrap(); + let r2ref = test_iter.next().unwrap(); + assert!(l1ref.min() == 10); + assert!(r1ref.min() == 20); + assert!(l2ref.min() == 30); + assert!(r2ref.min() == 40); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_hashmap2_iter_leafiter_5() { + let lnode = create_leaf_node_full(10); + let mut test_iter = LeafIter::new(lnode, true); + + assert!(test_iter.size_hint() == (1, Some(1))); + + let lref = test_iter.next().unwrap(); + assert!(lref.min() == 10); + assert!(test_iter.next().is_none()); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, lnode as *mut _); + } + + #[test] + fn test_hashmap2_iter_iter_1() { + // Make a tree + let lnode = create_leaf_node_full(10); + let rnode = create_leaf_node_full(20); + let root = Node::new_branch(0, lnode, rnode); + let test_iter: Iter = Iter::new(root as *mut _, H_CAPACITY * 2); + + assert!(test_iter.size_hint() == (H_CAPACITY * 2, Some(H_CAPACITY * 2))); + assert!(test_iter.count() == H_CAPACITY * 2); + // Iterate! + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } + + #[test] + fn test_hashmap2_iter_iter_2() { + let l1node = create_leaf_node_full(10); + let r1node = create_leaf_node_full(20); + let l2node = create_leaf_node_full(30); + let r2node = create_leaf_node_full(40); + let b1node = Node::new_branch(0, l1node, r1node); + let b2node = Node::new_branch(0, l2node, r2node); + let root: *mut Branch = + Node::new_branch(0, b1node as *mut _, b2node as *mut _); + let test_iter: Iter = Iter::new(root as *mut _, H_CAPACITY * 4); + + // println!("{:?}", test_iter.size_hint()); + + assert!(test_iter.size_hint() == (H_CAPACITY * 4, Some(H_CAPACITY * 4))); + assert!(test_iter.count() == H_CAPACITY * 4); + // This drops everything. + let _sb: SuperBlock = SuperBlock::new_test(1, root as *mut _); + } +} diff --git a/vendor/concread/src/internals/hashmap/macros.rs b/vendor/concread/src/internals/hashmap/macros.rs new file mode 100644 index 0000000..c725464 --- /dev/null +++ b/vendor/concread/src/internals/hashmap/macros.rs @@ -0,0 +1,48 @@ +macro_rules! hash_key { + ($self:expr, $k:expr) => {{ + let mut hasher = AHasher::new_with_keys($self.key1, $self.key2); + $k.hash(&mut hasher); + hasher.finish() + }}; +} + +macro_rules! debug_assert_leaf { + ($x:expr) => {{ + debug_assert!(unsafe { $x.ctrl.a.0.is_leaf() }); + }}; +} + +macro_rules! debug_assert_branch { + ($x:expr) => {{ + debug_assert!(unsafe { $x.ctrl.a.0.is_branch() }); + }}; +} + +macro_rules! self_meta { + ($x:expr) => {{ + #[allow(unused_unsafe)] + unsafe { + &mut *($x as *mut Meta) + } + }}; +} + +macro_rules! branch_ref { + ($x:expr, $k:ty, $v:ty) => {{ + #[allow(unused_unsafe)] + unsafe { + debug_assert!(unsafe { (*$x).ctrl.a.0.is_branch() }); + &mut *($x as *mut Branch<$k, $v>) + } + }}; +} + +macro_rules! leaf_ref { + ($x:expr, $k:ty, $v:ty) => {{ + #[allow(unused_unsafe)] + unsafe { + debug_assert!(unsafe { (*$x).ctrl.a.0.is_leaf() }); + &mut *($x as *mut Leaf<$k, $v>) + } + }}; +} diff --git a/vendor/concread/src/internals/hashmap/mod.rs b/vendor/concread/src/internals/hashmap/mod.rs new file mode 100644 index 0000000..65bed56 --- /dev/null +++ b/vendor/concread/src/internals/hashmap/mod.rs @@ -0,0 +1,26 @@ +//! HashMap - A concurrently readable HashMap +//! +//! This is a specialisation of the `BptreeMap`, allowing a concurrently readable +//! HashMap. Unlike a traditional hashmap it does *not* have `O(1)` lookup, as it +//! internally uses a tree-like structure to store a series of buckets. However +//! if you do not need key-ordering, due to the storage of the hashes as `u64` +//! the operations in the tree to seek the bucket is much faster than the use of +//! the same key in the `BptreeMap`. +//! +//! For more details. see the `BptreeMap` +//! +//! This structure is very different to the `im` crate. The `im` crate is +//! sync + send over individual operations. This means that multiple writes can +//! be interleaved atomicly and safely, and the readers always see the latest +//! data. While this is potentially useful to a set of problems, transactional +//! structures are suited to problems where readers have to maintain consistent +//! data views for a duration of time, cpu cache friendly behaviours and +//! database like transaction properties (ACID). + +#[macro_use] +mod macros; +pub mod cursor; +pub mod iter; +mod node; +mod simd; +mod states; diff --git a/vendor/concread/src/internals/hashmap/node.rs b/vendor/concread/src/internals/hashmap/node.rs new file mode 100644 index 0000000..a22d5b3 --- /dev/null +++ b/vendor/concread/src/internals/hashmap/node.rs @@ -0,0 +1,2749 @@ +use super::cursor::Datum; +use super::simd::*; +use super::states::*; +use crate::utils::*; +use crossbeam::utils::CachePadded; +use std::borrow::Borrow; +use std::fmt::{self, Debug, Error}; +use std::hash::Hash; +use std::marker::PhantomData; +use std::mem::ManuallyDrop; +use std::mem::MaybeUninit; +use std::ptr; + +use smallvec::SmallVec; + +#[cfg(feature = "simd_support")] +use core_simd::u64x8; + +#[cfg(all(test, not(miri)))] +use parking_lot::Mutex; +#[cfg(test)] +use std::collections::BTreeSet; +#[cfg(all(test, not(miri)))] +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub(crate) const TXID_MASK: u64 = 0x0fff_ffff_ffff_fff0; +const FLAG_MASK: u64 = 0xf000_0000_0000_0000; +const COUNT_MASK: u64 = 0x0000_0000_0000_000f; +pub(crate) const TXID_SHF: usize = 4; +// const FLAG__BRANCH: u64 = 0x1000_0000_0000_0000; +// const FLAG__LEAF: u64 = 0x2000_0000_0000_0000; +const FLAG_HASH_LEAF: u64 = 0x4000_0000_0000_0000; +const FLAG_HASH_BRANCH: u64 = 0x8000_0000_0000_0000; +const FLAG_DROPPED: u64 = 0xeeee_ffff_aaaa_bbbb; +#[cfg(all(test, not(miri)))] +const FLAG_POISON: u64 = 0xabcd_abcd_abcd_abcd; + +pub(crate) const H_CAPACITY: usize = 7; +const H_CAPACITY_N1: usize = H_CAPACITY - 1; +pub(crate) const HBV_CAPACITY: usize = H_CAPACITY + 1; + +const DEFAULT_BUCKET_ALLOC: usize = 1; + +#[cfg(not(feature = "simd_support"))] +#[allow(non_camel_case_types)] +pub struct u64x8 { + _data: [u64; 8], +} + +#[cfg(not(feature = "simd_support"))] +impl u64x8 { + #[inline(always)] + fn from_array(data: [u64; 8]) -> Self { + Self { _data: data } + } +} + +#[cfg(all(test, not(miri)))] +thread_local!(static NODE_COUNTER: AtomicUsize = AtomicUsize::new(1)); +#[cfg(all(test, not(miri)))] +thread_local!(static ALLOC_LIST: Mutex> = Mutex::new(BTreeSet::new())); + +#[cfg(all(test, not(miri)))] +fn alloc_nid() -> usize { + let nid: usize = NODE_COUNTER.with(|nc| nc.fetch_add(1, Ordering::AcqRel)); + #[cfg(all(test, not(miri)))] + { + ALLOC_LIST.with(|llist| llist.lock().insert(nid)); + } + // eprintln!("Allocate -> {:?}", nid); + nid +} + +#[cfg(all(test, not(miri)))] +fn release_nid(nid: usize) { + // println!("Release -> {:?}", nid); + // debug_assert!(nid != 3); + { + let r = ALLOC_LIST.with(|llist| llist.lock().remove(&nid)); + assert!(r == true); + } +} + +#[cfg(test)] +pub(crate) fn assert_released() { + #[cfg(not(miri))] + { + let is_empt = ALLOC_LIST.with(|llist| { + let x = llist.lock(); + // println!("Remaining -> {:?}", x); + x.is_empty() + }); + assert!(is_empt); + } +} + +#[repr(C)] +pub(crate) struct Meta(u64); + +pub(crate) union Ctrl { + pub a: ManuallyDrop<(Meta, [u64; H_CAPACITY])>, + pub simd: ManuallyDrop, +} + +#[repr(C, align(64))] +pub(crate) struct Branch +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + pub ctrl: Ctrl, + #[cfg(all(test, not(miri)))] + poison: u64, + nodes: [*mut Node; HBV_CAPACITY], + #[cfg(all(test, not(miri)))] + pub(crate) nid: usize, +} + +type Bucket = SmallVec<[Datum; DEFAULT_BUCKET_ALLOC]>; + +#[repr(C, align(64))] +pub(crate) struct Leaf +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + pub ctrl: Ctrl, + #[cfg(all(test, not(miri)))] + poison: u64, + pub values: [MaybeUninit>; H_CAPACITY], + #[cfg(all(test, not(miri)))] + pub nid: usize, +} + +#[repr(C, align(64))] +pub(crate) struct Node +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + pub(crate) ctrl: Ctrl, + k: PhantomData, + v: PhantomData, +} + +impl Node { + pub(crate) fn new_leaf(txid: u64) -> *mut Leaf { + // println!("Req new hash leaf"); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + let x: Box>> = Box::new(CachePadded::new(Leaf { + ctrl: Ctrl { + simd: ManuallyDrop::new(u64x8::from_array([ + (txid << TXID_SHF) | FLAG_HASH_LEAF, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + ])), + }, + #[cfg(all(test, not(miri)))] + poison: FLAG_POISON, + values: unsafe { MaybeUninit::uninit().assume_init() }, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + Box::into_raw(x) as *mut Leaf + } + + fn new_leaf_bk(flags: u64, h: u64, bk: Bucket) -> *mut Leaf { + // println!("Req new hash leaf ins"); + // debug_assert!(false); + debug_assert!((flags & FLAG_MASK) == FLAG_HASH_LEAF); + let x: Box>> = Box::new(CachePadded::new(Leaf { + // Let the flag, txid and the slots of value 1 through. + ctrl: Ctrl { + simd: ManuallyDrop::new(u64x8::from_array([ + flags & (TXID_MASK | FLAG_MASK | 1), + h, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + ])), + }, + #[cfg(all(test, not(miri)))] + poison: FLAG_POISON, + values: [ + MaybeUninit::new(bk), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + MaybeUninit::uninit(), + ], + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + Box::into_raw(x) as *mut Leaf + } + + fn new_leaf_ins(flags: u64, h: u64, k: K, v: V) -> *mut Leaf { + Self::new_leaf_bk(flags, h, smallvec![Datum { k, v }]) + } + + pub(crate) fn new_branch( + txid: u64, + l: *mut Node, + r: *mut Node, + ) -> *mut Branch { + // println!("Req new branch"); + debug_assert!(!l.is_null()); + debug_assert!(!r.is_null()); + debug_assert!(unsafe { (*l).verify() }); + debug_assert!(unsafe { (*r).verify() }); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + let pivot = unsafe { (*r).min() }; + let x: Box>> = Box::new(CachePadded::new(Branch { + // This sets the default (key) slots to 1, since we take an l/r + ctrl: Ctrl { + simd: ManuallyDrop::new(u64x8::from_array([ + (txid << TXID_SHF) | FLAG_HASH_BRANCH | 1, + pivot, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + ])), + }, + #[cfg(all(test, not(miri)))] + poison: FLAG_POISON, + nodes: [ + l, + r, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + ], + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + let b = Box::into_raw(x) as *mut Branch; + debug_assert!(unsafe { (*b).verify() }); + b + } + + #[inline(always)] + #[cfg(test)] + pub(crate) fn get_txid(&self) -> u64 { + unsafe { self.ctrl.a.0.get_txid() } + } + + #[inline(always)] + pub(crate) fn is_leaf(&self) -> bool { + unsafe { self.ctrl.a.0.is_leaf() } + } + + #[inline(always)] + #[allow(unused)] + pub(crate) fn is_branch(&self) -> bool { + unsafe { self.ctrl.a.0.is_branch() } + } + + #[cfg(test)] + pub(crate) fn tree_density(&self) -> (usize, usize, usize) { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + (lref.count(), lref.slots(), H_CAPACITY) + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + let mut lcount = 0; // leaf count + let mut lslots = 0; // leaf populated slots + let mut mslots = 0; // leaf max possible + for idx in 0..(bref.slots() + 1) { + let n = bref.nodes[idx] as *mut Node; + let (c, l, m) = unsafe { (*n).tree_density() }; + lcount += c; + lslots += l; + mslots += m; + } + (lcount, lslots, mslots) + } + _ => unreachable!(), + } + } + + pub(crate) fn leaf_count(&self) -> usize { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => 1, + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + let mut lcount = 0; // leaf count + for idx in 0..(bref.slots() + 1) { + let n = bref.nodes[idx] as *mut Node; + lcount += unsafe { (*n).leaf_count() }; + } + lcount + } + _ => unreachable!(), + } + } + + #[cfg(test)] + #[inline(always)] + pub(crate) fn get_ref(&self, h: u64, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Eq, + { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.get_ref(h, k) + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.get_ref(h, k) + } + _ => { + // println!("FLAGS: {:x}", self.meta.0); + unreachable!() + } + } + } + + #[inline(always)] + pub(crate) fn min(&self) -> u64 { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.min() + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.min() + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn max(&self) -> u64 { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.max() + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.max() + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn verify(&self) -> bool { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(self as *const _ as *const Leaf) }; + lref.verify() + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + bref.verify() + } + _ => unreachable!(), + } + } + + #[cfg(test)] + fn no_cycles_inner(&self, track: &mut BTreeSet<*const Self>) -> bool { + match unsafe { self.ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + // check if we are in the set? + track.insert(self as *const Self) + } + FLAG_HASH_BRANCH => { + if track.insert(self as *const Self) { + // check + let bref = unsafe { &*(self as *const _ as *const Branch) }; + for i in 0..(bref.slots() + 1) { + let n = bref.nodes[i]; + let r = unsafe { (*n).no_cycles_inner(track) }; + if r == false { + // panic!(); + return false; + } + } + true + } else { + // panic!(); + false + } + } + _ => { + // println!("FLAGS: {:x}", self.meta.0); + unreachable!() + } + } + } + + #[cfg(test)] + pub(crate) fn no_cycles(&self) -> bool { + let mut track = BTreeSet::new(); + self.no_cycles_inner(&mut track) + } + + pub(crate) fn sblock_collect(&mut self, alloc: &mut Vec<*mut Node>) { + // Reset our txid. + // self.meta.0 &= FLAG_MASK | COUNT_MASK; + // self.meta.0 |= txid << TXID_SHF; + + if (unsafe { self.ctrl.a.0 .0 } & FLAG_MASK) == FLAG_HASH_BRANCH { + let bref = unsafe { &*(self as *const _ as *const Branch) }; + for idx in 0..(bref.slots() + 1) { + alloc.push(bref.nodes[idx]); + let n = bref.nodes[idx] as *mut Node; + unsafe { (*n).sblock_collect(alloc) }; + } + } + } + + pub(crate) fn free(node: *mut Node) { + let self_meta = self_meta!(node); + match self_meta.0 & FLAG_MASK { + FLAG_HASH_LEAF => Leaf::free(node as *mut Leaf), + FLAG_HASH_BRANCH => Branch::free(node as *mut Branch), + _ => unreachable!(), + } + } +} + +impl Meta { + #[inline(always)] + fn set_slots(&mut self, c: usize) { + debug_assert!(c < 16); + // Zero the bits in the flag from the slots. + self.0 &= FLAG_MASK | TXID_MASK; + // Assign them. + self.0 |= c as u8 as u64; + } + + #[inline(always)] + pub(crate) fn slots(&self) -> usize { + (self.0 & COUNT_MASK) as usize + } + + #[inline(always)] + fn add_slots(&mut self, x: usize) { + self.set_slots(self.slots() + x); + } + + #[inline(always)] + fn inc_slots(&mut self) { + debug_assert!(self.slots() < 15); + // Since slots is the lowest bits, we can just inc + // dec this as normal. + self.0 += 1; + } + + #[inline(always)] + fn dec_slots(&mut self) { + debug_assert!(self.slots() > 0); + self.0 -= 1; + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + (self.0 & TXID_MASK) >> TXID_SHF + } + + #[inline(always)] + pub(crate) fn is_leaf(&self) -> bool { + (self.0 & FLAG_MASK) == FLAG_HASH_LEAF + } + + #[inline(always)] + pub(crate) fn is_branch(&self) -> bool { + (self.0 & FLAG_MASK) == FLAG_HASH_BRANCH + } +} + +impl Leaf { + #[inline(always)] + #[cfg(test)] + fn set_slots(&mut self, c: usize) { + debug_assert_leaf!(self); + unsafe { (*self.ctrl.a).0.set_slots(c) } + } + + #[inline(always)] + pub(crate) fn slots(&self) -> usize { + debug_assert_leaf!(self); + unsafe { self.ctrl.a.0.slots() } + } + + #[inline(always)] + fn inc_slots(&mut self) { + debug_assert_leaf!(self); + unsafe { (*self.ctrl.a).0.inc_slots() } + } + + #[inline(always)] + fn dec_slots(&mut self) { + debug_assert_leaf!(self); + unsafe { (*self.ctrl.a).0.dec_slots() } + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + debug_assert_leaf!(self); + unsafe { self.ctrl.a.0.get_txid() } + } + + pub(crate) fn count(&self) -> usize { + let mut c = 0; + for slot_idx in 0..self.slots() { + c += unsafe { (*self.values[slot_idx].as_ptr()).len() }; + } + c + } + + pub(crate) fn get_ref(&self, h: u64, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Eq, + { + debug_assert_leaf!(self); + leaf_simd_search(self, h, k) + .ok() + .map(|(slot_idx, bk_idx)| unsafe { + let bucket = (*self.values[slot_idx].as_ptr()).as_slice(); + &(bucket.get_unchecked(bk_idx).v) + }) + } + + pub(crate) fn get_mut_ref(&mut self, h: u64, k: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Eq, + { + debug_assert_leaf!(self); + leaf_simd_search(self, h, k) + .ok() + .map(|(slot_idx, bk_idx)| unsafe { + let bucket = (*self.values[slot_idx].as_mut_ptr()).as_mut_slice(); + &mut (bucket.get_unchecked_mut(bk_idx).v) + }) + } + + pub(crate) fn get_slot_mut_ref(&mut self, h: u64) -> Option<&mut [Datum]> + where + K: Borrow, + Q: Eq, + { + debug_assert_leaf!(self); + unsafe { + leaf_simd_get_slot(self, h) + .map(|slot_idx| (*self.values[slot_idx].as_mut_ptr()).as_mut_slice()) + } + } + + #[inline(always)] + pub(crate) fn get_kv_idx_checked(&self, slot_idx: usize, bk_idx: usize) -> Option<(&K, &V)> { + debug_assert_leaf!(self); + if slot_idx < self.slots() { + let bucket = unsafe { (*self.values[slot_idx].as_ptr()).as_slice() }; + bucket.get(bk_idx).map(|d| (&d.k, &d.v)) + } else { + None + } + } + + pub(crate) fn min(&self) -> u64 { + debug_assert!(self.slots() > 0); + unsafe { self.ctrl.a.1[0] } + } + + pub(crate) fn max(&self) -> u64 { + debug_assert!(self.slots() > 0); + unsafe { self.ctrl.a.1[self.slots() - 1] } + } + + pub(crate) fn req_clone(&self, txid: u64) -> Option<*mut Node> { + debug_assert_leaf!(self); + debug_assert!(txid < (TXID_MASK >> TXID_SHF)); + if self.get_txid() == txid { + // Same txn, no action needed. + None + } else { + // println!("Req clone leaf"); + // debug_assert!(false); + // Diff txn, must clone. + let new_txid = + (unsafe { self.ctrl.a.0 .0 } & (FLAG_MASK | COUNT_MASK)) | (txid << TXID_SHF); + let x: Box>> = Box::new(CachePadded::new(Leaf { + ctrl: Ctrl { + simd: ManuallyDrop::new(u64x8::from_array([ + new_txid, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + ])), + }, + #[cfg(all(test, not(miri)))] + poison: FLAG_POISON, + values: unsafe { MaybeUninit::uninit().assume_init() }, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + + let x = Box::into_raw(x); + let xr = x as *mut Leaf; + // Dup the keys + unsafe { + ptr::copy_nonoverlapping( + &self.ctrl.a.1 as *const u64, + (*(*xr).ctrl.a).1.as_mut_ptr(), + H_CAPACITY, + ) + } + + // Copy in the values to the correct location. + for idx in 0..self.slots() { + unsafe { + let lvalue = (*self.values[idx].as_ptr()).clone(); + (*x).values[idx].as_mut_ptr().write(lvalue); + } + } + + Some(x as *mut Node) + } + } + + pub(crate) fn insert_or_update(&mut self, h: u64, k: K, mut v: V) -> LeafInsertState { + debug_assert_leaf!(self); + // Find the location we need to update + let r = leaf_simd_search(self, h, &k); + match r { + KeyLoc::Ok(slot_idx, bk_idx) => { + // It exists at idx, replace the value. + let bucket = unsafe { &mut (*self.values[slot_idx].as_mut_ptr()) }; + let prev = unsafe { bucket.as_mut_slice().get_unchecked_mut(bk_idx) }; + std::mem::swap(&mut prev.v, &mut v); + // Prev now contains the original value, return it! + LeafInsertState::Ok(Some(v)) + } + KeyLoc::Collision(slot_idx) => { + // The hash collided, but that's okay! We just append to the slice. + let bucket = unsafe { &mut (*self.values[slot_idx].as_mut_ptr()) }; + bucket.push(Datum { k, v }); + LeafInsertState::Ok(None) + } + KeyLoc::Missing(idx) => { + if self.slots() >= H_CAPACITY { + // Overflow to a new node + if idx >= self.slots() { + // Greate than all else, split right + let rnode = Node::new_leaf_ins(unsafe { self.ctrl.a.0 .0 }, h, k, v); + LeafInsertState::Split(rnode) + } else if idx == 0 { + // Lower than all else, split left. + // let lnode = ...; + let lnode = Node::new_leaf_ins(unsafe { self.ctrl.a.0 .0 }, h, k, v); + LeafInsertState::RevSplit(lnode) + } else { + // Within our range, pop max, insert, and split right. + // This is not a bucket add, it's a new bucket! + let pk = unsafe { slice_remove(&mut (*self.ctrl.a).1, H_CAPACITY - 1) }; + let pbk = + unsafe { slice_remove(&mut self.values, H_CAPACITY - 1).assume_init() }; + + let rnode = Node::new_leaf_bk(unsafe { self.ctrl.a.0 .0 }, pk, pbk); + + unsafe { + slice_insert(&mut (*self.ctrl.a).1, h, idx); + slice_insert( + &mut self.values, + MaybeUninit::new(smallvec![Datum { k, v }]), + idx, + ); + } + + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + + LeafInsertState::Split(rnode) + } + } else { + // We have space. + unsafe { + // self.key[idx] = h; + slice_insert(&mut (*self.ctrl.a).1, h, idx); + slice_insert( + &mut self.values, + MaybeUninit::new(smallvec![Datum { k, v }]), + idx, + ); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + self.inc_slots(); + LeafInsertState::Ok(None) + } + } + } + } + + pub(crate) fn remove(&mut self, h: u64, k: &Q) -> LeafRemoveState + where + K: Borrow, + Q: Eq, + { + debug_assert_leaf!(self); + if self.slots() == 0 { + return LeafRemoveState::Shrink(None); + } + // We must have a value - where are you .... + match leaf_simd_search(self, h, k).ok() { + // Count still greater than 0, so Ok and None, + None => LeafRemoveState::Ok(None), + Some((slot_idx, bk_idx)) => { + // pop from the bucket. + let Datum { k: _pk, v: pv } = + unsafe { (*self.values[slot_idx].as_mut_ptr()).remove(bk_idx) }; + + // How much remains? + if unsafe { (*self.values[slot_idx].as_ptr()).is_empty() } { + unsafe { + // Get the kv out + let _ = slice_remove(&mut (*self.ctrl.a).1, slot_idx); + // AFTER the remove, set the top value to u64::MAX + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + + // Remove the bucket. + let _ = slice_remove(&mut self.values, slot_idx).assume_init(); + } + + self.dec_slots(); + if self.slots() == 0 { + LeafRemoveState::Shrink(Some(pv)) + } else { + LeafRemoveState::Ok(Some(pv)) + } + } else { + // The bucket lives! + LeafRemoveState::Ok(Some(pv)) + } + } + } + } + + pub(crate) fn take_from_l_to_r(&mut self, right: &mut Self) { + debug_assert!(right.slots() == 0); + let slots = self.slots() / 2; + let start_idx = self.slots() - slots; + + //move key and values + unsafe { + slice_move( + &mut (*right.ctrl.a).1, + 0, + &mut (*self.ctrl.a).1, + start_idx, + slots, + ); + slice_move(&mut right.values, 0, &mut self.values, start_idx, slots); + // Update the left keys to be valid. + // so we took from: + // [ a, b, c, d, e, f, g ] + // ^ ^ + // | slots + // start + // so we need to fill from start_idx + slots + let tgt_ptr = (*self.ctrl.a).1.as_mut_ptr().add(start_idx); + // https://doc.rust-lang.org/std/ptr/fn.write_bytes.html + // Sets count * size_of::() bytes of memory starting at dst to val. + ptr::write_bytes::(tgt_ptr, 0xff, slots); + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + } + + // update the slotss + unsafe { + (*self.ctrl.a).0.set_slots(start_idx); + (*right.ctrl.a).0.set_slots(slots); + } + } + + pub(crate) fn take_from_r_to_l(&mut self, right: &mut Self) { + debug_assert!(self.slots() == 0); + let slots = right.slots() / 2; + let start_idx = right.slots() - slots; + + // Move values from right to left. + unsafe { + slice_move(&mut (*self.ctrl.a).1, 0, &mut (*right.ctrl.a).1, 0, slots); + slice_move(&mut self.values, 0, &mut right.values, 0, slots); + } + // Shift the values in right down. + unsafe { + ptr::copy( + (*right.ctrl.a).1.as_ptr().add(slots), + (*right.ctrl.a).1.as_mut_ptr(), + start_idx, + ); + ptr::copy( + right.values.as_ptr().add(slots), + right.values.as_mut_ptr(), + start_idx, + ); + } + + // Fix the slotss. + unsafe { + (*self.ctrl.a).0.set_slots(slots); + (*right.ctrl.a).0.set_slots(start_idx); + } + // Update the upper keys in right + unsafe { + let tgt_ptr = (*right.ctrl.a).1.as_mut_ptr().add(start_idx); + // https://doc.rust-lang.org/std/ptr/fn.write_bytes.html + // Sets count * size_of::() bytes of memory starting at dst to val. + ptr::write_bytes::(tgt_ptr, 0xff, H_CAPACITY - start_idx); + #[cfg(all(test, not(miri)))] + debug_assert!(right.poison == FLAG_POISON); + } + } + + /* + pub(crate) fn remove_lt(&mut self, k: &Q) -> LeafPruneState + where + K: Borrow, + Q: Ord, + { + unimplemented!(); + } + */ + + #[inline(always)] + pub(crate) fn merge(&mut self, right: &mut Self) { + debug_assert_leaf!(self); + debug_assert_leaf!(right); + let sc = self.slots(); + let rc = right.slots(); + unsafe { + slice_merge(&mut (*self.ctrl.a).1, sc, &mut (*right.ctrl.a).1, rc); + slice_merge(&mut self.values, sc, &mut right.values, rc); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + unsafe { + (*self.ctrl.a).0.add_slots(right.count()); + (*right.ctrl.a).0.set_slots(0); + } + debug_assert!(self.verify()); + } + + pub(crate) fn verify(&self) -> bool { + debug_assert_leaf!(self); + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + // println!("verify leaf -> {:?}", self); + if unsafe { self.ctrl.a.0.slots() } == 0 { + return true; + } + // Check everything above slots is u64::max + for work_idx in unsafe { (*self.ctrl.a).0.slots() }..H_CAPACITY { + debug_assert!(unsafe { (*self.ctrl.a).1[work_idx] } == u64::MAX); + } + // Check key sorting + let mut lk: &u64 = unsafe { &(*self.ctrl.a).1[0] }; + for work_idx in 1..unsafe { self.ctrl.a.0.slots() } { + let rk: &u64 = unsafe { &(*self.ctrl.a).1[work_idx] }; + // Eq not ok as we have buckets. + if lk >= rk { + // println!("{:?}", self); + if cfg!(test) { + return false; + } else { + debug_assert!(false); + } + } + lk = rk; + } + true + } + + fn free(node: *mut Self) { + unsafe { + let _x: Box>> = + Box::from_raw(node as *mut CachePadded>); + } + } +} + +impl Debug for Leaf { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), Error> { + debug_assert_leaf!(self); + write!(f, "Leaf -> {}", self.slots())?; + #[cfg(all(test, not(miri)))] + write!(f, " nid: {}", self.nid)?; + write!(f, " \\-> [ ")?; + for idx in 0..self.slots() { + write!(f, "{:?}, ", unsafe { self.ctrl.a.1[idx] })?; + write!(f, "[")?; + for d in unsafe { (*self.values[idx].as_ptr()).as_slice().iter() } { + write!(f, "{:?}, ", d.k)?; + } + write!(f, "], ")?; + } + write!(f, " ]") + } +} + +impl Drop for Leaf { + fn drop(&mut self) { + debug_assert_leaf!(self); + #[cfg(all(test, not(miri)))] + release_nid(self.nid); + // Due to the use of maybe uninit we have to drop any contained values. + unsafe { + for idx in 0..self.slots() { + ptr::drop_in_place(self.values[idx].as_mut_ptr()); + } + } + // Done + unsafe { + (*self.ctrl.a).0 .0 = FLAG_DROPPED; + } + debug_assert!(unsafe { self.ctrl.a.0 .0 } & FLAG_MASK != FLAG_HASH_LEAF); + // #[cfg(test)] + // println!("set leaf {:?} to {:x}", self.nid, self.meta.0); + } +} + +impl Branch { + #[inline(always)] + #[allow(unused)] + fn set_slots(&mut self, c: usize) { + debug_assert_branch!(self); + unsafe { (*self.ctrl.a).0.set_slots(c) } + } + + #[inline(always)] + pub(crate) fn slots(&self) -> usize { + debug_assert_branch!(self); + unsafe { self.ctrl.a.0.slots() } + } + + #[inline(always)] + fn inc_slots(&mut self) { + debug_assert_branch!(self); + unsafe { (*self.ctrl.a).0.inc_slots() } + } + + #[inline(always)] + fn dec_slots(&mut self) { + debug_assert_branch!(self); + unsafe { (*self.ctrl.a).0.dec_slots() } + } + + #[inline(always)] + pub(crate) fn get_txid(&self) -> u64 { + debug_assert_branch!(self); + unsafe { self.ctrl.a.0.get_txid() } + } + + // Can't inline as this is recursive! + pub(crate) fn min(&self) -> u64 { + debug_assert_branch!(self); + unsafe { (*self.nodes[0]).min() } + } + + // Can't inline as this is recursive! + pub(crate) fn max(&self) -> u64 { + debug_assert_branch!(self); + // Remember, self.slots() is + 1 offset, so this gets + // the max node + unsafe { (*self.nodes[self.slots()]).max() } + } + + pub(crate) fn req_clone(&self, txid: u64) -> Option<*mut Node> { + debug_assert_branch!(self); + if self.get_txid() == txid { + // Same txn, no action needed. + None + } else { + // println!("Req clone branch"); + // Diff txn, must clone. + let new_txid = + (unsafe { self.ctrl.a.0 .0 } & (FLAG_MASK | COUNT_MASK)) | (txid << TXID_SHF); + + let x: Box>> = Box::new(CachePadded::new(Branch { + // This sets the default (key) slots to 1, since we take an l/r + ctrl: Ctrl { + simd: ManuallyDrop::new(u64x8::from_array([ + new_txid, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + ])), + }, + #[cfg(all(test, not(miri)))] + poison: FLAG_POISON, + // Can clone the node pointers. + nodes: self.nodes, + #[cfg(all(test, not(miri)))] + nid: alloc_nid(), + })); + + let x = Box::into_raw(x); + let xr = x as *mut Branch; + // Dup the keys + unsafe { + ptr::copy_nonoverlapping( + &self.ctrl.a.1 as *const u64, + (*(*xr).ctrl.a).1.as_mut_ptr(), + H_CAPACITY, + ) + } + + Some(x as *mut Node) + } + } + + #[inline(always)] + pub(crate) fn locate_node(&self, h: u64) -> usize { + debug_assert_branch!(self); + // If the value is Ok(idx), then that means + // we were located to the right node. This is because we + // exactly hit and located on the key. + // + // If the value is Err(idx), then we have the exact index already. + // as branches is of-by-one. + match branch_simd_search::(self, h) { + Err(idx) => idx, + Ok(idx) => idx + 1, + } + } + + #[inline(always)] + pub(crate) fn get_idx_unchecked(&self, idx: usize) -> *mut Node { + debug_assert_branch!(self); + debug_assert!(idx <= self.slots()); + debug_assert!(!self.nodes[idx].is_null()); + self.nodes[idx] + } + + #[inline(always)] + pub(crate) fn get_idx_checked(&self, idx: usize) -> Option<*mut Node> { + debug_assert_branch!(self); + // Remember, that nodes can have +1 to slots which is why <= here, not <. + if idx <= self.slots() { + debug_assert!(!self.nodes[idx].is_null()); + Some(self.nodes[idx]) + } else { + None + } + } + + #[cfg(test)] + pub(crate) fn get_ref(&self, h: u64, k: &Q) -> Option<&V> + where + K: Borrow, + Q: Eq, + { + debug_assert_branch!(self); + let idx = self.locate_node(h); + unsafe { (*self.nodes[idx]).get_ref(h, k) } + } + + pub(crate) fn add_node(&mut self, node: *mut Node) -> BranchInsertState { + debug_assert_branch!(self); + // do we have space? + if self.slots() == H_CAPACITY { + // if no space -> + // split and send two nodes back for new branch + // There are three possible states that this causes. + // 1 * The inserted node is the greater than all current values, causing l(max, node) + // to be returned. + // 2 * The inserted node is between max - 1 and max, causing l(node, max) to be returned. + // 3 * The inserted node is a low/middle value, causing max and max -1 to be returned. + // + let kr: u64 = unsafe { (*node).min() }; + let r = branch_simd_search(self, kr); + let ins_idx = r.unwrap_err(); + // Everything will pop max. + let max = unsafe { *(self.nodes.get_unchecked(HBV_CAPACITY - 1)) }; + let res = match ins_idx { + // Case 1 + H_CAPACITY => { + // println!("case 1"); + // Greater than all current values, so we'll just return max and node. + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + } // Now setup the ret val NOTICE compared to case 2 that we swap node and max? + BranchInsertState::Split(max, node) + } + // Case 2 + H_CAPACITY_N1 => { + // println!("case 2"); + // Greater than all but max, so we return max and node in the correct order. + // Drop the key between them. + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + } + // Now setup the ret val NOTICE compared to case 1 that we swap node and max? + BranchInsertState::Split(node, max) + } + // Case 3 + ins_idx => { + // Get the max - 1 and max nodes out. + let maxn1 = unsafe { *(self.nodes.get_unchecked(HBV_CAPACITY - 2)) }; + unsafe { + // Drop the key between them. + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + // Drop the key before us that we are about to replace. + (*self.ctrl.a).1[H_CAPACITY - 2] = u64::MAX; + } + // Add node and it's key to the correct location. + let leaf_ins_idx = ins_idx + 1; + unsafe { + slice_insert(&mut (*self.ctrl.a).1, kr, ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + + BranchInsertState::Split(maxn1, max) + } + }; + // Dec slots as we always reduce branch by one as we split return + // two. + self.dec_slots(); + res + } else { + // if space -> + // Get the nodes min-key - we clone it because we'll certainly be inserting it! + let k: u64 = unsafe { (*node).min() }; + // bst and find when min-key < key[idx] + let r = branch_simd_search(self, k); + // if r is ever found, I think this is a bug, because we should never be able to + // add a node with an existing min. + // + // [ 5 ] + // / \ + // [0,] [5,] + // + // So if we added here to [0, ], and it had to overflow to split, then everything + // must be < 5. Why? Because to get to [0,] as your insert target, you must be < 5. + // if we added to [5,] then a split must be greater than, or the insert would replace 5. + // + // if we consider + // + // [ 5 ] + // / \ + // [0,] [7,] + // + // Now we insert 5, and 7, splits. 5 would remain in the tree and we'd split 7 to the right + // + // As a result, any "Ok(idx)" must represent a corruption of the tree. + // debug_assert!(r.is_err()); + let ins_idx = r.unwrap_err(); + let leaf_ins_idx = ins_idx + 1; + // So why do we only need to insert right? Because the left-most + // leaf when it grows, it splits to the right. That importantly + // means that we only need to insert to replace the min and it's + // right leaf, or anything higher. As a result, we are always + // targetting ins_idx and leaf_ins_idx = ins_idx + 1. + // + // We have a situation like: + // + // [1, 3, 9, 18] + // + // and ins_idx is 2. IE: + // + // [1, 3, 9, 18] + // ^-- k=6 + // + // So this we need to shift those r-> and insert. + // + // [1, 3, x, 9, 18] + // ^-- k=6 + // + // [1, 3, 6, 9, 18] + // + // Now we need to consider the leaves too: + // + // [1, 3, 9, 18] + // | | | | | + // v v v v v + // 0 1 3 9 18 + // + // So that means we need to move leaf_ins_idx = (ins_idx + 1) + // right also + // + // [1, 3, x, 9, 18] + // | | | | | | + // v v v v v v + // 0 1 3 x 9 18 + // ^-- leaf for k=6 will go here. + // + // Now to talk about the right expand issue - lets say 0 conducted + // a split, it returns the new right node - which would push + // 3 to the right to insert a new right hand side as required. So we + // really never need to consider the left most leaf to have to be + // replaced in any conditions. + // + // Magic! + unsafe { + slice_insert(&mut (*self.ctrl.a).1, k, ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + // finally update the slots + self.inc_slots(); + // Return that we are okay to go! + BranchInsertState::Ok + } + } + + pub(crate) fn add_node_left( + &mut self, + lnode: *mut Node, + sibidx: usize, + ) -> BranchInsertState { + debug_assert_branch!(self); + if self.slots() == H_CAPACITY { + if sibidx == self.slots() { + // If sibidx == self.slots, then we must be going into max - 1. + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, k5, xx ] + // [ v1, v2, v3, v4, v5, v6, xx ] + // + // [ k1, k2, k3, k4, k5, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, v6, xx ] -> [ ln, v7 ] + // + // So in this case we drop k6, and return a split. + let max = self.nodes[HBV_CAPACITY - 1]; + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + } + self.dec_slots(); + BranchInsertState::Split(lnode, max) + } else if sibidx == (self.slots() - 1) { + // If sibidx == (self.slots - 1), then we must be going into max - 2 + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, dd, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // + // + // This means that we need to return v6,v7 in a split, and + // just append node after v5. + let maxn1 = self.nodes[HBV_CAPACITY - 2]; + let max = self.nodes[HBV_CAPACITY - 1]; + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + (*self.ctrl.a).1[H_CAPACITY - 2] = u64::MAX; + } + self.dec_slots(); + self.dec_slots(); + // [ k1, k2, k3, k4, dd, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, xx, xx ] -> [ v6, v7 ] + let h: u64 = unsafe { (*lnode).min() }; + + unsafe { + slice_insert(&mut (*self.ctrl.a).1, h, sibidx - 1); + slice_insert(&mut self.nodes, lnode, sibidx); + // slice_insert(&mut self.node, MaybeUninit::new(node), sibidx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + self.inc_slots(); + // + // [ k1, k2, k3, k4, nk, xx ] [ k6 ] + // [ v1, v2, v3, v4, v5, ln, xx ] -> [ v6, v7 ] + + BranchInsertState::Split(maxn1, max) + } else { + // All other cases; + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, dd, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // + // [ k1, k2, k3, nk, k4, dd ] [ k6 ] + // [ v1, v2, v3, ln, v4, v5, xx ] -> [ v6, v7 ] + // + // This means that we need to return v6,v7 in a split,, drop k5, + // then insert + + // Setup the nodes we intend to split away. + let maxn1 = self.nodes[HBV_CAPACITY - 2]; + let max = self.nodes[HBV_CAPACITY - 1]; + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + (*self.ctrl.a).1[H_CAPACITY - 2] = u64::MAX; + } + self.dec_slots(); + self.dec_slots(); + + // println!("pre-fixup -> {:?}", self); + + let sibnode = self.nodes[sibidx]; + let nkey: u64 = unsafe { (*sibnode).min() }; + + unsafe { + slice_insert(&mut (*self.ctrl.a).1, nkey, sibidx); + slice_insert(&mut self.nodes, lnode, sibidx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + + self.inc_slots(); + // println!("post fixup -> {:?}", self); + + BranchInsertState::Split(maxn1, max) + } + } else { + // We have space, so just put it in! + // [ k1, k2, k3, k4, xx, xx ] + // [ v1, v2, v3, v4, v5, xx, xx ] + // ^ ^-- sibidx + // \---- where left should go + // + // [ k1, k2, k3, k4, xx, xx ] + // [ v1, v2, v3, ln, v4, v5, xx ] + // + // [ k1, k2, k3, nk, k4, xx ] + // [ v1, v2, v3, ln, v4, v5, xx ] + // + + let sibnode = self.nodes[sibidx]; + let nkey: u64 = unsafe { (*sibnode).min() }; + + unsafe { + slice_insert(&mut self.nodes, lnode, sibidx); + slice_insert(&mut (*self.ctrl.a).1, nkey, sibidx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + + self.inc_slots(); + // println!("post fixup -> {:?}", self); + BranchInsertState::Ok + } + } + + fn remove_by_idx(&mut self, idx: usize) -> *mut Node { + debug_assert_branch!(self); + debug_assert!(idx <= self.slots()); + debug_assert!(idx > 0); + // remove by idx. + let _pk = unsafe { slice_remove(&mut (*self.ctrl.a).1, idx - 1) }; + // AFTER the remove, set the top value to u64::MAX + unsafe { + (*self.ctrl.a).1[H_CAPACITY - 1] = u64::MAX; + } + let pn = unsafe { slice_remove(&mut self.nodes, idx) }; + self.dec_slots(); + pn + } + + pub(crate) fn shrink_decision(&mut self, ridx: usize) -> BranchShrinkState { + // Given two nodes, we need to decide what to do with them! + // + // Remember, this isn't happening in a vacuum. This is really a manipulation of + // the following structure: + // + // parent (self) + // / \ + // left right + // + // We also need to consider the following situation too: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 r2 + // + // Imagine we have exhausted r2, so we need to merge: + // + // root + // / \ + // lbranch rbranch + // / \ / \ + // l1 l2 r1 <<-- r2 + // + // This leaves us with a partial state of + // + // root + // / \ + // lbranch rbranch (invalid!) + // / \ / + // l1 l2 r1 + // + // This means rbranch issues a cloneshrink to root. clone shrink must contain the remainer + // so that it can be reparented: + // + // root + // / + // lbranch -- + // / \ \ + // l1 l2 r1 + // + // Now root has to shrink too. + // + // root -- + // / \ \ + // l1 l2 r1 + // + // So, we have to analyse the situation. + // * Have left or right been emptied? (how to handle when branches) + // * Is left or right belowe a reasonable threshold? + // * Does the opposite have capacity to remain valid? + + debug_assert_branch!(self); + debug_assert!(ridx > 0 && ridx <= self.slots()); + let left = self.nodes[ridx - 1]; + let right = self.nodes[ridx]; + debug_assert!(!left.is_null()); + debug_assert!(!right.is_null()); + + match unsafe { (*left).ctrl.a.0 .0 & FLAG_MASK } { + FLAG_HASH_LEAF => { + let lmut = leaf_ref!(left, K, V); + let rmut = leaf_ref!(right, K, V); + + if lmut.slots() + rmut.slots() <= H_CAPACITY { + lmut.merge(rmut); + // remove the right node from parent + let dnode = self.remove_by_idx(ridx); + debug_assert!(dnode == right); + if self.slots() == 0 { + // We now need to be merged across as we only contain a single + // value now. + BranchShrinkState::Shrink(dnode) + } else { + // We are complete! + // #[cfg(test)] + // println!("🔥 {:?}", rmut.nid); + BranchShrinkState::Merge(dnode) + } + } else if rmut.slots() > (H_CAPACITY / 2) { + lmut.take_from_r_to_l(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else if lmut.slots() > (H_CAPACITY / 2) { + lmut.take_from_l_to_r(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else { + // Do nothing + BranchShrinkState::Balanced + } + } + FLAG_HASH_BRANCH => { + // right or left is now in a "corrupt" state with a single value that we need to relocate + // to left - or we need to borrow from left and fix it! + let lmut = branch_ref!(left, K, V); + let rmut = branch_ref!(right, K, V); + + debug_assert!(rmut.slots() == 0 || lmut.slots() == 0); + debug_assert!(rmut.slots() <= H_CAPACITY || lmut.slots() <= H_CAPACITY); + // println!("branch {:?} {:?}", lmut.slots(), rmut.slots()); + if lmut.slots() == H_CAPACITY { + // println!("branch take_from_l_to_r "); + lmut.take_from_l_to_r(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else if rmut.slots() == H_CAPACITY { + // println!("branch take_from_r_to_l "); + lmut.take_from_r_to_l(rmut); + self.rekey_by_idx(ridx); + BranchShrinkState::Balanced + } else { + // println!("branch merge"); + // merge the right to tail of left. + // println!("BL {:?}", lmut); + // println!("BR {:?}", rmut); + lmut.merge(rmut); + // println!("AL {:?}", lmut); + // println!("AR {:?}", rmut); + // Reduce our slots + let dnode = self.remove_by_idx(ridx); + debug_assert!(dnode == right); + if self.slots() == 0 { + // We now need to be merged across as we also only contain a single + // value now. + BranchShrinkState::Shrink(dnode) + } else { + // We are complete! + // #[cfg(test)] + // println!("🚨 {:?}", rmut.nid); + BranchShrinkState::Merge(dnode) + } + } + } + _ => unreachable!(), + } + } + + #[inline(always)] + pub(crate) fn extract_last_node(&self) -> *mut Node { + debug_assert_branch!(self); + self.nodes[0] + } + + pub(crate) fn rekey_by_idx(&mut self, idx: usize) { + debug_assert_branch!(self); + debug_assert!(idx <= self.slots()); + debug_assert!(idx > 0); + // For the node listed, rekey it. + let nref = self.nodes[idx]; + unsafe { (*self.ctrl.a).1[idx - 1] = (*nref).min() }; + } + + #[inline(always)] + pub(crate) fn merge(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + let sc = self.slots(); + let rc = right.slots(); + if rc == 0 { + let node = right.nodes[0]; + debug_assert!(!node.is_null()); + let h: u64 = unsafe { (*node).min() }; + let ins_idx = self.slots(); + let leaf_ins_idx = ins_idx + 1; + unsafe { + slice_insert(&mut (*self.ctrl.a).1, h, ins_idx); + slice_insert(&mut self.nodes, node, leaf_ins_idx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + self.inc_slots(); + } else { + debug_assert!(sc == 0); + unsafe { + // Move all the nodes from right. + slice_merge(&mut self.nodes, 1, &mut right.nodes, rc + 1); + // Move the related keys. + slice_merge(&mut (*self.ctrl.a).1, 1, &mut (*right.ctrl.a).1, rc); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + // Set our slots correctly. + unsafe { + (*self.ctrl.a).0.set_slots(rc + 1); + } + // Set right len to 0 + unsafe { + (*right.ctrl.a).0.set_slots(0); + } + // rekey the lowest pointer. + unsafe { + let nptr = self.nodes[1]; + let h: u64 = (*nptr).min(); + (*self.ctrl.a).1[0] = h; + } + // done! + } + } + + pub(crate) fn take_from_l_to_r(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + + debug_assert!(self.slots() > right.slots()); + // Starting index of where we move from. We work normally from a branch + // with only zero (but the base) branch item, but we do the math anyway + // to be sure incase we change later. + // + // So, self.len must be larger, so let's give a few examples here. + // 4 = 7 - (7 + 0) / 2 (will move 4, 5, 6) + // 3 = 6 - (6 + 0) / 2 (will move 3, 4, 5) + // 3 = 5 - (5 + 0) / 2 (will move 3, 4) + // 2 = 4 .... (will move 2, 3) + // + let slots = (self.slots() + right.slots()) / 2; + let start_idx = self.slots() - slots; + // Move the remaining element from r to the correct location. + // + // [ k1, k2, k3, k4, k5, k6 ] + // [ v1, v2, v3, v4, v5, v6, v7 ] -> [ v8, ------- ] + // + // To: + // + // [ k1, k2, k3, k4, k5, k6 ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, v5, v6, v7 ] -> [ --, --, --, v8, --, ... + // + unsafe { + ptr::swap( + right.nodes.get_unchecked_mut(0), + right.nodes.get_unchecked_mut(slots), + ) + } + // Move our values from the tail. + // We would move 3 now to: + // + // [ k1, k2, k3, k4, k5, k6 ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, --, --, -- ] -> [ v5, v6, v7, v8, --, ... + // + unsafe { + slice_move(&mut right.nodes, 0, &mut self.nodes, start_idx + 1, slots); + } + // Remove the keys from left. + // So we need to remove the corresponding keys. so that we get. + // + // [ k1, k2, k3, --, --, -- ] [ --, --, --, --, ... + // [ v1, v2, v3, v4, --, --, -- ] -> [ v5, v6, v7, v8, --, ... + // + // This means it's start_idx - 1 up to BK cap + unsafe { + let tgt_ptr = (*self.ctrl.a).1.as_mut_ptr().add(start_idx); + // https://doc.rust-lang.org/std/ptr/fn.write_bytes.html + // Sets count * size_of::() bytes of memory starting at dst to val. + ptr::write_bytes::(tgt_ptr, 0xff, H_CAPACITY - start_idx); + } + + // Adjust both slotss - we do this before rekey to ensure that the safety + // checks hold in debugging. + unsafe { + (*right.ctrl.a).0.set_slots(slots); + } + unsafe { + (*self.ctrl.a).0.set_slots(start_idx); + } + // Rekey right + for kidx in 1..(slots + 1) { + right.rekey_by_idx(kidx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + #[cfg(all(test, not(miri)))] + debug_assert!(right.poison == FLAG_POISON); + // Done! + debug_assert!(self.verify()); + debug_assert!(right.verify()); + } + + pub(crate) fn take_from_r_to_l(&mut self, right: &mut Self) { + debug_assert_branch!(self); + debug_assert_branch!(right); + debug_assert!(right.slots() >= self.slots()); + + let slots = (self.slots() + right.slots()) / 2; + let start_idx = right.slots() - slots; + + // We move slots from right to left. + unsafe { + slice_move(&mut self.nodes, 1, &mut right.nodes, 0, slots); + } + + // move keys down in right + unsafe { + ptr::copy( + right.ctrl.a.1.as_ptr().add(slots), + (*right.ctrl.a).1.as_mut_ptr(), + start_idx, + ); + } + + // Fix up the upper keys + /* + for idx in start_idx..H_CAPACITY { + right.key[idx] = u64::MAX; + } + */ + unsafe { + let tgt_ptr = (*right.ctrl.a).1.as_mut_ptr().add(start_idx); + // https://doc.rust-lang.org/std/ptr/fn.write_bytes.html + // Sets count * size_of::() bytes of memory starting at dst to val. + ptr::write_bytes::(tgt_ptr, 0xff, H_CAPACITY - start_idx); + } + #[cfg(all(test, not(miri)))] + debug_assert!(right.poison == FLAG_POISON); + + // move nodes down in right + unsafe { + ptr::copy( + right.nodes.as_ptr().add(slots), + right.nodes.as_mut_ptr(), + start_idx + 1, + ); + } + + // update slotss + unsafe { + (*right.ctrl.a).0.set_slots(start_idx); + } + unsafe { + (*self.ctrl.a).0.set_slots(slots); + } + // Rekey left + for kidx in 1..(slots + 1) { + self.rekey_by_idx(kidx); + } + debug_assert!(self.verify()); + debug_assert!(right.verify()); + // Done! + } + + #[inline(always)] + pub(crate) fn replace_by_idx(&mut self, idx: usize, node: *mut Node) { + debug_assert_branch!(self); + debug_assert!(idx <= self.slots()); + debug_assert!(!self.nodes[idx].is_null()); + self.nodes[idx] = node; + } + + pub(crate) fn clone_sibling_idx( + &mut self, + txid: u64, + idx: usize, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, + ) -> usize { + debug_assert_branch!(self); + // if we clone, return Some new ptr. if not, None. + let (ridx, idx) = if idx == 0 { + // println!("clone_sibling_idx clone right"); + // If we are 0 we clone our right sibling, + // and return thet right idx as 1. + (1, 1) + } else { + // println!("clone_sibling_idx clone left"); + // Else we clone the left, and leave our index unchanged + // as we are the right node. + (idx, idx - 1) + }; + // Now clone the item at idx. + debug_assert!(idx <= self.slots()); + let sib_ptr = self.nodes[idx]; + debug_assert!(!sib_ptr.is_null()); + // Do we need to clone? + let res = match unsafe { (*sib_ptr).ctrl.a.0 .0 } & FLAG_MASK { + FLAG_HASH_LEAF => { + let lref = unsafe { &*(sib_ptr as *const _ as *const Leaf) }; + lref.req_clone(txid) + } + FLAG_HASH_BRANCH => { + let bref = unsafe { &*(sib_ptr as *const _ as *const Branch) }; + bref.req_clone(txid) + } + _ => unreachable!(), + }; + + // If it did clone, it's a some, so we map that to have the from and new ptrs for + // the memory management. + if let Some(n_ptr) = res { + // println!("ls push 101 {:?}", sib_ptr); + first_seen.push(n_ptr); + last_seen.push(sib_ptr); + // Put the pointer in place. + self.nodes[idx] = n_ptr; + }; + // Now return the right index + ridx + } + + /* + pub(crate) fn trim_lt_key( + &mut self, + k: &Q, + last_seen: &mut Vec<*mut Node>, + first_seen: &mut Vec<*mut Node>, + ) -> BranchTrimState + where + K: Borrow, + Q: Ord, + { + debug_assert_branch!(self); + // The possible states of a branch are + // + // [ 0, 4, 8, 12 ] + // [n1, n2, n3, n4, n5] + // + let r = key_search!(self, k); + + let sc = self.slots(); + + match r { + Ok(idx) => { + debug_assert!(idx < sc); + // * A key matches exactly a value. IE k is 4. This means we can remove + // n1 and n2 because we know 4 must be in n3 as the min. + + // NEED MM + debug_assert!(false); + + unsafe { + slice_slide_and_drop(&mut self.key, idx, sc - (idx + 1)); + slice_slide(&mut self.nodes.as_mut(), idx, sc - idx); + } + self.meta.set_slots(sc - (idx + 1)); + + if self.slots() == 0 { + let rnode = self.extract_last_node(); + BranchTrimState::Promote(rnode) + } else { + BranchTrimState::Complete + } + } + Err(idx) => { + if idx == 0 { + // * The key is less than min. IE it wants to remove the lowest value. + // Check the "max" value of the subtree to know if we can proceed. + let tnode: *mut Node = self.nodes[0]; + let branch_k: &K = unsafe { (*tnode).max() }; + if branch_k.borrow() < k { + // Everything is smaller, let's remove it that subtree. + // NEED MM + debug_assert!(false); + let _pk = unsafe { slice_remove(&mut self.key, 0).assume_init() }; + let _pn = unsafe { slice_remove(self.nodes.as_mut(), 0) }; + self.dec_slots(); + BranchTrimState::Complete + } else { + BranchTrimState::Complete + } + } else if idx >= self.slots() { + // remove everything except max. + unsafe { + // NEED MM + debug_assert!(false); + // We just drop all the keys. + for kidx in 0..self.slots() { + ptr::drop_in_place(self.key[kidx].as_mut_ptr()); + // ptr::drop_in_place(self.nodes[kidx].as_mut_ptr()); + } + // Move the last node to the bottom. + self.nodes[0] = self.nodes[sc]; + } + self.meta.set_slots(0); + let rnode = self.extract_last_node(); + // Something may still be valid, hand it on. + BranchTrimState::Promote(rnode) + } else { + // * A key is between two values. We can remove everything less, but not + // the assocated. For example, remove 6 would cause n1, n2 to be removed, but + // the prune/walk will have to examine n3 to know about further changes. + debug_assert!(idx > 0); + + let tnode: *mut Node = self.nodes[0]; + let branch_k: &K = unsafe { (*tnode).max() }; + + if branch_k.borrow() < k { + // NEED MM + debug_assert!(false); + // Remove including idx. + unsafe { + slice_slide_and_drop(&mut self.key, idx, sc - (idx + 1)); + slice_slide(self.nodes.as_mut(), idx, sc - idx); + } + self.meta.set_slots(sc - (idx + 1)); + } else { + // NEED MM + debug_assert!(false); + unsafe { + slice_slide_and_drop(&mut self.key, idx - 1, sc - idx); + slice_slide(self.nodes.as_mut(), idx - 1, sc - (idx - 1)); + } + self.meta.set_slots(sc - idx); + } + + if self.slots() == 0 { + // NEED MM + debug_assert!(false); + let rnode = self.extract_last_node(); + BranchTrimState::Promote(rnode) + } else { + BranchTrimState::Complete + } + } + } + } + } + */ + + pub(crate) fn verify(&self) -> bool { + debug_assert_branch!(self); + #[cfg(all(test, not(miri)))] + debug_assert!(self.poison == FLAG_POISON); + if self.slots() == 0 { + // Not possible to be valid! + debug_assert!(false); + return false; + } + // println!("verify branch -> {:?}", self); + // Check everything above slots is u64::max + for work_idx in unsafe { self.ctrl.a.0.slots() }..H_CAPACITY { + if unsafe { self.ctrl.a.1[work_idx] } != u64::MAX { + eprintln!("FAILED ARRAY -> {:?}", unsafe { self.ctrl.a.1 }); + debug_assert!(false); + } + } + // Check we are sorted. + let mut lk: u64 = unsafe { self.ctrl.a.1[0] }; + for work_idx in 1..self.slots() { + let rk: u64 = unsafe { self.ctrl.a.1[work_idx] }; + // println!("{:?} >= {:?}", lk, rk); + if lk >= rk { + debug_assert!(false); + return false; + } + lk = rk; + } + // Recursively call verify + for work_idx in 0..self.slots() { + let node = unsafe { &*self.nodes[work_idx] }; + if !node.verify() { + for work_idx in 0..(self.slots() + 1) { + let nref = unsafe { &*self.nodes[work_idx] }; + if !nref.verify() { + // println!("Failed children"); + debug_assert!(false); + return false; + } + } + } + } + // Check descendants are validly ordered. + // V-- remember, there are slots + 1 nodes. + for work_idx in 0..self.slots() { + // get left max and right min + let lnode = unsafe { &*self.nodes[work_idx] }; + let rnode = unsafe { &*self.nodes[work_idx + 1] }; + + let pkey = unsafe { self.ctrl.a.1[work_idx] }; + let lkey: u64 = lnode.max(); + let rkey: u64 = rnode.min(); + if lkey >= pkey || pkey > rkey { + /* + println!("++++++"); + println!("{:?} >= {:?}, {:?} > {:?}", lkey, pkey, pkey, rkey); + println!("out of order key found {}", work_idx); + // println!("left --> {:?}", lnode); + // println!("right -> {:?}", rnode); + println!("prnt -> {:?}", self); + */ + debug_assert!(false); + return false; + } + } + // All good! + true + } + + #[allow(clippy::cast_ptr_alignment)] + fn free(node: *mut Self) { + unsafe { + let mut _x: Box>> = + Box::from_raw(node as *mut CachePadded>); + } + } +} + +impl Debug for Branch { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), Error> { + debug_assert_branch!(self); + write!(f, "Branch -> {}", self.slots())?; + #[cfg(all(test, not(miri)))] + write!(f, " nid: {}", self.nid)?; + write!(f, " \\-> [ ")?; + for idx in 0..self.slots() { + write!(f, "{:?}, ", unsafe { self.ctrl.a.1[idx] })?; + } + write!(f, " ]") + } +} + +impl Drop for Branch { + fn drop(&mut self) { + debug_assert_branch!(self); + #[cfg(all(test, not(miri)))] + release_nid(self.nid); + // Done + unsafe { + (*self.ctrl.a).0 .0 = FLAG_DROPPED; + } + debug_assert!(unsafe { self.ctrl.a.0 .0 } & FLAG_MASK != FLAG_HASH_BRANCH); + // println!("set branch {:?} to {:x}", self.nid, self.meta.0); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /* + #[test] + fn test_hashmap2_node_cache_size() { + let ls = std::mem::size_of::>() - std::mem::size_of::(); + let bs = std::mem::size_of::>() - std::mem::size_of::(); + println!("ls {:?}, bs {:?}", ls, bs); + assert!(ls <= 128); + assert!(bs <= 128); + } + */ + + #[test] + fn test_hashmap2_node_test_weird_basics() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + + assert!(leaf.get_txid() == 1); + // println!("{:?}", leaf); + + leaf.set_slots(1); + assert!(leaf.slots() == 1); + leaf.set_slots(0); + assert!(leaf.slots() == 0); + + leaf.inc_slots(); + leaf.inc_slots(); + leaf.inc_slots(); + assert!(leaf.slots() == 3); + leaf.dec_slots(); + leaf.dec_slots(); + leaf.dec_slots(); + assert!(leaf.slots() == 0); + + /* + let branch: *mut Branch = Node::new_branch(1, ptr::null_mut(), ptr::null_mut()); + let branch = unsafe { &mut *branch }; + assert!(branch.get_txid() == 1); + // println!("{:?}", branch); + + branch.set_slots(3); + assert!(branch.slots() == 3); + branch.set_slots(0); + assert!(branch.slots() == 0); + Branch::free(branch as *mut _); + */ + + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_in_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for kv in 0..H_CAPACITY { + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + // Check update to capacity + for kv in 0..H_CAPACITY { + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_collision_in_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + let hash: u64 = 1; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for kv in 0..H_CAPACITY { + let r = leaf.insert_or_update(hash, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(hash, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + // Check update to capacity + for kv in 0..H_CAPACITY { + let r = leaf.insert_or_update(hash, kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(hash, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_out_of_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + + assert!(H_CAPACITY <= 8); + let kvs = [7, 5, 1, 6, 2, 3, 0, 8]; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.slots() == H_CAPACITY); + // Check update to capacity + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.slots() == H_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_collision_out_of_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + let hash: u64 = 1; + + assert!(H_CAPACITY <= 8); + let kvs = [7, 5, 1, 6, 2, 3, 0, 8]; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(hash, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(hash, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == H_CAPACITY); + assert!(leaf.slots() == 1); + // Check update to capacity + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(hash, kv, kv); + if let LeafInsertState::Ok(Some(pkv)) = r { + assert!(pkv == kv); + assert!(leaf.get_ref(hash, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == H_CAPACITY); + assert!(leaf.slots() == 1); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_min() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(H_CAPACITY <= 8); + + let kvs = [3, 2, 6, 4, 5, 1, 9, 0]; + let min: [u64; 8] = [3, 2, 2, 2, 2, 1, 1, 0]; + + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + assert!(leaf.min() == min[idx]); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.slots() == H_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_max() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + assert!(H_CAPACITY <= 8); + + let kvs = [1, 3, 2, 6, 4, 5, 9, 0]; + let max: [u64; 8] = [1, 3, 3, 6, 6, 6, 9, 9]; + + for idx in 0..H_CAPACITY { + let kv = kvs[idx]; + let r = leaf.insert_or_update(kv as u64, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(kv as u64, &kv) == Some(&kv)); + assert!(leaf.max() == max[idx]); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.slots() == H_CAPACITY); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_remove_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + leaf.insert_or_update(kv as u64, kv, kv); + } + // Remove all but one. + for kv in 0..(H_CAPACITY - 1) { + let r = leaf.remove(kv as u64, &kv); + if let LeafRemoveState::Ok(Some(rkv)) = r { + assert!(rkv == kv); + } else { + assert!(false); + } + } + assert!(leaf.slots() == 1); + assert!(leaf.max() == (H_CAPACITY - 1) as u64); + // Remove a non-existant value. + let r = leaf.remove((H_CAPACITY + 20) as u64, &(H_CAPACITY + 20)); + if let LeafRemoveState::Ok(None) = r { + // Ok! + } else { + assert!(false); + } + // Finally clear the node, should request a shrink. + let kv = H_CAPACITY - 1; + let r = leaf.remove(kv as u64, &kv); + if let LeafRemoveState::Shrink(Some(rkv)) = r { + assert!(rkv == kv); + } else { + assert!(false); + } + assert!(leaf.slots() == 0); + // Remove non-existant post shrink. Should never happen + // but safety first! + let r = leaf.remove(0, &0); + if let LeafRemoveState::Shrink(None) = r { + // Ok! + } else { + assert!(false); + } + + assert!(leaf.slots() == 0); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_remove_out_of_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + leaf.insert_or_update(kv as u64, kv, kv); + } + let mid = H_CAPACITY / 2; + // This test removes all BUT one node to keep the states simple. + for kv in mid..(H_CAPACITY - 1) { + let r = leaf.remove(kv as u64, &kv); + match r { + LeafRemoveState::Ok(_) => {} + _ => panic!(), + } + } + + for kv in 0..(H_CAPACITY / 2) { + let r = leaf.remove(kv as u64, &kv); + match r { + LeafRemoveState::Ok(_) => {} + _ => panic!(), + } + } + + assert!(leaf.slots() == 1); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_remove_collision_in_order() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + let hash: u64 = 1; + assert!(leaf.get_txid() == 1); + // Check insert to capacity + for kv in 0..H_CAPACITY { + let r = leaf.insert_or_update(hash, kv, kv); + if let LeafInsertState::Ok(None) = r { + assert!(leaf.get_ref(hash, &kv) == Some(&kv)); + } else { + assert!(false); + } + } + assert!(leaf.verify()); + assert!(leaf.count() == H_CAPACITY); + assert!(leaf.slots() == 1); + // Check remove to cap - 1 + for kv in 1..H_CAPACITY { + let r = leaf.remove(hash, &kv); + match r { + LeafRemoveState::Ok(_) => {} + _ => panic!(), + } + } + assert!(leaf.count() == 1); + assert!(leaf.slots() == 1); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + #[test] + fn test_hashmap2_node_leaf_insert_split() { + let leaf: *mut Leaf = Node::new_leaf(1); + let leaf = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + let x = kv + 10; + leaf.insert_or_update(x as u64, x, x); + } + + // Split right + let y = H_CAPACITY + 10; + let r = leaf.insert_or_update(y as u64, y, y); + if let LeafInsertState::Split(rleaf) = r { + unsafe { + assert!((&*rleaf).slots() == 1); + } + Leaf::free(rleaf); + } else { + panic!(); + } + + // Split left + let r = leaf.insert_or_update(0, 0, 0); + if let LeafInsertState::RevSplit(lleaf) = r { + unsafe { + assert!((&*lleaf).slots() == 1); + } + Leaf::free(lleaf); + } else { + panic!(); + } + + assert!(leaf.slots() == H_CAPACITY); + assert!(leaf.verify()); + Leaf::free(leaf as *mut _); + assert_released(); + } + + /* + #[test] + fn test_bptree_leaf_remove_lt() { + // This is used in split off. + // Remove none + let leaf1: *mut Leaf = Node::new_leaf(1); + let leaf1 = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + let _ = leaf1.insert_or_update(kv + 10, kv); + } + leaf1.remove_lt(&5); + assert!(leaf1.slots() == H_CAPACITY); + Leaf::free(leaf1 as *mut _); + + // Remove all + let leaf2: *mut Leaf = Node::new_leaf(1); + let leaf2 = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + let _ = leaf2.insert_or_update(kv + 10, kv); + } + leaf2.remove_lt(&(H_CAPACITY + 10)); + assert!(leaf2.slots() == 0); + Leaf::free(leaf2 as *mut _); + + // Remove from middle + let leaf3: *mut Leaf = Node::new_leaf(1); + let leaf3 = unsafe { &mut *leaf }; + for kv in 0..H_CAPACITY { + let _ = leaf3.insert_or_update(kv + 10, kv); + } + leaf3.remove_lt(&((H_CAPACITY / 2) + 10)); + assert!(leaf3.slots() == (H_CAPACITY / 2)); + Leaf::free(leaf3 as *mut _); + + // Remove less than not in leaf. + let leaf4: *mut Leaf = Node::new_leaf(1); + let leaf4 = unsafe { &mut *leaf }; + let _ = leaf4.insert_or_update(5, 5); + let _ = leaf4.insert_or_update(15, 15); + leaf4.remove_lt(&10); + assert!(leaf4.slots() == 1); + + // Add another and remove all. + let _ = leaf4.insert_or_update(20, 20); + leaf4.remove_lt(&25); + assert!(leaf4.slots() == 0); + Leaf::free(leaf4 as *mut _); + // Done! + assert_released(); + } + */ + + /* ============================================ */ + // Branch tests here! + + #[test] + fn test_hashmap2_node_branch_new() { + // Create a new branch, and test it. + let left: *mut Leaf = Node::new_leaf(1); + let left_ref = unsafe { &mut *left }; + let right: *mut Leaf = Node::new_leaf(1); + let right_ref = unsafe { &mut *right }; + + // add kvs to l and r + for kv in 0..H_CAPACITY { + let lkv = kv + 10; + let rkv = kv + 20; + left_ref.insert_or_update(lkv as u64, lkv, lkv); + right_ref.insert_or_update(rkv as u64, rkv, rkv); + } + // create branch + let branch: *mut Branch = Node::new_branch( + 1, + left as *mut Node, + right as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + // Test .min works on our descendants + assert!(branch_ref.min() == 10); + // Test .max works on our descendats. + assert!(branch_ref.max() == (20 + H_CAPACITY - 1) as u64); + // Get some k within the leaves. + assert!(branch_ref.get_ref(11, &11) == Some(&11)); + assert!(branch_ref.get_ref(21, &21) == Some(&21)); + // get some k that is out of bounds. + assert!(branch_ref.get_ref(1, &1) == None); + assert!(branch_ref.get_ref(100, &100) == None); + + Leaf::free(left as *mut _); + Leaf::free(right as *mut _); + Branch::free(branch as *mut _); + assert_released(); + } + + // Helpers + macro_rules! test_3_leaf { + ($fun:expr) => {{ + let a: *mut Leaf = Node::new_leaf(1); + let b: *mut Leaf = Node::new_leaf(1); + let c: *mut Leaf = Node::new_leaf(1); + + unsafe { + (*a).insert_or_update(10, 10, 10); + (*b).insert_or_update(20, 20, 20); + (*c).insert_or_update(30, 30, 30); + } + + $fun(a, b, c); + + Leaf::free(a as *mut _); + Leaf::free(b as *mut _); + Leaf::free(c as *mut _); + assert_released(); + }}; + } + + #[test] + fn test_hashmap2_node_branch_add_min() { + // This pattern occurs with "revsplit" to help with reverse + // ordered inserts. + test_3_leaf!(|a, b, c| { + // Add the max two to the branch + let branch: *mut Branch = Node::new_branch( + 1, + b as *mut Node, + c as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + // Now min node (uses a diff function!) + let r = branch_ref.add_node_left(a as *mut Node, 0); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + #[test] + fn test_hashmap2_node_branch_add_mid() { + test_3_leaf!(|a, b, c| { + // Add the outer two to the branch + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + c as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + let r = branch_ref.add_node(b as *mut Node); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + #[test] + fn test_hashmap2_node_branch_add_max() { + test_3_leaf!(|a, b, c| { + // add the bottom two + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + b as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + // verify + assert!(branch_ref.verify()); + let r = branch_ref.add_node(c as *mut Node); + match r { + BranchInsertState::Ok => {} + _ => debug_assert!(false), + }; + // Assert okay + verify + assert!(branch_ref.verify()); + Branch::free(branch as *mut _); + }) + } + + // Helpers + macro_rules! test_max_leaf { + ($fun:expr) => {{ + let a: *mut Leaf = Node::new_leaf(1); + let b: *mut Leaf = Node::new_leaf(1); + let c: *mut Leaf = Node::new_leaf(1); + let d: *mut Leaf = Node::new_leaf(1); + let e: *mut Leaf = Node::new_leaf(1); + let f: *mut Leaf = Node::new_leaf(1); + let g: *mut Leaf = Node::new_leaf(1); + let h: *mut Leaf = Node::new_leaf(1); + + unsafe { + (*a).insert_or_update(10, 10, 10); + (*b).insert_or_update(20, 20, 20); + (*c).insert_or_update(30, 30, 30); + (*d).insert_or_update(40, 40, 40); + (*e).insert_or_update(50, 50, 50); + (*f).insert_or_update(60, 60, 60); + (*g).insert_or_update(70, 70, 70); + (*h).insert_or_update(80, 80, 80); + } + + let branch: *mut Branch = Node::new_branch( + 1, + a as *mut Node, + b as *mut Node, + ); + let branch_ref = unsafe { &mut *branch }; + branch_ref.add_node(c as *mut Node); + branch_ref.add_node(d as *mut Node); + branch_ref.add_node(e as *mut Node); + branch_ref.add_node(f as *mut Node); + branch_ref.add_node(g as *mut Node); + branch_ref.add_node(h as *mut Node); + + assert!(branch_ref.slots() == H_CAPACITY); + + $fun(branch_ref, 80); + + // MUST NOT verify here, as it's a use after free of the tests inserted node! + Branch::free(branch as *mut _); + Leaf::free(a as *mut _); + Leaf::free(b as *mut _); + Leaf::free(c as *mut _); + Leaf::free(d as *mut _); + Leaf::free(e as *mut _); + Leaf::free(f as *mut _); + Leaf::free(g as *mut _); + Leaf::free(h as *mut _); + assert_released(); + }}; + } + + #[test] + fn test_hashmap2_node_branch_add_split_min() { + // Used in rev split + } + + #[test] + fn test_hashmap2_node_branch_add_split_mid() { + test_max_leaf!(|branch_ref: &mut Branch, max: u64| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to H_CAPACITY, incs of 10 + unsafe { + (*node).insert_or_update(15, 15, 15); + }; + + // Add in the middle + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(x, y) => { + unsafe { + assert!((*x).min() == max - 10); + assert!((*y).min() == max); + } + // X, Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } + + #[test] + fn test_hashmap2_node_branch_add_split_max() { + test_max_leaf!(|branch_ref: &mut Branch, max: u64| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to H_CAPACITY, incs of 10 + unsafe { + (*node).insert_or_update(200, 200, 200); + }; + + // Add in at the end. + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(y, mynode) => { + unsafe { + // println!("{:?}", (*y).min()); + // println!("{:?}", (*mynode).min()); + assert!((*y).min() == max); + assert!((*mynode).min() == 200); + } + // Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } + + #[test] + fn test_hashmap2_node_branch_add_split_n1max() { + // Add one before the end! + test_max_leaf!(|branch_ref: &mut Branch, max: u64| { + let node: *mut Leaf = Node::new_leaf(1); + // Branch already has up to H_CAPACITY, incs of 10 + let x = (max - 5) as usize; + unsafe { + (*node).insert_or_update(max - 5, x, x); + }; + + // Add in one before the end. + let r = branch_ref.add_node(node as *mut _); + match r { + BranchInsertState::Split(mynode, y) => { + unsafe { + assert!((*mynode).min() == max - 5); + assert!((*y).min() == max); + } + // Y will be freed by the macro caller. + } + _ => debug_assert!(false), + }; + assert!(branch_ref.verify()); + // Free node. + Leaf::free(node as *mut _); + }) + } +} diff --git a/vendor/concread/src/internals/hashmap/simd.rs b/vendor/concread/src/internals/hashmap/simd.rs new file mode 100644 index 0000000..d5eb9be --- /dev/null +++ b/vendor/concread/src/internals/hashmap/simd.rs @@ -0,0 +1,293 @@ +#[cfg(feature = "simd_support")] +use core_simd::u64x8; +use std::borrow::Borrow; +use std::fmt::Debug; +use std::hash::Hash; + +use super::node::{Branch, Leaf}; + +pub(crate) enum KeyLoc { + Ok(usize, usize), + Collision(usize), + Missing(usize), +} + +impl KeyLoc { + pub(crate) fn ok(self) -> Option<(usize, usize)> { + if let KeyLoc::Ok(a, b) = self { + Some((a, b)) + } else { + None + } + } +} + +#[cfg(not(feature = "simd_support"))] +pub(crate) fn branch_simd_search(branch: &Branch, h: u64) -> Result +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + debug_assert!(h < u64::MAX); + for i in 0..branch.slots() { + if h == unsafe { branch.ctrl.a.1[i] } { + return Ok(i); + } + } + + for i in 0..branch.slots() { + if h < unsafe { branch.ctrl.a.1[i] } { + return Err(i); + } + } + Err(branch.slots()) +} + +#[cfg(feature = "simd_support")] +pub(crate) fn branch_simd_search(branch: &Branch, h: u64) -> Result +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + debug_assert!(h < u64::MAX); + + debug_assert!({ + let want = u64x8::splat(u64::MAX); + let r1 = want.lanes_eq(unsafe { *branch.ctrl.simd }); + let mask = r1.to_bitmask()[0] & 0b1111_1110; + + match (mask, branch.slots()) { + (0b1111_1110, 0) + | (0b1111_1100, 1) + | (0b1111_1000, 2) + | (0b1111_0000, 3) + | (0b1110_0000, 4) + | (0b1100_0000, 5) + | (0b1000_0000, 6) + | (0b0000_0000, 7) => true, + _ => { + eprintln!("branch mask -> {:b}", mask); + eprintln!("branch slots -> {:?}", branch.slots()); + false + } + } + }); + + let want = u64x8::splat(h); + let r1 = want.lanes_eq(unsafe { *branch.ctrl.simd }); + + let mask = r1.to_bitmask()[0] & 0b1111_1110; + + match mask { + 0b0000_0001 => unreachable!(), + 0b0000_0010 => return Ok(0), + 0b0000_0100 => return Ok(1), + 0b0000_1000 => return Ok(2), + 0b0001_0000 => return Ok(3), + 0b0010_0000 => return Ok(4), + 0b0100_0000 => return Ok(5), + 0b1000_0000 => return Ok(6), + 0b0000_0000 => {} + _ => unreachable!(), + }; + + let r2 = want.lanes_lt(unsafe { *branch.ctrl.simd }); + let mask = r2.to_bitmask()[0] & 0b1111_1110; + + match mask { + 0b1111_1110 => Err(0), + 0b1111_1100 => Err(1), + 0b1111_1000 => Err(2), + 0b1111_0000 => Err(3), + 0b1110_0000 => Err(4), + 0b1100_0000 => Err(5), + 0b1000_0000 => Err(6), + 0b0000_0000 => Err(7), + // Means something is out of order or invalid! + _ => unreachable!(), + } +} + +#[cfg(not(feature = "simd_support"))] +pub(crate) fn leaf_simd_get_slot(leaf: &Leaf, h: u64) -> Option +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + debug_assert!(h < u64::MAX); + + for cand_idx in 0..leaf.slots() { + if h == unsafe { leaf.ctrl.a.1[cand_idx] } { + return Some(cand_idx); + } + } + + None +} + +#[cfg(feature = "simd_support")] +pub(crate) fn leaf_simd_get_slot(leaf: &Leaf, h: u64) -> Option +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + // This is an important piece of logic! + debug_assert!(h < u64::MAX); + + debug_assert!({ + let want = u64x8::splat(u64::MAX); + let r1 = want.lanes_eq(unsafe { *leaf.ctrl.simd }); + let mask = r1.to_bitmask()[0] & 0b1111_1110; + + match (mask, leaf.slots()) { + (0b1111_1110, 0) + | (0b1111_1100, 1) + | (0b1111_1000, 2) + | (0b1111_0000, 3) + | (0b1110_0000, 4) + | (0b1100_0000, 5) + | (0b1000_0000, 6) + | (0b0000_0000, 7) => true, + _ => false, + } + }); + + let want = u64x8::splat(h); + let r1 = want.lanes_eq(unsafe { *leaf.ctrl.simd }); + + // println!("want: {:?}", want); + // println!("ctrl: {:?}", unsafe { *leaf.ctrl.simd }); + + // Always discard the meta field + let mask = r1.to_bitmask()[0] & 0b1111_1110; + // println!("res eq: 0b{:b}", mask); + + if mask != 0 { + // Something was equal + let cand_idx = match mask { + // 0b0000_0001 => {}, + 0b0000_0010 => 0, + 0b0000_0100 => 1, + 0b0000_1000 => 2, + 0b0001_0000 => 3, + 0b0010_0000 => 4, + 0b0100_0000 => 5, + 0b1000_0000 => 6, + _ => unreachable!(), + }; + return Some(cand_idx); + } + None +} + +#[cfg(not(feature = "simd_support"))] +pub(crate) fn leaf_simd_search(leaf: &Leaf, h: u64, k: &Q) -> KeyLoc +where + K: Hash + Eq + Clone + Debug + Borrow, + V: Clone, + Q: Eq, +{ + debug_assert!(h < u64::MAX); + + for cand_idx in 0..leaf.slots() { + if h == unsafe { leaf.ctrl.a.1[cand_idx] } { + let bucket = unsafe { (*leaf.values[cand_idx].as_ptr()).as_slice() }; + for (i, d) in bucket.iter().enumerate() { + if k.eq(d.k.borrow()) { + return KeyLoc::Ok(cand_idx, i); + } + } + // Wasn't found despite all the collisions, err. + return KeyLoc::Collision(cand_idx); + } + } + + for i in 0..leaf.slots() { + if h < unsafe { leaf.ctrl.a.1[i] } { + return KeyLoc::Missing(i); + } + } + KeyLoc::Missing(leaf.slots()) +} + +#[cfg(feature = "simd_support")] +pub(crate) fn leaf_simd_search(leaf: &Leaf, h: u64, k: &Q) -> KeyLoc +where + K: Hash + Eq + Clone + Debug + Borrow, + V: Clone, + Q: Eq, +{ + // This is an important piece of logic! + debug_assert!(h < u64::MAX); + + debug_assert!({ + let want = u64x8::splat(u64::MAX); + let r1 = want.lanes_eq(unsafe { *leaf.ctrl.simd }); + let mask = r1.to_bitmask()[0] & 0b1111_1110; + + match (mask, leaf.slots()) { + (0b1111_1110, 0) + | (0b1111_1100, 1) + | (0b1111_1000, 2) + | (0b1111_0000, 3) + | (0b1110_0000, 4) + | (0b1100_0000, 5) + | (0b1000_0000, 6) + | (0b0000_0000, 7) => true, + _ => false, + } + }); + + let want = u64x8::splat(h); + let r1 = want.lanes_eq(unsafe { *leaf.ctrl.simd }); + + // println!("want: {:?}", want); + // println!("ctrl: {:?}", unsafe { *leaf.ctrl.simd }); + + // Always discard the meta field + let mask = r1.to_bitmask()[0] & 0b1111_1110; + // println!("res eq: 0b{:b}", mask); + + if mask != 0 { + // Something was equal + let cand_idx = match mask { + // 0b0000_0001 => {}, + 0b0000_0010 => 0, + 0b0000_0100 => 1, + 0b0000_1000 => 2, + 0b0001_0000 => 3, + 0b0010_0000 => 4, + 0b0100_0000 => 5, + 0b1000_0000 => 6, + _ => unreachable!(), + }; + + // Search in the bucket. Generally this is inlined and one element. + let bucket = unsafe { (*leaf.values[cand_idx].as_ptr()).as_slice() }; + for (i, d) in bucket.iter().enumerate() { + if k.eq(d.k.borrow()) { + return KeyLoc::Ok(cand_idx, i); + } + } + // Wasn't found despite all the collisions, err. + return KeyLoc::Collision(cand_idx); + } + + let r2 = want.lanes_lt(unsafe { *leaf.ctrl.simd }); + // Always discard the meta field + let mask = r2.to_bitmask()[0] & 0b1111_1110; + // println!("res lt: 0b{:b}", mask); + let r = match mask { + 0b1111_1110 => 0, + 0b1111_1100 => 1, + 0b1111_1000 => 2, + 0b1111_0000 => 3, + 0b1110_0000 => 4, + 0b1100_0000 => 5, + 0b1000_0000 => 6, + 0b0000_0000 => 7, + // Means something is out of order or invalid! + _ => unreachable!(), + }; + KeyLoc::Missing(r) +} diff --git a/vendor/concread/src/internals/hashmap/states.rs b/vendor/concread/src/internals/hashmap/states.rs new file mode 100644 index 0000000..1e8b635 --- /dev/null +++ b/vendor/concread/src/internals/hashmap/states.rs @@ -0,0 +1,120 @@ +use super::node::{Leaf, Node}; +use std::fmt::Debug; +use std::hash::Hash; + +#[derive(Debug)] +pub(crate) enum LeafInsertState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Ok(Option), + // Split(K, V), + Split(*mut Leaf), + // We split in the reverse direction. + RevSplit(*mut Leaf), +} + +#[derive(Debug)] +pub(crate) enum LeafRemoveState +where + V: Clone, +{ + Ok(Option), + // Indicate that we found the associated value, but this + // removal means we no longer exist so should be removed. + Shrink(Option), +} + +#[derive(Debug)] +pub(crate) enum BranchInsertState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Ok, + // Two nodes that need addition to a new branch? + Split(*mut Node, *mut Node), +} + +#[derive(Debug)] +pub(crate) enum BranchShrinkState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Balanced, + Merge(*mut Node), + Shrink(*mut Node), +} + +/* +#[derive(Debug)] +pub(crate) enum BranchTrimState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Complete, + Promote(*mut Node), +} +*/ + +/* +pub(crate) enum CRTrimState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Complete, + Clone(*mut Node), + Promote(*mut Node), +} +*/ + +#[derive(Debug)] +pub(crate) enum CRInsertState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + // We did not need to clone, here is the result. + NoClone(Option), + // We had to clone the referenced node provided. + Clone(Option, *mut Node), + // We had to split, but did not need a clone. + // REMEMBER: In all split cases it means the key MUST NOT have + // previously existed, so it implies return none to the + // caller. + Split(*mut Node), + RevSplit(*mut Node), + // We had to clone and split. + CloneSplit(*mut Node, *mut Node), + CloneRevSplit(*mut Node, *mut Node), +} + +#[derive(Debug)] +pub(crate) enum CRCloneState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + Clone(*mut Node), + NoClone, +} + +#[derive(Debug)] +pub(crate) enum CRRemoveState +where + K: Hash + Eq + Clone + Debug, + V: Clone, +{ + // We did not need to clone, here is the result. + NoClone(Option), + // We had to clone the referenced node provided. + Clone(Option, *mut Node), + // + Shrink(Option), + // + CloneShrink(Option, *mut Node), +} diff --git a/vendor/concread/src/internals/lincowcell/mod.rs b/vendor/concread/src/internals/lincowcell/mod.rs new file mode 100644 index 0000000..e94a447 --- /dev/null +++ b/vendor/concread/src/internals/lincowcell/mod.rs @@ -0,0 +1,631 @@ +//! A CowCell with linear drop behaviour +//! +//! YOU SHOULD NOT USE THIS TYPE! Normaly concurrent cells do NOT require the linear dropping +//! behaviour that this implements, and it will only make your application +//! worse for it. Consider `CowCell` and `EbrCell` instead. + +/* + * The reason this exists is for protecting the major concurrently readable structures + * that can corrupt if intermediate transactions are removed early. Effectively what we + * need to create is: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ ^ + * read read read + * + * This way if we drop the reader on B: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ + * read read + * + * Notice that A is not dropped. It's only when A is dropped: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ + * read + * + * [ X ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ + * read + * [ X ] -> [ X ] -> [ C ] -> [ Write Head ] + * ^ + * read + * + * [ C ] -> [ Write Head ] + * ^ + * read + * + * At this point we drop A and B. To achieve this we need to consider that: + * - If WriteHead is dropped, C continues to live. + * - If A/B are dropped, we don't affect C. + * - Everything is dropped in order until a read txn exists. + * - When we drop the main structure, no readers can exist. + * - A writer must be able to commit to a stable location. + * + * + * T T T + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ ^ + * RRR RR R + * + * + * As the write head proceeds, it must be able to interact with past versions to commit + * garbage that is "last seen" in the formers generation. + * + */ + +use parking_lot::{Mutex, MutexGuard}; +use std::marker::PhantomData; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Arc; + +/// Do not implement this. You don't need this negativity in your life. +pub trait LinCowCellCapable { + /// Create the first reader snapshot for a new instance. + fn create_reader(&self) -> R; + + /// Create a writer that may be rolled back. + fn create_writer(&self) -> U; + + /// Given the current active reader, and the writer to commit, update our + /// main structure as mut self, and our previously linear generations based on + /// what was updated. + fn pre_commit(&mut self, new: U, prev: &R) -> R; +} + +#[derive(Debug)] +/// A concurrently readable cell with linearised drop behaviour. +pub struct LinCowCell { + updater: PhantomData, + write: Mutex, + active: Mutex>>, +} + +#[derive(Debug)] +/// A write txn over a linear cell. +pub struct LinCowCellWriteTxn<'a, T: 'a, R, U> { + // This way we know who to contact for updating our data .... + caller: &'a LinCowCell, + guard: MutexGuard<'a, T>, + work: U, +} + +#[derive(Debug)] +struct LinCowCellInner { + // This gives the chain effect. + pin: Mutex>>>, + data: R, +} + +#[derive(Debug)] +/// A read txn over a linear cell. +pub struct LinCowCellReadTxn<'a, T: 'a, R, U> { + // We must outlive the root + _caller: &'a LinCowCell, + // We pin the current version. + work: Arc>, +} + +impl LinCowCellInner { + pub fn new(data: R) -> Self { + LinCowCellInner { + pin: Mutex::new(None), + data, + } + } +} + +impl LinCowCell +where + T: LinCowCellCapable, +{ + /// Create a new linear 🐄 cell. + pub fn new(data: T) -> Self { + let r = data.create_reader(); + LinCowCell { + updater: PhantomData, + write: Mutex::new(data), + active: Mutex::new(Arc::new(LinCowCellInner::new(r))), + } + } + + /// Begin a read txn + pub fn read(&self) -> LinCowCellReadTxn { + let rwguard = self.active.lock(); + LinCowCellReadTxn { + _caller: self, + // inc the arc. + work: rwguard.clone(), + } + } + + /// Begin a write txn + pub fn write(&self) -> LinCowCellWriteTxn { + /* Take the exclusive write lock first */ + let write_guard = self.write.lock(); + /* Now take a ro-txn to get the data copied */ + // let active_guard = self.active.lock(); + /* This copies the data */ + let work: U = (*write_guard).create_writer(); + /* Now build the write struct */ + LinCowCellWriteTxn { + caller: self, + guard: write_guard, + work, + } + } + + /// Attempt a write txn + pub fn try_write(&self) -> Option> { + self.write.try_lock().map(|write_guard| { + /* This copies the data */ + let work: U = (*write_guard).create_writer(); + /* Now build the write struct */ + LinCowCellWriteTxn { + caller: self, + guard: write_guard, + work, + } + }) + } + + fn commit(&self, write: LinCowCellWriteTxn) { + // Destructure our writer. + let LinCowCellWriteTxn { + // This is self. + caller: _caller, + mut guard, + work, + } = write; + + // Get the previous generation. + let mut rwguard = self.active.lock(); + // Start to setup for the commit. + let newdata = guard.pre_commit(work, &rwguard.data); + + let new_inner = Arc::new(LinCowCellInner::new(newdata)); + { + // This modifies the next pointer of the existing read txns + let mut rwguard_inner = rwguard.pin.lock(); + // Create the arc pointer to our new data + // add it to the last value + *rwguard_inner = Some(new_inner.clone()); + } + // now over-write the last value in the mutex. + *rwguard = new_inner; + } +} + +impl<'a, T, R, U> Deref for LinCowCellReadTxn<'a, T, R, U> { + type Target = R; + + #[inline] + fn deref(&self) -> &R { + &self.work.data + } +} + +impl<'a, T, R, U> AsRef for LinCowCellReadTxn<'a, T, R, U> { + #[inline] + fn as_ref(&self) -> &R { + &self.work.data + } +} + +impl<'a, T, R, U> LinCowCellWriteTxn<'a, T, R, U> +where + T: LinCowCellCapable, +{ + #[inline] + /// Get the mutable inner of this type + pub fn get_mut(&mut self) -> &mut U { + &mut self.work + } + + /// Commit the active changes. + pub fn commit(self) { + /* Write our data back to the LinCowCell */ + self.caller.commit(self); + } +} + +impl<'a, T, R, U> Deref for LinCowCellWriteTxn<'a, T, R, U> { + type Target = U; + + #[inline] + fn deref(&self) -> &U { + &self.work + } +} + +impl<'a, T, R, U> DerefMut for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn deref_mut(&mut self) -> &mut U { + &mut self.work + } +} + +impl<'a, T, R, U> AsRef for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn as_ref(&self) -> &U { + &self.work + } +} + +impl<'a, T, R, U> AsMut for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn as_mut(&mut self) -> &mut U { + &mut self.work + } +} + +#[cfg(test)] +mod tests { + use super::LinCowCell; + use super::LinCowCellCapable; + use crossbeam_utils::thread::scope; + use std::sync::atomic::{AtomicUsize, Ordering}; + + #[derive(Debug)] + struct TestData { + x: i64, + } + + #[derive(Debug)] + struct TestDataReadTxn { + x: i64, + } + + #[derive(Debug)] + struct TestDataWriteTxn { + x: i64, + } + + impl LinCowCellCapable for TestData { + fn create_reader(&self) -> TestDataReadTxn { + TestDataReadTxn { x: self.x } + } + + fn create_writer(&self) -> TestDataWriteTxn { + TestDataWriteTxn { x: self.x } + } + + fn pre_commit( + &mut self, + new: TestDataWriteTxn, + _prev: &TestDataReadTxn, + ) -> TestDataReadTxn { + // Update self if needed. + self.x = new.x; + // return a new reader. + TestDataReadTxn { x: new.x } + } + } + + #[test] + fn test_simple_create() { + let data = TestData { x: 0 }; + let cc = LinCowCell::new(data); + + let cc_rotxn_a = cc.read(); + println!("cc_rotxn_a -> {:?}", cc_rotxn_a); + assert_eq!(cc_rotxn_a.work.data.x, 0); + + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write(); + println!("cc_wrtxn -> {:?}", cc_wrtxn); + assert_eq!(cc_wrtxn.work.x, 0); + assert_eq!(cc_wrtxn.as_ref().x, 0); + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(mut_ptr.x, 0); + mut_ptr.x = 1; + assert_eq!(mut_ptr.x, 1); + } + // Check we haven't mutated the old data. + assert_eq!(cc_rotxn_a.work.data.x, 0); + } + // The writer is dropped here. Assert no changes. + assert_eq!(cc_rotxn_a.work.data.x, 0); + { + /* Take a new write txn */ + let mut cc_wrtxn = cc.write(); + println!("cc_wrtxn -> {:?}", cc_wrtxn); + assert_eq!(cc_wrtxn.work.x, 0); + assert_eq!(cc_wrtxn.as_ref().x, 0); + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(mut_ptr.x, 0); + mut_ptr.x = 2; + assert_eq!(mut_ptr.x, 2); + } + // Check we haven't mutated the old data. + assert_eq!(cc_rotxn_a.work.data.x, 0); + // Now commit + cc_wrtxn.commit(); + } + // Should not be percieved by the old txn. + assert_eq!(cc_rotxn_a.work.data.x, 0); + let cc_rotxn_c = cc.read(); + // Is visible to the new one though. + assert_eq!(cc_rotxn_c.work.data.x, 2); + } + + // == mt tests == + + fn mt_writer(cc: &LinCowCell) { + let mut last_value: i64 = 0; + while last_value < 500 { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + assert!(mut_ptr.x >= last_value); + last_value = mut_ptr.x; + mut_ptr.x = mut_ptr.x + 1; + } + cc_wrtxn.commit(); + } + } + + fn rt_writer(cc: &LinCowCell) { + let mut last_value: i64 = 0; + while last_value < 500 { + let cc_rotxn = cc.read(); + { + assert!(cc_rotxn.work.data.x >= last_value); + last_value = cc_rotxn.work.data.x; + } + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_multithread_create() { + let start = time::Instant::now(); + // Create the new cowcell. + let data = TestData { x: 0 }; + let cc = LinCowCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + + let _readers: Vec<_> = (0..7) + .map(|_| { + scope.spawn(move |_| { + rt_writer(cc_ref); + }) + }) + .collect(); + + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + mt_writer(cc_ref); + }) + }) + .collect(); + }) + .is_ok()); + + let end = time::Instant::now(); + print!("Arc MT create :{:?} ", end - start); + } + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + #[derive(Debug)] + struct TestGcWrapperReadTxn { + _data: T, + } + + #[derive(Debug)] + struct TestGcWrapperWriteTxn { + data: T, + } + + impl LinCowCellCapable, TestGcWrapperWriteTxn> + for TestGcWrapper + { + fn create_reader(&self) -> TestGcWrapperReadTxn { + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + + fn create_writer(&self) -> TestGcWrapperWriteTxn { + TestGcWrapperWriteTxn { + data: self.data.clone(), + } + } + + fn pre_commit( + &mut self, + new: TestGcWrapperWriteTxn, + _prev: &TestGcWrapperReadTxn, + ) -> TestGcWrapperReadTxn { + // Update self if needed. + self.data = new.data.clone(); + // return a new reader. + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + } + + impl Drop for TestGcWrapperReadTxn { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + fn test_gc_operation_thread( + cc: &LinCowCell, TestGcWrapperReadTxn, TestGcWrapperWriteTxn>, + ) { + while GC_COUNT.load(Ordering::Acquire) < 50 { + // thread::sleep(std::time::Duration::from_millis(200)); + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + } + } + + #[test] + #[cfg_attr(miri, ignore)] + fn test_gc_operation() { + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = LinCowCell::new(data); + + assert!(scope(|scope| { + let cc_ref = &cc; + let _writers: Vec<_> = (0..3) + .map(|_| { + scope.spawn(move |_| { + test_gc_operation_thread(cc_ref); + }) + }) + .collect(); + }) + .is_ok()); + + assert!(GC_COUNT.load(Ordering::Acquire) >= 50); + } +} + +#[cfg(test)] +mod tests_linear { + use super::LinCowCell; + use super::LinCowCellCapable; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + #[derive(Debug)] + struct TestGcWrapperReadTxn { + _data: T, + } + + #[derive(Debug)] + struct TestGcWrapperWriteTxn { + data: T, + } + + impl LinCowCellCapable, TestGcWrapperWriteTxn> + for TestGcWrapper + { + fn create_reader(&self) -> TestGcWrapperReadTxn { + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + + fn create_writer(&self) -> TestGcWrapperWriteTxn { + TestGcWrapperWriteTxn { + data: self.data.clone(), + } + } + + fn pre_commit( + &mut self, + new: TestGcWrapperWriteTxn, + _prev: &TestGcWrapperReadTxn, + ) -> TestGcWrapperReadTxn { + // Update self if needed. + self.data = new.data.clone(); + // return a new reader. + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + } + + impl Drop for TestGcWrapperReadTxn { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + /* + * This tests an important property of the lincowcell over the cow cell + * that read txns are dropped *in order*. + */ + #[test] + fn test_gc_operation_linear() { + GC_COUNT.store(0, Ordering::Release); + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + let data = TestGcWrapper { data: 0 }; + let cc = LinCowCell::new(data); + + // Open a read A. + let cc_rotxn_a = cc.read(); + let cc_rotxn_a_2 = cc.read(); + // open a write, change and commit + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + // open a read B. + let cc_rotxn_b = cc.read(); + // open a write, change and commit + { + let mut cc_wrtxn = cc.write(); + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit(); + } + // open a read C + let cc_rotxn_c = cc.read(); + + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop B + drop(cc_rotxn_b); + + // gc count should be 0. + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop C + drop(cc_rotxn_c); + + // gc count should be 0 + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // Drop the second A, should not trigger yet. + drop(cc_rotxn_a_2); + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop A + drop(cc_rotxn_a); + + // gc count should be 2 (A + B, C is still live) + assert!(GC_COUNT.load(Ordering::Acquire) == 2); + } +} diff --git a/vendor/concread/src/internals/lincowcell_async/mod.rs b/vendor/concread/src/internals/lincowcell_async/mod.rs new file mode 100644 index 0000000..ce1946c --- /dev/null +++ b/vendor/concread/src/internals/lincowcell_async/mod.rs @@ -0,0 +1,608 @@ +//! A CowCell with linear drop behaviour, and async locking. +//! +//! YOU SHOULD NOT USE THIS TYPE! Normaly concurrent cells do NOT require the linear dropping +//! behaviour that this implements, and it will only make your application +//! worse for it. Consider `CowCell` and `EbrCell` instead. + +/* + * The reason this exists is for protecting the major concurrently readable structures + * that can corrupt if intermediate transactions are removed early. Effectively what we + * need to create is: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ ^ + * read read read + * + * This way if we drop the reader on B: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ + * read read + * + * Notice that A is not dropped. It's only when A is dropped: + * + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ + * read + * + * [ X ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ + * read + * [ X ] -> [ X ] -> [ C ] -> [ Write Head ] + * ^ + * read + * + * [ C ] -> [ Write Head ] + * ^ + * read + * + * At this point we drop A and B. To achieve this we need to consider that: + * - If WriteHead is dropped, C continues to live. + * - If A/B are dropped, we don't affect C. + * - Everything is dropped in order until a read txn exists. + * - When we drop the main structure, no readers can exist. + * - A writer must be able to commit to a stable location. + * + * + * T T T + * [ A ] -> [ B ] -> [ C ] -> [ Write Head ] + * ^ ^ ^ + * RRR RR R + * + * + * As the write head proceeds, it must be able to interact with past versions to commit + * garbage that is "last seen" in the formers generation. + * + */ + +use parking_lot::Mutex as SyncMutex; +use std::marker::PhantomData; +use std::ops::Deref; +use std::ops::DerefMut; +use std::sync::Arc; +use tokio::sync::{Mutex, MutexGuard}; + +use crate::internals::lincowcell::LinCowCellCapable; + +#[derive(Debug)] +/// A concurrently readable cell with linearised drop behaviour. +pub struct LinCowCell { + updater: PhantomData, + write: Mutex, + active: Mutex>>, +} + +#[derive(Debug)] +/// A write txn over a linear cell. +pub struct LinCowCellWriteTxn<'a, T: 'a, R, U> { + // This way we know who to contact for updating our data .... + caller: &'a LinCowCell, + guard: MutexGuard<'a, T>, + work: U, +} + +#[derive(Debug)] +struct LinCowCellInner { + // This gives the chain effect. + pin: SyncMutex>>>, + data: R, +} + +#[derive(Debug)] +/// A read txn over a linear cell. +pub struct LinCowCellReadTxn<'a, T: 'a, R, U> { + // We must outlive the root + _caller: &'a LinCowCell, + // We pin the current version. + work: Arc>, +} + +impl LinCowCellInner { + pub fn new(data: R) -> Self { + LinCowCellInner { + pin: SyncMutex::new(None), + data, + } + } +} + +impl LinCowCell +where + T: LinCowCellCapable, +{ + /// Create a new linear 🐄 cell. + pub fn new(data: T) -> Self { + let r = data.create_reader(); + LinCowCell { + updater: PhantomData, + write: Mutex::new(data), + active: Mutex::new(Arc::new(LinCowCellInner::new(r))), + } + } + + /// Begin a read txn + pub async fn read(&self) -> LinCowCellReadTxn<'_, T, R, U> { + let rwguard = self.active.lock().await; + LinCowCellReadTxn { + _caller: self, + // inc the arc. + work: rwguard.clone(), + } + } + + /// Begin a write txn + pub async fn write<'x>(&'x self) -> LinCowCellWriteTxn<'x, T, R, U> { + /* Take the exclusive write lock first */ + let write_guard = self.write.lock().await; + /* Now take a ro-txn to get the data copied */ + // let active_guard = self.active.lock(); + /* This copies the data */ + let work: U = (*write_guard).create_writer(); + /* Now build the write struct */ + LinCowCellWriteTxn { + caller: self, + guard: write_guard, + work, + } + } + + /// Attempt a write txn + pub fn try_write(&self) -> Option> { + self.write + .try_lock() + .map(|write_guard| { + /* This copies the data */ + let work: U = (*write_guard).create_writer(); + /* Now build the write struct */ + LinCowCellWriteTxn { + caller: self, + guard: write_guard, + work, + } + }) + .ok() + } + + async fn commit(&self, write: LinCowCellWriteTxn<'_, T, R, U>) { + // Destructure our writer. + let LinCowCellWriteTxn { + // This is self. + caller: _caller, + mut guard, + work, + } = write; + + // Get the previous generation. + let mut rwguard = self.active.lock().await; + // Start to setup for the commit. + let newdata = guard.pre_commit(work, &rwguard.data); + + let new_inner = Arc::new(LinCowCellInner::new(newdata)); + { + // This modifies the next pointer of the existing read txns + let mut rwguard_inner = rwguard.pin.lock(); + // Create the arc pointer to our new data + // add it to the last value + *rwguard_inner = Some(new_inner.clone()); + } + // now over-write the last value in the mutex. + *rwguard = new_inner; + } +} + +impl<'a, T, R, U> Deref for LinCowCellReadTxn<'a, T, R, U> { + type Target = R; + + #[inline] + fn deref(&self) -> &R { + &self.work.data + } +} + +impl<'a, T, R, U> AsRef for LinCowCellReadTxn<'a, T, R, U> { + #[inline] + fn as_ref(&self) -> &R { + &self.work.data + } +} + +impl<'a, T, R, U> LinCowCellWriteTxn<'a, T, R, U> +where + T: LinCowCellCapable, +{ + #[inline] + /// Get the mutable inner of this type + pub fn get_mut(&mut self) -> &mut U { + &mut self.work + } + + /// Commit the active changes. + pub async fn commit(self) { + /* Write our data back to the LinCowCell */ + self.caller.commit(self).await; + } +} + +impl<'a, T, R, U> Deref for LinCowCellWriteTxn<'a, T, R, U> { + type Target = U; + + #[inline] + fn deref(&self) -> &U { + &self.work + } +} + +impl<'a, T, R, U> DerefMut for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn deref_mut(&mut self) -> &mut U { + &mut self.work + } +} + +impl<'a, T, R, U> AsRef for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn as_ref(&self) -> &U { + &self.work + } +} + +impl<'a, T, R, U> AsMut for LinCowCellWriteTxn<'a, T, R, U> { + #[inline] + fn as_mut(&mut self) -> &mut U { + &mut self.work + } +} + +#[cfg(test)] +mod tests { + use super::LinCowCell; + use super::LinCowCellCapable; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::Arc; + + #[derive(Debug)] + struct TestData { + x: i64, + } + + #[derive(Debug)] + struct TestDataReadTxn { + x: i64, + } + + #[derive(Debug)] + struct TestDataWriteTxn { + x: i64, + } + + impl LinCowCellCapable for TestData { + fn create_reader(&self) -> TestDataReadTxn { + TestDataReadTxn { x: self.x } + } + + fn create_writer(&self) -> TestDataWriteTxn { + TestDataWriteTxn { x: self.x } + } + + fn pre_commit( + &mut self, + new: TestDataWriteTxn, + _prev: &TestDataReadTxn, + ) -> TestDataReadTxn { + // Update self if needed. + self.x = new.x; + // return a new reader. + TestDataReadTxn { x: new.x } + } + } + + #[tokio::test] + async fn test_simple_create() { + let data = TestData { x: 0 }; + let cc = LinCowCell::new(data); + + let cc_rotxn_a = cc.read().await; + println!("cc_rotxn_a -> {:?}", cc_rotxn_a); + assert_eq!(cc_rotxn_a.work.data.x, 0); + + { + /* Take a write txn */ + let mut cc_wrtxn = cc.write().await; + println!("cc_wrtxn -> {:?}", cc_wrtxn); + assert_eq!(cc_wrtxn.work.x, 0); + assert_eq!(cc_wrtxn.as_ref().x, 0); + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(mut_ptr.x, 0); + mut_ptr.x = 1; + assert_eq!(mut_ptr.x, 1); + } + // Check we haven't mutated the old data. + assert_eq!(cc_rotxn_a.work.data.x, 0); + } + // The writer is dropped here. Assert no changes. + assert_eq!(cc_rotxn_a.work.data.x, 0); + { + /* Take a new write txn */ + let mut cc_wrtxn = cc.write().await; + println!("cc_wrtxn -> {:?}", cc_wrtxn); + assert_eq!(cc_wrtxn.work.x, 0); + assert_eq!(cc_wrtxn.as_ref().x, 0); + { + let mut_ptr = cc_wrtxn.get_mut(); + /* Assert it's 0 */ + assert_eq!(mut_ptr.x, 0); + mut_ptr.x = 2; + assert_eq!(mut_ptr.x, 2); + } + // Check we haven't mutated the old data. + assert_eq!(cc_rotxn_a.work.data.x, 0); + // Now commit + cc_wrtxn.commit().await; + } + // Should not be percieved by the old txn. + assert_eq!(cc_rotxn_a.work.data.x, 0); + let cc_rotxn_c = cc.read().await; + // Is visible to the new one though. + assert_eq!(cc_rotxn_c.work.data.x, 2); + } + + // == mt tests == + + async fn mt_writer(cc: Arc>) { + let mut last_value: i64 = 0; + while last_value < 500 { + let mut cc_wrtxn = cc.write().await; + { + let mut_ptr = cc_wrtxn.get_mut(); + assert!(mut_ptr.x >= last_value); + last_value = mut_ptr.x; + mut_ptr.x = mut_ptr.x + 1; + } + cc_wrtxn.commit().await; + } + } + + async fn rt_writer(cc: Arc>) { + let mut last_value: i64 = 0; + while last_value < 500 { + let cc_rotxn = cc.read().await; + { + assert!(cc_rotxn.work.data.x >= last_value); + last_value = cc_rotxn.work.data.x; + } + } + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] + async fn test_concurrent_create() { + let start = time::Instant::now(); + // Create the new cowcell. + let data = TestData { x: 0 }; + let cc = Arc::new(LinCowCell::new(data)); + + let _ = tokio::join!( + tokio::task::spawn(rt_writer(cc.clone())), + tokio::task::spawn(rt_writer(cc.clone())), + tokio::task::spawn(rt_writer(cc.clone())), + tokio::task::spawn(rt_writer(cc.clone())), + tokio::task::spawn(mt_writer(cc.clone())), + tokio::task::spawn(mt_writer(cc.clone())), + ); + + let end = time::Instant::now(); + print!("Arc MT create :{:?} ", end - start); + } + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + #[derive(Debug)] + struct TestGcWrapperReadTxn { + _data: T, + } + + #[derive(Debug)] + struct TestGcWrapperWriteTxn { + data: T, + } + + impl LinCowCellCapable, TestGcWrapperWriteTxn> + for TestGcWrapper + { + fn create_reader(&self) -> TestGcWrapperReadTxn { + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + + fn create_writer(&self) -> TestGcWrapperWriteTxn { + TestGcWrapperWriteTxn { + data: self.data.clone(), + } + } + + fn pre_commit( + &mut self, + new: TestGcWrapperWriteTxn, + _prev: &TestGcWrapperReadTxn, + ) -> TestGcWrapperReadTxn { + // Update self if needed. + self.data = new.data.clone(); + // return a new reader. + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + } + + impl Drop for TestGcWrapperReadTxn { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + async fn test_gc_operation_thread( + cc: Arc< + LinCowCell, TestGcWrapperReadTxn, TestGcWrapperWriteTxn>, + >, + ) { + while GC_COUNT.load(Ordering::Acquire) < 50 { + // thread::sleep(std::time::Duration::from_millis(200)); + { + let mut cc_wrtxn = cc.write().await; + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit().await; + } + } + } + + #[tokio::test] + #[cfg_attr(miri, ignore)] + async fn test_gc_operation() { + GC_COUNT.store(0, Ordering::Release); + let data = TestGcWrapper { data: 0 }; + let cc = Arc::new(LinCowCell::new(data)); + + let _ = tokio::join!( + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + tokio::task::spawn(test_gc_operation_thread(cc.clone())), + ); + + assert!(GC_COUNT.load(Ordering::Acquire) >= 50); + } +} + +#[cfg(test)] +mod tests_linear { + use super::LinCowCell; + use super::LinCowCellCapable; + use std::sync::atomic::{AtomicUsize, Ordering}; + + static GC_COUNT: AtomicUsize = AtomicUsize::new(0); + + #[derive(Debug, Clone)] + struct TestGcWrapper { + data: T, + } + + #[derive(Debug)] + struct TestGcWrapperReadTxn { + _data: T, + } + + #[derive(Debug)] + struct TestGcWrapperWriteTxn { + data: T, + } + + impl LinCowCellCapable, TestGcWrapperWriteTxn> + for TestGcWrapper + { + fn create_reader(&self) -> TestGcWrapperReadTxn { + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + + fn create_writer(&self) -> TestGcWrapperWriteTxn { + TestGcWrapperWriteTxn { + data: self.data.clone(), + } + } + + fn pre_commit( + &mut self, + new: TestGcWrapperWriteTxn, + _prev: &TestGcWrapperReadTxn, + ) -> TestGcWrapperReadTxn { + // Update self if needed. + self.data = new.data.clone(); + // return a new reader. + TestGcWrapperReadTxn { + _data: self.data.clone(), + } + } + } + + impl Drop for TestGcWrapperReadTxn { + fn drop(&mut self) { + // Add to the atomic counter ... + GC_COUNT.fetch_add(1, Ordering::Release); + } + } + + /* + * This tests an important property of the lincowcell over the cow cell + * that read txns are dropped *in order*. + */ + #[tokio::test] + async fn test_gc_operation_linear() { + GC_COUNT.store(0, Ordering::Release); + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + let data = TestGcWrapper { data: 0 }; + let cc = LinCowCell::new(data); + + // Open a read A. + let cc_rotxn_a = cc.read().await; + let cc_rotxn_a_2 = cc.read().await; + // open a write, change and commit + { + let mut cc_wrtxn = cc.write().await; + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit().await; + } + // open a read B. + let cc_rotxn_b = cc.read().await; + // open a write, change and commit + { + let mut cc_wrtxn = cc.write().await; + { + let mut_ptr = cc_wrtxn.get_mut(); + mut_ptr.data = mut_ptr.data + 1; + } + cc_wrtxn.commit().await; + } + // open a read C + let cc_rotxn_c = cc.read().await; + + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop B + drop(cc_rotxn_b); + + // gc count should be 0. + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop C + drop(cc_rotxn_c); + + // gc count should be 0 + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // Drop the second A, should not trigger yet. + drop(cc_rotxn_a_2); + assert!(GC_COUNT.load(Ordering::Acquire) == 0); + + // drop A + drop(cc_rotxn_a); + + // gc count should be 2 (A + B, C is still live) + assert!(GC_COUNT.load(Ordering::Acquire) == 2); + } +} diff --git a/vendor/concread/src/internals/mod.rs b/vendor/concread/src/internals/mod.rs new file mode 100644 index 0000000..0e13ca4 --- /dev/null +++ b/vendor/concread/src/internals/mod.rs @@ -0,0 +1,17 @@ +//! This module contains all the internals of how the complex concurrent datastructures +//! are implemented. You should turn back now. Nothing of value is here. This module can +//! only inlfict horror upon you. +//! +//! This module exists for one purpose - to allow external composition of structures +//! coordinated by a single locking manager. This makes every element of this module +//! unsafe in every meaning of the word. If you handle this module at all, you will +//! probably cause space time to unravel. +//! +//! ⚠️ ⚠️ ⚠️ + +pub mod bptree; +pub mod hashmap; +pub mod lincowcell; + +#[cfg(feature = "asynch")] +pub mod lincowcell_async; diff --git a/vendor/concread/src/lib.rs b/vendor/concread/src/lib.rs new file mode 100644 index 0000000..2126a10 --- /dev/null +++ b/vendor/concread/src/lib.rs @@ -0,0 +1,53 @@ +//! Concread - Concurrently Readable Datastructures +//! +//! Concurrently readable is often referred to as Copy-On-Write, Multi-Version-Concurrency-Control. +//! +//! These structures allow multiple readers with transactions +//! to proceed while single writers can operate. A reader is guaranteed the content +//! will remain the same for the duration of the read, and readers do not block writers. +//! Writers are serialised, just like a mutex. +//! +//! You can use these in place of a RwLock, and will likely see improvements in +//! parallel throughput. +//! +//! The best use is in place of mutex/rwlock, where the reader exists for a +//! non-trivial amount of time. +//! +//! For example, if you have a RwLock where the lock is taken, data changed or read, and dropped +//! immediately, this probably won't help you. +//! +//! However, if you have a RwLock where you hold the read lock for any amount of time, +//! writers will begin to stall - or inversely, the writer will cause readers to block +//! and wait as the writer proceeds. +//! +//! In the future, a concurrent BTree and HashTree will be added, that can be used inplace +//! of a `RwLock` or `RwLock`. Stay tuned! + +#![deny(warnings)] +#![warn(unused_extern_crates)] +#![warn(missing_docs)] +#![allow(clippy::needless_lifetimes)] +#![cfg_attr(feature = "simd_support", feature(portable_simd))] + +#[macro_use] +extern crate smallvec; + +// This is where the gud rust lives. +mod utils; + +// This is where the scary rust lives. +pub mod internals; + +// pub mod hpcell; +pub mod cowcell; +pub mod ebrcell; + +pub mod arcache; +#[cfg(feature = "tcache")] +pub mod threadcache; + +pub mod bptree; +pub mod hashmap; + +pub use cowcell::CowCell; +pub use ebrcell::EbrCell; diff --git a/vendor/concread/src/threadcache/mod.rs b/vendor/concread/src/threadcache/mod.rs new file mode 100644 index 0000000..a305cf4 --- /dev/null +++ b/vendor/concread/src/threadcache/mod.rs @@ -0,0 +1,323 @@ +//! ThreadCache - A per-thread cache with transactional behaviour. +//! +//! This provides a per-thread cache, which uses a broadcast invalidation +//! queue to manage local content. This is similar to how a CPU cache works +//! in hardware. Generally this is best for small, distinct caches with very +//! few changes / writes. +//! +//! It's worth noting that each thread needs to frequently "read" it's cache. +//! Any idle thread will end up with invalidations building up, that can consume +//! a large volume of memory. This means you need your "readers" to have transactions +//! opened/closed periodically to ensure that invalidations are acknowledged. +//! +//! Generally you should prefer to use `ARCache` over this module unless you really require +//! the properties of this module. + +use parking_lot::{Mutex, MutexGuard}; +use std::collections::HashSet; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::mpsc::{channel, Receiver, Sender}; +use std::sync::Arc; + +use std::fmt::Debug; +use std::hash::Hash; + +use lru::LruCache; + +/// An instance of a threads local cache store. +pub struct ThreadLocal +where + K: Hash + Eq + Debug + Clone, +{ + tid: usize, + rx: Receiver>, + wrlock: Arc>>, + inv_up_to_txid: Arc, + last_inv: Option>, + cache: LruCache, +} + +struct Writer +where + K: Hash + Eq + Debug + Clone, +{ + txs: Vec>>, +} + +/// A write transaction over this local threads cache. If you hold the write txn, no +/// other thread can be in the write state. Changes to this cache will be broadcast to +/// other threads to ensure they can revalidate their content correctly. +pub struct ThreadLocalWriteTxn<'a, K, V> +where + K: Hash + Eq + Debug + Clone, +{ + txid: u64, + parent: &'a mut ThreadLocal, + guard: MutexGuard<'a, Writer>, + rollback: HashSet, +} + +/// A read transaction of this cache. During a read, it is guaranteed that the content +/// of this cache will not be updated or invalidated unless by this threads actions. +pub struct ThreadLocalReadTxn<'a, K, V> +where + K: Hash + Eq + Debug + Clone, +{ + // txid: u64, + parent: &'a mut ThreadLocal, +} + +#[derive(Clone)] +struct Invalidate +where + K: Hash + Eq + Debug + Clone, +{ + k: K, + txid: u64, +} + +impl ThreadLocal +where + K: Hash + Eq + Debug + Clone, +{ + /// Create a new set of caches. You must specify the number of threads caches to + /// create, and the per-thread size of the cache in capacity. An array of the + /// cache instances will be returned that you can then distribute to the threads. + pub fn new(threads: usize, capacity: usize) -> Vec { + assert!(threads > 0); + let (txs, rxs): (Vec<_>, Vec<_>) = (0..threads) + .into_iter() + .map(|_| channel::>()) + .unzip(); + + // Create an Arc> for the writer. + let inv_up_to_txid = Arc::new(AtomicU64::new(0)); + let wrlock = Arc::new(Mutex::new(Writer { txs })); + + // Then for each thread, take one rx and a clone of the broadcast tbl. + // Allocate a threadid (tid). + + rxs.into_iter() + .enumerate() + .map(|(tid, rx)| ThreadLocal { + tid, + rx, + wrlock: wrlock.clone(), + inv_up_to_txid: inv_up_to_txid.clone(), + last_inv: None, + cache: LruCache::new(capacity), + }) + .collect() + } + + /// Begin a read transaction of this thread local cache. In the start of this read + /// invalidation requests will be acknowledged. + pub fn read<'a>(&'a mut self) -> ThreadLocalReadTxn<'a, K, V> { + let txid = self.inv_up_to_txid.load(Ordering::Acquire); + self.invalidate(txid); + ThreadLocalReadTxn { parent: self } + } + + /// Begin a write transaction of this thread local cache. Once granted, only this + /// thread may be in the write state - all other threads will either block on + /// acquiring the write, or they can proceed to read. + pub fn write<'a>(&'a mut self) -> ThreadLocalWriteTxn<'a, K, V> { + // SAFETY this is safe, because while we are duplicating the mutable reference + // which conflicts with the mutex, we aren't change the wrlock value so the mutex + // is fine. + let parent: &mut Self = unsafe { &mut *(self as *mut _) }; + // We are the only writer! + let guard = self.wrlock.lock(); + let txid = parent.inv_up_to_txid.load(Ordering::Acquire); + let txid = txid + 1; + parent.invalidate(txid); + ThreadLocalWriteTxn { + txid, + parent, + guard, + rollback: HashSet::new(), + } + } + + /* + pub fn try_write<'a>(&'a mut self) -> Option> { + unimplemented!(); + } + */ + + fn invalidate(&mut self, up_to: u64) { + if let Some(inv) = self.last_inv.as_ref() { + if inv.txid > up_to { + return; + } else { + self.cache.pop(&inv.k); + } + } + + // We got here, so we must have acted on last_inv. + self.last_inv = None; + + while let Ok(inv) = self.rx.try_recv() { + if inv.txid > up_to { + // Stash this for next loop. + self.last_inv = Some(inv); + return; + } else { + self.cache.pop(&inv.k); + } + } + } +} + +impl<'a, K, V> ThreadLocalWriteTxn<'a, K, V> +where + K: Hash + Eq + Debug + Clone, +{ + /// Attempt to retrieve a k-v pain from the cache. If it is not present, `None` is returned. + pub fn get(&mut self, k: &K) -> Option<&V> { + self.parent.cache.get(k) + } + + /// Determine if the key exists in the cache. + pub fn contains_key(&mut self, k: &K) -> bool { + self.parent.cache.get(k).is_some() + } + + /// Insert a new item to this cache for this transaction. + pub fn insert(&mut self, k: K, v: V) -> Option { + // Store the k in our rollback set. + self.rollback.insert(k.clone()); + self.parent.cache.put(k, v) + } + + /// Remove an item from the cache for this transaction. IE you are deleting the k-v. + pub fn remove(&mut self, k: &K) -> Option { + self.rollback.insert(k.clone()); + self.parent.cache.pop(k) + } + + /// Commit the changes to this cache so they are visible to others. If you do NOT call + /// commit, all changes to this cache are rolled back to prevent invalidate states. + pub fn commit(mut self) { + // We are commiting, so lets get ready. + // First, anything that we touched in the rollback set will need + // to be invalidated from other caches. It doesn't matter if we + // removed or inserted, it has the same effect on them. + self.guard.txs.iter().enumerate().for_each(|(i, tx)| { + if i != self.parent.tid { + self.rollback.iter().for_each(|k| { + // Ignore channel failures. + let _ = tx.send(Invalidate { + k: k.clone(), + txid: self.txid, + }); + }); + } + }); + // Now we have issued our invalidations, we can tell people to invalidate up to this txid + self.parent + .inv_up_to_txid + .store(self.txid, Ordering::Release); + // Ensure our rollback set is empty now to avoid the drop handler. + self.rollback.clear(); + // We're done! + } +} + +impl<'a, K, V> Drop for ThreadLocalWriteTxn<'a, K, V> +where + K: Hash + Eq + Debug + Clone, +{ + fn drop(&mut self) { + // Clear anything that's in the rollback. + for k in self.rollback.iter() { + self.parent.cache.pop(k); + } + } +} + +impl<'a, K, V> ThreadLocalReadTxn<'a, K, V> +where + K: Hash + Eq + Debug + Clone, +{ + /// Attempt to retrieve a k-v pain from the cache. If it is not present, `None` is returned. + pub fn get(&mut self, k: &K) -> Option<&V> { + self.parent.cache.get(k) + } + + /// Determine if the key exists in the cache. + pub fn contains_key(&mut self, k: &K) -> bool { + self.parent.cache.get(k).is_some() + } + + /// Insert a new item to this cache for this transaction. + pub fn insert(&mut self, k: K, v: V) -> Option { + self.parent.cache.put(k, v) + } +} + +#[cfg(test)] +mod tests { + use super::ThreadLocal; + + #[test] + fn test_basic() { + let mut cache: Vec> = ThreadLocal::new(2, 8); + let mut cache_a = cache.pop().unwrap(); + let mut cache_b = cache.pop().unwrap(); + + let mut wr_txn = cache_a.write(); + let mut rd_txn = cache_b.read(); + + wr_txn.insert(1, 1); + wr_txn.insert(2, 2); + assert!(wr_txn.contains_key(&1)); + assert!(wr_txn.contains_key(&2)); + + assert!(!rd_txn.contains_key(&1)); + assert!(!rd_txn.contains_key(&2)); + wr_txn.commit(); + + drop(rd_txn); + + let mut rd_txn = cache_b.read(); + // Even in a new txn, we don't have this in our cache. + assert!(!rd_txn.contains_key(&1)); + assert!(!rd_txn.contains_key(&2)); + // But we can insert it to match + rd_txn.insert(1, 1); + rd_txn.insert(2, 2); + drop(rd_txn); + + // Repeat use of rd should still show it. + let mut rd_txn = cache_b.read(); + assert!(rd_txn.contains_key(&1)); + assert!(rd_txn.contains_key(&2)); + drop(rd_txn); + + // Add new items. + let mut wr_txn = cache_a.write(); + assert!(wr_txn.contains_key(&1)); + assert!(wr_txn.contains_key(&2)); + wr_txn.insert(3, 3); + assert!(wr_txn.contains_key(&3)); + drop(wr_txn); + + let mut wr_txn = cache_a.write(); + assert!(wr_txn.contains_key(&1)); + assert!(wr_txn.contains_key(&2)); + // Should have been rolled back. + assert!(!wr_txn.contains_key(&3)); + + // Now invalidate 1/2 + wr_txn.remove(&1); + wr_txn.remove(&2); + wr_txn.commit(); + + // This sends invalidation reqs, so we should now have removed this in the other cache. + let mut rd_txn = cache_b.read(); + // Even in a new txn, we don't have this in our cache. + assert!(!rd_txn.contains_key(&1)); + assert!(!rd_txn.contains_key(&2)); + } +} diff --git a/vendor/concread/src/unsound.rs b/vendor/concread/src/unsound.rs new file mode 100644 index 0000000..686911b --- /dev/null +++ b/vendor/concread/src/unsound.rs @@ -0,0 +1,54 @@ +extern crate concread; +use concread::cowcell::CowCell; +use std::ops::Deref; +// use crossbeam_epoch::*; +use std::mem::forget; +use std::rc::Rc; + +struct StrRef<'a> { + r: &'a str, +} + +impl<'a> Drop for StrRef<'a> { + fn drop(&mut self) { + println!("{}", self.r); + } +} + +impl<'a> Clone for StrRef<'a> { + fn clone(&self) -> StrRef<'a> { + StrRef { r: self.r } + } +} + +struct ChangesItselfString { + pub s: Rc, +} + +impl Drop for ChangesItselfString { + fn drop(&mut self) { + Rc::get_mut(&mut self.s) + .unwrap() + .as_mut_str() + .make_ascii_uppercase(); + // Keep object alive. + forget(self.s.clone()); + } +} + +fn main() { + { + let s = ChangesItselfString { + s: Rc::new(String::from("lowercase_string")), + }; + let f = StrRef { r: s.s.deref() }; + let cell = CowCell::new(f); + drop(cell); + } + println!("ChangesItselfString is gone!"); + + // StrRef drop() references possibly freed memory! I made it view a leaked Rc so as to + // not get a segfault, but in general this can be made to do all kinds of wrong things. + // pin().flush(); + // pin().flush(); +} diff --git a/vendor/concread/src/unsound2.rs b/vendor/concread/src/unsound2.rs new file mode 100644 index 0000000..c64b99a --- /dev/null +++ b/vendor/concread/src/unsound2.rs @@ -0,0 +1,44 @@ +#![forbid(unsafe_code)] +extern crate concread; + +#[cfg(not(feature = "unsoundness"))] +fn main() { + eprintln!("Recompile with --features unsoundness"); +} + +#[cfg(feature = "unsoundness")] +fn main() { + use concread::arcache::ARCache; + use std::rc::Rc; + use std::sync::Arc; + + let non_sync_item = Rc::new(0); // neither `Send` nor `Sync` + assert_eq!(Rc::strong_count(&non_sync_item), 1); + + let cache = ARCache::>::new_size(5, 5); + let mut writer = cache.write(); + writer.insert(0, non_sync_item); + writer.commit(); + + let arc_parent = Arc::new(cache); + + let mut handles = vec![]; + for _ in 0..5 { + let arc_child = arc_parent.clone(); + let child_handle = std::thread::spawn(move || { + let reader = arc_child.read(); // new Reader of ARCache + let smuggled_rc = reader.get(&0).unwrap(); + + for _ in 0..1000 { + let _dummy_clone = Rc::clone(&smuggled_rc); // Increment `strong_count` of `Rc` + // When `_dummy_clone` is dropped, `strong_count` is decremented. + } + }); + handles.push(child_handle); + } + for handle in handles { + handle.join().expect("failed to join child thread"); + } + + assert_eq!(Rc::strong_count(arc_parent.read().get(&0).unwrap()), 1); +} diff --git a/vendor/concread/src/unsound3.rs b/vendor/concread/src/unsound3.rs new file mode 100644 index 0000000..89b27bf --- /dev/null +++ b/vendor/concread/src/unsound3.rs @@ -0,0 +1,58 @@ +#![forbid(unsafe_code)] +extern crate concread; + +#[cfg(not(feature = "unsoundness"))] +fn main() { + eprintln!("Recompile with --features unsoundness"); +} + +#[derive(Debug, Clone, Copy)] +#[cfg(feature = "unsoundness")] +enum RefOrInt<'a> { + Ref(&'a u64), + Int(u64), +} + +#[cfg(feature = "unsoundness")] +fn main() { + use concread::arcache::ARCache; + use std::cell::Cell; + use std::sync::Arc; + + static PARENT_STATIC: u64 = 1; + + // `Cell` is `Send` but not `Sync`. + let item_not_sync = Cell::new(RefOrInt::Ref(&PARENT_STATIC)); + + let cache = ARCache::>::new_size(5, 5); + let mut writer = cache.write(); + writer.insert(0, item_not_sync); + writer.commit(); + + let arc_parent = Arc::new(cache); + let arc_child = arc_parent.clone(); + + std::thread::spawn(move || { + let arc_child = arc_child; + // new `Reader` of `ARCache` + let reader = arc_child.read(); + let ref_to_smuggled_cell = reader.get(&0).unwrap(); + + static CHILD_STATIC: u64 = 1; + loop { + ref_to_smuggled_cell.set(RefOrInt::Ref(&CHILD_STATIC)); + ref_to_smuggled_cell.set(RefOrInt::Int(0xDEADBEEF)); + } + }); + + let reader = arc_parent.read(); + let ref_to_inner_cell = reader.get(&0).unwrap(); + loop { + if let RefOrInt::Ref(addr) = ref_to_inner_cell.get() { + if addr as *const _ as usize == 0xDEADBEEF { + println!("We have bypassed enum checking"); + println!("Dereferencing `addr` will now segfault : {}", *addr); + } + } + } +} diff --git a/vendor/concread/src/utils.rs b/vendor/concread/src/utils.rs new file mode 100644 index 0000000..18874a0 --- /dev/null +++ b/vendor/concread/src/utils.rs @@ -0,0 +1,82 @@ +use std::borrow::Borrow; +use std::cmp::Ordering; +// use std::mem::MaybeUninit; +use std::ptr; + +pub(crate) unsafe fn slice_insert(slice: &mut [T], new: T, idx: usize) { + ptr::copy( + slice.as_ptr().add(idx), + slice.as_mut_ptr().add(idx + 1), + slice.len() - idx - 1, + ); + ptr::write(slice.get_unchecked_mut(idx), new); +} + +// From std::collections::btree::node.rs +pub(crate) unsafe fn slice_remove(slice: &mut [T], idx: usize) -> T { + // setup the value to be returned, IE give ownership to ret. + let ret = ptr::read(slice.get_unchecked(idx)); + ptr::copy( + slice.as_ptr().add(idx + 1), + slice.as_mut_ptr().add(idx), + slice.len() - idx - 1, + ); + ret +} + +pub(crate) unsafe fn slice_merge(dst: &mut [T], start_idx: usize, src: &mut [T], count: usize) { + let dst_ptr = dst.as_mut_ptr().add(start_idx); + let src_ptr = src.as_ptr(); + + ptr::copy_nonoverlapping(src_ptr, dst_ptr, count); +} + +pub(crate) unsafe fn slice_move( + dst: &mut [T], + dst_start_idx: usize, + src: &mut [T], + src_start_idx: usize, + count: usize, +) { + let dst_ptr = dst.as_mut_ptr().add(dst_start_idx); + let src_ptr = src.as_ptr().add(src_start_idx); + + ptr::copy_nonoverlapping(src_ptr, dst_ptr, count); +} + +/* +pub(crate) unsafe fn slice_slide_and_drop( + slice: &mut [MaybeUninit], + idx: usize, + count: usize, +) { + // drop everything up to and including idx + for item in slice.iter_mut().take(idx + 1) { + // These are dropped here ...? + ptr::drop_in_place(item.as_mut_ptr()); + } + // now move everything down. + ptr::copy(slice.as_ptr().add(idx + 1), slice.as_mut_ptr(), count); +} + +pub(crate) unsafe fn slice_slide(slice: &mut [T], idx: usize, count: usize) { + // now move everything down. + ptr::copy(slice.as_ptr().add(idx + 1), slice.as_mut_ptr(), count); +} +*/ + +pub(crate) fn slice_search_linear(slice: &[K], k: &Q) -> Result +where + K: Borrow, + Q: Ord, +{ + for (idx, nk) in slice.iter().enumerate() { + let r = k.cmp(nk.borrow()); + match r { + Ordering::Greater => {} + Ordering::Equal => return Ok(idx), + Ordering::Less => return Err(idx), + } + } + Err(slice.len()) +} diff --git a/vendor/concread/static/arc_1.png b/vendor/concread/static/arc_1.png new file mode 100644 index 0000000..28a6ab8 Binary files /dev/null and b/vendor/concread/static/arc_1.png differ diff --git a/vendor/concread/static/arc_2.png b/vendor/concread/static/arc_2.png new file mode 100644 index 0000000..1feb834 Binary files /dev/null and b/vendor/concread/static/arc_2.png differ diff --git a/vendor/concread/static/cow_1.png b/vendor/concread/static/cow_1.png new file mode 100644 index 0000000..c3d8368 Binary files /dev/null and b/vendor/concread/static/cow_1.png differ diff --git a/vendor/concread/static/cow_2.png b/vendor/concread/static/cow_2.png new file mode 100644 index 0000000..7002c7a Binary files /dev/null and b/vendor/concread/static/cow_2.png differ diff --git a/vendor/concread/static/cow_3.png b/vendor/concread/static/cow_3.png new file mode 100644 index 0000000..4c7d534 Binary files /dev/null and b/vendor/concread/static/cow_3.png differ diff --git a/vendor/concread/static/cow_arc_1.png b/vendor/concread/static/cow_arc_1.png new file mode 100644 index 0000000..0e7a5e5 Binary files /dev/null and b/vendor/concread/static/cow_arc_1.png differ diff --git a/vendor/concread/static/cow_arc_2.png b/vendor/concread/static/cow_arc_2.png new file mode 100644 index 0000000..095c8d9 Binary files /dev/null and b/vendor/concread/static/cow_arc_2.png differ diff --git a/vendor/concread/static/cow_arc_3.png b/vendor/concread/static/cow_arc_3.png new file mode 100644 index 0000000..0d7a52e Binary files /dev/null and b/vendor/concread/static/cow_arc_3.png differ diff --git a/vendor/concread/static/cow_arc_4.png b/vendor/concread/static/cow_arc_4.png new file mode 100644 index 0000000..d91ecc4 Binary files /dev/null and b/vendor/concread/static/cow_arc_4.png differ diff --git a/vendor/concread/static/cow_arc_5.png b/vendor/concread/static/cow_arc_5.png new file mode 100644 index 0000000..e11a4b9 Binary files /dev/null and b/vendor/concread/static/cow_arc_5.png differ diff --git a/vendor/concread/static/cow_arc_6.png b/vendor/concread/static/cow_arc_6.png new file mode 100644 index 0000000..d37642f Binary files /dev/null and b/vendor/concread/static/cow_arc_6.png differ diff --git a/vendor/concread/static/cow_arc_7.png b/vendor/concread/static/cow_arc_7.png new file mode 100644 index 0000000..f0c7d5b Binary files /dev/null and b/vendor/concread/static/cow_arc_7.png differ diff --git a/vendor/concread/static/cow_arc_8.png b/vendor/concread/static/cow_arc_8.png new file mode 100644 index 0000000..188184a Binary files /dev/null and b/vendor/concread/static/cow_arc_8.png differ diff --git a/vendor/concread/static/cow_arc_9.png b/vendor/concread/static/cow_arc_9.png new file mode 100644 index 0000000..1a64a07 Binary files /dev/null and b/vendor/concread/static/cow_arc_9.png differ diff --git a/vendor/uuid/.cargo-checksum.json b/vendor/uuid/.cargo-checksum.json new file mode 100644 index 0000000..02c1349 --- /dev/null +++ b/vendor/uuid/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"CODEOWNERS":"2196aad5760b74fc2ccf3450f49a748f0f482fe5119832b3d13e996d674cdb29","CODE_OF_CONDUCT.md":"62180f4ffe379a4599af2912182150a41b7be9e03eed1ac499238da72e2701e9","CONTRIBUTING.md":"51618830985862dea1198da34dc8c67dc6b9daac07d9d22c3d4d7da67b8060f7","COPYRIGHT":"807242105c35b459ada8a1e76a94e017103d230e8e8fefd0bbcee625fb0d816d","Cargo.toml":"028b304fafcc70813ff5a52c0f32fa5184d50a1910daf33b54f69d3e4779b058","LICENSE-APACHE":"7cfd738c53d61c79f07e348f622bf7707c9084237054d37fbe07788a75f5881c","LICENSE-MIT":"dd23b9037fa9f086b90fb5e908f896a3ed2b4a3eca961792ee0caf77444ea587","README.md":"651a0d4ff4db342fa7be7a9e738becaaae29f3d652f41053989b1beb76df7c3a","README.tpl":"6c3c04de0e4e0619218947a64d4de5d0ade44765f0f733c3c68a430465e16e91","benches/format_str.rs":"2c571eaedbfa5070a6e45a79914cfe2ea186a2dc85626cd4c6017dcdd3a84cb9","benches/invalid_parse_str.rs":"551479b152118ea22d657f0d2ef7f681f2331b3c8efb1707ea46adaf1bebd1be","benches/mod.rs":"f26df117229cd7afd7f22f33135aeafc0155154124509fe384e9e509c757d2e0","benches/serde_support.rs":"43cd9e16a250290dfb4857216b33fe16942c158cdaf3519c75fad6efa6cdafe7","benches/slog_support/mod.rs":"633fc467b0d1f65a2da554a39f80ec3a2e807eeed1680dee2bb072026ab2b7df","benches/slog_support/parse_str.rs":"6d14b295d3d09346d3599feb6a04430ddeda7601edc6ba38c778ba53bb294592","benches/valid_parse_str.rs":"d618d789c3e97cdfe47b8b8556bb4f8f578d92fff308ac0713d327d09afd6c56","src/adapter/compact.rs":"228a3606a777b9a28f5692337aed377408a66bc16f1a6acef9a15d32888b5e8b","src/adapter/mod.rs":"c643cee204128de8f5c4c58eb55bd78f571c01bd859cffd967b6026cb0d15ed3","src/builder/error.rs":"fc958714033c976980a133fe41c8625cb17ed05fcb4df4d3ed156019c81cee49","src/builder/mod.rs":"efb1de85c11bd3e9c445eaa8e2a00e3a8895de42e01a18978f5f4bbb671ed924","src/error.rs":"3718c1c61e6b76ddfa1d275fc395c90c5d4fe136ab43aeff2f278be1b239e891","src/lib.rs":"c797f1ddb8efcef25d8a2ec97aac51b722d9b759dbab99eba844197ce80a0fb8","src/parser/error.rs":"a512a659669753ad2e51137ce32dd8ce8173e80212c8f369f1b059aa3dd3ab88","src/parser/mod.rs":"05ce48d001511bec1d565c5a0f3271377d16fc66eda612aeb057a2152edccfcd","src/prelude.rs":"c7ff4039be52af0a4c30700c800c0fa6a67903c5aab039986af74496555f20a5","src/serde_support.rs":"5d8fdf452a7ab64f53ef83542430e4425e5a13092c7f9d9be88f57de84b4ac3c","src/slog_support.rs":"95ad3976f0af9fb1a2e096715336cf2df41c05f4dc7076197412fa91c07ba7e1","src/test_util.rs":"6a62bc3a54bacdd8ff3f078e58cb9498b5c6e2275936a1aaa1ffd81ff9ac616f","src/v1.rs":"e8f8f0e92888399360e046e698b2896642c33631f22ae480c4957675f841c7fe","src/v3.rs":"672db94219fa031c9472698c08c60310b84d860e143398628c3cd72a34e95bc7","src/v4.rs":"fc25658ac4e6701241ef24f1f9d2d47f463837c4269511c0bf805eb87d2c6430","src/v5.rs":"902332bcfa849dfe9474a45983119513162282d36cd7314fa9bd3e67f9fe0ddf","src/winapi_support.rs":"d94c62e1ad11c4e7b6306d4dceaacec40eece8d3c4fb14fda4679d023ff033fb"},"package":"bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"} \ No newline at end of file diff --git a/vendor/uuid/CODEOWNERS b/vendor/uuid/CODEOWNERS new file mode 100644 index 0000000..4846ccc --- /dev/null +++ b/vendor/uuid/CODEOWNERS @@ -0,0 +1,16 @@ +# CI +.travis.yml @kinggoesgaming @KodrAus @Dylan-DPC @radix +appveyor.yml @kinggoesgaming @KodrAus @Dylan-DPC @radix + +# Cargo.toml +Cargo.toml @kinggoesgaming @KodrAus @Dylan-DPC @radix + +# Rust +*.rs @kinggoesgaming @KodrAus @Dylan-DPC @radix + +# CODEOWNERS +CODEOWNERS @kinggoesgaming @KodrAus @Dylan-DPC @radix + +#>> Critical +# bors.toml file +bors.toml @kinggoesgaming @KodrAus @Dylan-DPC @radix diff --git a/vendor/uuid/CODE_OF_CONDUCT.md b/vendor/uuid/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..8ffa6a4 --- /dev/null +++ b/vendor/uuid/CODE_OF_CONDUCT.md @@ -0,0 +1,77 @@ +# Contributor Covenant Code of Conduct + +The latest version of the CODE OF CONDUCT can be found [here]. + +[here]: https://github.com/uuid-rs/conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at report@uuid-rs.groups.io. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org \ No newline at end of file diff --git a/vendor/uuid/CONTRIBUTING.md b/vendor/uuid/CONTRIBUTING.md new file mode 100644 index 0000000..baa7535 --- /dev/null +++ b/vendor/uuid/CONTRIBUTING.md @@ -0,0 +1,149 @@ +Contributing to Uuid +--- +[Contributing to Uuid]: #contributing-to-uuid + +Thank you for your interest in contributing to the Uuid Project! + +* [Feature Requests](#feature-requests) +* [Bug Reports](#bug-reports) +* [Pull Requests](#pull-requests) +* [Writing Documentation](#writing-documentation) +* [Issue Triage](#issue-triage) +* [Out-of-tree Contributions](#out-of-tree-contributions) +* [Helpful Links](#helpful-links) + +For any questions, please make a post on [users.rust-lang.org][u-r-l-o], post +on [uuid-rs mailing list] or join our [gitter] channel. + +> All contributors need to follow our [Code of Conduct]. + +[Code of Conduct]: CODE_OF_CONDUCT.md + +# Feature Requests +[Feature Requests]: #feature-requests + +The `uuid` crate is still in flux. All features desired may not be present. As +such you are welcome to request for new features. Keep in mind that `uuid` is +a general purpose library. We want to provide features that most users would +find useful. As such not every feature may be accepted. + +If you have the chance, please [search existing issues], as there is a chance +that someone has already requested your feature. + +File your feature request with a descriptive title, as this helps others find +your request. + +You can request your feature by following [this link][Feature Request Link] and +filling it in. + +> We welcome pull requests for your own feature requests, provided they have +been discussed. + +[Feature Request Link]: https://github.com/uuid-rs/uuid/issues/new?template=Feature_request.md + +# Bug Reports +[Bug Reports]: #bug-reports + +While no one likes bugs, they are an unfortunate reality in software. Remember +we can't fix bugs we don't know about, so don't be shy about reporting. + +If you have the chance, please [search existing issues], as there is a chance +that someone has already reported your error. This isn't strictly needed, as +sometimes you might not what exactly you are looking for. + +File your issue with a descriptive title, as this helps others find your issue. + +Reporting a bug is as easy as following [this link][Bug Report Link] and +filling it in. + +Sometimes a backtrace may be needed. In that case, set `RUST_BACKTRACE` +environment variable to `1`. For example: + +```bash +$ RUST_BACKTRACE=1 cargo build +``` + +> We welcome pull requests for your own bug reports, provided they have been +discussed. + +[Bug Report Link]: https://github.com/uuid-rs/uuid/issues/new?template=Bug_report.md + +# Pull Requests +[Pull Requests]: #pull-requests + +Pull requests(PRs) are the primary mechanism we use to change Uuid. GitHub itself +has some [great documentation] on using the Pull Request feature. We use the +"fork and pull" model described [here][fnp], where contributors push changes to +their personal fork and create pull requests to bring those changes into the +source repository. + +Unless the changes are fairly minor (like documentation changes or tiny +patches), we require PRs to relevant issues. + +Please open PRs against branch: +* `master` when making non-breaking changes +* `breaking` when your changes alter the public API in a breaking manner + +If the pull request is still a work in progress, prepend`[WIP] ` in your +title. `WIP bot` will make sure that the PR doesn't accidentally get merged. + +> Uuid Project has a minimum rust version policy. Currently `uuid` should +compile with atleast `1.22.0`, and is enforced on our CI builds. + +When you feel that the PR is ready, please ping one of the maintainers so +they can review your changes. + +[great documentation]: https://help.github.com/articles/about-pull-requests/ +[fnp]: https://help.github.com/articles/about-collaborative-development-models/ + +# Writing Documentation +[Writing Documentation]: #writing-documentation + +Documentation is an important part of Uuid. Lackluster or incorrect +documentation can cause headaches for the users of `uuid`. Therefore, +improvements to documentation are always welcome. + +We follow the documentation style guidelines as given by [RFC 1574]. + +[RFC 1574]: https://github.com/rust-lang/rfcs/blob/master/text/1574-more-api-documentation-conventions.md#appendix-a-full-conventions-text + +# Issue Triage +[Issue Triage]: #issue-triage + +Sometimes, an issue might stay open even after the relevant bug has been fixed. +Other times, the bug report may become invalid. Or we may just forget about the +bug. + +You can help to go through old bug reports and check if they are still valid. +You can follow [this link][lrus] to look for issues like this. + +[lrus]: https://github.com/uuid-rs/uuid/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-asc + +# Out-of-tree Contributions +[Out-of-tree Contributions]: #out-of-tree-contributions + +You can contribute to Uuid in other ways: + +* Answer questions on [users.rust-lang.org][u-r-l-o], [uuid-rs mailing list] and/or +[gitter] channel. +* Find the [crates depending on `uuid`][dependent] and sending PRs to them, +helping them keep their version of `uuid` up-to-date. + +[dependent]: https://crates.io/crates/uuid/reverse_dependencies + +# Helpful Links +[Helpful Links]: #helpful-links + +For people new to Uuid, and just starting to contribute, or even for more +seasoned developers, some useful places to look for information are: + +* The Wikipedia entry on [Universally Unique Identifier][wiki-uuid]. +* [RFC 4122] which gives the specification of Uuids. + +[wiki-uuid]: https://en.wikipedia.org/wiki/Universally_unique_identifier +[RFC 4122]: https://www.ietf.org/rfc/rfc4122.txt + +[u-r-l-o]: https://users.rust-lang.org +[uuid-rs mailing list]: https://uuid-rs.groups.io +[gitter]: https://gitter.im/uuid-rs/Lobby +[search existing issues]: https://github.com/uuid-rs/uuid/search?q=&type=Issues&utf8=%E2%9C%93 diff --git a/vendor/uuid/COPYRIGHT b/vendor/uuid/COPYRIGHT new file mode 100644 index 0000000..fbcde1c --- /dev/null +++ b/vendor/uuid/COPYRIGHT @@ -0,0 +1,8 @@ +The Uuid Project is copyright 2013-2014, The Rust Project Developers and +copyright 2018, The Uuid Developers. + +Licensed under the Apache License, Version 2.0 or the MIT License , at your option. All files in the project +carrying such notice may not be copied, modified, or distributed except +according to those terms. diff --git a/vendor/uuid/Cargo.toml b/vendor/uuid/Cargo.toml new file mode 100644 index 0000000..1803f73 --- /dev/null +++ b/vendor/uuid/Cargo.toml @@ -0,0 +1,91 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +edition = "2018" +name = "uuid" +version = "0.8.2" +authors = ["Ashley Mannix", "Christopher Armstrong", "Dylan DPC", "Hunar Roop Kahlon"] +exclude = [".github/**", ".travis.yml", "appveyor.yml", "bors.toml"] +description = "A library to generate and parse UUIDs." +homepage = "https://github.com/uuid-rs/uuid" +documentation = "https://docs.rs/uuid" +readme = "README.md" +keywords = ["guid", "unique", "uuid"] +categories = ["data-structures", "no-std", "parser-implementations", "wasm"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/uuid-rs/uuid" +[package.metadata.docs.rs] +default-target = "x86_64-pc-windows-msvc" +features = ["guid", "serde", "slog", "v1", "v3", "v4", "v5"] + +[package.metadata.playground] +features = ["serde", "v1", "v3", "v4", "v5"] +[dependencies.getrandom] +version = "0.2.0" +optional = true + +[dependencies.md5] +version = "0.7" +optional = true + +[dependencies.serde] +version = "1.0.56" +optional = true +default-features = false + +[dependencies.sha1] +version = "0.6" +optional = true + +[dependencies.slog] +version = "2" +optional = true +[dev-dependencies.bincode] +version = "1.0" + +[dev-dependencies.serde_derive] +version = "1.0.79" + +[dev-dependencies.serde_json] +version = "1.0" + +[dev-dependencies.serde_test] +version = "1.0.56" + +[features] +default = ["std"] +guid = ["winapi"] +std = [] +stdweb = ["getrandom", "getrandom/js"] +v1 = [] +v3 = ["md5"] +v4 = ["getrandom"] +v5 = ["sha1"] +wasm-bindgen = ["getrandom", "getrandom/js"] +[target."cfg(windows)".dependencies.winapi] +version = "0.3" +optional = true +[badges.appveyor] +repository = "uuid-rs/uuid" + +[badges.is-it-maintained-issue-resolution] +repository = "uuid-rs/uuid" + +[badges.is-it-maintained-open-issues] +repository = "uuid-rs/uuid" + +[badges.maintenance] +status = "actively-developed" + +[badges.travis-ci] +repository = "uuid-rs/uuid" diff --git a/vendor/uuid/LICENSE-APACHE b/vendor/uuid/LICENSE-APACHE new file mode 100644 index 0000000..f47c941 --- /dev/null +++ b/vendor/uuid/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/uuid/LICENSE-MIT b/vendor/uuid/LICENSE-MIT new file mode 100644 index 0000000..21d90fa --- /dev/null +++ b/vendor/uuid/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright (c) 2014 The Rust Project Developers +Copyright (c) 2018 Ashley Mannix, Christopher Armstrong, Dylan DPC, Hunar Roop Kahlon + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/vendor/uuid/README.md b/vendor/uuid/README.md new file mode 100644 index 0000000..1592ffd --- /dev/null +++ b/vendor/uuid/README.md @@ -0,0 +1,141 @@ +uuid +--------- + +[![Latest Version](https://img.shields.io/crates/v/uuid.svg)](https://crates.io/crates/uuid) +[![Join the chat at https://gitter.im/uuid-rs/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/uuid-rs/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.34.0+-yellow.svg) +[![Build Status](https://ci.appveyor.com/api/projects/status/github/uuid-rs/uuid?branch=master&svg=true)](https://ci.appveyor.com/project/uuid-rs/uuid/branch/master) +[![Build Status](https://travis-ci.org/uuid-rs/uuid.svg?branch=master)](https://travis-ci.org/uuid-rs/uuid) +[![Average time to resolve an issue](https://isitmaintained.com/badge/resolution/uuid-rs/uuid.svg)](https://isitmaintained.com/project/uuid-rs/uuid "Average time to resolve an issue") +[![Percentage of issues still open](https://isitmaintained.com/badge/open/uuid-rs/uuid.svg)](https://isitmaintained.com/project/uuid-rs/uuid "Percentage of issues still open") +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid?ref=badge_shield) + +--- + +Generate and parse UUIDs. + +Provides support for Universally Unique Identifiers (UUIDs). A UUID is a +unique 128-bit number, stored as 16 octets. UUIDs are used to assign +unique identifiers to entities without requiring a central allocating +authority. + +They are particularly useful in distributed systems, though they can be used in +disparate areas, such as databases and network protocols. Typically a UUID +is displayed in a readable string form as a sequence of hexadecimal digits, +separated into groups by hyphens. + +The uniqueness property is not strictly guaranteed, however for all +practical purposes, it can be assumed that an unintentional collision would +be extremely unlikely. + +## Dependencies + +By default, this crate depends on nothing but `std` and cannot generate +[`Uuid`]s. You need to enable the following Cargo features to enable +various pieces of functionality: + +* `v1` - adds the `Uuid::new_v1` function and the ability to create a V1 + using an implementation of `uuid::v1::ClockSequence` (usually +`uuid::v1::Context`) and a timestamp from `time::timespec`. +* `v3` - adds the `Uuid::new_v3` function and the ability to create a V3 + UUID based on the MD5 hash of some data. +* `v4` - adds the `Uuid::new_v4` function and the ability to randomly + generate a `Uuid`. +* `v5` - adds the `Uuid::new_v5` function and the ability to create a V5 + UUID based on the SHA1 hash of some data. +* `serde` - adds the ability to serialize and deserialize a `Uuid` using the + `serde` crate. + +You need to enable one of the following Cargo features together with +`v3`, `v4` or `v5` feature if you're targeting `wasm32-unknown-unknown` target: + +* `stdweb` - enables support for `OsRng` on `wasm32-unknown-unknown` via + `stdweb` combined with `cargo-web` +* `wasm-bindgen` - `wasm-bindgen` enables support for `OsRng` on + `wasm32-unknown-unknown` via [`wasm-bindgen`] + +By default, `uuid` can be depended on with: + +```toml +[dependencies] +uuid = "0.8" +``` + +To activate various features, use syntax like: + +```toml +[dependencies] +uuid = { version = "0.8", features = ["serde", "v4"] } +``` + +You can disable default features with: + +```toml +[dependencies] +uuid = { version = "0.8", default-features = false } +``` + +## Examples + +To parse a UUID given in the simple format and print it as a urn: + +```rust +use uuid::Uuid; + +fn main() -> Result<(), uuid::Error> { + let my_uuid = + Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8")?; + println!("{}", my_uuid.to_urn()); + Ok(()) +} +``` + +To create a new random (V4) UUID and print it out in hexadecimal form: + +```rust +// Note that this requires the `v4` feature enabled in the uuid crate. + +use uuid::Uuid; + +fn main() { + let my_uuid = Uuid::new_v4(); + println!("{}", my_uuid); + Ok(()) +} +``` + +## Strings + +Examples of string representations: + +* simple: `936DA01F9ABD4d9d80C702AF85C822A8` +* hyphenated: `550e8400-e29b-41d4-a716-446655440000` +* urn: `urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4` + +## References + +* [Wikipedia: Universally Unique Identifier]( http://en.wikipedia.org/wiki/Universally_unique_identifier) +* [RFC4122: A Universally Unique IDentifier (UUID) URN Namespace]( http://tools.ietf.org/html/rfc4122) + +[`wasm-bindgen`]: https://github.com/rustwasm/wasm-bindgen + +[`Uuid`]: https://docs.rs/uuid/0.8.2/uuid/struct.Uuid.html + +--- +# License + +Licensed under either of + +* Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT) + +at your option. + + +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fuuid-rs%2Fuuid?ref=badge_large) + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. \ No newline at end of file diff --git a/vendor/uuid/README.tpl b/vendor/uuid/README.tpl new file mode 100644 index 0000000..85faa79 --- /dev/null +++ b/vendor/uuid/README.tpl @@ -0,0 +1,29 @@ +{{crate}} +--------- + +[![Latest Version](https://img.shields.io/crates/v/uuid.svg)](https://crates.io/crates/uuid) +[![Join the chat at https://gitter.im/uuid-rs/Lobby](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/uuid-rs/Lobby?utm_source=badge&utm_medium=badge&utm_content=badge) +![Minimum rustc version](https://img.shields.io/badge/rustc-1.34.0+-yellow.svg) +{{badges}} + +--- + +{{readme}} + +[`Uuid`]: https://docs.rs/uuid/{{version}}/uuid/struct.Uuid.html + +--- +# License + +Licensed under either of + +* Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0) +* MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT) + +at your option. + +## Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/vendor/uuid/benches/format_str.rs b/vendor/uuid/benches/format_str.rs new file mode 100644 index 0000000..0ae576d --- /dev/null +++ b/vendor/uuid/benches/format_str.rs @@ -0,0 +1,66 @@ +#![feature(test)] +extern crate test; + +use std::io::Write; +use test::Bencher; +use uuid::Uuid; + +#[bench] +fn bench_hyphen(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 36]; + write!(&mut buffer as &mut [_], "{:x}", uuid.to_hyphenated()).unwrap(); + test::black_box(buffer); + }); +} + +#[bench] +fn bench_simple(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 32]; + write!(&mut buffer as &mut [_], "{:x}", uuid.to_simple()).unwrap(); + test::black_box(buffer); + }) +} + +#[bench] +fn bench_urn(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 36 + 9]; + write!(&mut buffer as &mut [_], "{:x}", uuid.to_urn()).unwrap(); + test::black_box(buffer); + }) +} + +#[bench] +fn bench_encode_hyphen(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 36]; + uuid.to_hyphenated().encode_lower(&mut buffer); + test::black_box(buffer); + }); +} + +#[bench] +fn bench_encode_simple(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 32]; + uuid.to_simple().encode_lower(&mut buffer); + test::black_box(buffer); + }) +} + +#[bench] +fn bench_encode_urn(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + b.iter(|| { + let mut buffer = [0_u8; 36 + 9]; + uuid.to_urn().encode_lower(&mut buffer); + test::black_box(buffer); + }) +} diff --git a/vendor/uuid/benches/invalid_parse_str.rs b/vendor/uuid/benches/invalid_parse_str.rs new file mode 100644 index 0000000..447fa80 --- /dev/null +++ b/vendor/uuid/benches/invalid_parse_str.rs @@ -0,0 +1,58 @@ +#![feature(test)] +extern crate test; + +use test::Bencher; +use uuid::Uuid; + +#[bench] +fn bench_parse_invalid_strings(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str(""); + let _ = Uuid::parse_str("!"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4"); + let _ = Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4"); + let _ = Uuid::parse_str("01020304-1112-2122-3132-41424344"); + let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88"); + let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8"); + let _ = Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8"); + + // Test error reporting + let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c"); + let _ = Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd"); + let _ = Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4"); + }); +} + +#[bench] +fn bench_parse_invalid_len(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4"); + }) +} + +#[bench] +fn bench_parse_invalid_character(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4"); + }) +} + +#[bench] +fn bench_parse_invalid_group_len(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("01020304-1112-2122-3132-41424344"); + }); +} + +#[bench] +fn bench_parse_invalid_groups(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4"); + }); +} diff --git a/vendor/uuid/benches/mod.rs b/vendor/uuid/benches/mod.rs new file mode 100644 index 0000000..61045c6 --- /dev/null +++ b/vendor/uuid/benches/mod.rs @@ -0,0 +1,4 @@ +#![feature(test)] + +#[cfg(feature = "slog")] +pub mod slog_support; diff --git a/vendor/uuid/benches/serde_support.rs b/vendor/uuid/benches/serde_support.rs new file mode 100644 index 0000000..9ec38a6 --- /dev/null +++ b/vendor/uuid/benches/serde_support.rs @@ -0,0 +1,48 @@ +#![cfg(feature = "serde")] +#![feature(test)] + +use bincode; +use serde_json; +extern crate test; + +use test::Bencher; +use uuid::Uuid; + +#[bench] +fn bench_json_encode(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + let mut buffer = [0_u8; 38]; + b.iter(|| { + serde_json::to_writer(&mut buffer as &mut [u8], &uuid).unwrap(); + test::black_box(buffer); + }); + b.bytes = buffer.len() as u64; +} + +#[bench] +fn bench_json_decode(b: &mut Bencher) { + let s = "\"F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4\""; + b.iter(|| serde_json::from_str::(s).unwrap()); + b.bytes = s.len() as u64; +} + +#[bench] +fn bench_bincode_encode(b: &mut Bencher) { + let uuid = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").unwrap(); + let mut buffer = [0_u8; 24]; + b.iter(|| { + bincode::serialize_into(&mut buffer as &mut [u8], &uuid).unwrap(); + test::black_box(buffer); + }); + b.bytes = buffer.len() as u64; +} + +#[bench] +fn bench_bincode_decode(b: &mut Bencher) { + let bytes = [ + 16, 0, 0, 0, 0, 0, 0, 0, 249, 22, 140, 94, 206, 178, 79, 170, 182, 191, + 50, 155, 243, 159, 161, 228, + ]; + b.iter(|| bincode::deserialize::(&bytes).unwrap()); + b.bytes = bytes.len() as u64; +} diff --git a/vendor/uuid/benches/slog_support/mod.rs b/vendor/uuid/benches/slog_support/mod.rs new file mode 100644 index 0000000..12b38c8 --- /dev/null +++ b/vendor/uuid/benches/slog_support/mod.rs @@ -0,0 +1 @@ +pub mod parse_str; diff --git a/vendor/uuid/benches/slog_support/parse_str.rs b/vendor/uuid/benches/slog_support/parse_str.rs new file mode 100644 index 0000000..1c837f5 --- /dev/null +++ b/vendor/uuid/benches/slog_support/parse_str.rs @@ -0,0 +1,15 @@ +extern crate test; + +#[bench] +#[cfg(feature = "slog")] +pub fn bench_log_discard_kv(b: &mut test::Bencher) { + let u1 = + uuid::Uuid::parse_str("F9168C5E-CEB2-4FAB-B6BF-329BF39FA1E4").unwrap(); + let root = + slog::Logger::root(::slog::Drain::fuse(::slog::Discard), slog::o!()); + + b.iter(|| { + #[cfg(feature = "slog")] + slog::crit!(root, "test"; "u1" => u1); + }); +} diff --git a/vendor/uuid/benches/valid_parse_str.rs b/vendor/uuid/benches/valid_parse_str.rs new file mode 100644 index 0000000..7e36676 --- /dev/null +++ b/vendor/uuid/benches/valid_parse_str.rs @@ -0,0 +1,39 @@ +#![feature(test)] + +extern crate test; + +use test::Bencher; +use uuid::Uuid; + +#[bench] +fn bench_parse_valid_strings(b: &mut Bencher) { + b.iter(|| { + // Valid + let _ = Uuid::parse_str("00000000000000000000000000000000"); + let _ = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8"); + let _ = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8"); + let _ = Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4"); + let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8"); + let _ = Uuid::parse_str("01020304-1112-2122-3132-414243444546"); + let _ = + Uuid::parse_str("urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8"); + + // Nil + let _ = Uuid::parse_str("00000000000000000000000000000000"); + let _ = Uuid::parse_str("00000000-0000-0000-0000-000000000000"); + }); +} + +#[bench] +fn bench_valid_hyphenated(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8"); + }); +} + +#[bench] +fn bench_valid_short(b: &mut Bencher) { + b.iter(|| { + let _ = Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8"); + }); +} diff --git a/vendor/uuid/src/adapter/compact.rs b/vendor/uuid/src/adapter/compact.rs new file mode 100644 index 0000000..1b53682 --- /dev/null +++ b/vendor/uuid/src/adapter/compact.rs @@ -0,0 +1,81 @@ +//! Module for use with `#[serde(with = "...")]` to serialize a [`Uuid`] +//! as a `[u8; 16]`. +//! +//! [`Uuid`]: ../../struct.Uuid.html + +/// Serializer for a [`Uuid`] into a `[u8; 16]` +/// +/// [`Uuid`]: ../../struct.Uuid.html +pub fn serialize(u: &crate::Uuid, serializer: S) -> Result +where + S: serde::Serializer, +{ + serde::Serialize::serialize(u.as_bytes(), serializer) +} + +/// Deserializer from a `[u8; 16]` into a [`Uuid`] +/// +/// [`Uuid`]: ../../struct.Uuid.html +pub fn deserialize<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let bytes: [u8; 16] = serde::Deserialize::deserialize(deserializer)?; + + Ok(crate::Uuid::from_bytes(bytes)) +} + +#[cfg(test)] +mod tests { + + use serde_test; + + #[test] + fn test_serialize_compact() { + #[derive( + serde_derive::Serialize, Debug, serde_derive::Deserialize, PartialEq, + )] + struct UuidContainer { + #[serde(with = "super")] + u: crate::Uuid, + } + use serde_test::Configure; + + let uuid_bytes = b"F9168C5E-CEB2-4F"; + let container = UuidContainer { + u: crate::Uuid::from_slice(uuid_bytes).unwrap(), + }; + + // more complex because of the struct wrapping the actual UUID + // serialization + serde_test::assert_tokens( + &container.compact(), + &[ + serde_test::Token::Struct { + name: "UuidContainer", + len: 1, + }, + serde_test::Token::Str("u"), + serde_test::Token::Tuple { len: 16 }, + serde_test::Token::U8(uuid_bytes[0]), + serde_test::Token::U8(uuid_bytes[1]), + serde_test::Token::U8(uuid_bytes[2]), + serde_test::Token::U8(uuid_bytes[3]), + serde_test::Token::U8(uuid_bytes[4]), + serde_test::Token::U8(uuid_bytes[5]), + serde_test::Token::U8(uuid_bytes[6]), + serde_test::Token::U8(uuid_bytes[7]), + serde_test::Token::U8(uuid_bytes[8]), + serde_test::Token::U8(uuid_bytes[9]), + serde_test::Token::U8(uuid_bytes[10]), + serde_test::Token::U8(uuid_bytes[11]), + serde_test::Token::U8(uuid_bytes[12]), + serde_test::Token::U8(uuid_bytes[13]), + serde_test::Token::U8(uuid_bytes[14]), + serde_test::Token::U8(uuid_bytes[15]), + serde_test::Token::TupleEnd, + serde_test::Token::StructEnd, + ], + ) + } +} diff --git a/vendor/uuid/src/adapter/mod.rs b/vendor/uuid/src/adapter/mod.rs new file mode 100644 index 0000000..412920e --- /dev/null +++ b/vendor/uuid/src/adapter/mod.rs @@ -0,0 +1,1027 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Adapters for various formats for UUIDs + +use crate::prelude::*; +use crate::std::{fmt, str}; + +#[cfg(feature = "serde")] +pub mod compact; + +/// An adaptor for formatting an [`Uuid`] as a hyphenated string. +/// +/// Takes an owned instance of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Hyphenated(Uuid); + +/// An adaptor for formatting an [`Uuid`] as a hyphenated string. +/// +/// Takes a reference of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct HyphenatedRef<'a>(&'a Uuid); + +/// An adaptor for formatting an [`Uuid`] as a simple string. +/// +/// Takes an owned instance of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Simple(Uuid); + +/// An adaptor for formatting an [`Uuid`] as a simple string. +/// +/// Takes a reference of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct SimpleRef<'a>(&'a Uuid); + +/// An adaptor for formatting an [`Uuid`] as a URN string. +/// +/// Takes an owned instance of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Urn(Uuid); + +/// An adaptor for formatting an [`Uuid`] as a URN string. +/// +/// Takes a reference of the [`Uuid`]. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct UrnRef<'a>(&'a Uuid); + +impl Uuid { + /// Get a [`Hyphenated`] formatter. + /// + /// [`Hyphenated`]: adapter/struct.Hyphenated.html + #[inline] + pub const fn to_hyphenated(self) -> Hyphenated { + Hyphenated::from_uuid(self) + } + + /// Get a borrowed [`HyphenatedRef`] formatter. + /// + /// [`HyphenatedRef`]: adapter/struct.HyphenatedRef.html + #[inline] + pub const fn to_hyphenated_ref(&self) -> HyphenatedRef<'_> { + HyphenatedRef::from_uuid_ref(self) + } + + /// Get a [`Simple`] formatter. + /// + /// [`Simple`]: adapter/struct.Simple.html + #[inline] + pub const fn to_simple(self) -> Simple { + Simple::from_uuid(self) + } + + /// Get a borrowed [`SimpleRef`] formatter. + /// + /// [`SimpleRef`]: adapter/struct.SimpleRef.html + #[inline] + pub const fn to_simple_ref(&self) -> SimpleRef<'_> { + SimpleRef::from_uuid_ref(self) + } + + /// Get a [`Urn`] formatter. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`Urn`]: adapter/struct.Urn.html + #[inline] + pub const fn to_urn(self) -> Urn { + Urn::from_uuid(self) + } + + /// Get a borrowed [`UrnRef`] formatter. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`UrnRef`]: adapter/struct.UrnRef.html + #[inline] + pub const fn to_urn_ref(&self) -> UrnRef<'_> { + UrnRef::from_uuid_ref(self) + } +} + +const UPPER: [u8; 16] = [ + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', + b'C', b'D', b'E', b'F', +]; +const LOWER: [u8; 16] = [ + b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'a', b'b', + b'c', b'd', b'e', b'f', +]; +/// The segments of a UUID's [u8; 16] corresponding to each group. +const BYTE_POSITIONS: [usize; 6] = [0, 4, 6, 8, 10, 16]; +/// The locations that hyphens are written into the buffer, after each +/// group. +const HYPHEN_POSITIONS: [usize; 4] = [8, 13, 18, 23]; + +/// Encodes the `uuid` possibly with hyphens, and possibly in upper +/// case, to full_buffer[start..] and returns the str sliced from +/// full_buffer[..start + encoded_length]. +/// +/// The `start` parameter allows writing a prefix (such as +/// "urn:uuid:") to the buffer that's included in the final encoded +/// UUID. +#[allow(clippy::needless_range_loop)] +fn encode<'a>( + full_buffer: &'a mut [u8], + start: usize, + uuid: &Uuid, + hyphens: bool, + upper: bool, +) -> &'a mut str { + let len = if hyphens { 36 } else { 32 }; + + { + let buffer = &mut full_buffer[start..start + len]; + let bytes = uuid.as_bytes(); + + let hex = if upper { &UPPER } else { &LOWER }; + + for group in 0..5 { + // If we're writing hyphens, we need to shift the output + // location along by how many of them have been written + // before this point. That's exactly the (0-indexed) group + // number. + let hyphens_before = if hyphens { group } else { 0 }; + for idx in BYTE_POSITIONS[group]..BYTE_POSITIONS[group + 1] { + let b = bytes[idx]; + let out_idx = hyphens_before + 2 * idx; + + buffer[out_idx] = hex[(b >> 4) as usize]; + buffer[out_idx + 1] = hex[(b & 0b1111) as usize]; + } + + if group != 4 && hyphens { + buffer[HYPHEN_POSITIONS[group]] = b'-'; + } + } + } + + str::from_utf8_mut(&mut full_buffer[..start + len]) + .expect("found non-ASCII output characters while encoding a UUID") +} + +impl Hyphenated { + /// The length of a hyphenated [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 36; + + /// Creates a [`Hyphenated`] from a [`Uuid`]. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`Hyphenated`]: struct.Hyphenated.html + pub const fn from_uuid(uuid: Uuid) -> Self { + Hyphenated(uuid) + } + + /// Writes the [`Uuid`] as a lower-case hyphenated string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_hyphenated() + /// .encode_lower(&mut Uuid::encode_buffer()), + /// "936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 40]; + /// uuid.to_hyphenated().encode_lower(&mut buf); + /// assert_eq!( + /// &buf as &[_], + /// b"936da01f-9abd-4d9d-80c7-02af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, &self.0, true, false) + } + + /// Writes the [`Uuid`] as an upper-case hyphenated string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_hyphenated() + /// .encode_upper(&mut Uuid::encode_buffer()), + /// "936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 40]; + /// uuid.to_hyphenated().encode_upper(&mut buf); + /// assert_eq!( + /// &buf as &[_], + /// b"936DA01F-9ABD-4D9D-80C7-02AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, &self.0, true, true) + } +} + +impl<'a> HyphenatedRef<'a> { + /// The length of a hyphenated [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 36; + + /// Creates a [`HyphenatedRef`] from a [`Uuid`] reference. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`HyphenatedRef`]: struct.HyphenatedRef.html + pub const fn from_uuid_ref(uuid: &'a Uuid) -> Self { + HyphenatedRef(uuid) + } + + /// Writes the [`Uuid`] as a lower-case hyphenated string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_hyphenated() + /// .encode_lower(&mut Uuid::encode_buffer()), + /// "936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 40]; + /// uuid.to_hyphenated().encode_lower(&mut buf); + /// assert_eq!( + /// uuid.to_hyphenated().encode_lower(&mut buf), + /// "936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936da01f-9abd-4d9d-80c7-02af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, self.0, true, false) + } + + /// Writes the [`Uuid`] as an upper-case hyphenated string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_hyphenated() + /// .encode_upper(&mut Uuid::encode_buffer()), + /// "936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 40]; + /// assert_eq!( + /// uuid.to_hyphenated().encode_upper(&mut buf), + /// "936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936DA01F-9ABD-4D9D-80C7-02AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, self.0, true, true) + } +} + +impl Simple { + /// The length of a simple [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 32; + + /// Creates a [`Simple`] from a [`Uuid`]. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`Simple`]: struct.Simple.html + pub const fn from_uuid(uuid: Uuid) -> Self { + Simple(uuid) + } + + /// Writes the [`Uuid`] as a lower-case simple string to `buffer`, + /// and returns the subslice of the buffer that contains the encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_simple().encode_lower(&mut Uuid::encode_buffer()), + /// "936da01f9abd4d9d80c702af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 36]; + /// assert_eq!( + /// uuid.to_simple().encode_lower(&mut buf), + /// "936da01f9abd4d9d80c702af85c822a8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936da01f9abd4d9d80c702af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, &self.0, false, false) + } + + /// Writes the [`Uuid`] as an upper-case simple string to `buffer`, + /// and returns the subslice of the buffer that contains the encoded UUID. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_simple().encode_upper(&mut Uuid::encode_buffer()), + /// "936DA01F9ABD4D9D80C702AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 36]; + /// assert_eq!( + /// uuid.to_simple().encode_upper(&mut buf), + /// "936DA01F9ABD4D9D80C702AF85C822A8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936DA01F9ABD4D9D80C702AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, &self.0, false, true) + } +} + +impl<'a> SimpleRef<'a> { + /// The length of a simple [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 32; + + /// Creates a [`SimpleRef`] from a [`Uuid`] reference. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`SimpleRef`]: struct.SimpleRef.html + pub const fn from_uuid_ref(uuid: &'a Uuid) -> Self { + SimpleRef(uuid) + } + + /// Writes the [`Uuid`] as a lower-case simple string to `buffer`, + /// and returns the subslice of the buffer that contains the encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_simple().encode_lower(&mut Uuid::encode_buffer()), + /// "936da01f9abd4d9d80c702af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 36]; + /// assert_eq!( + /// uuid.to_simple().encode_lower(&mut buf), + /// "936da01f9abd4d9d80c702af85c822a8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936da01f9abd4d9d80c702af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, self.0, false, false) + } + + /// Writes the [`Uuid`] as an upper-case simple string to `buffer`, + /// and returns the subslice of the buffer that contains the encoded UUID. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_simple().encode_upper(&mut Uuid::encode_buffer()), + /// "936DA01F9ABD4D9D80C702AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 36]; + /// assert_eq!( + /// uuid.to_simple().encode_upper(&mut buf), + /// "936DA01F9ABD4D9D80C702AF85C822A8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"936DA01F9ABD4D9D80C702AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + encode(buffer, 0, self.0, false, true) + } +} + +impl Urn { + /// The length of a URN [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 45; + + /// Creates a [`Urn`] from a [`Uuid`]. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`Urn`]: struct.Urn.html + pub const fn from_uuid(uuid: Uuid) -> Self { + Urn(uuid) + } + + /// Writes the [`Uuid`] as a lower-case URN string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_urn().encode_lower(&mut Uuid::encode_buffer()), + /// "urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 49]; + /// uuid.to_urn().encode_lower(&mut buf); + /// assert_eq!( + /// uuid.to_urn().encode_lower(&mut buf), + /// "urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + buffer[..9].copy_from_slice(b"urn:uuid:"); + encode(buffer, 9, &self.0, true, false) + } + + /// Writes the [`Uuid`] as an upper-case URN string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_urn().encode_upper(&mut Uuid::encode_buffer()), + /// "urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 49]; + /// assert_eq!( + /// uuid.to_urn().encode_upper(&mut buf), + /// "urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + buffer[..9].copy_from_slice(b"urn:uuid:"); + encode(buffer, 9, &self.0, true, true) + } +} + +impl<'a> UrnRef<'a> { + /// The length of a URN [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + pub const LENGTH: usize = 45; + + /// Creates a [`UrnRef`] from a [`Uuid`] reference. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// [`UrnRef`]: struct.UrnRef.html + pub const fn from_uuid_ref(uuid: &'a Uuid) -> Self { + UrnRef(&uuid) + } + + /// Writes the [`Uuid`] as a lower-case URN string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_urn().encode_lower(&mut Uuid::encode_buffer()), + /// "urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 49]; + /// uuid.to_urn().encode_lower(&mut buf); + /// assert_eq!( + /// uuid.to_urn().encode_lower(&mut buf), + /// "urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"urn:uuid:936da01f-9abd-4d9d-80c7-02af85c822a8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_lower<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + buffer[..9].copy_from_slice(b"urn:uuid:"); + encode(buffer, 9, self.0, true, false) + } + + /// Writes the [`Uuid`] as an upper-case URN string to + /// `buffer`, and returns the subslice of the buffer that contains the + /// encoded UUID. + /// + /// This is slightly more efficient than using the formatting + /// infrastructure as it avoids virtual calls, and may avoid + /// double buffering. + /// + /// [`Uuid`]: ../struct.Uuid.html + /// + /// # Panics + /// + /// Panics if the buffer is not large enough: it must have length at least + /// [`LENGTH`]. [`Uuid::encode_buffer`] can be used to get a + /// sufficiently-large temporary buffer. + /// + /// [`LENGTH`]: #associatedconstant.LENGTH + /// [`Uuid::encode_buffer`]: ../struct.Uuid.html#method.encode_buffer + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936da01f9abd4d9d80c702af85c822a8")?; + /// + /// // the encoded portion is returned + /// assert_eq!( + /// uuid.to_urn().encode_upper(&mut Uuid::encode_buffer()), + /// "urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// + /// // the buffer is mutated directly, and trailing contents remains + /// let mut buf = [b'!'; 49]; + /// assert_eq!( + /// uuid.to_urn().encode_upper(&mut buf), + /// "urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8" + /// ); + /// assert_eq!( + /// &buf as &[_], + /// b"urn:uuid:936DA01F-9ABD-4D9D-80C7-02AF85C822A8!!!!" as &[_] + /// ); + /// + /// Ok(()) + /// } + /// ``` + /// */ + pub fn encode_upper<'buf>(&self, buffer: &'buf mut [u8]) -> &'buf mut str { + buffer[..9].copy_from_slice(b"urn:uuid:"); + encode(buffer, 9, self.0, true, true) + } +} + +macro_rules! impl_adapter_traits { + ($($T:ident<$($a:lifetime),*>),+) => {$( + impl<$($a),*> fmt::Display for $T<$($a),*> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } + } + + impl<$($a),*> fmt::LowerHex for $T<$($a),*> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Self doesn't work https://github.com/rust-lang/rust/issues/52808 + f.write_str(self.encode_lower(&mut [0; $T::LENGTH])) + } + } + + impl<$($a),*> fmt::UpperHex for $T<$($a),*> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Self doesn't work https://github.com/rust-lang/rust/issues/52808 + f.write_str(self.encode_upper(&mut [0; $T::LENGTH])) + } + } + + impl_adapter_from!($T<$($a),*>); + )+} +} + +macro_rules! impl_adapter_from { + ($T:ident<>) => { + impl From for $T { + #[inline] + fn from(f: Uuid) -> Self { + $T::from_uuid(f) + } + } + }; + ($T:ident<$a:lifetime>) => { + impl<$a> From<&$a Uuid> for $T<$a> { + #[inline] + fn from(f: &$a Uuid) -> Self { + $T::from_uuid_ref(f) + } + } + }; +} + +impl_adapter_traits! { + Hyphenated<>, + HyphenatedRef<'a>, + Simple<>, + SimpleRef<'a>, + Urn<>, + UrnRef<'a> +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn hyphenated_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_hyphenated().encode_lower(&mut buf).len(); + assert_eq!(len, super::Hyphenated::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + fn hyphenated_ref_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_hyphenated().encode_lower(&mut buf).len(); + assert_eq!(len, super::HyphenatedRef::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + fn simple_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_simple().encode_lower(&mut buf).len(); + assert_eq!(len, super::Simple::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + fn simple_ref_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_simple().encode_lower(&mut buf).len(); + assert_eq!(len, super::SimpleRef::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + fn urn_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_urn().encode_lower(&mut buf).len(); + assert_eq!(len, super::Urn::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + fn urn_ref_trailing() { + let mut buf = [b'x'; 100]; + let len = Uuid::nil().to_urn().encode_lower(&mut buf).len(); + assert_eq!(len, super::UrnRef::LENGTH); + assert!(buf[len..].iter().all(|x| *x == b'x')); + } + + #[test] + #[should_panic] + fn hyphenated_too_small() { + Uuid::nil().to_hyphenated().encode_lower(&mut [0; 35]); + } + + #[test] + #[should_panic] + fn hyphenated_ref_too_small() { + Uuid::nil().to_hyphenated_ref().encode_lower(&mut [0; 35]); + } + + #[test] + #[should_panic] + fn simple_too_small() { + Uuid::nil().to_simple().encode_lower(&mut [0; 31]); + } + #[test] + #[should_panic] + fn simple_ref_too_small() { + Uuid::nil().to_simple_ref().encode_lower(&mut [0; 31]); + } + #[test] + #[should_panic] + fn urn_too_small() { + Uuid::nil().to_urn().encode_lower(&mut [0; 44]); + } + #[test] + #[should_panic] + fn urn_ref_too_small() { + Uuid::nil().to_urn_ref().encode_lower(&mut [0; 44]); + } +} diff --git a/vendor/uuid/src/builder/error.rs b/vendor/uuid/src/builder/error.rs new file mode 100644 index 0000000..ec7c2ac --- /dev/null +++ b/vendor/uuid/src/builder/error.rs @@ -0,0 +1,52 @@ +use crate::std::fmt; + +/// The error that can occur when creating a [`Uuid`]. +/// +/// [`Uuid`]: struct.Uuid.html +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub(crate) struct Error { + expected: usize, + found: usize, +} + +impl Error { + /// The expected number of bytes. + #[inline] + const fn expected(&self) -> usize { + self.expected + } + + /// The number of bytes found. + #[inline] + const fn found(&self) -> usize { + self.found + } + + /// Create a new [`UuidError`]. + /// + /// [`UuidError`]: struct.UuidError.html + #[inline] + pub(crate) const fn new(expected: usize, found: usize) -> Self { + Error { expected, found } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "invalid bytes length: expected {}, found {}", + self.expected(), + self.found() + ) + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + + use crate::std::error; + + impl error::Error for Error {} +} diff --git a/vendor/uuid/src/builder/mod.rs b/vendor/uuid/src/builder/mod.rs new file mode 100644 index 0000000..4360964 --- /dev/null +++ b/vendor/uuid/src/builder/mod.rs @@ -0,0 +1,473 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! A Builder type for [`Uuid`]s. +//! +//! [`Uuid`]: ../struct.Uuid.html + +mod error; +pub(crate) use self::error::Error; + +use crate::prelude::*; + +impl Uuid { + /// The 'nil UUID'. + /// + /// The nil UUID is special form of UUID that is specified to have all + /// 128 bits set to zero, as defined in [IETF RFC 4122 Section 4.1.7][RFC]. + /// + /// [RFC]: https://tools.ietf.org/html/rfc4122.html#section-4.1.7 + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Uuid; + /// + /// let uuid = Uuid::nil(); + /// + /// assert_eq!( + /// uuid.to_hyphenated().to_string(), + /// "00000000-0000-0000-0000-000000000000" + /// ); + /// ``` + pub const fn nil() -> Self { + Uuid::from_bytes([0; 16]) + } + + /// Creates a UUID from four field values in big-endian order. + /// + /// # Errors + /// + /// This function will return an error if `d4`'s length is not 8 bytes. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Uuid; + /// + /// let d4 = [12, 3, 9, 56, 54, 43, 8, 9]; + /// + /// let uuid = Uuid::from_fields(42, 12, 5, &d4); + /// let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + /// + /// let expected_uuid = + /// Ok(String::from("0000002a-000c-0005-0c03-0938362b0809")); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + pub fn from_fields( + d1: u32, + d2: u16, + d3: u16, + d4: &[u8], + ) -> Result { + const D4_LEN: usize = 8; + + let len = d4.len(); + + if len != D4_LEN { + Err(Error::new(D4_LEN, len))?; + } + + Ok(Uuid::from_bytes([ + (d1 >> 24) as u8, + (d1 >> 16) as u8, + (d1 >> 8) as u8, + d1 as u8, + (d2 >> 8) as u8, + d2 as u8, + (d3 >> 8) as u8, + d3 as u8, + d4[0], + d4[1], + d4[2], + d4[3], + d4[4], + d4[5], + d4[6], + d4[7], + ])) + } + + /// Creates a UUID from four field values in little-endian order. + /// + /// The bytes in the `d1`, `d2` and `d3` fields will + /// be converted into big-endian order. + /// + /// # Examples + /// + /// ``` + /// use uuid::Uuid; + /// + /// let d1 = 0xAB3F1097u32; + /// let d2 = 0x501Eu16; + /// let d3 = 0xB736u16; + /// let d4 = [12, 3, 9, 56, 54, 43, 8, 9]; + /// + /// let uuid = Uuid::from_fields_le(d1, d2, d3, &d4); + /// let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + /// + /// let expected_uuid = + /// Ok(String::from("97103fab-1e50-36b7-0c03-0938362b0809")); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + pub fn from_fields_le( + d1: u32, + d2: u16, + d3: u16, + d4: &[u8], + ) -> Result { + const D4_LEN: usize = 8; + + let len = d4.len(); + + if len != D4_LEN { + Err(Error::new(D4_LEN, len))?; + } + + Ok(Uuid::from_bytes([ + d1 as u8, + (d1 >> 8) as u8, + (d1 >> 16) as u8, + (d1 >> 24) as u8, + (d2) as u8, + (d2 >> 8) as u8, + d3 as u8, + (d3 >> 8) as u8, + d4[0], + d4[1], + d4[2], + d4[3], + d4[4], + d4[5], + d4[6], + d4[7], + ])) + } + + /// Creates a UUID from a 128bit value in big-endian order. + pub const fn from_u128(v: u128) -> Self { + Uuid::from_bytes([ + (v >> 120) as u8, + (v >> 112) as u8, + (v >> 104) as u8, + (v >> 96) as u8, + (v >> 88) as u8, + (v >> 80) as u8, + (v >> 72) as u8, + (v >> 64) as u8, + (v >> 56) as u8, + (v >> 48) as u8, + (v >> 40) as u8, + (v >> 32) as u8, + (v >> 24) as u8, + (v >> 16) as u8, + (v >> 8) as u8, + v as u8, + ]) + } + + /// Creates a UUID from a 128bit value in little-endian order. + pub const fn from_u128_le(v: u128) -> Self { + Uuid::from_bytes([ + v as u8, + (v >> 8) as u8, + (v >> 16) as u8, + (v >> 24) as u8, + (v >> 32) as u8, + (v >> 40) as u8, + (v >> 48) as u8, + (v >> 56) as u8, + (v >> 64) as u8, + (v >> 72) as u8, + (v >> 80) as u8, + (v >> 88) as u8, + (v >> 96) as u8, + (v >> 104) as u8, + (v >> 112) as u8, + (v >> 120) as u8, + ]) + } + + /// Creates a UUID using the supplied big-endian bytes. + /// + /// # Errors + /// + /// This function will return an error if `b` has any length other than 16. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Uuid; + /// + /// let bytes = [4, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, 87]; + /// + /// let uuid = Uuid::from_slice(&bytes); + /// let uuid = uuid.map(|uuid| uuid.to_hyphenated().to_string()); + /// + /// let expected_uuid = + /// Ok(String::from("0436430c-2b02-624c-2032-570501212b57")); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + /// + /// An incorrect number of bytes: + /// + /// ``` + /// use uuid::Uuid; + /// + /// let bytes = [4, 54, 67, 12, 43, 2, 98, 76]; + /// + /// let uuid = Uuid::from_slice(&bytes); + /// + /// assert!(uuid.is_err()); + /// ``` + pub fn from_slice(b: &[u8]) -> Result { + const BYTES_LEN: usize = 16; + + let len = b.len(); + + if len != BYTES_LEN { + Err(Error::new(BYTES_LEN, len))?; + } + + let mut bytes: Bytes = [0; 16]; + bytes.copy_from_slice(b); + Ok(Uuid::from_bytes(bytes)) + } + + /// Creates a UUID using the supplied big-endian bytes. + pub const fn from_bytes(bytes: Bytes) -> Uuid { + Uuid(bytes) + } +} + +/// A builder struct for creating a UUID. +/// +/// # Examples +/// +/// Creating a v4 UUID from externally generated bytes: +/// +/// ``` +/// use uuid::{Builder, Variant, Version}; +/// +/// # let rng = || [ +/// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, 195, 90, +/// # 145, 63, 62, +/// # ]; +/// let random_bytes = rng(); +/// let uuid = Builder::from_bytes(random_bytes) +/// .set_variant(Variant::RFC4122) +/// .set_version(Version::Random) +/// .build(); +/// ``` +// TODO: remove in 1.0.0 +#[allow(dead_code)] +#[deprecated] +pub type Builder = crate::Builder; + +impl crate::Builder { + /// Creates a `Builder` using the supplied big-endian bytes. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let bytes: uuid::Bytes = [ + /// 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, 195, 90, 145, 63, 62, + /// ]; + /// + /// let mut builder = uuid::Builder::from_bytes(bytes); + /// let uuid = builder.build().to_hyphenated().to_string(); + /// + /// let expected_uuid = String::from("46ebd0ee-0e6d-43c9-b90d-ccc35a913f3e"); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + /// + /// An incorrect number of bytes: + /// + /// ```compile_fail + /// let bytes: uuid::Bytes = [4, 54, 67, 12, 43, 2, 98, 76]; // doesn't compile + /// + /// let uuid = uuid::Builder::from_bytes(bytes); + /// ``` + pub const fn from_bytes(b: Bytes) -> Self { + Builder(b) + } + + /// Creates a `Builder` using the supplied big-endian bytes. + /// + /// # Errors + /// + /// This function will return an error if `b` has any length other than 16. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let bytes = [4, 54, 67, 12, 43, 2, 98, 76, 32, 50, 87, 5, 1, 33, 43, 87]; + /// + /// let builder = uuid::Builder::from_slice(&bytes); + /// let uuid = + /// builder.map(|mut builder| builder.build().to_hyphenated().to_string()); + /// + /// let expected_uuid = + /// Ok(String::from("0436430c-2b02-624c-2032-570501212b57")); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + /// + /// An incorrect number of bytes: + /// + /// ``` + /// let bytes = [4, 54, 67, 12, 43, 2, 98, 76]; + /// + /// let builder = uuid::Builder::from_slice(&bytes); + /// + /// assert!(builder.is_err()); + /// ``` + pub fn from_slice(b: &[u8]) -> Result { + const BYTES_LEN: usize = 16; + + let len = b.len(); + + if len != BYTES_LEN { + Err(Error::new(BYTES_LEN, len))?; + } + + let mut bytes: crate::Bytes = [0; 16]; + bytes.copy_from_slice(b); + Ok(Self::from_bytes(bytes)) + } + + /// Creates a `Builder` from four big-endian field values. + /// + /// # Errors + /// + /// This function will return an error if `d4`'s length is not 8 bytes. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let d4 = [12, 3, 9, 56, 54, 43, 8, 9]; + /// + /// let builder = uuid::Builder::from_fields(42, 12, 5, &d4); + /// let uuid = + /// builder.map(|mut builder| builder.build().to_hyphenated().to_string()); + /// + /// let expected_uuid = + /// Ok(String::from("0000002a-000c-0005-0c03-0938362b0809")); + /// + /// assert_eq!(expected_uuid, uuid); + /// ``` + /// + /// An invalid length: + /// + /// ``` + /// let d4 = [12]; + /// + /// let builder = uuid::Builder::from_fields(42, 12, 5, &d4); + /// + /// assert!(builder.is_err()); + /// ``` + pub fn from_fields( + d1: u32, + d2: u16, + d3: u16, + d4: &[u8], + ) -> Result { + Uuid::from_fields(d1, d2, d3, d4).map(|uuid| { + let bytes = *uuid.as_bytes(); + + crate::Builder::from_bytes(bytes) + }) + } + + /// Creates a `Builder` from a big-endian 128bit value. + pub fn from_u128(v: u128) -> Self { + crate::Builder::from_bytes(*Uuid::from_u128(v).as_bytes()) + } + + /// Creates a `Builder` with an initial [`Uuid::nil`]. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Builder; + /// + /// let mut builder = Builder::nil(); + /// + /// assert_eq!( + /// builder.build().to_hyphenated().to_string(), + /// "00000000-0000-0000-0000-000000000000" + /// ); + /// ``` + pub const fn nil() -> Self { + Builder([0; 16]) + } + + /// Specifies the variant of the UUID. + pub fn set_variant(&mut self, v: crate::Variant) -> &mut Self { + let byte = self.0[8]; + + self.0[8] = match v { + crate::Variant::NCS => byte & 0x7f, + crate::Variant::RFC4122 => (byte & 0x3f) | 0x80, + crate::Variant::Microsoft => (byte & 0x1f) | 0xc0, + crate::Variant::Future => (byte & 0x1f) | 0xe0, + }; + + self + } + + /// Specifies the version number of the UUID. + pub fn set_version(&mut self, v: crate::Version) -> &mut Self { + self.0[6] = (self.0[6] & 0x0f) | ((v as u8) << 4); + + self + } + + /// Hands over the internal constructed [`Uuid`]. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Builder; + /// + /// let uuid = Builder::nil().build(); + /// + /// assert_eq!( + /// uuid.to_hyphenated().to_string(), + /// "00000000-0000-0000-0000-000000000000" + /// ); + /// ``` + /// + /// [`Uuid`]: struct.Uuid.html + pub fn build(&mut self) -> Uuid { + Uuid::from_bytes(self.0) + } +} diff --git a/vendor/uuid/src/error.rs b/vendor/uuid/src/error.rs new file mode 100644 index 0000000..59f3f5e --- /dev/null +++ b/vendor/uuid/src/error.rs @@ -0,0 +1,79 @@ +use crate::std::fmt; +use crate::{builder, parser}; + +/// A general error that can occur when working with UUIDs. +// TODO: improve the doc +// BODY: This detail should be fine for initial merge +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct Error(Inner); + +// TODO: write tests for Error +// BODY: not immediately blocking, but should be covered for 1.0 +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +enum Inner { + /// An error occurred while handling [`Uuid`] bytes. + /// + /// See [`BytesError`] + /// + /// [`BytesError`]: struct.BytesError.html + /// [`Uuid`]: struct.Uuid.html + Build(builder::Error), + + /// An error occurred while parsing a [`Uuid`] string. + /// + /// See [`parser::ParseError`] + /// + /// [`parser::ParseError`]: parser/enum.ParseError.html + /// [`Uuid`]: struct.Uuid.html + Parser(parser::Error), +} + +impl From for Error { + fn from(err: builder::Error) -> Self { + Error(Inner::Build(err)) + } +} + +impl From for Error { + fn from(err: parser::Error) -> Self { + Error(Inner::Parser(err)) + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Inner::Build(ref err) => fmt::Display::fmt(&err, f), + Inner::Parser(ref err) => fmt::Display::fmt(&err, f), + } + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + use crate::std::error; + + impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self.0 { + Inner::Build(ref err) => Some(err), + Inner::Parser(ref err) => Some(err), + } + } + } +} + +#[cfg(test)] +mod test_util { + use super::*; + + impl Error { + pub(crate) fn expect_parser(self) -> parser::Error { + match self.0 { + Inner::Parser(err) => err, + _ => panic!("expected a `parser::Error` variant"), + } + } + } +} diff --git a/vendor/uuid/src/lib.rs b/vendor/uuid/src/lib.rs new file mode 100644 index 0000000..51d3877 --- /dev/null +++ b/vendor/uuid/src/lib.rs @@ -0,0 +1,1070 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Generate and parse UUIDs. +//! +//! Provides support for Universally Unique Identifiers (UUIDs). A UUID is a +//! unique 128-bit number, stored as 16 octets. UUIDs are used to assign +//! unique identifiers to entities without requiring a central allocating +//! authority. +//! +//! They are particularly useful in distributed systems, though can be used in +//! disparate areas, such as databases and network protocols. Typically a UUID +//! is displayed in a readable string form as a sequence of hexadecimal digits, +//! separated into groups by hyphens. +//! +//! The uniqueness property is not strictly guaranteed, however for all +//! practical purposes, it can be assumed that an unintentional collision would +//! be extremely unlikely. +//! +//! # Dependencies +//! +//! By default, this crate depends on nothing but `std` and cannot generate +//! UUIDs. You need to enable the following Cargo features to enable +//! various pieces of functionality: +//! +//! * `v1` - adds the [`Uuid::new_v1`] function and the ability to create a V1 +//! using an implementation of [`v1::ClockSequence`] (usually +//! [`v1::Context`]) and a timestamp from `time::timespec`. +//! * `v3` - adds the [`Uuid::new_v3`] function and the ability to create a V3 +//! UUID based on the MD5 hash of some data. +//! * `v4` - adds the [`Uuid::new_v4`] function and the ability to randomly +//! generate a UUID. +//! * `v5` - adds the [`Uuid::new_v5`] function and the ability to create a V5 +//! UUID based on the SHA1 hash of some data. +//! * `serde` - adds the ability to serialize and deserialize a UUID using the +//! `serde` crate. +//! +//! For WebAssembly, enable one of the following features depending +//! on your JavaScript interop toolchain of choice: +//! +//! * `stdweb` - for [`stdweb`] combined with [`cargo-web`] +//! * `wasm-bindgen` - for [`wasm-bindgen`] +//! +//! By default, `uuid` can be depended on with: +//! +//! ```toml +//! [dependencies] +//! uuid = "0.8" +//! ``` +//! +//! To activate various features, use syntax like: +//! +//! ```toml +//! [dependencies] +//! uuid = { version = "0.8", features = ["serde", "v4"] } +//! ``` +//! +//! You can disable default features with: +//! +//! ```toml +//! [dependencies] +//! uuid = { version = "0.8", default-features = false } +//! ``` +//! +//! # Examples +//! +//! To parse a UUID given in the simple format and print it as a urn: +//! +//! ```rust +//! use uuid::Uuid; +//! +//! fn main() -> Result<(), uuid::Error> { +//! let my_uuid = +//! Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8")?; +//! println!("{}", my_uuid.to_urn()); +//! Ok(()) +//! } +//! ``` +//! +//! To create a new random (V4) UUID and print it out in hexadecimal form: +//! +//! ```ignore,rust +//! // Note that this requires the `v4` feature enabled in the uuid crate. +//! +//! use uuid::Uuid; +//! +//! fn main() -> Result<(), Box> { +//! #[cfg(feature = "v4")] { +//! let my_uuid = Uuid::new_v4()?; +//! println!("{}", my_uuid); +//! } +//! Ok(()) +//! } +//! ``` +//! +//! # Strings +//! +//! Examples of string representations: +//! +//! * simple: `936DA01F9ABD4d9d80C702AF85C822A8` +//! * hyphenated: `550e8400-e29b-41d4-a716-446655440000` +//! * urn: `urn:uuid:F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4` +//! +//! # References +//! +//! * [Wikipedia: Universally Unique Identifier](http://en.wikipedia.org/wiki/Universally_unique_identifier) +//! * [RFC4122: A Universally Unique IDentifier (UUID) URN Namespace](http://tools.ietf.org/html/rfc4122) +//! +//! [`wasm-bindgen`]: https://crates.io/crates/wasm-bindgen +//! [`cargo-web`]: https://crates.io/crates/cargo-web +//! [`stdweb`]: https://crates.io/crates/stdweb +//! [`Uuid`]: struct.Uuid.html +//! [`Uuid::new_v1`]: struct.Uuid.html#method.new_v1 +//! [`Uuid::new_v3`]: struct.Uuid.html#method.new_v3 +//! [`Uuid::new_v4`]: struct.Uuid.html#method.new_v4 +//! [`Uuid::new_v5`]: struct.Uuid.html#method.new_v5 +//! [`v1::ClockSequence`]: v1/trait.ClockSequence.html +//! [`v1::Context`]: v1/struct.Context.html + +#![no_std] +#![deny(missing_debug_implementations, missing_docs)] +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://docs.rs/uuid/0.8.2" +)] + +#[cfg(any(feature = "std", test))] +#[macro_use] +extern crate std; + +#[cfg(all(not(feature = "std"), not(test)))] +#[macro_use] +extern crate core as std; + +mod builder; +mod error; +mod parser; +mod prelude; + +pub mod adapter; +#[cfg(feature = "v1")] +pub mod v1; + +#[cfg(feature = "serde")] +mod serde_support; +#[cfg(feature = "slog")] +mod slog_support; +#[cfg(test)] +mod test_util; +#[cfg(all( + feature = "v3", + any( + not(target_arch = "wasm32"), + target_os = "wasi", + all( + target_arch = "wasm32", + any(feature = "stdweb", feature = "wasm-bindgen") + ) + ) +))] +mod v3; +#[cfg(all( + feature = "v4", + any( + not(target_arch = "wasm32"), + target_os = "wasi", + all( + target_arch = "wasm32", + any(feature = "stdweb", feature = "wasm-bindgen") + ) + ) +))] +mod v4; +#[cfg(all( + feature = "v5", + any( + not(target_arch = "wasm32"), + target_os = "wasi", + all( + target_arch = "wasm32", + any(feature = "stdweb", feature = "wasm-bindgen") + ) + ) +))] +mod v5; +#[cfg(all(windows, feature = "winapi"))] +mod winapi_support; + +use crate::std::{fmt, str}; + +pub use crate::error::Error; + +/// A builder struct for creating a UUID. +/// +/// # Examples +/// +/// Creating a v4 UUID from externally generated bytes: +/// +/// ``` +/// use uuid::{Builder, Variant, Version}; +/// +/// # let rng = || [ +/// # 70, 235, 208, 238, 14, 109, 67, 201, 185, 13, 204, 195, 90, +/// # 145, 63, 62, +/// # ]; +/// let random_bytes = rng(); +/// let uuid = Builder::from_bytes(random_bytes) +/// .set_variant(Variant::RFC4122) +/// .set_version(Version::Random) +/// .build(); +/// ``` +#[allow(missing_copy_implementations)] +#[derive(Debug)] +pub struct Builder(Bytes); + +/// A 128-bit (16 byte) buffer containing the ID. +pub type Bytes = [u8; 16]; + +/// The version of the UUID, denoting the generating algorithm. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Version { + /// Special case for `nil` UUID. + Nil = 0, + /// Version 1: MAC address. + Mac, + /// Version 2: DCE Security. + Dce, + /// Version 3: MD5 hash. + Md5, + /// Version 4: Random. + Random, + /// Version 5: SHA-1 hash. + Sha1, +} + +/// The reserved variants of UUIDs. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Variant { + /// Reserved by the NCS for backward compatibility. + NCS = 0, + /// As described in the RFC4122 Specification (default). + RFC4122, + /// Reserved by Microsoft for backward compatibility. + Microsoft, + /// Reserved for future expansion. + Future, +} + +/// A Universally Unique Identifier (UUID). +#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Uuid(Bytes); + +impl Uuid { + /// UUID namespace for Domain Name System (DNS). + pub const NAMESPACE_DNS: Self = Uuid([ + 0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, + 0x4f, 0xd4, 0x30, 0xc8, + ]); + + /// UUID namespace for ISO Object Identifiers (OIDs). + pub const NAMESPACE_OID: Self = Uuid([ + 0x6b, 0xa7, 0xb8, 0x12, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, + 0x4f, 0xd4, 0x30, 0xc8, + ]); + + /// UUID namespace for Uniform Resource Locators (URLs). + pub const NAMESPACE_URL: Self = Uuid([ + 0x6b, 0xa7, 0xb8, 0x11, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, + 0x4f, 0xd4, 0x30, 0xc8, + ]); + + /// UUID namespace for X.500 Distinguished Names (DNs). + pub const NAMESPACE_X500: Self = Uuid([ + 0x6b, 0xa7, 0xb8, 0x14, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, + 0x4f, 0xd4, 0x30, 0xc8, + ]); + + /// Returns the variant of the UUID structure. + /// + /// This determines the interpretation of the structure of the UUID. + /// Currently only the RFC4122 variant is generated by this module. + /// + /// * [Variant Reference](http://tools.ietf.org/html/rfc4122#section-4.1.1) + pub fn get_variant(&self) -> Option { + match self.as_bytes()[8] { + x if x & 0x80 == 0x00 => Some(Variant::NCS), + x if x & 0xc0 == 0x80 => Some(Variant::RFC4122), + x if x & 0xe0 == 0xc0 => Some(Variant::Microsoft), + x if x & 0xe0 == 0xe0 => Some(Variant::Future), + _ => None, + } + } + + /// Returns the version number of the UUID. + /// + /// This represents the algorithm used to generate the contents. + /// + /// Currently only the Random (V4) algorithm is supported by this + /// module. There are security and privacy implications for using + /// older versions - see [Wikipedia: Universally Unique Identifier]( + /// http://en.wikipedia.org/wiki/Universally_unique_identifier) for + /// details. + /// + /// * [Version Reference](http://tools.ietf.org/html/rfc4122#section-4.1.3) + pub const fn get_version_num(&self) -> usize { + (self.as_bytes()[6] >> 4) as usize + } + + /// Returns the version of the UUID. + /// + /// This represents the algorithm used to generate the contents + pub fn get_version(&self) -> Option { + let v = self.as_bytes()[6] >> 4; + match v { + 0 if self.is_nil() => Some(Version::Nil), + 1 => Some(Version::Mac), + 2 => Some(Version::Dce), + 3 => Some(Version::Md5), + 4 => Some(Version::Random), + 5 => Some(Version::Sha1), + _ => None, + } + } + + /// Returns the four field values of the UUID in big-endian order. + /// + /// These values can be passed to the `from_fields()` method to get the + /// original `Uuid` back. + /// + /// * The first field value represents the first group of (eight) hex + /// digits, taken as a big-endian `u32` value. For V1 UUIDs, this field + /// represents the low 32 bits of the timestamp. + /// * The second field value represents the second group of (four) hex + /// digits, taken as a big-endian `u16` value. For V1 UUIDs, this field + /// represents the middle 16 bits of the timestamp. + /// * The third field value represents the third group of (four) hex digits, + /// taken as a big-endian `u16` value. The 4 most significant bits give + /// the UUID version, and for V1 UUIDs, the last 12 bits represent the + /// high 12 bits of the timestamp. + /// * The last field value represents the last two groups of four and twelve + /// hex digits, taken in order. The first 1-3 bits of this indicate the + /// UUID variant, and for V1 UUIDs, the next 13-15 bits indicate the clock + /// sequence and the last 48 bits indicate the node ID. + /// + /// # Examples + /// + /// ``` + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::nil(); + /// assert_eq!(uuid.as_fields(), (0, 0, 0, &[0u8; 8])); + /// + /// let uuid = Uuid::parse_str("936DA01F-9ABD-4D9D-80C7-02AF85C822A8")?; + /// assert_eq!( + /// uuid.as_fields(), + /// ( + /// 0x936DA01F, + /// 0x9ABD, + /// 0x4D9D, + /// b"\x80\xC7\x02\xAF\x85\xC8\x22\xA8" + /// ) + /// ); + /// + /// Ok(()) + /// } + /// ``` + pub fn as_fields(&self) -> (u32, u16, u16, &[u8; 8]) { + let d1 = u32::from(self.as_bytes()[0]) << 24 + | u32::from(self.as_bytes()[1]) << 16 + | u32::from(self.as_bytes()[2]) << 8 + | u32::from(self.as_bytes()[3]); + + let d2 = + u16::from(self.as_bytes()[4]) << 8 | u16::from(self.as_bytes()[5]); + + let d3 = + u16::from(self.as_bytes()[6]) << 8 | u16::from(self.as_bytes()[7]); + + let d4: &[u8; 8] = + unsafe { &*(self.as_bytes()[8..16].as_ptr() as *const [u8; 8]) }; + (d1, d2, d3, d4) + } + + /// Returns the four field values of the UUID in little-endian order. + /// + /// The bytes in the returned integer fields will + /// be converted from big-endian order. + /// + /// # Examples + /// + /// ``` + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01F-9ABD-4D9D-80C7-02AF85C822A8")?; + /// assert_eq!( + /// uuid.to_fields_le(), + /// ( + /// 0x1FA06D93, + /// 0xBD9A, + /// 0x9D4D, + /// b"\x80\xC7\x02\xAF\x85\xC8\x22\xA8" + /// ) + /// ); + /// Ok(()) + /// } + /// ``` + pub fn to_fields_le(&self) -> (u32, u16, u16, &[u8; 8]) { + let d1 = u32::from(self.as_bytes()[0]) + | u32::from(self.as_bytes()[1]) << 8 + | u32::from(self.as_bytes()[2]) << 16 + | u32::from(self.as_bytes()[3]) << 24; + + let d2 = + u16::from(self.as_bytes()[4]) | u16::from(self.as_bytes()[5]) << 8; + + let d3 = + u16::from(self.as_bytes()[6]) | u16::from(self.as_bytes()[7]) << 8; + + let d4: &[u8; 8] = + unsafe { &*(self.as_bytes()[8..16].as_ptr() as *const [u8; 8]) }; + (d1, d2, d3, d4) + } + + /// Returns a 128bit value containing the UUID data. + /// + /// The bytes in the UUID will be packed into a `u128`, like the + /// [`Uuid::as_bytes`] method. + /// + /// # Examples + /// + /// ``` + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01F-9ABD-4D9D-80C7-02AF85C822A8")?; + /// assert_eq!( + /// uuid.as_u128(), + /// 0x936DA01F9ABD4D9D80C702AF85C822A8, + /// ); + /// Ok(()) + /// } + /// ``` + pub fn as_u128(&self) -> u128 { + u128::from(self.as_bytes()[0]) << 120 + | u128::from(self.as_bytes()[1]) << 112 + | u128::from(self.as_bytes()[2]) << 104 + | u128::from(self.as_bytes()[3]) << 96 + | u128::from(self.as_bytes()[4]) << 88 + | u128::from(self.as_bytes()[5]) << 80 + | u128::from(self.as_bytes()[6]) << 72 + | u128::from(self.as_bytes()[7]) << 64 + | u128::from(self.as_bytes()[8]) << 56 + | u128::from(self.as_bytes()[9]) << 48 + | u128::from(self.as_bytes()[10]) << 40 + | u128::from(self.as_bytes()[11]) << 32 + | u128::from(self.as_bytes()[12]) << 24 + | u128::from(self.as_bytes()[13]) << 16 + | u128::from(self.as_bytes()[14]) << 8 + | u128::from(self.as_bytes()[15]) + } + + /// Returns a 128bit little-endian value containing the UUID data. + /// + /// The bytes in the UUID will be reversed and packed into a `u128`. + /// Note that this will produce a different result than + /// [`Uuid::to_fields_le`], because the entire UUID is reversed, rather + /// than reversing the individual fields in-place. + /// + /// # Examples + /// + /// ``` + /// use uuid::Uuid; + /// + /// fn main() -> Result<(), uuid::Error> { + /// let uuid = Uuid::parse_str("936DA01F-9ABD-4D9D-80C7-02AF85C822A8")?; + /// + /// assert_eq!( + /// uuid.to_u128_le(), + /// 0xA822C885AF02C7809D4DBD9A1FA06D93, + /// ); + /// Ok(()) + /// } + /// ``` + pub fn to_u128_le(&self) -> u128 { + u128::from(self.as_bytes()[0]) + | u128::from(self.as_bytes()[1]) << 8 + | u128::from(self.as_bytes()[2]) << 16 + | u128::from(self.as_bytes()[3]) << 24 + | u128::from(self.as_bytes()[4]) << 32 + | u128::from(self.as_bytes()[5]) << 40 + | u128::from(self.as_bytes()[6]) << 48 + | u128::from(self.as_bytes()[7]) << 56 + | u128::from(self.as_bytes()[8]) << 64 + | u128::from(self.as_bytes()[9]) << 72 + | u128::from(self.as_bytes()[10]) << 80 + | u128::from(self.as_bytes()[11]) << 88 + | u128::from(self.as_bytes()[12]) << 96 + | u128::from(self.as_bytes()[13]) << 104 + | u128::from(self.as_bytes()[14]) << 112 + | u128::from(self.as_bytes()[15]) << 120 + } + + /// Returns an array of 16 octets containing the UUID data. + pub const fn as_bytes(&self) -> &Bytes { + &self.0 + } + + /// Tests if the UUID is nil. + pub fn is_nil(&self) -> bool { + self.as_bytes().iter().all(|&b| b == 0) + } + + /// A buffer that can be used for `encode_...` calls, that is + /// guaranteed to be long enough for any of the adapters. + /// + /// # Examples + /// + /// ```rust + /// use uuid::Uuid; + /// + /// let uuid = Uuid::nil(); + /// + /// assert_eq!( + /// uuid.to_simple().encode_lower(&mut Uuid::encode_buffer()), + /// "00000000000000000000000000000000" + /// ); + /// + /// assert_eq!( + /// uuid.to_hyphenated() + /// .encode_lower(&mut Uuid::encode_buffer()), + /// "00000000-0000-0000-0000-000000000000" + /// ); + /// + /// assert_eq!( + /// uuid.to_urn().encode_lower(&mut Uuid::encode_buffer()), + /// "urn:uuid:00000000-0000-0000-0000-000000000000" + /// ); + /// ``` + pub const fn encode_buffer() -> [u8; adapter::Urn::LENGTH] { + [0; adapter::Urn::LENGTH] + } +} + +impl fmt::Debug for Uuid { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl fmt::Display for Uuid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl fmt::Display for Variant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Variant::NCS => write!(f, "NCS"), + Variant::RFC4122 => write!(f, "RFC4122"), + Variant::Microsoft => write!(f, "Microsoft"), + Variant::Future => write!(f, "Future"), + } + } +} + +impl fmt::LowerHex for Uuid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(&self.to_hyphenated_ref(), f) + } +} + +impl fmt::UpperHex for Uuid { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::UpperHex::fmt(&self.to_hyphenated_ref(), f) + } +} + +impl str::FromStr for Uuid { + type Err = Error; + + fn from_str(uuid_str: &str) -> Result { + Uuid::parse_str(uuid_str) + } +} + +impl Default for Uuid { + #[inline] + fn default() -> Self { + Uuid::nil() + } +} + +#[cfg(test)] +mod tests { + use crate::{ + prelude::*, + std::string::{String, ToString}, + test_util, + }; + + macro_rules! check { + ($buf:ident, $format:expr, $target:expr, $len:expr, $cond:expr) => { + $buf.clear(); + write!($buf, $format, $target).unwrap(); + assert!($buf.len() == $len); + assert!($buf.chars().all($cond), "{}", $buf); + }; + } + + #[test] + fn test_uuid_compare() { + let uuid1 = test_util::new(); + let uuid2 = test_util::new2(); + + assert_eq!(uuid1, uuid1); + assert_eq!(uuid2, uuid2); + + assert_ne!(uuid1, uuid2); + assert_ne!(uuid2, uuid1); + } + + #[test] + fn test_uuid_default() { + let default_uuid = Uuid::default(); + let nil_uuid = Uuid::nil(); + + assert_eq!(default_uuid, nil_uuid); + } + + #[test] + fn test_uuid_display() { + use super::fmt::Write; + + let uuid = test_util::new(); + let s = uuid.to_string(); + let mut buffer = String::new(); + + assert_eq!(s, uuid.to_hyphenated().to_string()); + + check!(buffer, "{}", uuid, 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); + } + + #[test] + fn test_uuid_lowerhex() { + use super::fmt::Write; + + let mut buffer = String::new(); + let uuid = test_util::new(); + + check!(buffer, "{:x}", uuid, 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); + } + + // noinspection RsAssertEqual + #[test] + fn test_uuid_operator_eq() { + let uuid1 = test_util::new(); + let uuid1_dup = uuid1.clone(); + let uuid2 = test_util::new2(); + + assert!(uuid1 == uuid1); + assert!(uuid1 == uuid1_dup); + assert!(uuid1_dup == uuid1); + + assert!(uuid1 != uuid2); + assert!(uuid2 != uuid1); + assert!(uuid1_dup != uuid2); + assert!(uuid2 != uuid1_dup); + } + + #[test] + fn test_uuid_to_string() { + use super::fmt::Write; + + let uuid = test_util::new(); + let s = uuid.to_string(); + let mut buffer = String::new(); + + assert_eq!(s.len(), 36); + + check!(buffer, "{}", s, 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); + } + + #[test] + fn test_uuid_upperhex() { + use super::fmt::Write; + + let mut buffer = String::new(); + let uuid = test_util::new(); + + check!(buffer, "{:X}", uuid, 36, |c| c.is_uppercase() + || c.is_digit(10) + || c == '-'); + } + + #[test] + fn test_nil() { + let nil = Uuid::nil(); + let not_nil = test_util::new(); + let from_bytes = Uuid::from_bytes([ + 4, 54, 67, 12, 43, 2, 2, 76, 32, 50, 87, 5, 1, 33, 43, 87, + ]); + + assert_eq!(from_bytes.get_version(), None); + + assert!(nil.is_nil()); + assert!(!not_nil.is_nil()); + + assert_eq!(nil.get_version(), Some(Version::Nil)); + assert_eq!(not_nil.get_version(), Some(Version::Random)) + } + + #[test] + fn test_predefined_namespaces() { + assert_eq!( + Uuid::NAMESPACE_DNS.to_hyphenated().to_string(), + "6ba7b810-9dad-11d1-80b4-00c04fd430c8" + ); + assert_eq!( + Uuid::NAMESPACE_URL.to_hyphenated().to_string(), + "6ba7b811-9dad-11d1-80b4-00c04fd430c8" + ); + assert_eq!( + Uuid::NAMESPACE_OID.to_hyphenated().to_string(), + "6ba7b812-9dad-11d1-80b4-00c04fd430c8" + ); + assert_eq!( + Uuid::NAMESPACE_X500.to_hyphenated().to_string(), + "6ba7b814-9dad-11d1-80b4-00c04fd430c8" + ); + } + + #[cfg(feature = "v3")] + #[test] + fn test_get_version_v3() { + let uuid = + Uuid::new_v3(&Uuid::NAMESPACE_DNS, "rust-lang.org".as_bytes()); + + assert_eq!(uuid.get_version().unwrap(), Version::Md5); + assert_eq!(uuid.get_version_num(), 3); + } + + #[test] + fn test_get_variant() { + let uuid1 = test_util::new(); + let uuid2 = + Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap(); + let uuid3 = + Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap(); + let uuid4 = + Uuid::parse_str("936DA01F9ABD4d9dC0C702AF85C822A8").unwrap(); + let uuid5 = + Uuid::parse_str("F9168C5E-CEB2-4faa-D6BF-329BF39FA1E4").unwrap(); + let uuid6 = + Uuid::parse_str("f81d4fae-7dec-11d0-7765-00a0c91e6bf6").unwrap(); + + assert_eq!(uuid1.get_variant().unwrap(), Variant::RFC4122); + assert_eq!(uuid2.get_variant().unwrap(), Variant::RFC4122); + assert_eq!(uuid3.get_variant().unwrap(), Variant::RFC4122); + assert_eq!(uuid4.get_variant().unwrap(), Variant::Microsoft); + assert_eq!(uuid5.get_variant().unwrap(), Variant::Microsoft); + assert_eq!(uuid6.get_variant().unwrap(), Variant::NCS); + } + + #[test] + fn test_to_simple_string() { + let uuid1 = test_util::new(); + let s = uuid1.to_simple().to_string(); + + assert_eq!(s.len(), 32); + assert!(s.chars().all(|c| c.is_digit(16))); + } + + #[test] + fn test_to_hyphenated_string() { + let uuid1 = test_util::new(); + let s = uuid1.to_hyphenated().to_string(); + + assert!(s.len() == 36); + assert!(s.chars().all(|c| c.is_digit(16) || c == '-')); + } + + #[test] + fn test_upper_lower_hex() { + use std::fmt::Write; + + let mut buf = String::new(); + let u = test_util::new(); + + macro_rules! check { + ($buf:ident, $format:expr, $target:expr, $len:expr, $cond:expr) => { + $buf.clear(); + write!($buf, $format, $target).unwrap(); + assert!(buf.len() == $len); + assert!($buf.chars().all($cond), "{}", $buf); + }; + } + + check!(buf, "{:X}", u, 36, |c| c.is_uppercase() + || c.is_digit(10) + || c == '-'); + check!(buf, "{:X}", u.to_hyphenated(), 36, |c| c.is_uppercase() + || c.is_digit(10) + || c == '-'); + check!(buf, "{:X}", u.to_simple(), 32, |c| c.is_uppercase() + || c.is_digit(10)); + + check!(buf, "{:x}", u.to_hyphenated(), 36, |c| c.is_lowercase() + || c.is_digit(10) + || c == '-'); + check!(buf, "{:x}", u.to_simple(), 32, |c| c.is_lowercase() + || c.is_digit(10)); + } + + #[test] + fn test_to_urn_string() { + let uuid1 = test_util::new(); + let ss = uuid1.to_urn().to_string(); + let s = &ss[9..]; + + assert!(ss.starts_with("urn:uuid:")); + assert_eq!(s.len(), 36); + assert!(s.chars().all(|c| c.is_digit(16) || c == '-')); + } + + #[test] + fn test_to_simple_string_matching() { + let uuid1 = test_util::new(); + + let hs = uuid1.to_hyphenated().to_string(); + let ss = uuid1.to_simple().to_string(); + + let hsn = hs.chars().filter(|&c| c != '-').collect::(); + + assert_eq!(hsn, ss); + } + + #[test] + fn test_string_roundtrip() { + let uuid = test_util::new(); + + let hs = uuid.to_hyphenated().to_string(); + let uuid_hs = Uuid::parse_str(&hs).unwrap(); + assert_eq!(uuid_hs, uuid); + + let ss = uuid.to_string(); + let uuid_ss = Uuid::parse_str(&ss).unwrap(); + assert_eq!(uuid_ss, uuid); + } + + #[test] + fn test_from_fields() { + let d1: u32 = 0xa1a2a3a4; + let d2: u16 = 0xb1b2; + let d3: u16 = 0xc1c2; + let d4 = [0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8]; + + let u = Uuid::from_fields(d1, d2, d3, &d4).unwrap(); + + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + let result = u.to_simple().to_string(); + assert_eq!(result, expected); + } + + #[test] + fn test_from_fields_le() { + let d1: u32 = 0xa4a3a2a1; + let d2: u16 = 0xb2b1; + let d3: u16 = 0xc2c1; + let d4 = [0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8]; + + let u = Uuid::from_fields_le(d1, d2, d3, &d4).unwrap(); + + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + let result = u.to_simple().to_string(); + assert_eq!(result, expected); + } + + #[test] + fn test_as_fields() { + let u = test_util::new(); + let (d1, d2, d3, d4) = u.as_fields(); + + assert_ne!(d1, 0); + assert_ne!(d2, 0); + assert_ne!(d3, 0); + assert_eq!(d4.len(), 8); + assert!(!d4.iter().all(|&b| b == 0)); + } + + #[test] + fn test_fields_roundtrip() { + let d1_in: u32 = 0xa1a2a3a4; + let d2_in: u16 = 0xb1b2; + let d3_in: u16 = 0xc1c2; + let d4_in = &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8]; + + let u = Uuid::from_fields(d1_in, d2_in, d3_in, d4_in).unwrap(); + let (d1_out, d2_out, d3_out, d4_out) = u.as_fields(); + + assert_eq!(d1_in, d1_out); + assert_eq!(d2_in, d2_out); + assert_eq!(d3_in, d3_out); + assert_eq!(d4_in, d4_out); + } + + #[test] + fn test_fields_le_roundtrip() { + let d1_in: u32 = 0xa4a3a2a1; + let d2_in: u16 = 0xb2b1; + let d3_in: u16 = 0xc2c1; + let d4_in = &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8]; + + let u = Uuid::from_fields_le(d1_in, d2_in, d3_in, d4_in).unwrap(); + let (d1_out, d2_out, d3_out, d4_out) = u.to_fields_le(); + + assert_eq!(d1_in, d1_out); + assert_eq!(d2_in, d2_out); + assert_eq!(d3_in, d3_out); + assert_eq!(d4_in, d4_out); + } + + #[test] + fn test_fields_le_are_actually_le() { + let d1_in: u32 = 0xa1a2a3a4; + let d2_in: u16 = 0xb1b2; + let d3_in: u16 = 0xc1c2; + let d4_in = &[0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8]; + + let u = Uuid::from_fields(d1_in, d2_in, d3_in, d4_in).unwrap(); + let (d1_out, d2_out, d3_out, d4_out) = u.to_fields_le(); + + assert_eq!(d1_in, d1_out.swap_bytes()); + assert_eq!(d2_in, d2_out.swap_bytes()); + assert_eq!(d3_in, d3_out.swap_bytes()); + assert_eq!(d4_in, d4_out); + } + + #[test] + fn test_from_u128() { + let v_in: u128 = 0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8; + + let u = Uuid::from_u128(v_in); + + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + let result = u.to_simple().to_string(); + assert_eq!(result, expected); + } + + #[test] + fn test_from_u128_le() { + let v_in: u128 = 0xd8d7d6d5d4d3d2d1c2c1b2b1a4a3a2a1; + + let u = Uuid::from_u128_le(v_in); + + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + let result = u.to_simple().to_string(); + assert_eq!(result, expected); + } + + #[test] + fn test_u128_roundtrip() { + let v_in: u128 = 0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8; + + let u = Uuid::from_u128(v_in); + let v_out = u.as_u128(); + + assert_eq!(v_in, v_out); + } + + #[test] + fn test_u128_le_roundtrip() { + let v_in: u128 = 0xd8d7d6d5d4d3d2d1c2c1b2b1a4a3a2a1; + + let u = Uuid::from_u128_le(v_in); + let v_out = u.to_u128_le(); + + assert_eq!(v_in, v_out); + } + + #[test] + fn test_u128_le_is_actually_le() { + let v_in: u128 = 0xa1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8; + + let u = Uuid::from_u128(v_in); + let v_out = u.to_u128_le(); + + assert_eq!(v_in, v_out.swap_bytes()); + } + + #[test] + fn test_from_slice() { + let b = [ + 0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + ]; + + let u = Uuid::from_slice(&b).unwrap(); + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + + assert_eq!(u.to_simple().to_string(), expected); + } + + #[test] + fn test_from_bytes() { + let b = [ + 0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + ]; + + let u = Uuid::from_bytes(b); + let expected = "a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"; + + assert_eq!(u.to_simple().to_string(), expected); + } + + #[test] + fn test_as_bytes() { + let u = test_util::new(); + let ub = u.as_bytes(); + + assert_eq!(ub.len(), 16); + assert!(!ub.iter().all(|&b| b == 0)); + } + + #[test] + fn test_bytes_roundtrip() { + let b_in: crate::Bytes = [ + 0xa1, 0xa2, 0xa3, 0xa4, 0xb1, 0xb2, 0xc1, 0xc2, 0xd1, 0xd2, 0xd3, + 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, + ]; + + let u = Uuid::from_slice(&b_in).unwrap(); + + let b_out = u.as_bytes(); + + assert_eq!(&b_in, b_out); + } + + #[test] + fn test_iterbytes_impl_for_uuid() { + let mut set = std::collections::HashSet::new(); + let id1 = test_util::new(); + let id2 = test_util::new2(); + set.insert(id1.clone()); + + assert!(set.contains(&id1)); + assert!(!set.contains(&id2)); + } +} diff --git a/vendor/uuid/src/parser/error.rs b/vendor/uuid/src/parser/error.rs new file mode 100644 index 0000000..01d76e8 --- /dev/null +++ b/vendor/uuid/src/parser/error.rs @@ -0,0 +1,149 @@ +use crate::std::fmt; + +/// An error that can occur while parsing a [`Uuid`] string. +/// +/// [`Uuid`]: ../struct.Uuid.html +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +#[allow(clippy::enum_variant_names)] +pub(crate) enum Error { + /// Invalid character in the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidCharacter { + /// The expected characters. + expected: &'static str, + /// The invalid character found. + found: char, + /// The invalid character position. + index: usize, + /// Indicates the [`Uuid`] starts with `urn:uuid:`. + /// + /// This is a special case for [`Urn`] adapter parsing. + /// + /// [`Uuid`]: ../Uuid.html + urn: UrnPrefix, + }, + /// Invalid number of segments in the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidGroupCount { + /// The expected number of segments. + // TODO: explain multiple segment count. + // BODY: Parsers can expect a range of Uuid segment count. + // This needs to be expanded on. + expected: ExpectedLength, + /// The number of segments found. + found: usize, + }, + /// Invalid length of a segment in a [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidGroupLength { + /// The expected length of the segment. + expected: ExpectedLength, + /// The length of segment found. + found: usize, + /// The segment with invalid length. + group: usize, + }, + /// Invalid length of the [`Uuid`] string. + /// + /// [`Uuid`]: ../struct.Uuid.html + InvalidLength { + /// The expected length(s). + // TODO: explain multiple lengths. + // BODY: Parsers can expect a range of Uuid lenghts. + // This needs to be expanded on. + expected: ExpectedLength, + /// The invalid length found. + found: usize, + }, +} + +/// The expected length. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub(crate) enum ExpectedLength { + /// Expected any one of the given values. + Any(&'static [usize]), + /// Expected the given value. + Exact(usize), +} + +/// Urn prefix value. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub(crate) enum UrnPrefix { + /// The `urn:uuid:` prefix should optionally provided. + Optional, +} + +impl Error { + fn _description(&self) -> &str { + match *self { + Error::InvalidCharacter { .. } => "invalid character", + Error::InvalidGroupCount { .. } => "invalid number of groups", + Error::InvalidGroupLength { .. } => "invalid group length", + Error::InvalidLength { .. } => "invalid length", + } + } +} + +impl fmt::Display for ExpectedLength { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + ExpectedLength::Any(crits) => write!(f, "one of {:?}", crits), + ExpectedLength::Exact(crit) => write!(f, "{}", crit), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: ", self._description())?; + + match *self { + Error::InvalidCharacter { + expected, + found, + index, + urn, + } => { + let urn_str = match urn { + UrnPrefix::Optional => { + " an optional prefix of `urn:uuid:` followed by" + } + }; + + write!( + f, + "expected{} {}, found {} at {}", + urn_str, expected, found, index + ) + } + Error::InvalidGroupCount { + ref expected, + found, + } => write!(f, "expected {}, found {}", expected, found), + Error::InvalidGroupLength { + ref expected, + found, + group, + } => write!( + f, + "expected {}, found {} in group {}", + expected, found, group, + ), + Error::InvalidLength { + ref expected, + found, + } => write!(f, "expected {}, found {}", expected, found), + } + } +} + +#[cfg(feature = "std")] +mod std_support { + use super::*; + use crate::std::error; + + impl error::Error for Error {} +} diff --git a/vendor/uuid/src/parser/mod.rs b/vendor/uuid/src/parser/mod.rs new file mode 100644 index 0000000..a80f696 --- /dev/null +++ b/vendor/uuid/src/parser/mod.rs @@ -0,0 +1,447 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! [`Uuid`] parsing constructs and utilities. +//! +//! [`Uuid`]: ../struct.Uuid.html + +pub(crate) mod error; +pub(crate) use self::error::Error; + +use crate::{adapter, Uuid}; + +/// Check if the length matches any of the given criteria lengths. +fn len_matches_any(len: usize, crits: &[usize]) -> bool { + for crit in crits { + if len == *crit { + return true; + } + } + + false +} + +/// Check if the length matches any criteria lengths in the given range +/// (inclusive). +#[allow(dead_code)] +fn len_matches_range(len: usize, min: usize, max: usize) -> bool { + for crit in min..=max { + if len == crit { + return true; + } + } + + false +} + +// Accumulated length of each hyphenated group in hex digits. +const ACC_GROUP_LENS: [usize; 5] = [8, 12, 16, 20, 32]; + +// Length of each hyphenated group in hex digits. +const GROUP_LENS: [usize; 5] = [8, 4, 4, 4, 12]; + +impl Uuid { + /// Parses a `Uuid` from a string of hexadecimal digits with optional + /// hyphens. + /// + /// Any of the formats generated by this module (simple, hyphenated, urn) + /// are supported by this parsing function. + pub fn parse_str(mut input: &str) -> Result { + // Ensure length is valid for any of the supported formats + let len = input.len(); + + if len == adapter::Urn::LENGTH && input.starts_with("urn:uuid:") { + input = &input[9..]; + } else if !len_matches_any( + len, + &[adapter::Hyphenated::LENGTH, adapter::Simple::LENGTH], + ) { + Err(Error::InvalidLength { + expected: error::ExpectedLength::Any(&[ + adapter::Hyphenated::LENGTH, + adapter::Simple::LENGTH, + ]), + found: len, + })?; + } + + // `digit` counts only hexadecimal digits, `i_char` counts all chars. + let mut digit = 0; + let mut group = 0; + let mut acc = 0; + let mut buffer = [0u8; 16]; + + for (i_char, chr) in input.bytes().enumerate() { + if digit as usize >= adapter::Simple::LENGTH && group != 4 { + if group == 0 { + Err(Error::InvalidLength { + expected: error::ExpectedLength::Any(&[ + adapter::Hyphenated::LENGTH, + adapter::Simple::LENGTH, + ]), + found: len, + })?; + } + + Err(Error::InvalidGroupCount { + expected: error::ExpectedLength::Any(&[1, 5]), + found: group + 1, + })?; + } + + if digit % 2 == 0 { + // First digit of the byte. + match chr { + // Calulate upper half. + b'0'..=b'9' => acc = chr - b'0', + b'a'..=b'f' => acc = chr - b'a' + 10, + b'A'..=b'F' => acc = chr - b'A' + 10, + // Found a group delimiter + b'-' => { + // TODO: remove the u8 cast + // BODY: this only needed until we switch to + // ParseError + if ACC_GROUP_LENS[group] as u8 != digit { + // Calculate how many digits this group consists of + // in the input. + let found = if group > 0 { + // TODO: remove the u8 cast + // BODY: this only needed until we switch to + // ParseError + digit - ACC_GROUP_LENS[group - 1] as u8 + } else { + digit + }; + + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact( + GROUP_LENS[group], + ), + found: found as usize, + group, + })?; + } + // Next group, decrement digit, it is incremented again + // at the bottom. + group += 1; + digit -= 1; + } + _ => { + Err(Error::InvalidCharacter { + expected: "0123456789abcdefABCDEF-", + found: input[i_char..].chars().next().unwrap(), + index: i_char, + urn: error::UrnPrefix::Optional, + })?; + } + } + } else { + // Second digit of the byte, shift the upper half. + acc *= 16; + match chr { + b'0'..=b'9' => acc += chr - b'0', + b'a'..=b'f' => acc += chr - b'a' + 10, + b'A'..=b'F' => acc += chr - b'A' + 10, + b'-' => { + // The byte isn't complete yet. + let found = if group > 0 { + // TODO: remove the u8 cast + // BODY: this only needed until we switch to + // ParseError + digit - ACC_GROUP_LENS[group - 1] as u8 + } else { + digit + }; + + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact( + GROUP_LENS[group], + ), + found: found as usize, + group, + })?; + } + _ => { + Err(Error::InvalidCharacter { + expected: "0123456789abcdefABCDEF-", + found: input[i_char..].chars().next().unwrap(), + index: i_char, + urn: error::UrnPrefix::Optional, + })?; + } + } + buffer[(digit / 2) as usize] = acc; + } + digit += 1; + } + + // Now check the last group. + // TODO: remove the u8 cast + // BODY: this only needed until we switch to + // ParseError + if ACC_GROUP_LENS[4] as u8 != digit { + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact(GROUP_LENS[4]), + found: (digit as usize - ACC_GROUP_LENS[3]), + group, + })?; + } + + Ok(Uuid::from_bytes(buffer)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{adapter, std::string::ToString, test_util}; + + #[test] + fn test_parse_uuid_v4() { + const EXPECTED_UUID_LENGTHS: error::ExpectedLength = + error::ExpectedLength::Any(&[ + adapter::Hyphenated::LENGTH, + adapter::Simple::LENGTH, + ]); + + const EXPECTED_GROUP_COUNTS: error::ExpectedLength = + error::ExpectedLength::Any(&[1, 5]); + + const EXPECTED_CHARS: &'static str = "0123456789abcdefABCDEF-"; + + // Invalid + assert_eq!( + Uuid::parse_str("").map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 0, + }) + ); + + assert_eq!( + Uuid::parse_str("!").map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 1 + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E45") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 37, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa-BBF-329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 35 + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa-BGBF-329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidCharacter { + expected: EXPECTED_CHARS, + found: 'G', + index: 20, + urn: error::UrnPrefix::Optional, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2F4faaFB6BFF329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupCount { + expected: EXPECTED_GROUP_COUNTS, + found: 2 + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faaFB6BFF329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupCount { + expected: EXPECTED_GROUP_COUNTS, + found: 3, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa-B6BFF329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupCount { + expected: EXPECTED_GROUP_COUNTS, + found: 4, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 18, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faaXB6BFF329BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidCharacter { + expected: EXPECTED_CHARS, + found: 'X', + index: 18, + urn: error::UrnPrefix::Optional, + }) + ); + + assert_eq!( + Uuid::parse_str("F9168C5E-CEB-24fa-eB6BFF32-BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact(4), + found: 3, + group: 1, + }) + ); + // (group, found, expecting) + // + assert_eq!( + Uuid::parse_str("01020304-1112-2122-3132-41424344") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact(12), + found: 8, + group: 4, + }) + ); + + assert_eq!( + Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 31, + }) + ); + + assert_eq!( + Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c88") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 33, + }) + ); + + assert_eq!( + Uuid::parse_str("67e5504410b1426f9247bb680e5fe0cg8") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 33, + }) + ); + + assert_eq!( + Uuid::parse_str("67e5504410b1426%9247bb680e5fe0c8") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidCharacter { + expected: EXPECTED_CHARS, + found: '%', + index: 15, + urn: error::UrnPrefix::Optional, + }) + ); + + assert_eq!( + Uuid::parse_str("231231212212423424324323477343246663") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 36, + }) + ); + + // Valid + assert!(Uuid::parse_str("00000000000000000000000000000000").is_ok()); + assert!(Uuid::parse_str("67e55044-10b1-426f-9247-bb680e5fe0c8").is_ok()); + assert!(Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF-329BF39FA1E4").is_ok()); + assert!(Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c8").is_ok()); + assert!(Uuid::parse_str("01020304-1112-2122-3132-414243444546").is_ok()); + assert!(Uuid::parse_str( + "urn:uuid:67e55044-10b1-426f-9247-bb680e5fe0c8" + ) + .is_ok()); + + // Nil + let nil = Uuid::nil(); + assert_eq!( + Uuid::parse_str("00000000000000000000000000000000").unwrap(), + nil + ); + assert_eq!( + Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap(), + nil + ); + + // Round-trip + let uuid_orig = test_util::new(); + let orig_str = uuid_orig.to_string(); + let uuid_out = Uuid::parse_str(&orig_str).unwrap(); + assert_eq!(uuid_orig, uuid_out); + + // Test error reporting + assert_eq!( + Uuid::parse_str("67e5504410b1426f9247bb680e5fe0c") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidLength { + expected: EXPECTED_UUID_LENGTHS, + found: 31, + }) + ); + assert_eq!( + Uuid::parse_str("67e550X410b1426f9247bb680e5fe0cd") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidCharacter { + expected: EXPECTED_CHARS, + found: 'X', + index: 6, + urn: error::UrnPrefix::Optional, + }) + ); + assert_eq!( + Uuid::parse_str("67e550-4105b1426f9247bb680e5fe0c") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact(8), + found: 6, + group: 0, + }) + ); + assert_eq!( + Uuid::parse_str("F9168C5E-CEB2-4faa-B6BF1-02BF39FA1E4") + .map_err(crate::Error::expect_parser), + Err(Error::InvalidGroupLength { + expected: error::ExpectedLength::Exact(4), + found: 5, + group: 3, + }) + ); + } +} diff --git a/vendor/uuid/src/prelude.rs b/vendor/uuid/src/prelude.rs new file mode 100644 index 0000000..63fbb4b --- /dev/null +++ b/vendor/uuid/src/prelude.rs @@ -0,0 +1,47 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The [`uuid`] prelude. +//! +//! This module contains the most important items of the [`uuid`] crate. +//! +//! To use the prelude, include the following in your crate root: +//! +//! ```rust +//! extern crate uuid; +//! ``` +//! +//! # Prelude Contents +//! +//! Currently the prelude reexports the following: +//! +//! [`uuid`]`::{`[`Error`], [`Uuid`], [`Variant`], [`Version`], +//! builder::[`Builder`]`}`: The fundamental types used in [`uuid`] crate. +//! +//! [`uuid`]: ../index.html +//! [`Error`]: ../enum.Error.html +//! [`Uuid`]: ../struct.Uuid.html +//! [`Variant`]: ../enum.Variant.html +//! [`Version`]: ../enum.Version.html +//! [`Builder`]: ../builder/struct.Builder.html +//! +#![cfg_attr(feature = "v1", +doc = " +[`uuid::v1`]`::{`[`ClockSequence`],[`Context`]`}`: The types useful for +handling uuid version 1. Requires feature `v1`. + +[`uuid::v1`]: ../v1/index.html +[`Context`]: ../v1/struct.Context.html +[`ClockSequence`]: ../v1/trait.ClockSequence.html")] + +pub use super::{Builder, Bytes, Error, Uuid, Variant, Version}; +#[cfg(feature = "v1")] +pub use crate::v1::{ClockSequence, Context}; diff --git a/vendor/uuid/src/serde_support.rs b/vendor/uuid/src/serde_support.rs new file mode 100644 index 0000000..4ec2ffb --- /dev/null +++ b/vendor/uuid/src/serde_support.rs @@ -0,0 +1,125 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::prelude::*; +use core::fmt; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +impl Serialize for Uuid { + fn serialize( + &self, + serializer: S, + ) -> Result { + if serializer.is_human_readable() { + serializer + .serialize_str(&self.to_hyphenated().encode_lower(&mut [0; 36])) + } else { + serializer.serialize_bytes(self.as_bytes()) + } + } +} + +impl<'de> Deserialize<'de> for Uuid { + fn deserialize>( + deserializer: D, + ) -> Result { + fn de_error(e: crate::Error) -> E { + E::custom(format_args!("UUID parsing failed: {}", e)) + } + + if deserializer.is_human_readable() { + struct UuidStringVisitor; + + impl<'vi> de::Visitor<'vi> for UuidStringVisitor { + type Value = Uuid; + + fn expecting( + &self, + formatter: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + write!(formatter, "a UUID string") + } + + fn visit_str( + self, + value: &str, + ) -> Result { + value.parse::().map_err(de_error) + } + + fn visit_bytes( + self, + value: &[u8], + ) -> Result { + Uuid::from_slice(value).map_err(de_error) + } + } + + deserializer.deserialize_str(UuidStringVisitor) + } else { + struct UuidBytesVisitor; + + impl<'vi> de::Visitor<'vi> for UuidBytesVisitor { + type Value = Uuid; + + fn expecting( + &self, + formatter: &mut fmt::Formatter<'_>, + ) -> fmt::Result { + write!(formatter, "bytes") + } + + fn visit_bytes( + self, + value: &[u8], + ) -> Result { + Uuid::from_slice(value).map_err(de_error) + } + } + + deserializer.deserialize_bytes(UuidBytesVisitor) + } + } +} + +#[cfg(test)] +mod serde_tests { + use serde_test::{Compact, Configure, Readable, Token}; + + use crate::prelude::*; + + #[test] + fn test_serialize_readable() { + let uuid_str = "f9168c5e-ceb2-4faa-b6bf-329bf39fa1e4"; + let u = Uuid::parse_str(uuid_str).unwrap(); + serde_test::assert_tokens(&u.readable(), &[Token::Str(uuid_str)]); + } + + #[test] + fn test_serialize_compact() { + let uuid_bytes = b"F9168C5E-CEB2-4F"; + let u = Uuid::from_slice(uuid_bytes).unwrap(); + serde_test::assert_tokens(&u.compact(), &[Token::Bytes(uuid_bytes)]); + } + + #[test] + fn test_de_failure() { + serde_test::assert_de_tokens_error::>( + &[Token::Str("hello_world")], + "UUID parsing failed: invalid length: expected one of [36, 32], found 11", + ); + + serde_test::assert_de_tokens_error::>( + &[Token::Bytes(b"hello_world")], + "UUID parsing failed: invalid bytes length: expected 16, found 11", + ); + } +} diff --git a/vendor/uuid/src/slog_support.rs b/vendor/uuid/src/slog_support.rs new file mode 100644 index 0000000..472b39a --- /dev/null +++ b/vendor/uuid/src/slog_support.rs @@ -0,0 +1,39 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::prelude::*; +use slog; + +impl slog::Value for Uuid { + fn serialize( + &self, + _: &slog::Record<'_>, + key: slog::Key, + serializer: &mut dyn slog::Serializer, + ) -> Result<(), slog::Error> { + serializer.emit_arguments(key, &format_args!("{}", self)) + } +} + +#[cfg(test)] +mod tests { + + #[test] + fn test_slog_kv() { + use crate::test_util; + use slog; + use slog::{crit, Drain}; + + let root = slog::Logger::root(slog::Discard.fuse(), slog::o!()); + let u1 = test_util::new(); + crit!(root, "test"; "u1" => u1); + } +} diff --git a/vendor/uuid/src/test_util.rs b/vendor/uuid/src/test_util.rs new file mode 100644 index 0000000..ebb45ab --- /dev/null +++ b/vendor/uuid/src/test_util.rs @@ -0,0 +1,26 @@ +// Copyright 2013-2014 The Rust Project Developers. +// Copyright 2018 The Uuid Project Developers. +// +// See the COPYRIGHT file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::prelude::*; + +pub const fn new() -> Uuid { + Uuid::from_bytes([ + 0xF9, 0x16, 0x8C, 0x5E, 0xCE, 0xB2, 0x4F, 0xAA, 0xB6, 0xBF, 0x32, 0x9B, + 0xF3, 0x9F, 0xA1, 0xE4, + ]) +} + +pub const fn new2() -> Uuid { + Uuid::from_bytes([ + 0xF9, 0x16, 0x8C, 0x5E, 0xCE, 0xB2, 0x4F, 0xAB, 0xB6, 0xBF, 0x32, 0x9B, + 0xF3, 0x9F, 0xA1, 0xE4, + ]) +} diff --git a/vendor/uuid/src/v1.rs b/vendor/uuid/src/v1.rs new file mode 100644 index 0000000..de692b7 --- /dev/null +++ b/vendor/uuid/src/v1.rs @@ -0,0 +1,326 @@ +//! The implementation for Version 1 UUIDs. +//! +//! Note that you need feature `v1` in order to use these features. + +use crate::prelude::*; +use core::sync::atomic; + +/// The number of 100 ns ticks between the UUID epoch +/// `1582-10-15 00:00:00` and the Unix epoch `1970-01-01 00:00:00`. +const UUID_TICKS_BETWEEN_EPOCHS: u64 = 0x01B2_1DD2_1381_4000; + +/// A thread-safe, stateful context for the v1 generator to help ensure +/// process-wide uniqueness. +#[derive(Debug)] +pub struct Context { + count: atomic::AtomicUsize, +} + +/// Stores the number of nanoseconds from an epoch and a counter for ensuring +/// V1 ids generated on the same host are unique. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Timestamp { + ticks: u64, + counter: u16, +} + +impl Timestamp { + /// Construct a `Timestamp` from its raw component values: an RFC4122 + /// timestamp and counter. + /// + /// RFC4122, which defines the V1 UUID, specifies a 60-byte timestamp format + /// as the number of 100-nanosecond intervals elapsed since 00:00:00.00, + /// 15 Oct 1582, "the date of the Gregorian reform of the Christian + /// calendar." + /// + /// The counter value is used to differentiate between ids generated by + /// the same host computer in rapid succession (i.e. with the same observed + /// time). See the [`ClockSequence`] trait for a generic interface to any + /// counter generators that might be used. + /// + /// Internally, the timestamp is stored as a `u64`. For this reason, dates + /// prior to October 1582 are not supported. + /// + /// [`ClockSequence`]: trait.ClockSequence.html + pub const fn from_rfc4122(ticks: u64, counter: u16) -> Self { + Timestamp { ticks, counter } + } + + /// Construct a `Timestamp` from a unix timestamp and sequence-generating + /// `context`. + /// + /// A unix timestamp represents the elapsed time since Jan 1 1970. Libc's + /// `clock_gettime` and other popular implementations traditionally + /// represent this duration as a `timespec`: a struct with `u64` and + /// `u32` fields representing the seconds, and "subsecond" or fractional + /// nanoseconds elapsed since the timestamp's second began, + /// respectively. + /// + /// This constructs a `Timestamp` from the seconds and fractional + /// nanoseconds of a unix timestamp, converting the duration since 1970 + /// into the number of 100-nanosecond intervals since 00:00:00.00, 15 + /// Oct 1582 specified by RFC4122 and used internally by `Timestamp`. + /// + /// The function is not guaranteed to produce monotonically increasing + /// values however. There is a slight possibility that two successive + /// equal time values could be supplied and the sequence counter wraps back + /// over to 0. + /// + /// If uniqueness and monotonicity is required, the user is responsible for + /// ensuring that the time value always increases between calls (including + /// between restarts of the process and device). + pub fn from_unix( + context: impl ClockSequence, + seconds: u64, + subsec_nanos: u32, + ) -> Self { + let counter = context.generate_sequence(seconds, subsec_nanos); + let ticks = UUID_TICKS_BETWEEN_EPOCHS + + seconds * 10_000_000 + + u64::from(subsec_nanos) / 100; + + Timestamp { ticks, counter } + } + + /// Returns the raw RFC4122 timestamp and counter values stored by the + /// `Timestamp`. + /// + /// The timestamp (the first, `u64` element in the tuple) represents the + /// number of 100-nanosecond intervals since 00:00:00.00, 15 Oct 1582. + /// The counter is used to differentiate between ids generated on the + /// same host computer with the same observed time. + pub const fn to_rfc4122(&self) -> (u64, u16) { + (self.ticks, self.counter) + } + + /// Returns the timestamp converted to the seconds and fractional + /// nanoseconds since Jan 1 1970. + /// + /// Internally, the time is stored in 100-nanosecond intervals, + /// thus the maximum precision represented by the fractional nanoseconds + /// value is less than its unit size (100 ns vs. 1 ns). + pub const fn to_unix(&self) -> (u64, u32) { + ( + (self.ticks - UUID_TICKS_BETWEEN_EPOCHS) / 10_000_000, + ((self.ticks - UUID_TICKS_BETWEEN_EPOCHS) % 10_000_000) as u32 + * 100, + ) + } + + /// Returns the timestamp converted into nanoseconds elapsed since Jan 1 + /// 1970. Internally, the time is stored in 100-nanosecond intervals, + /// thus the maximum precision represented is less than the units it is + /// measured in (100 ns vs. 1 ns). The value returned represents the + /// same duration as [`Timestamp::to_unix`]; this provides it in nanosecond + /// units for convenience. + pub const fn to_unix_nanos(&self) -> u64 { + (self.ticks - UUID_TICKS_BETWEEN_EPOCHS) * 100 + } +} + +/// A trait that abstracts over generation of UUID v1 "Clock Sequence" values. +pub trait ClockSequence { + /// Return a 16-bit number that will be used as the "clock sequence" in + /// the UUID. The number must be different if the time has changed since + /// the last time a clock sequence was requested. + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> u16; +} + +impl<'a, T: ClockSequence + ?Sized> ClockSequence for &'a T { + fn generate_sequence(&self, seconds: u64, subsec_nanos: u32) -> u16 { + (**self).generate_sequence(seconds, subsec_nanos) + } +} + +impl Uuid { + /// Create a new UUID (version 1) using a time value + sequence + + /// *NodeId*. + /// + /// When generating [`Timestamp`]s using a [`ClockSequence`], this function + /// is only guaranteed to produce unique values if the following conditions + /// hold: + /// + /// 1. The *NodeId* is unique for this process, + /// 2. The *Context* is shared across all threads which are generating v1 + /// UUIDs, + /// 3. The [`ClockSequence`] implementation reliably returns unique + /// clock sequences (this crate provides [`Context`] for this + /// purpose. However you can create your own [`ClockSequence`] + /// implementation, if [`Context`] does not meet your needs). + /// + /// The NodeID must be exactly 6 bytes long. + /// + /// Note that usage of this method requires the `v1` feature of this crate + /// to be enabled. + /// + /// # Examples + /// + /// A UUID can be created from a unix [`Timestamp`] with a + /// [`ClockSequence`]: + /// + /// ```rust + /// use uuid::v1::{Timestamp, Context}; + /// use uuid::Uuid; + /// + /// let context = Context::new(42); + /// let ts = Timestamp::from_unix(&context, 1497624119, 1234); + /// let uuid = Uuid::new_v1(ts, &[1, 2, 3, 4, 5, 6]).expect("failed to generate UUID"); + /// + /// assert_eq!( + /// uuid.to_hyphenated().to_string(), + /// "f3b4958c-52a1-11e7-802a-010203040506" + /// ); + /// ``` + /// + /// The timestamp can also be created manually as per RFC4122: + /// + /// ``` + /// use uuid::v1::{Timestamp, Context}; + /// use uuid::Uuid; + /// + /// let context = Context::new(42); + /// let ts = Timestamp::from_rfc4122(1497624119, 0); + /// let uuid = Uuid::new_v1(ts, &[1, 2, 3, 4, 5, 6]).expect("failed to generate UUID"); + /// + /// assert_eq!( + /// uuid.to_hyphenated().to_string(), + /// "5943ee37-0000-1000-8000-010203040506" + /// ); + /// ``` + /// + /// [`Timestamp`]: v1/struct.Timestamp.html + /// [`ClockSequence`]: v1/struct.ClockSequence.html + /// [`Context`]: v1/struct.Context.html + pub fn new_v1(ts: Timestamp, node_id: &[u8]) -> Result { + const NODE_ID_LEN: usize = 6; + + let len = node_id.len(); + if len != NODE_ID_LEN { + Err(crate::builder::Error::new(NODE_ID_LEN, len))?; + } + + let time_low = (ts.ticks & 0xFFFF_FFFF) as u32; + let time_mid = ((ts.ticks >> 32) & 0xFFFF) as u16; + let time_high_and_version = + (((ts.ticks >> 48) & 0x0FFF) as u16) | (1 << 12); + + let mut d4 = [0; 8]; + + { + d4[0] = (((ts.counter & 0x3F00) >> 8) as u8) | 0x80; + d4[1] = (ts.counter & 0xFF) as u8; + } + + d4[2..].copy_from_slice(node_id); + + Uuid::from_fields(time_low, time_mid, time_high_and_version, &d4) + } + + /// Returns an optional [`Timestamp`] storing the timestamp and + /// counter portion parsed from a V1 UUID. + /// + /// Returns `None` if the supplied UUID is not V1. + /// + /// The V1 timestamp format defined in RFC4122 specifies a 60-bit + /// integer representing the number of 100-nanosecond intervals + /// since 00:00:00.00, 15 Oct 1582. + /// + /// [`Timestamp`] offers several options for converting the raw RFC4122 + /// value into more commonly-used formats, such as a unix timestamp. + /// + /// [`Timestamp`]: v1/struct.Timestamp.html + pub fn to_timestamp(&self) -> Option { + if self + .get_version() + .map(|v| v != Version::Mac) + .unwrap_or(true) + { + return None; + } + + let ticks: u64 = u64::from(self.as_bytes()[6] & 0x0F) << 56 + | u64::from(self.as_bytes()[7]) << 48 + | u64::from(self.as_bytes()[4]) << 40 + | u64::from(self.as_bytes()[5]) << 32 + | u64::from(self.as_bytes()[0]) << 24 + | u64::from(self.as_bytes()[1]) << 16 + | u64::from(self.as_bytes()[2]) << 8 + | u64::from(self.as_bytes()[3]); + + let counter: u16 = u16::from(self.as_bytes()[8] & 0x3F) << 8 + | u16::from(self.as_bytes()[9]); + + Some(Timestamp::from_rfc4122(ticks, counter)) + } +} + +impl Context { + /// Creates a thread-safe, internally mutable context to help ensure + /// uniqueness. + /// + /// This is a context which can be shared across threads. It maintains an + /// internal counter that is incremented at every request, the value ends + /// up in the clock_seq portion of the UUID (the fourth group). This + /// will improve the probability that the UUID is unique across the + /// process. + pub const fn new(count: u16) -> Self { + Self { + count: atomic::AtomicUsize::new(count as usize), + } + } +} + +impl ClockSequence for Context { + fn generate_sequence(&self, _: u64, _: u32) -> u16 { + (self.count.fetch_add(1, atomic::Ordering::SeqCst) & 0xffff) as u16 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::std::string::ToString; + + #[test] + fn test_new_v1() { + let time: u64 = 1_496_854_535; + let time_fraction: u32 = 812_946_000; + let node = [1, 2, 3, 4, 5, 6]; + let context = Context::new(0); + + { + let uuid = Uuid::new_v1( + Timestamp::from_unix(&context, time, time_fraction), + &node, + ) + .unwrap(); + + assert_eq!(uuid.get_version(), Some(Version::Mac)); + assert_eq!(uuid.get_variant(), Some(Variant::RFC4122)); + assert_eq!( + uuid.to_hyphenated().to_string(), + "20616934-4ba2-11e7-8000-010203040506" + ); + + let ts = uuid.to_timestamp().unwrap().to_rfc4122(); + + assert_eq!(ts.0 - 0x01B2_1DD2_1381_4000, 14_968_545_358_129_460); + assert_eq!(ts.1, 0); + }; + + { + let uuid2 = Uuid::new_v1( + Timestamp::from_unix(&context, time, time_fraction), + &node, + ) + .unwrap(); + + assert_eq!( + uuid2.to_hyphenated().to_string(), + "20616934-4ba2-11e7-8001-010203040506" + ); + assert_eq!(uuid2.to_timestamp().unwrap().to_rfc4122().1, 1) + }; + } +} diff --git a/vendor/uuid/src/v3.rs b/vendor/uuid/src/v3.rs new file mode 100644 index 0000000..5be542b --- /dev/null +++ b/vendor/uuid/src/v3.rs @@ -0,0 +1,146 @@ +use crate::prelude::*; +use md5; + +impl Uuid { + /// Creates a UUID using a name from a namespace, based on the MD5 + /// hash. + /// + /// A number of namespaces are available as constants in this crate: + /// + /// * [`NAMESPACE_DNS`] + /// * [`NAMESPACE_OID`] + /// * [`NAMESPACE_URL`] + /// * [`NAMESPACE_X500`] + /// + /// Note that usage of this method requires the `v3` feature of this crate + /// to be enabled. + /// + /// [`NAMESPACE_DNS`]: #associatedconstant.NAMESPACE_DNS + /// [`NAMESPACE_OID`]: #associatedconstant.NAMESPACE_OID + /// [`NAMESPACE_URL`]: #associatedconstant.NAMESPACE_URL + /// [`NAMESPACE_X500`]: #associatedconstant.NAMESPACE_X500 + pub fn new_v3(namespace: &Uuid, name: &[u8]) -> Uuid { + let mut context = md5::Context::new(); + + context.consume(namespace.as_bytes()); + context.consume(name); + + let computed = context.compute(); + let bytes = computed.into(); + + let mut builder = crate::Builder::from_bytes(bytes); + + builder + .set_variant(Variant::RFC4122) + .set_version(Version::Md5); + + builder.build() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::std::string::ToString; + + static FIXTURE: &'static [(&'static Uuid, &'static str, &'static str)] = &[ + ( + &Uuid::NAMESPACE_DNS, + "example.org", + "04738bdf-b25a-3829-a801-b21a1d25095b", + ), + ( + &Uuid::NAMESPACE_DNS, + "rust-lang.org", + "c6db027c-615c-3b4d-959e-1a917747ca5a", + ), + ( + &Uuid::NAMESPACE_DNS, + "42", + "5aab6e0c-b7d3-379c-92e3-2bfbb5572511", + ), + ( + &Uuid::NAMESPACE_DNS, + "lorem ipsum", + "4f8772e9-b59c-3cc9-91a9-5c823df27281", + ), + ( + &Uuid::NAMESPACE_URL, + "example.org", + "39682ca1-9168-3da2-a1bb-f4dbcde99bf9", + ), + ( + &Uuid::NAMESPACE_URL, + "rust-lang.org", + "7ed45aaf-e75b-3130-8e33-ee4d9253b19f", + ), + ( + &Uuid::NAMESPACE_URL, + "42", + "08998a0c-fcf4-34a9-b444-f2bfc15731dc", + ), + ( + &Uuid::NAMESPACE_URL, + "lorem ipsum", + "e55ad2e6-fb89-34e8-b012-c5dde3cd67f0", + ), + ( + &Uuid::NAMESPACE_OID, + "example.org", + "f14eec63-2812-3110-ad06-1625e5a4a5b2", + ), + ( + &Uuid::NAMESPACE_OID, + "rust-lang.org", + "6506a0ec-4d79-3e18-8c2b-f2b6b34f2b6d", + ), + ( + &Uuid::NAMESPACE_OID, + "42", + "ce6925a5-2cd7-327b-ab1c-4b375ac044e4", + ), + ( + &Uuid::NAMESPACE_OID, + "lorem ipsum", + "5dd8654f-76ba-3d47-bc2e-4d6d3a78cb09", + ), + ( + &Uuid::NAMESPACE_X500, + "example.org", + "64606f3f-bd63-363e-b946-fca13611b6f7", + ), + ( + &Uuid::NAMESPACE_X500, + "rust-lang.org", + "bcee7a9c-52f1-30c6-a3cc-8c72ba634990", + ), + ( + &Uuid::NAMESPACE_X500, + "42", + "c1073fa2-d4a6-3104-b21d-7a6bdcf39a23", + ), + ( + &Uuid::NAMESPACE_X500, + "lorem ipsum", + "02f09a3f-1624-3b1d-8409-44eff7708208", + ), + ]; + + #[test] + fn test_new() { + for &(ref ns, ref name, _) in FIXTURE { + let uuid = Uuid::new_v3(*ns, name.as_bytes()); + assert_eq!(uuid.get_version().unwrap(), Version::Md5); + assert_eq!(uuid.get_variant().unwrap(), Variant::RFC4122); + } + } + + #[test] + fn test_to_hyphenated_string() { + for &(ref ns, ref name, ref expected) in FIXTURE { + let uuid = Uuid::new_v3(*ns, name.as_bytes()); + assert_eq!(uuid.to_hyphenated().to_string(), *expected); + } + } +} diff --git a/vendor/uuid/src/v4.rs b/vendor/uuid/src/v4.rs new file mode 100644 index 0000000..0928dc7 --- /dev/null +++ b/vendor/uuid/src/v4.rs @@ -0,0 +1,60 @@ +use crate::prelude::*; + +impl Uuid { + /// Creates a random UUID. + /// + /// This uses the [`getrandom`] crate to utilise the operating system's RNG + /// as the source of random numbers. If you'd like to use a custom + /// generator, don't use this method: generate random bytes using your + /// custom generator and pass them to the + /// [`uuid::Builder::from_bytes`][from_bytes] function instead. + /// + /// Note that usage of this method requires the `v4` feature of this crate + /// to be enabled. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use uuid::Uuid; + /// + /// let uuid = Uuid::new_v4(); + /// ``` + /// + /// [`getrandom`]: https://crates.io/crates/getrandom + /// [from_bytes]: struct.Builder.html#method.from_bytes + pub fn new_v4() -> Uuid { + let mut bytes = [0u8; 16]; + getrandom::getrandom(&mut bytes).unwrap_or_else(|err| { + // NB: getrandom::Error has no source; this is adequate display + panic!("could not retreive random bytes for uuid: {}", err) + }); + + crate::Builder::from_bytes(bytes) + .set_variant(Variant::RFC4122) + .set_version(Version::Random) + .build() + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn test_new() { + let uuid = Uuid::new_v4(); + + assert_eq!(uuid.get_version(), Some(Version::Random)); + assert_eq!(uuid.get_variant(), Some(Variant::RFC4122)); + } + + #[test] + fn test_get_version() { + let uuid = Uuid::new_v4(); + + assert_eq!(uuid.get_version(), Some(Version::Random)); + assert_eq!(uuid.get_version_num(), 4) + } +} diff --git a/vendor/uuid/src/v5.rs b/vendor/uuid/src/v5.rs new file mode 100644 index 0000000..e42d70d --- /dev/null +++ b/vendor/uuid/src/v5.rs @@ -0,0 +1,158 @@ +use crate::prelude::*; +use sha1; + +impl Uuid { + /// Creates a UUID using a name from a namespace, based on the SHA-1 hash. + /// + /// A number of namespaces are available as constants in this crate: + /// + /// * [`NAMESPACE_DNS`] + /// * [`NAMESPACE_OID`] + /// * [`NAMESPACE_URL`] + /// * [`NAMESPACE_X500`] + /// + /// Note that usage of this method requires the `v5` feature of this crate + /// to be enabled. + /// + /// [`NAMESPACE_DNS`]: struct.Uuid.html#associatedconst.NAMESPACE_DNS + /// [`NAMESPACE_OID`]: struct.Uuid.html#associatedconst.NAMESPACE_OID + /// [`NAMESPACE_URL`]: struct.Uuid.html#associatedconst.NAMESPACE_URL + /// [`NAMESPACE_X500`]: struct.Uuid.html#associatedconst.NAMESPACE_X500 + pub fn new_v5(namespace: &Uuid, name: &[u8]) -> Uuid { + let mut hash = sha1::Sha1::new(); + + hash.update(namespace.as_bytes()); + hash.update(name); + + let buffer = hash.digest().bytes(); + + let mut bytes = crate::Bytes::default(); + bytes.copy_from_slice(&buffer[..16]); + + let mut builder = crate::Builder::from_bytes(bytes); + builder + .set_variant(Variant::RFC4122) + .set_version(Version::Sha1); + + builder.build() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::std::string::ToString; + + static FIXTURE: &'static [(&'static Uuid, &'static str, &'static str)] = &[ + ( + &Uuid::NAMESPACE_DNS, + "example.org", + "aad03681-8b63-5304-89e0-8ca8f49461b5", + ), + ( + &Uuid::NAMESPACE_DNS, + "rust-lang.org", + "c66bbb60-d62e-5f17-a399-3a0bd237c503", + ), + ( + &Uuid::NAMESPACE_DNS, + "42", + "7c411b5e-9d3f-50b5-9c28-62096e41c4ed", + ), + ( + &Uuid::NAMESPACE_DNS, + "lorem ipsum", + "97886a05-8a68-5743-ad55-56ab2d61cf7b", + ), + ( + &Uuid::NAMESPACE_URL, + "example.org", + "54a35416-963c-5dd6-a1e2-5ab7bb5bafc7", + ), + ( + &Uuid::NAMESPACE_URL, + "rust-lang.org", + "c48d927f-4122-5413-968c-598b1780e749", + ), + ( + &Uuid::NAMESPACE_URL, + "42", + "5c2b23de-4bad-58ee-a4b3-f22f3b9cfd7d", + ), + ( + &Uuid::NAMESPACE_URL, + "lorem ipsum", + "15c67689-4b85-5253-86b4-49fbb138569f", + ), + ( + &Uuid::NAMESPACE_OID, + "example.org", + "34784df9-b065-5094-92c7-00bb3da97a30", + ), + ( + &Uuid::NAMESPACE_OID, + "rust-lang.org", + "8ef61ecb-977a-5844-ab0f-c25ef9b8d5d6", + ), + ( + &Uuid::NAMESPACE_OID, + "42", + "ba293c61-ad33-57b9-9671-f3319f57d789", + ), + ( + &Uuid::NAMESPACE_OID, + "lorem ipsum", + "6485290d-f79e-5380-9e64-cb4312c7b4a6", + ), + ( + &Uuid::NAMESPACE_X500, + "example.org", + "e3635e86-f82b-5bbc-a54a-da97923e5c76", + ), + ( + &Uuid::NAMESPACE_X500, + "rust-lang.org", + "26c9c3e9-49b7-56da-8b9f-a0fb916a71a3", + ), + ( + &Uuid::NAMESPACE_X500, + "42", + "e4b88014-47c6-5fe0-a195-13710e5f6e27", + ), + ( + &Uuid::NAMESPACE_X500, + "lorem ipsum", + "b11f79a5-1e6d-57ce-a4b5-ba8531ea03d0", + ), + ]; + + #[test] + fn test_get_version() { + let uuid = + Uuid::new_v5(&Uuid::NAMESPACE_DNS, "rust-lang.org".as_bytes()); + + assert_eq!(uuid.get_version(), Some(Version::Sha1)); + assert_eq!(uuid.get_version_num(), 5); + } + + #[test] + fn test_hyphenated() { + for &(ref ns, ref name, ref expected) in FIXTURE { + let uuid = Uuid::new_v5(*ns, name.as_bytes()); + + assert_eq!(uuid.to_hyphenated().to_string(), *expected) + } + } + + #[test] + fn test_new() { + for &(ref ns, ref name, ref u) in FIXTURE { + let uuid = Uuid::new_v5(*ns, name.as_bytes()); + + assert_eq!(uuid.get_variant(), Some(Variant::RFC4122)); + assert_eq!(uuid.get_version(), Some(Version::Sha1)); + assert_eq!(Ok(uuid), u.parse()); + } + } +} diff --git a/vendor/uuid/src/winapi_support.rs b/vendor/uuid/src/winapi_support.rs new file mode 100644 index 0000000..29bed06 --- /dev/null +++ b/vendor/uuid/src/winapi_support.rs @@ -0,0 +1,79 @@ +use crate::prelude::*; +use winapi::shared::guiddef; + +#[cfg(feature = "guid")] +impl Uuid { + /// Attempts to create a [`Uuid`] from a little endian winapi `GUID` + /// + /// [`Uuid`]: ../struct.Uuid.html + pub fn from_guid(guid: guiddef::GUID) -> Result { + Uuid::from_fields_le( + guid.Data1 as u32, + guid.Data2 as u16, + guid.Data3 as u16, + &(guid.Data4 as [u8; 8]), + ) + } + + /// Converts a [`Uuid`] into a little endian winapi `GUID` + /// + /// [`Uuid`]: ../struct.Uuid.html + pub fn to_guid(&self) -> guiddef::GUID { + let (data1, data2, data3, data4) = self.to_fields_le(); + + guiddef::GUID { + Data1: data1, + Data2: data2, + Data3: data3, + Data4: *data4, + } + } +} + +#[cfg(feature = "guid")] +#[cfg(test)] +mod tests { + use super::*; + + use crate::std::string::ToString; + use winapi::shared::guiddef; + + #[test] + fn test_from_guid() { + let guid = guiddef::GUID { + Data1: 0x4a35229d, + Data2: 0x5527, + Data3: 0x4f30, + Data4: [0x86, 0x47, 0x9d, 0xc5, 0x4e, 0x1e, 0xe1, 0xe8], + }; + + let uuid = Uuid::from_guid(guid).unwrap(); + assert_eq!( + "9d22354a-2755-304f-8647-9dc54e1ee1e8", + uuid.to_hyphenated().to_string() + ); + } + + #[test] + fn test_guid_roundtrip() { + let guid_in = guiddef::GUID { + Data1: 0x4a35229d, + Data2: 0x5527, + Data3: 0x4f30, + Data4: [0x86, 0x47, 0x9d, 0xc5, 0x4e, 0x1e, 0xe1, 0xe8], + }; + + let uuid = Uuid::from_guid(guid_in).unwrap(); + let guid_out = uuid.to_guid(); + + assert_eq!( + (guid_in.Data1, guid_in.Data2, guid_in.Data3, guid_in.Data4), + ( + guid_out.Data1, + guid_out.Data2, + guid_out.Data3, + guid_out.Data4 + ) + ); + } +} diff --git a/watch b/watch new file mode 100644 index 0000000..2df2a1b --- /dev/null +++ b/watch @@ -0,0 +1,9 @@ +#git=https://github.com/389ds/389-ds-base +version=4 + +opts="\ + dversionmangle=s/\+dfsg\d*$//, \ + oversionmangle=s/$/+dfsg1/, \ + filenamemangle=s#@ANY_VERSION@$#$1.tar.bz2#, \ + downloadurlmangle=s#/tag/#/download/#;s#@ANY_VERSION@$#$1/$1.tar.bz2#" \ +https://github.com/389ds/@PACKAGE@/tags .*/releases/tag/@PACKAGE@-@ANY_VERSION@