From 01f0122f8a87c159c7df3615b9babca461a21301 Mon Sep 17 00:00:00 2001 From: Austin Hartzheim Date: Wed, 11 Sep 2024 22:05:34 -0500 Subject: [PATCH] Add optional support for write locks These write locks prevent accidental misuse of `Synchronizer`s by establishing an advisory lock on the state file using the `flock` system call. Correctly configured writers will check this lock to prevent duplicate writers. By default, write locks are disabled and carry no additional cost through use of generics. Unused functionality is optimized out by the compiler during monomorphization. Write locking is only supported on Unix-like platforms due to use of the `flock` API. Other locking mechanisms and lock modes could be supported in the future. --- Cargo.lock | 463 ++++++++++++++++------------------------ Cargo.toml | 6 + benches/synchronizer.rs | 100 ++++++++- src/lib.rs | 2 + src/locks.rs | 165 ++++++++++++++ src/state.rs | 125 ++++++++--- src/synchronizer.rs | 53 ++++- 7 files changed, 589 insertions(+), 325 deletions(-) create mode 100644 src/locks.rs diff --git a/Cargo.lock b/Cargo.lock index 4dbcfec..8296c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,24 +4,24 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.7.6" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ "getrandom", "once_cell", @@ -58,35 +58,35 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -136,9 +136,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytecheck" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -147,9 +147,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" dependencies = [ "proc-macro2", "quote", @@ -158,15 +158,21 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.16.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cast" @@ -176,9 +182,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -215,18 +224,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.9" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.9" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstyle", "clap_lex", @@ -234,15 +243,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cpp_demangle" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" dependencies = [ "cfg-if", ] @@ -337,33 +346,19 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "findshlibs" @@ -391,9 +386,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -402,9 +397,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "half" @@ -422,7 +417,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash 0.7.6", + "ahash 0.7.8", ] [[package]] @@ -433,15 +428,15 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hermit-abi" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "indexmap" -version = "2.2.6" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -449,9 +444,9 @@ dependencies = [ [[package]] name = "inferno" -version = "0.11.19" +version = "0.11.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" +checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash 0.8.11", "indexmap", @@ -465,31 +460,11 @@ dependencies = [ "str_stack", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" dependencies = [ "hermit-abi", "libc", @@ -513,36 +488,36 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] name = "libm" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -577,11 +552,11 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] @@ -590,6 +565,7 @@ version = "1.0.4" dependencies = [ "bytecheck", "criterion", + "libc", "memmap2", "pprof", "proptest", @@ -622,9 +598,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -632,18 +608,18 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "oorandom" @@ -669,16 +645,16 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.2", + "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-targets", ] [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -689,15 +665,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -728,9 +704,12 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro2" @@ -743,19 +722,19 @@ dependencies = [ [[package]] name = "proptest" -version = "1.2.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", - "bitflags 1.3.2", - "byteorder", + "bit-vec", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", - "regex-syntax 0.6.29", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -823,9 +802,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.28" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -897,32 +876,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.4", + "regex-syntax", ] [[package]] @@ -933,15 +903,9 @@ checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.4" @@ -950,30 +914,31 @@ checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rend" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" dependencies = [ "bytecheck", ] [[package]] name = "rgb" -version = "0.8.44" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aee83dc281d5a3200d37b299acd13b81066ea126a7f16f0eae70fc9aed241d9" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" dependencies = [ "bytemuck", ] [[package]] name = "rkyv" -version = "0.7.42" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" dependencies = [ "bitvec", "bytecheck", + "bytes", "hashbrown 0.12.3", "ptr_meta", "rend", @@ -985,9 +950,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.42" +version = "0.7.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" dependencies = [ "proc-macro2", "quote", @@ -1002,16 +967,15 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.37.20" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96e891d04aa506a6d1f318d2771bcb1c7dfda84e126660ace067c9b474bb2c0" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1055,35 +1019,42 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.99" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simdutf8" version = "0.1.4" @@ -1110,9 +1081,9 @@ checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" [[package]] name = "symbolic-common" -version = "12.9.2" +version = "12.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" +checksum = "9c1db5ac243c7d7f8439eb3b8f0357888b37cf3732957e91383b0ad61756374e" dependencies = [ "debugid", "memmap2", @@ -1122,9 +1093,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.9.2" +version = "12.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" +checksum = "ea26e430c27d4a8a5dea4c4b81440606c7c1a415bd611451ef6af8c81416afc3" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -1144,9 +1115,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1161,36 +1132,35 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.6.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ - "autocfg", "cfg-if", "fastrand", - "redox_syscall 0.3.5", + "once_cell", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.77", ] [[package]] @@ -1205,9 +1175,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -1226,21 +1196,21 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-ident" -version = "1.0.9" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.3.3" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" @@ -1269,34 +1239,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.77", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1304,28 +1275,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -1349,11 +1320,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1362,37 +1333,22 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.0" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows-targets", ] [[package]] @@ -1401,46 +1357,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -1453,48 +1391,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -1525,6 +1439,7 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ + "byteorder", "zerocopy-derive", ] @@ -1536,5 +1451,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.77", ] diff --git a/Cargo.toml b/Cargo.toml index c00de5a..e401571 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ rkyv = { version = "0.7.40", features = ["validation", "strict"] } thiserror = "1.0.30" wyhash = "0.5.0" +[target.'cfg(unix)'.dependencies] +libc = "0.2.0" + [dev-dependencies] criterion = { version = "0.5.1", features = ["html_reports"] } pprof = { version = "0.13.0", features = ["flamegraph", "protobuf-codec", "criterion"] } @@ -26,3 +29,6 @@ rand = "0.8.5" [[bench]] name = "synchronizer" harness = false + +[package.metadata.docs.rs] +all-features = true diff --git a/benches/synchronizer.rs b/benches/synchronizer.rs index 5c040a8..2d381f1 100644 --- a/benches/synchronizer.rs +++ b/benches/synchronizer.rs @@ -2,10 +2,14 @@ use std::time::Duration; use bytecheck::CheckBytes; use criterion::{black_box, criterion_group, criterion_main, Criterion, Throughput}; -use mmap_sync::synchronizer::Synchronizer; use pprof::criterion::PProfProfiler; -use rkyv::{Archive, Deserialize, Serialize}; +use rkyv::{AlignedVec, Archive, Deserialize, Serialize}; +#[cfg(unix)] +use wyhash::WyHash; +#[cfg(unix)] +use mmap_sync::locks::{LockDisabled, SingleWriter}; +use mmap_sync::synchronizer::Synchronizer; /// Example data-structure shared between writer and reader(s) #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] #[archive_attr(derive(CheckBytes))] @@ -14,14 +18,20 @@ pub struct HelloWorld { pub messages: Vec, } -pub fn bench_synchronizer(c: &mut Criterion) { - let mut synchronizer = Synchronizer::new("/dev/shm/hello_world".as_ref()); +fn build_mock_data() -> (HelloWorld, AlignedVec) { let data = HelloWorld { version: 7, messages: vec!["Hello".to_string(), "World".to_string(), "!".to_string()], }; let bytes = rkyv::to_bytes::(&data).unwrap(); + (data, bytes) +} + +pub fn bench_synchronizer(c: &mut Criterion) { + let mut synchronizer = Synchronizer::new("/dev/shm/hello_world".as_ref()); + let (data, bytes) = build_mock_data(); + let mut group = c.benchmark_group("synchronizer"); group.throughput(Throughput::Elements(1)); @@ -56,9 +66,89 @@ pub fn bench_synchronizer(c: &mut Criterion) { }); } +#[cfg(unix)] +fn build_synchronizers_for_strategies() -> ( + Synchronizer, + Synchronizer, +) { + let disabled_path = "/dev/shm/mmap_sync_lock_disabled"; + let single_writer_path = "/dev/shm/mmap_sync_lock_single_writer"; + + ( + Synchronizer::::with_params( + disabled_path.as_ref(), + ), + Synchronizer::::with_params( + single_writer_path.as_ref(), + ), + ) +} + +#[cfg(unix)] +pub fn bench_locked_writes(c: &mut Criterion) { + let mut group = c.benchmark_group("synchronizer_locked_write"); + group.throughput(Throughput::Elements(1)); + + let (mut synchronizer_disabled, mut synchronizer_single_writer) = + build_synchronizers_for_strategies(); + let (data, _) = build_mock_data(); + + group.bench_function("disabled", |b| { + b.iter(|| { + synchronizer_disabled + .write(black_box(&data), Duration::from_nanos(10)) + .expect("failed to write data"); + }) + }); + + group.bench_function("single_writer", |b| { + b.iter(|| { + synchronizer_single_writer + .write(black_box(&data), Duration::from_nanos(10)) + .expect("failed to write data"); + }) + }); +} + +#[cfg(unix)] +pub fn bench_locked_reads(c: &mut Criterion) { + let mut group = c.benchmark_group("synchronizer_locked_read"); + group.throughput(Throughput::Elements(1)); + + let (mut synchronizer_disabled, mut synchronizer_single_writer) = + build_synchronizers_for_strategies(); + let (data, _) = build_mock_data(); + + // Populate data to make it available to read. + synchronizer_disabled + .write(&data, Duration::from_nanos(10)) + .expect("failed to populate initial data"); + synchronizer_single_writer + .write(&data, Duration::from_nanos(10)) + .expect("failed to populate initial data"); + + group.bench_function("disabled", |b| { + b.iter(|| { + let archived = unsafe { synchronizer_disabled.read::(false).unwrap() }; + assert_eq!(archived.version, data.version); + }) + }); + + group.bench_function("single_writer", |b| { + b.iter(|| { + let archived = unsafe { + synchronizer_single_writer + .read::(false) + .unwrap() + }; + assert_eq!(archived.version, data.version); + }) + }); +} + criterion_group! { name = benches; config = Criterion::default().with_profiler(PProfProfiler::new(100, pprof::criterion::Output::Protobuf)); - targets = bench_synchronizer + targets = bench_synchronizer, bench_locked_reads, bench_locked_writes } criterion_main!(benches); diff --git a/src/lib.rs b/src/lib.rs index d130c01..4170f6a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(docsrs, feature(doc_cfg))] //! `mmap-sync` is a high-performance, concurrent data access library for Rust. It is designed to handle data access between a single writer process and multiple reader processes efficiently using memory-mapped files, wait-free synchronization, and zero-copy deserialization. //! //! ## Features @@ -12,5 +13,6 @@ mod data; pub mod guard; pub mod instance; +pub mod locks; mod state; pub mod synchronizer; diff --git a/src/locks.rs b/src/locks.rs new file mode 100644 index 0000000..05ff308 --- /dev/null +++ b/src/locks.rs @@ -0,0 +1,165 @@ +//! Lock strategies. +//! +//! # Safety +//! This crate requires that only one writer be active at a time. It is the caller's responsibility +//! to uphold th is guarantee. +//! +//! Note: if multiple writers are active, it is the caller's responsibility to ensure that each +//! writer is configured with an appropriate lock strategy. For example, mixing the `LockDisabled` +//! strategy with any other strategy is incorrect because it disables lock checks from one of the +//! synchronizers. + +#[cfg(unix)] +use std::os::fd::AsRawFd; +use std::{ + fs::File, + ops::{Deref, DerefMut}, +}; + +use memmap2::MmapMut; + +use crate::synchronizer::SynchronizerError; + +/// The write lock strategy supports different lock implementations which can be chosen based on +/// the guarantees required, platform support, and performance constraints. +/// +/// Note: the lock implementations are sealed to avoid committing to a specific lock interface. +#[allow(private_bounds)] +pub trait WriteLockStrategy<'a>: WriteLockStrategySealed<'a> {} + +/// Sealed trait i +pub(crate) trait WriteLockStrategySealed<'a> { + type Guard: DerefMut + 'a; + + /// Create a new instance of this lock strategy. + /// + /// The `mmap` parameter will have write access controlled by the lock. + /// + /// The `file` parameter is required because lock strategies depending on `flock` must hold + /// the file descriptor open so the kernel does not release the lock. + fn new(mmap: MmapMut, file: File) -> Self; + + /// Provide read access to mmaped memory. + fn read(&'a self) -> &'a [u8]; + + /// Acquire the lock as specified by the lock strategy. + /// + /// On success, return a lock guard which can be used to access the underlying mmaped memory + /// via [`Deref`]/[`DerefMut`]. + fn lock(&'a mut self) -> Result; +} + +/// Lock protection is disabled. +/// +/// # Safety +/// Callers must ensure that there is only a single active writer. For example, the caller might +/// ensure that only one process attempts to write to the synchronizer, and ensure that multiple +/// instances of the process are not spawned. +pub struct LockDisabled(MmapMut); + +impl<'a> WriteLockStrategySealed<'a> for LockDisabled { + type Guard = DisabledGuard<'a>; + + #[inline] + fn new(mmap: MmapMut, _file: File) -> Self { + // No need to hold the file descriptor because lock functionality is disabled. + Self(mmap) + } + + #[inline] + fn read(&'a self) -> &'a [u8] { + &self.0 + } + + #[inline] + fn lock(&'a mut self) -> Result { + Ok(DisabledGuard(&mut self.0)) + } +} + +impl<'a> WriteLockStrategy<'a> for LockDisabled {} + +pub struct DisabledGuard<'a>(&'a mut MmapMut); + +impl<'a> Deref for DisabledGuard<'a> { + type Target = MmapMut; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl<'a> DerefMut for DisabledGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 + } +} + +/// Acquire the lock. Once acquired, hold the lock until dropped. +/// +/// The `flock` API holds the lock as long as the file descriptor is open, and closes the lock +/// when the descriptor is closed. The descriptor is automatically closed when `File` is dropped. +#[cfg(unix)] +pub struct SingleWriter { + mmap: MmapMut, + file: File, + locked: bool, +} + +#[cfg(unix)] +impl<'a> WriteLockStrategySealed<'a> for SingleWriter { + type Guard = SingleWriterGuard<'a>; + + #[inline] + fn new(mmap: MmapMut, file: File) -> Self { + Self { + mmap, + file, + locked: false, + } + } + + #[inline] + fn read(&'a self) -> &'a [u8] { + &self.mmap + } + + #[inline] + fn lock(&'a mut self) -> Result { + // We already hold the lock, so return success. + if self.locked { + return Ok(SingleWriterGuard(&mut self.mmap)); + } + + // Acquire the lock for the first time. + // Note: the file descriptor must remain open to hold the lock. + match unsafe { libc::flock(self.file.as_raw_fd(), libc::LOCK_EX | libc::LOCK_NB) } { + 0 => { + // Hold the lock until this structure is dropped. + self.locked = true; + Ok(SingleWriterGuard(&mut self.mmap)) + } + _ => Err(SynchronizerError::WriteLockConflict), + } + } +} + +impl<'a> WriteLockStrategy<'a> for SingleWriter {} + +/// A simple guard which does not release the lock upon being dropped. +#[cfg(unix)] +pub struct SingleWriterGuard<'a>(&'a mut MmapMut); + +impl<'a> Deref for SingleWriterGuard<'a> { + type Target = MmapMut; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl<'a> DerefMut for SingleWriterGuard<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.0 + } +} diff --git a/src/state.rs b/src/state.rs index 3efd7b4..4084893 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,7 @@ use memmap2::MmapMut; use std::ffi::{OsStr, OsString}; use std::fs::OpenOptions; -use std::ops::Add; +use std::ops::{Add, DerefMut}; use std::sync::atomic::{AtomicU32, AtomicU64, Ordering}; use std::time::{Duration, Instant}; use std::{mem, thread}; @@ -10,6 +10,7 @@ use std::{mem, thread}; use std::os::unix::fs::OpenOptionsExt; use crate::instance::InstanceVersion; +use crate::locks::WriteLockStrategy; use crate::synchronizer::SynchronizerError; use crate::synchronizer::SynchronizerError::*; @@ -102,16 +103,19 @@ impl Default for State { /// State container stores memory mapped state file, which is used for /// synchronization purposes with a help of atomics -pub(crate) struct StateContainer { +pub(crate) struct StateContainer { /// State file path state_path: OsString, - /// Modifiable memory mapped file storing state - mmap: Option, + /// Modifiable memory mapped file storing state. + /// + /// The [`MmapMut`] type is wrapped in a [`WriteLockStrategy`] to require lock acquisition + /// prior to writing. + mmap: Option, } const STATE_SUFFIX: &str = "_state"; -impl StateContainer { +impl<'a, WL: WriteLockStrategy<'a>> StateContainer { /// Create new instance of `StateContainer` pub(crate) fn new(path_prefix: &OsStr) -> Self { let mut state_path = path_prefix.to_os_string(); @@ -122,40 +126,91 @@ impl StateContainer { } } - /// Fetch state from existing memory mapped file or create new one + /// Fetch state from existing memory mapped file or create new one. + /// + /// If this is a write, call the configured write lock strategy and return a lock conflict + /// error if the lock cannot be acquired. #[inline] - pub(crate) fn state(&mut self, create: bool) -> Result<&mut State, SynchronizerError> { + pub(crate) fn state( + &'a mut self, + create: bool, + ) -> Result<&'a mut State, SynchronizerError> { if self.mmap.is_none() { - let mut opts = OpenOptions::new(); - opts.read(true).write(true).create(create); - - // Only add mode on Unix-based systems to allow read/write from owner/group only - #[cfg(unix)] - opts.mode(0o660); - - let state_file = opts.open(&self.state_path).map_err(FailedStateRead)?; - - let mut need_init = false; - // Reset state file size to match exactly `STATE_SIZE` - if state_file.metadata().map_err(FailedStateRead)?.len() as usize != STATE_SIZE { - state_file - .set_len(STATE_SIZE as u64) - .map_err(FailedStateRead)?; - need_init = true; - } + self.prepare_mmap(create)?; + } - let mut mmap = unsafe { MmapMut::map_mut(&state_file).map_err(FailedStateRead)? }; - if need_init { - // Create new state and write it to mapped memory - let new_state = State::default(); - unsafe { - mmap.as_mut_ptr() - .copy_from((&new_state as *const State) as *const u8, STATE_SIZE) - }; - }; + if WRITE { + let mut guard = self.mmap.as_mut().unwrap().lock()?; + Ok(unsafe { &mut *(guard.deref_mut().as_ptr() as *mut State) }) + } else { + let mmap = self.mmap.as_ref().unwrap().read(); + Ok(unsafe { &mut *(mmap.as_ptr() as *mut State) }) + } + } - self.mmap = Some(mmap); + /// Initialize mmaped memory from the state file. + #[inline] + pub(crate) fn prepare_mmap(&mut self, create: bool) -> Result<(), SynchronizerError> { + let mut opts = OpenOptions::new(); + opts.read(true).write(true).create(create); + + // Only add mode on Unix-based systems to allow read/write from owner/group only + #[cfg(unix)] + opts.mode(0o660); + + let state_file = opts.open(&self.state_path).map_err(FailedStateRead)?; + + let mut need_init = false; + // Reset state file size to match exactly `STATE_SIZE` + if state_file.metadata().map_err(FailedStateRead)?.len() as usize != STATE_SIZE { + state_file + .set_len(STATE_SIZE as u64) + .map_err(FailedStateRead)?; + need_init = true; } - Ok(unsafe { &mut *(self.mmap.as_ref().unwrap().as_ptr() as *mut State) }) + + let mut mmap = unsafe { MmapMut::map_mut(&state_file).map_err(FailedStateRead)? }; + if need_init { + // Create new state and write it to mapped memory + let new_state = State::default(); + unsafe { + mmap.as_mut_ptr() + .copy_from((&new_state as *const State) as *const u8, STATE_SIZE) + }; + }; + + self.mmap = Some(WL::new(mmap, state_file)); + Ok(()) + } +} + +#[cfg(all(test, unix))] +mod tests { + use super::*; + use crate::locks::SingleWriter; + use crate::synchronizer::SynchronizerError; + + #[test] + fn single_writer_lock_mode_prevents_duplicate_writer() { + static PATH: &str = "/tmp/single_writer_lock_test"; + let mut state1 = StateContainer::::new(PATH.as_ref()); + let mut state2 = StateContainer::::new(PATH.as_ref()); + + assert!(state1.state::(true).is_ok()); + assert!(matches!( + state2.state::(true), + Err(SynchronizerError::WriteLockConflict) + )); + } + + #[test] + fn single_writer_lock_freed_on_drop() { + static PATH: &str = "/tmp/single_writer_lock_drop_test"; + let mut state1 = StateContainer::::new(PATH.as_ref()); + let mut state2 = StateContainer::::new(PATH.as_ref()); + + assert!(state1.state::(true).is_ok()); + drop(state1); + assert!(state2.state::(true).is_ok()); } } diff --git a/src/synchronizer.rs b/src/synchronizer.rs index 0bc3c66..2cf87d2 100644 --- a/src/synchronizer.rs +++ b/src/synchronizer.rs @@ -18,6 +18,7 @@ use wyhash::WyHash; use crate::data::DataContainer; use crate::guard::{ReadGuard, ReadResult}; use crate::instance::InstanceVersion; +use crate::locks::{LockDisabled, WriteLockStrategy}; use crate::state::StateContainer; use crate::synchronizer::SynchronizerError::*; @@ -27,15 +28,17 @@ use crate::synchronizer::SynchronizerError::*; /// /// Template parameters: /// - `H` - hasher used for checksum calculation +/// - `WL` - optional write locking to prevent multiple writers. (default [`LockDisabled`]) /// - `N` - serializer scratch space size /// - `SD` - sleep duration in nanoseconds used by writer during lock acquisition (default 1s) pub struct Synchronizer< H: Hasher + Default = WyHash, + WL = LockDisabled, const N: usize = 1024, const SD: u64 = 1_000_000_000, > { /// Container storing state mmap - state_container: StateContainer, + state_container: StateContainer, /// Container storing data mmap data_container: DataContainer, /// Hasher used for checksum calculation @@ -70,6 +73,9 @@ pub enum SynchronizerError { /// The instance version parameters were invalid. #[error("invalid instance version params")] InvalidInstanceVersionParams, + /// Write locking is enabled and the lock is held by another writer. + #[error("write blocked by conflicting lock")] + WriteLockConflict, } impl Synchronizer { @@ -79,7 +85,11 @@ impl Synchronizer { } } -impl Synchronizer { +impl<'a, H, WL, const N: usize, const SD: u64> Synchronizer +where + H: Hasher + Default, + WL: WriteLockStrategy<'a>, +{ /// Create new instance of `Synchronizer` using given `path_prefix` and template parameters pub fn with_params(path_prefix: &OsStr) -> Self { Synchronizer { @@ -108,7 +118,7 @@ impl Synchronizer /// A result containing a tuple of the number of bytes written and a boolean indicating whether /// the reader count was reset, or a `SynchronizerError` if the operation fails. pub fn write( - &mut self, + &'a mut self, entity: &T, grace_duration: Duration, ) -> Result<(usize, bool), SynchronizerError> @@ -134,7 +144,7 @@ impl Synchronizer check_archived_root::(&data).map_err(|_| FailedEntityRead)?; // fetch current state from mapped memory - let state = self.state_container.state(true)?; + let state = self.state_container.state::(true)?; // calculate data checksum let mut hasher = self.build_hasher.build_hasher(); @@ -160,7 +170,7 @@ impl Synchronizer /// Returns number of bytes written to data file and a boolean flag, for diagnostic purposes, /// indicating that we have reset our readers counter after a reader died without decrementing it. pub fn write_raw( - &mut self, + &'a mut self, data: &[u8], grace_duration: Duration, ) -> Result<(usize, bool), SynchronizerError> @@ -169,7 +179,7 @@ impl Synchronizer T::Archived: for<'b> CheckBytes>, { // fetch current state from mapped memory - let state = self.state_container.state(true)?; + let state = self.state_container.state::(true)?; // calculate data checksum let mut hasher = self.build_hasher.build_hasher(); @@ -206,13 +216,16 @@ impl Synchronizer /// `rkyv::archived_root` function, which has its own safety considerations. Particularly, it /// assumes the byte slice provided to it accurately represents an archived object, and that the /// root of the object is stored at the end of the slice. - pub unsafe fn read(&mut self, check_bytes: bool) -> Result, SynchronizerError> + pub unsafe fn read( + &'a mut self, + check_bytes: bool, + ) -> Result, SynchronizerError> where T: Archive, T::Archived: for<'b> CheckBytes>, { // fetch current state from mapped memory - let state = self.state_container.state(false)?; + let state = self.state_container.state::(false)?; // fetch current version let version = state.version()?; @@ -234,9 +247,9 @@ impl Synchronizer /// Returns current `InstanceVersion` stored within the state, useful for detecting /// whether synchronized `entity` has changed. - pub fn version(&mut self) -> Result { + pub fn version(&'a mut self) -> Result { // fetch current state from mapped memory - let state = self.state_container.state(false)?; + let state = self.state_container.state::(false)?; // fetch current version state.version() @@ -246,7 +259,8 @@ impl Synchronizer #[cfg(test)] mod tests { use crate::instance::InstanceVersion; - use crate::synchronizer::Synchronizer; + use crate::locks::SingleWriter; + use crate::synchronizer::{Synchronizer, SynchronizerError}; use bytecheck::CheckBytes; use rand::distributions::Uniform; use rand::prelude::*; @@ -255,6 +269,7 @@ mod tests { use std::fs; use std::path::Path; use std::time::Duration; + use wyhash::WyHash; #[derive(Archive, Deserialize, Serialize, Debug, PartialEq)] #[archive_attr(derive(CheckBytes))] @@ -384,4 +399,20 @@ mod tests { assert_eq!(actual_entity.version, expected_entity.version); assert_eq!(actual_entity.is_switched(), expected_is_switched); } + + #[test] + fn single_writer_lock_prevents_multiple_writers() { + static PATH: &str = "/tmp/synchronizer_single_writer"; + let mut entity_generator = MockEntityGenerator::new(3); + let entity = entity_generator.gen(100); + + let mut writer1 = Synchronizer::::with_params(PATH.as_ref()); + let mut writer2 = Synchronizer::::with_params(PATH.as_ref()); + + writer1.write(&entity, Duration::from_secs(1)).unwrap(); + assert!(matches!( + writer2.write(&entity, Duration::from_secs(1)), + Err(SynchronizerError::WriteLockConflict) + )); + } }