diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66113a7c..851ec469 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,12 +29,15 @@ jobs: with: options: "--check --diff -C" + - name: Update Stable Rust toolchain + run: rustup update stable + - name: Install rust components run: | - rustup component add clippy rustfmt + rustup +stable component add clippy rustfmt - name: Run rustfmt - run: cargo fmt --check -- --config "unstable_features=true,imports_granularity=Crate,group_imports=StdExternalCrate" + run: cargo +stable fmt --check -- --config "unstable_features=true,imports_granularity=Crate,group_imports=StdExternalCrate" - name: Clippy no-default-features run: cargo +stable clippy --no-default-features --all-targets -- --deny warnings diff --git a/Cargo.lock b/Cargo.lock index cf8897de..689f84ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,16 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "advisory-lock" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6caee7d48f930f9ad3fc9546f8cbf843365da0c5b0ca4eee1d1ac3dd12d8f93" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "aes" version = "0.8.4" @@ -181,6 +191,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.6.0" @@ -199,6 +215,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "buddy_system_allocator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7913f22349ffcfc6ca0ca9a656ec26cfbba538ed49c31a273dff2c5d1ea83d9" +dependencies = [ + "spin 0.9.8", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -238,6 +263,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "cfg_aliases" version = "0.2.1" @@ -1061,13 +1092,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-uring" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", "libc", ] @@ -1183,7 +1223,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -1198,7 +1238,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags", + "bitflags 2.6.0", "libc", ] @@ -1210,9 +1250,9 @@ checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1299,9 +1339,9 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags", + "bitflags 2.6.0", "cfg-if", - "cfg_aliases", + "cfg_aliases 0.2.1", "libc", ] @@ -1461,6 +1501,31 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + [[package]] name = "paste" version = "1.0.15" @@ -1842,6 +1907,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -1948,7 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db09040cc89e461f1a265139777a2bde7f8d8c67c4936f700c63ce3e2904d468" dependencies = [ "base64", - "bitflags", + "bitflags 2.6.0", "serde", "serde_derive", "unicode-ident", @@ -2200,7 +2274,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -2508,6 +2582,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags 1.3.2", + "cfg_aliases 0.1.1", + "libc", + "parking_lot", + "parking_lot_core", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1389c88ddd739ec6d3f8f83343764a0e944cd23cfbf126a9796a714b0b6edd6f" +dependencies = [ + "cfg_aliases 0.1.1", + "memchr", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "strsim" version = "0.11.1" @@ -2553,6 +2655,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "talc" +version = "4.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3ae828aa394de34c7de08f522d1b86bd1c182c668d27da69caadda00590f26d" + [[package]] name = "target-lexicon" version = "0.13.3" @@ -2599,6 +2707,20 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "thread-priority" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe075d7053dae61ac5413a34ea7d4913b6e6207844fd726bdd858b37ff72bf5" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "libc", + "log", + "rustversion", + "winapi", +] + [[package]] name = "thread_local" version = "1.1.8" @@ -3124,6 +3246,15 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "win-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b7b128a98c1cfa201b09eb49ba285887deb3cbe7466a98850eb1adabb452be5" +dependencies = [ + "windows", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3155,6 +3286,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +dependencies = [ + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -3249,6 +3393,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -3261,6 +3411,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -3279,6 +3435,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -3291,6 +3453,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -3315,6 +3483,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -3398,7 +3572,7 @@ dependencies = [ [[package]] name = "zenoh" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "ahash", "arc-swap", @@ -3439,6 +3613,7 @@ dependencies = [ "zenoh-protocol", "zenoh-result", "zenoh-runtime", + "zenoh-shm", "zenoh-sync", "zenoh-task", "zenoh-transport", @@ -3448,7 +3623,7 @@ dependencies = [ [[package]] name = "zenoh-buffers" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "zenoh-collections", ] @@ -3456,18 +3631,19 @@ dependencies = [ [[package]] name = "zenoh-codec" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "tracing", "uhlc", "zenoh-buffers", "zenoh-protocol", + "zenoh-shm", ] [[package]] name = "zenoh-collections" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "ahash", ] @@ -3475,7 +3651,7 @@ dependencies = [ [[package]] name = "zenoh-config" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "json5", "nonempty-collections", @@ -3499,7 +3675,7 @@ dependencies = [ [[package]] name = "zenoh-core" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "lazy_static", "tokio", @@ -3510,7 +3686,7 @@ dependencies = [ [[package]] name = "zenoh-crypto" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "aes", "hmac", @@ -3523,7 +3699,7 @@ dependencies = [ [[package]] name = "zenoh-ext" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "bincode", @@ -3542,7 +3718,7 @@ dependencies = [ [[package]] name = "zenoh-keyexpr" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "getrandom 0.2.15", "hashbrown 0.16.0", @@ -3557,7 +3733,7 @@ dependencies = [ [[package]] name = "zenoh-link" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "zenoh-config", "zenoh-link-commons", @@ -3575,7 +3751,7 @@ dependencies = [ [[package]] name = "zenoh-link-commons" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "base64", @@ -3608,7 +3784,7 @@ dependencies = [ [[package]] name = "zenoh-link-quic" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "base64", @@ -3633,7 +3809,7 @@ dependencies = [ [[package]] name = "zenoh-link-quic_datagram" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "quinn", @@ -3653,7 +3829,7 @@ dependencies = [ [[package]] name = "zenoh-link-tcp" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "socket2 0.5.7", @@ -3670,7 +3846,7 @@ dependencies = [ [[package]] name = "zenoh-link-tls" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "base64", @@ -3699,7 +3875,7 @@ dependencies = [ [[package]] name = "zenoh-link-udp" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "libc", @@ -3720,7 +3896,7 @@ dependencies = [ [[package]] name = "zenoh-link-unixsock_stream" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "nix", @@ -3738,7 +3914,7 @@ dependencies = [ [[package]] name = "zenoh-link-ws" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "futures-util", @@ -3758,7 +3934,7 @@ dependencies = [ [[package]] name = "zenoh-macros" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "proc-macro2", "quote", @@ -3769,7 +3945,7 @@ dependencies = [ [[package]] name = "zenoh-plugin-trait" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "git-version", "libloading", @@ -3786,7 +3962,7 @@ dependencies = [ [[package]] name = "zenoh-protocol" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "const_format", "rand", @@ -3801,11 +3977,8 @@ dependencies = [ name = "zenoh-python" version = "1.5.1" dependencies = [ - "flume", - "json5", "paste", "pyo3", - "validated_struct", "zenoh", "zenoh-ext", ] @@ -3813,7 +3986,7 @@ dependencies = [ [[package]] name = "zenoh-result" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "anyhow", ] @@ -3821,7 +3994,7 @@ dependencies = [ [[package]] name = "zenoh-runtime" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "lazy_static", "ron", @@ -3832,10 +4005,39 @@ dependencies = [ "zenoh-result", ] +[[package]] +name = "zenoh-shm" +version = "1.5.1" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" +dependencies = [ + "advisory-lock", + "async-trait", + "buddy_system_allocator", + "cfg_aliases 0.2.1", + "crossbeam-channel", + "crossbeam-queue", + "nix", + "num-traits", + "rand", + "stabby", + "static_assertions", + "static_init", + "talc", + "thread-priority", + "tokio", + "tracing", + "win-sys", + "winapi", + "zenoh-buffers", + "zenoh-core", + "zenoh-macros", + "zenoh-result", +] + [[package]] name = "zenoh-sync" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "arc-swap", "event-listener", @@ -3849,7 +4051,7 @@ dependencies = [ [[package]] name = "zenoh-task" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "futures", "tokio", @@ -3862,7 +4064,7 @@ dependencies = [ [[package]] name = "zenoh-transport" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "crossbeam-utils", @@ -3888,6 +4090,7 @@ dependencies = [ "zenoh-protocol", "zenoh-result", "zenoh-runtime", + "zenoh-shm", "zenoh-sync", "zenoh-task", "zenoh-util", @@ -3896,7 +4099,7 @@ dependencies = [ [[package]] name = "zenoh-util" version = "1.5.1" -source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#2215c6b6e0b3fa17f7cada92f8118f8282b5caa4" +source = "git+https://github.com/eclipse-zenoh/zenoh.git?branch=main#b6e4078ca07821c05b544aeba7efc5f5cb556780" dependencies = [ "async-trait", "const_format", diff --git a/Cargo.toml b/Cargo.toml index 620b16ab..a2f968d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,20 +34,15 @@ name = "zenoh" crate-type = ["cdylib"] [features] -default = ["zenoh/default", "zenoh-ext"] -zenoh-ext = ["dep:zenoh-ext", "zenoh-ext/unstable", "zenoh-ext/internal"] +default = ["shared-memory", "zenoh-ext", "zenoh/default"] +shared-memory = ["zenoh/shared-memory"] +zenoh-ext = ["dep:zenoh-ext", "zenoh-ext/internal", "zenoh-ext/unstable"] [badges] maintenance = { status = "actively-developed" } [dependencies] -flume = "0.11.0" -json5 = "0.4.1" paste = "1.0.14" -pyo3 = { version = "0.25.1", features = [ - "extension-module", - "abi3-py38", -] } -validated_struct = "2.1.0" -zenoh = { version = "1.5.1", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["unstable", "internal"], default-features = false } +pyo3 = { version = "0.25.1", features = ["abi3-py39", "extension-module"] } +zenoh = { version = "1.5.1", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal", "unstable"], default-features = false } zenoh-ext = { version = "1.5.1", git = "https://github.com/eclipse-zenoh/zenoh.git", branch = "main", features = ["internal"], optional = true } diff --git a/docs/conf.py b/docs/conf.py index 1f6d3957..95c44bd0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -27,6 +27,8 @@ import zenoh import zenoh.ext +import zenoh.handlers +import zenoh.shm # -- Project information ----------------------------------------------------- diff --git a/docs/index.rst b/docs/index.rst index 48e511c9..b0fc7b2e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -81,3 +81,10 @@ module zenoh.ext .. automodule:: zenoh.ext :members: :undoc-members: + +module zenoh.shm +================ + +.. automodule:: zenoh.shm + :members: + :undoc-members: diff --git a/docs/stubs_to_sources.py b/docs/stubs_to_sources.py index 3e5a99fe..8c37df29 100644 --- a/docs/stubs_to_sources.py +++ b/docs/stubs_to_sources.py @@ -27,8 +27,6 @@ from pathlib import Path PACKAGE = (Path(__file__) / "../../zenoh").resolve() -__INIT__ = PACKAGE / "__init__.py" -EXT = PACKAGE / "ext.py" def _unstable(item): @@ -40,13 +38,18 @@ def _unstable(item): return item -class RemoveOverload(ast.NodeTransformer): +class Sourcify(ast.NodeTransformer): def __init__(self): self.current_cls = None # only the first overloaded signature is modified, others are removed # modified functions are stored here self.overloaded_by_class: defaultdict[str | None, set[str]] = defaultdict(set) + def visit_ImportFrom(self, node: ast.ImportFrom): + # remove `from . import ext` kind of imports, + # as they cause circular import outside of stubs + return node if node.module is not None else None + def visit_ClassDef(self, node: ast.ClassDef): # register the current class for method name disambiguation self.current_cls = node.name @@ -55,6 +58,9 @@ def visit_ClassDef(self, node: ast.ClassDef): return res def visit_FunctionDef(self, node: ast.FunctionDef): + # replace _unstable + if node.name == "_unstable": + return ast.parse(inspect.getsource(_unstable)) for decorator in node.decorator_list: if isinstance(decorator, ast.Name) and decorator.id == "overload": if node.name in self.overloaded_by_class[self.current_cls]: @@ -75,20 +81,20 @@ def visit_FunctionDef(self, node: ast.FunctionDef): node.decorator_list.clear() if node.name not in ("recv", "try_recv", "__iter__"): # retrieve the handled type (Scout/Reply/etc.) from the return type - assert isinstance(node.returns, ast.Subscript) - if isinstance(node.returns.slice, ast.Subscript): - # `Subscriber[Handler[Sample]]` case - tp = node.returns.slice.slice - else: - # `Handler[Reply]` case - tp = node.returns.slice - assert isinstance(tp, ast.Name) - # replace `handler` parameter annotation - annotation = f"_RustHandler[{tp.id}] | tuple[Callable[[{tp.id}], Any], Any] | Callable[[{tp.id}], Any] | None" - for arg in (*node.args.args, *node.args.kwonlyargs): - if arg.arg == "handler": - arg.annotation = ast.parse(annotation) - node.returns = node.returns.value + if isinstance(node.returns, ast.Subscript): + if isinstance(node.returns.slice, ast.Subscript): + # `Subscriber[Handler[Sample]]` case + tp = node.returns.slice.slice + else: + # `Handler[Reply]` case + tp = node.returns.slice + assert isinstance(tp, ast.Name) + # replace `handler` parameter annotation + annotation = f"_RustHandler[{tp.id}] | tuple[Callable[[{tp.id}], Any], Any] | Callable[[{tp.id}], Any] | None" + for arg in (*node.args.args, *node.args.kwonlyargs): + if arg.arg == "handler": + arg.annotation = ast.parse(annotation) + node.returns = node.returns.value # stringify all parameters and return annotation for arg in (*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs): if ann := arg.annotation: @@ -99,26 +105,17 @@ def visit_FunctionDef(self, node: ast.FunctionDef): def main(): - fnames = [__INIT__, EXT] - for fname in fnames: - # remove *.py - fname.unlink() - # rename stubs for entry in PACKAGE.glob("*.pyi"): - entry.rename(PACKAGE / f"{entry.stem}.py") - for fname in fnames: - # read stub code - with open(fname) as f: + # read stub file + with open(entry) as f: stub: ast.Module = ast.parse(f.read()) - # replace _unstable - for i, stmt in enumerate(stub.body): - if isinstance(stmt, ast.FunctionDef) and stmt.name == "_unstable": - stub.body[i] = ast.parse(inspect.getsource(_unstable)) - # remove overload - stub = RemoveOverload().visit(stub) - # write modified code - with open(fname, "w") as f: + # update ast to make it like source + stub = Sourcify().visit(stub) + # write modified code into source file + with open(PACKAGE / f"{entry.stem}.py", "w") as f: f.write(ast.unparse(stub)) + # remove stub file + entry.unlink() if __name__ == "__main__": diff --git a/examples/z_pub_shm.py b/examples/z_pub_shm.py new file mode 100644 index 00000000..dabcf983 --- /dev/null +++ b/examples/z_pub_shm.py @@ -0,0 +1,89 @@ +# +# Copyright (c) 2022 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# +import time +from typing import Optional + +import zenoh + + +def main( + conf: zenoh.Config, key: str, payload: str, iter: Optional[int], interval: int +): + # initiate logging + zenoh.init_log_from_env_or("error") + + print("Opening session...") + with zenoh.open(conf) as session: + + print("Creating POSIX SHM provider...") + provider = zenoh.shm.ShmProvider.default_backend(1024 * 1024) + + print(f"Declaring Publisher on '{key}'...") + pub = session.declare_publisher(key) + + print("Press CTRL-C to quit...") + for idx in itertools.count() if iter is None else range(iter): + time.sleep(interval) + prefix = f"[{idx:4d}] " + sbuf = provider.alloc( + len(prefix) + len(payload), + policy=zenoh.shm.BlockOn(zenoh.shm.GarbageCollect()), + ) + sbuf[: len(prefix)] = prefix.encode() + sbuf[len(prefix) :] = payload.encode() + + print(f"Putting Data ('{key}': '{sbuf}')...") + pub.put(sbuf) + + +# --- Command line argument parsing --- --- --- --- --- --- +if __name__ == "__main__": + import argparse + import itertools + + import common + + parser = argparse.ArgumentParser(prog="z_pub", description="zenoh pub example") + common.add_config_arguments(parser) + parser.add_argument( + "--key", + "-k", + dest="key", + default="demo/example/zenoh-python-pub", + type=str, + help="The key expression to publish onto.", + ) + parser.add_argument( + "--payload", + "-p", + dest="payload", + default="Pub from Python!", + type=str, + help="The payload to publish.", + ) + parser.add_argument( + "--iter", dest="iter", type=int, help="How many puts to perform" + ) + parser.add_argument( + "--interval", + dest="interval", + type=float, + default=1.0, + help="Interval between each put", + ) + + args = parser.parse_args() + conf = common.get_config_from_args(args) + + main(conf, args.key, args.payload, args.iter, args.interval) diff --git a/src/bytes.rs b/src/bytes.rs index 8f9640f7..921bb9e3 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,4 +1,3 @@ -use std::borrow::Cow; // // Copyright (c) 2024 ZettaScale Technology // @@ -12,7 +11,7 @@ use std::borrow::Cow; // Contributors: // ZettaScale Zenoh Team, // -use std::io::Read; +use std::{borrow::Cow, io::Read}; use pyo3::{ exceptions::{PyTypeError, PyValueError}, @@ -42,6 +41,10 @@ impl ZBytes { } else if let Ok(string) = obj.downcast::() { Ok(Self(string.to_string().into())) } else { + #[cfg(feature = "shared-memory")] + if let Ok(buf) = obj.downcast_exact::() { + return Ok(Self(buf.borrow_mut().take()?.into())); + } Err(PyTypeError::new_err(format!( "expected bytes/str type, found '{}'", obj.get_type().name().unwrap() diff --git a/src/config.rs b/src/config.rs index b787bca8..26769c3b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -162,7 +162,7 @@ wrapper!(zenoh::config::ZenohId: Clone, Copy); #[pymethods] impl ZenohId { fn __bytes__<'py>(&self, py: Python<'py>) -> PyResult> { - TimestampId(self.0.to_le_bytes().try_into().into_pyres()?).__bytes__(py) + Ok(TimestampId(self.0.to_le_bytes().try_into().into_pyres()?).__bytes__(py)) } fn __eq__(&self, other: ZenohId) -> PyResult { diff --git a/src/lib.rs b/src/lib.rs index e5c1926e..68df51e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,6 +28,8 @@ mod query; mod sample; mod scouting; mod session; +#[cfg(feature = "shared-memory")] +mod shm; mod time; mod utils; @@ -35,6 +37,7 @@ use pyo3::prelude::*; pyo3::create_exception!(zenoh, ZError, pyo3::exceptions::PyException); // must be defined here or exporting doesn't work +#[cfg(feature = "zenoh-ext")] pyo3::create_exception!(zenoh, ZDeserializeError, pyo3::exceptions::PyException); #[pymodule] @@ -82,13 +85,24 @@ pub(crate) mod zenoh { #[pymodule] mod _ext { #[pymodule_export] - use crate::ext::{ - declare_advanced_publisher, declare_advanced_subscriber, z_deserialize, z_serialize, - AdvancedPublisher, AdvancedSubscriber, CacheConfig, HistoryConfig, Miss, - MissDetectionConfig, RecoveryConfig, RepliesConfig, SampleMissListener, + use crate::{ + ext::{ + declare_advanced_publisher, declare_advanced_subscriber, z_deserialize, + z_serialize, AdvancedPublisher, AdvancedSubscriber, CacheConfig, HistoryConfig, + Miss, MissDetectionConfig, RecoveryConfig, RepliesConfig, SampleMissListener, + }, + ZDeserializeError, }; + } + + #[cfg(feature = "shared-memory")] + #[pymodule] + mod shm { #[pymodule_export] - use crate::ZDeserializeError; + use crate::shm::{ + AllocAlignment, BlockOn, Deallocate, Defragment, GarbageCollect, JustAlloc, + MemoryLayout, ShmProvider, ZShmMut, + }; } #[pymodule_init] @@ -97,6 +111,8 @@ pub(crate) mod zenoh { sys_modules.set_item("zenoh.handlers", m.getattr("handlers")?)?; #[cfg(feature = "zenoh-ext")] sys_modules.set_item("zenoh._ext", m.getattr("_ext")?)?; + #[cfg(feature = "shared-memory")] + sys_modules.set_item("zenoh.shm", m.getattr("shm")?)?; // TODO // crate::logging::init_logger(m.py())?; Ok(()) diff --git a/src/macros.rs b/src/macros.rs index 9ab99a88..16f63edd 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -141,6 +141,9 @@ macro_rules! wrapper { ($($path:ident)::* $(<$arg:lifetime>)? $(:$($derive:ty),*)?) => { $crate::macros::wrapper!(@ $($path)::*, $($path)::* $(<$arg>)? $(:$($derive),*)?); }; + ($($path:ident)::* $(<$($arg:ty),*>)? $(:$($derive:ty),*)?) => { + $crate::macros::wrapper!(@ $($path)::*, $($path)::* $(<$($arg),*>)? $(:$($derive),*)?); + }; (@ $ty:ident::$($tt:ident)::*, $path:path $(:$($derive:ty),*)?) => { $crate::macros::wrapper!(@ $($tt)::*, $path $(:$($derive),*)?); }; @@ -183,8 +186,8 @@ macro_rules! option_wrapper { ($($path:ident)::* $(<$arg:lifetime>)?, $error:literal) => { $crate::macros::option_wrapper!(@ $($path)::*, $($path)::* $(<$arg>)?, $error); }; - ($($path:ident)::* $(<$arg:ty>)?, $error:literal) => { - $crate::macros::option_wrapper!(@ $($path)::*, $($path)::* $(<$arg>)?, $error); + ($($path:ident)::* $(<$($arg:ty),*>)?, $error:literal) => { + $crate::macros::option_wrapper!(@ $($path)::*, $($path)::* $(<$($arg),*>)?, $error); }; (@ $ty:ident::$($tt:ident)::*, $path:path, $error:literal) => { $crate::macros::option_wrapper!(@ $($tt)::*, $path, $error); diff --git a/src/shm.rs b/src/shm.rs new file mode 100644 index 00000000..798d6210 --- /dev/null +++ b/src/shm.rs @@ -0,0 +1,283 @@ +use std::{num::NonZeroUsize, str, sync::Arc}; + +use pyo3::{ + exceptions::{PyTypeError, PyValueError}, + prelude::*, + types::{PyByteArray, PyBytes, PySlice, PyString, PyType}, +}; +use zenoh::shm::{ChunkAllocResult, PosixShmProviderBackend}; + +use crate::{ + macros::{downcast_or_new, wrapper, zerror}, + utils::{wait, IntoPyResult, MapInto}, +}; + +wrapper!(zenoh::shm::AllocAlignment: Clone); + +#[pymethods] +impl AllocAlignment { + #[classattr] + const ALIGN_1_BYTE: Self = Self(zenoh::shm::AllocAlignment::ALIGN_1_BYTE); + #[classattr] + const ALIGN_2_BYTE: Self = Self(zenoh::shm::AllocAlignment::ALIGN_2_BYTES); + #[classattr] + const ALIGN_4_BYTE: Self = Self(zenoh::shm::AllocAlignment::ALIGN_4_BYTES); + #[classattr] + const ALIGN_8_BYTE: Self = Self(zenoh::shm::AllocAlignment::ALIGN_8_BYTES); + + #[new] + fn new(pow: u8) -> PyResult { + Ok(Self(zenoh::shm::AllocAlignment::new(pow).into_pyres()?)) + } + + fn get_alignment_value(&self) -> NonZeroUsize { + self.0.get_alignment_value() + } + + fn align_size(&self, size: NonZeroUsize) -> NonZeroUsize { + self.0.align_size(size) + } +} + +#[derive(Clone)] +pub struct AllocPolicy( + Option + Send + Sync>>, +); + +impl FromPyObject<'_> for AllocPolicy { + fn extract_bound(ob: &Bound<'_, PyAny>) -> PyResult { + if ob.is_none() || ob.is_exact_instance_of::() { + Ok(Self(None)) + } else if let Ok(policy) = ob.extract::() { + Ok(Self(Some(Arc::new(policy.0)))) + } else if let Ok(policy) = ob.extract::() { + Ok(Self(Some(Arc::new(policy.0)))) + } else if let Ok(policy) = ob.extract::() { + Ok(Self(Some(Arc::new(policy.0)))) + } else if let Ok(policy) = ob.extract::() { + Ok(Self(Some(Arc::new(policy.0)))) + } else { + Err(PyTypeError::new_err("expected policy type")) + } + } +} + +impl zenoh::shm::AllocPolicy for AllocPolicy { + fn alloc( + &self, + layout: &zenoh::shm::MemoryLayout, + provider: &zenoh::shm::ShmProvider, + ) -> ChunkAllocResult { + self.0 + .as_deref() + .unwrap_or(&zenoh::shm::JustAlloc) + .alloc(layout, provider) + } +} + +wrapper!(zenoh::shm::BlockOn: Clone); + +#[pymethods] +impl BlockOn { + #[new] + #[pyo3(signature = (inner_policy = AllocPolicy(None)))] + fn new(inner_policy: AllocPolicy) -> Self { + Self(zenoh::shm::BlockOn::new(inner_policy)) + } +} + +wrapper!(zenoh::shm::Deallocate: Clone); + +#[pymethods] +impl Deallocate { + #[new] + #[pyo3(signature = (limit, inner_policy = AllocPolicy(None), alt_policy = AllocPolicy(None)) + )] + fn new(limit: usize, inner_policy: AllocPolicy, alt_policy: AllocPolicy) -> Self { + Self(zenoh::shm::Deallocate::new(limit, inner_policy, alt_policy)) + } +} + +wrapper!(zenoh::shm::Defragment: Clone); + +#[pymethods] +impl Defragment { + #[new] + #[pyo3(signature = (inner_policy = AllocPolicy(None), alt_policy = AllocPolicy(None)))] + fn new(inner_policy: AllocPolicy, alt_policy: AllocPolicy) -> Self { + Self(zenoh::shm::Defragment::new(inner_policy, alt_policy)) + } +} + +wrapper!(zenoh::shm::GarbageCollect: Clone); + +#[pymethods] +impl GarbageCollect { + #[new] + #[pyo3(signature = (inner_policy = AllocPolicy(None), alt_policy = AllocPolicy(None), safe = true) + )] + fn new(inner_policy: AllocPolicy, alt_policy: AllocPolicy, safe: bool) -> Self { + Self(zenoh::shm::GarbageCollect::new( + inner_policy, + alt_policy, + safe, + )) + } +} + +wrapper!(zenoh::shm::JustAlloc:Clone); + +#[pymethods] +impl JustAlloc { + #[new] + fn new() -> Self { + Self(zenoh::shm::JustAlloc) + } +} + +wrapper!(zenoh::shm::MemoryLayout: Clone); +downcast_or_new!(MemoryLayout); + +#[pymethods] +impl MemoryLayout { + #[new] + fn new(obj: &Bound) -> PyResult { + let layout = if let Ok(layout) = obj.extract::() { + layout.try_into() + } else if let Ok((size, layout)) = obj.extract::<(usize, AllocAlignment)>() { + (size, layout.0).try_into() + } else { + return Err(PyTypeError::new_err( + "expected int/tuple[int, AllocAlignment]", + )); + }; + Ok(Self(layout.into_pyres()?)) + } + + #[getter] + fn size(&self) -> NonZeroUsize { + self.0.size() + } + + #[getter] + fn alignment(&self) -> AllocAlignment { + AllocAlignment(self.0.alignment()) + } +} + +wrapper!(zenoh::shm::ShmProvider); + +#[pymethods] +impl ShmProvider { + #[classmethod] + fn default_backend( + _cls: &Bound, + py: Python, + #[pyo3(from_py_with = MemoryLayout::from_py)] layout: MemoryLayout, + ) -> PyResult { + let builder = zenoh::shm::ShmProviderBuilder::default_backend(layout.0); + wait(py, builder).map_into() + } + + #[pyo3(signature = (layout, policy = AllocPolicy(None)))] + fn alloc( + &self, + py: Python, + #[pyo3(from_py_with = MemoryLayout::from_py)] layout: MemoryLayout, + policy: AllocPolicy, + ) -> PyResult { + // SAFETY: we are in Python... + let builder = unsafe { self.0.alloc(layout.0).with_runtime_policy(policy) }; + wait(py, builder).map_into() + } + + fn defragment(&self) { + self.0.defragment(); + } + + fn garbage_collect(&self) -> usize { + self.0.garbage_collect() + } + + fn garbage_collect_unsafe(&self) -> usize { + // SAFETY: we are in Python... + unsafe { self.0.garbage_collect_unsafe() } + } + + #[getter] + fn available(&self) -> usize { + self.0.available() + } +} + +#[pyclass] +pub(crate) struct ZShmMut { + buf: Option, +} + +impl ZShmMut { + fn get(&self) -> PyResult<&zenoh::shm::ZShmMut> { + self.buf + .as_ref() + .ok_or_else(|| zerror!("ZShmMut has been consumed by ZBytes conversion")) + } + fn get_mut(&mut self) -> PyResult<&mut zenoh::shm::ZShmMut> { + self.get()?; + Ok(self.buf.as_mut().unwrap()) + } + pub(crate) fn take(&mut self) -> PyResult { + self.get()?; + Ok(self.buf.take().unwrap()) + } +} + +#[pymethods] +impl ZShmMut { + fn __setitem__(this: &Bound, key: &Bound, value: &Bound) -> PyResult<()> { + if let Ok(key) = key.extract::() { + if let Ok(value) = value.extract::() { + if let Some(entry) = this.borrow_mut().get_mut()?.get_mut(key) { + *entry = value; + return Ok(()); + } + } + } else if let Ok(key) = key.downcast::() { + let mut buffer = this.borrow_mut(); + let slice = buffer.get_mut()?; + let indices = key.indices(slice.len() as isize)?; + let mut copy_bytes = |b: &[u8]| { + if b.len() != indices.slicelength { + return Err(PyValueError::new_err( + "memoryview assignment: lvalue and rvalue have different structures", + )); + } + slice[indices.start as usize..indices.stop as usize].copy_from_slice(b); + Ok(()) + }; + if let Ok(bytes) = value.downcast::() { + return copy_bytes(unsafe { bytes.as_bytes() }); + } else if let Ok(bytes) = value.downcast::() { + return copy_bytes(bytes.as_bytes()); + } + } + Err(PyTypeError::new_err("expected bytes like argument")) + } + + fn __str__<'py>(&self, py: Python<'py>) -> PyResult> { + Ok(PyString::new(py, str::from_utf8(self.get()?).into_pyres()?)) + } + + fn __bytes__<'py>(&self, py: Python<'py>) -> PyResult> { + Ok(PyBytes::new(py, self.get()?)) + } + + fn __repr__(&self) -> PyResult { + Ok(format!("{:?}", self.get()?)) + } +} + +impl From for ZShmMut { + fn from(value: zenoh::shm::ZShmMut) -> Self { + Self { buf: Some(value) } + } +} diff --git a/src/time.rs b/src/time.rs index d162f409..0e298e84 100644 --- a/src/time.rs +++ b/src/time.rs @@ -47,16 +47,12 @@ impl TimestampId { } } - pub(crate) fn __bytes__<'py>(&self, py: Python<'py>) -> PyResult> { - let len = self.0.size(); - PyBytes::new_with(py, len, |bytes| { - bytes.copy_from_slice(&self.0.to_le_bytes()[..len]); - Ok(()) - }) + pub(crate) fn __bytes__<'py>(&self, py: Python<'py>) -> Bound<'py, PyBytes> { + PyBytes::new(py, &self.0.to_le_bytes()[..self.0.size()]) } fn __hash__(&self, py: Python) -> PyResult { - self.__bytes__(py)?.hash() + self.__bytes__(py).hash() } fn __repr__(&self) -> String { diff --git a/src/utils.rs b/src/utils.rs index 3a87492c..c3add006 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -114,9 +114,9 @@ pub(crate) fn short_type_name() -> &'static str { name.rsplit_once("::").map_or(name, |(_, name)| name) } -pub(crate) fn wait( +pub(crate) fn wait( py: Python, - resolve: impl zenoh::Resolve>, + resolve: impl zenoh::Wait> + Send, ) -> PyResult { py.allow_threads(|| resolve.wait()).into_pyres() } diff --git a/tests/examples_check.py b/tests/examples_check.py index 38118318..34f259e3 100644 --- a/tests/examples_check.py +++ b/tests/examples_check.py @@ -386,3 +386,28 @@ def test_z_advanced_pub_z_advanced_sub(): assert not pub.errors assert not sub.errors + + +def test_z_pub_shm(): + """Test z_pub_shm.""" + ## Run z_sub + sub = Pyrun("z_sub.py") + time.sleep(3) + ## z_pub: Put two messages (to storage & sub) + pub = Pyrun("z_pub.py", ["--iter=1", "--interval=0"]) + if error := pub.status(): + pub.dbg() + pub.errors.append(error) + ## z_sub_queued: Should receive two messages + if error := sub.interrupt(): + sub.dbg() + sub.errors.append(error) + sub_out = "".join(sub.stdout) + if not ( + "Received SampleKind.PUT ('demo/example/zenoh-python-pub': '[ 0] Pub from Python!')" + in sub_out + ): + sub.errors.append("z_sub_queued didn't catch the first z_pub") + + assert not pub.errors + assert not sub.errors diff --git a/zenoh/__init__.py b/zenoh/__init__.py index cbccd941..0d5b6844 100644 --- a/zenoh/__init__.py +++ b/zenoh/__init__.py @@ -17,3 +17,14 @@ from . import ext except ImportError: pass +try: + from . import shm +except ImportError: + pass + + +def __getattr__(name): + if name == "ext": + import zenoh.ext + elif name == "shm": + import zenoh.shm diff --git a/zenoh/__init__.pyi b/zenoh/__init__.pyi index 914164df..5928ae81 100644 --- a/zenoh/__init__.pyi +++ b/zenoh/__init__.pyi @@ -11,14 +11,15 @@ # Contributors: # ZettaScale Zenoh Team, # - from collections.abc import Callable from datetime import datetime, timedelta from enum import Enum, auto from pathlib import Path from typing import Any, Generic, Never, Self, TypeVar, final, overload +from . import ext as ext from . import handlers as handlers +from . import shm as shm from .handlers import Handler as Handler _T = TypeVar("_T") @@ -1229,7 +1230,7 @@ class ZBytes: encouraged to use any data format of their choice like JSON, protobuf, flatbuffers, etc.""" - def __new__(cls, bytes: bytearray | bytes | str = None) -> Self: ... + def __new__(cls, bytes: bytearray | bytes | str | shm.ZShmMut = None) -> Self: ... def to_bytes(self) -> bytes: ... def to_string(self) -> str: ... def __bool__(self) -> bool: ... diff --git a/zenoh/ext.py b/zenoh/ext.py index 028ae250..aa5fd671 100644 --- a/zenoh/ext.py +++ b/zenoh/ext.py @@ -1,3 +1,16 @@ +# +# Copyright (c) 2024 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# try: from zenoh._ext import * except ImportError: diff --git a/zenoh/ext.pyi b/zenoh/ext.pyi index 840156ce..6ae4ebc6 100644 --- a/zenoh/ext.pyi +++ b/zenoh/ext.pyi @@ -1,3 +1,16 @@ +# +# Copyright (c) 2024 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# from collections.abc import Callable from typing import Any, Generic, Literal, Never, Self, TypeVar, final, overload @@ -11,12 +24,12 @@ from zenoh import ( Priority, Reliability, Sample, + Session, Subscriber, Timestamp, ZBytes, handlers, ) -from zenoh.zenoh import Session _T = TypeVar("_T") _H = TypeVar("_H") diff --git a/zenoh/shm.py b/zenoh/shm.py new file mode 100644 index 00000000..e057e55d --- /dev/null +++ b/zenoh/shm.py @@ -0,0 +1,21 @@ +# +# Copyright (c) 2025 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# +try: + from zenoh._shm import * +except ImportError: + import warnings + + raise ModuleNotFoundError( + "No module named 'zenoh.shm'.\nzenoh must be built with shared-memory feature to enable it." + ) from None diff --git a/zenoh/shm.pyi b/zenoh/shm.pyi new file mode 100644 index 00000000..8834dc40 --- /dev/null +++ b/zenoh/shm.pyi @@ -0,0 +1,135 @@ +# +# Copyright (c) 2025 ZettaScale Technology +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +# which is available at https://www.apache.org/licenses/LICENSE-2.0. +# +# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +# +# Contributors: +# ZettaScale Zenoh Team, +# +from typing import Self, TypeVar, final, overload + +_T = TypeVar("_T") + +def _unstable(item: _T) -> _T: + """marker for unstable functionality""" + +@_unstable +@final +class AllocAlignment: + """alignment in powers of 2: 0 == 1-byte alignment, 1 == 2byte, 2 == 4byte, 3 == 8byte etc""" + + ALIGN_1_BYTE: Self + ALIGN_2_BYTE: Self + ALIGN_4_BYTE: Self + ALIGN_8_BYTE: Self + + def __new__(cls, pow: int) -> Self: ... + def get_alignment_value(self) -> int: + """Get alignment in normal units (bytes)""" + + def align_size(self, size: int) -> int: + """Align size according to inner alignment. + This call may extend the size""" + +@_unstable +@final +class JustAlloc: + """Just try to allocate""" + +@_unstable +@final +class BlockOn: + def __new__(self, inner_policy: _AllocPolicy = JustAlloc()) -> Self: ... + +@_unstable +@final +class Deallocate: + """Deallocating policy. + Forcibly deallocate up to N buffers until allocation succeeds.""" + + def __new__( + cls, + inner_policy: _AllocPolicy = JustAlloc(), + alt_policy: _AllocPolicy = JustAlloc(), + ) -> Self: ... + +@_unstable +@final +class Defragment: + def __new__( + self, + inner_policy: _AllocPolicy = JustAlloc(), + alt_policy: _AllocPolicy = JustAlloc(), + ) -> Self: ... + +@_unstable +@final +class GarbageCollect: + def __new__( + self, + inner_policy: _AllocPolicy = JustAlloc(), + alt_policy: _AllocPolicy = JustAlloc(), + *, + safe: bool = True, + ) -> Self: ... + +_AllocPolicy = JustAlloc | BlockOn | Defragment | GarbageCollect + +@_unstable +@final +class MemoryLayout: + """Memory layout representation: alignment and size aligned for this alignment""" + + def __new__(cls, size: int, alignment: AllocAlignment) -> Self: ... + @property + def size(self) -> int: ... + @property + def alignment(self) -> AllocAlignment: ... + +@_unstable +@final +class ShmProvider: + """A generalized interface for shared memory data sources""" + + @classmethod + def default_backend(cls, layout: _IntoMemoryLayout) -> Self: + """Set the default backend""" + + def alloc( + self, layout: _IntoMemoryLayout, policy: _AllocPolicy = JustAlloc() + ) -> ZShmMut: + """Rich interface for making allocations""" + + def defragment(self): + """Defragment memory""" + + def garbage_collect(self) -> int: + """Try to collect free chunks. + Returns the size of largest collected chunk""" + + def garbage_collect_unsafe(self) -> int: + """Try to collect free chunks. + Returns the size of largest collected chunk""" + + @property + def available(self) -> int: + """Bytes available for use""" + +_IntoMemoryLayout = MemoryLayout | tuple[int, AllocAlignment] | int + +@_unstable +@final +class ZShmMut: + """A mutable SHM buffer""" + + def __bytes__(self) -> bytes: ... + def __str__(self) -> str: ... + @overload + def __setitem__(self, item: int, value: int): ... + @overload + def __setitem__(self, item: slice, value: bytes | bytearray): ...