Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 + tools/ci) with a "Connect to a Different Server" menu item and popout that lets the desktop app switch to any self-hosted Fluxer instance, plus fixes for well-known discovery on single-domain self-hosted deployments and a false-positive ERR_ABORTED on same-origin client redirects during the switch. Defaults to chat.fluxr.chat and uses an isolated userData directory from the official build.
This commit is contained in:
+522
@@ -0,0 +1,522 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "cfg_aliases"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||
|
||||
[[package]]
|
||||
name = "codespan-reporting"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "document-features"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
|
||||
dependencies = [
|
||||
"litrs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fluxer_gpu_rebuild"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"num-traits",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "hexf-parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||
|
||||
[[package]]
|
||||
name = "litrs"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||
dependencies = [
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd91265cc2454558f659b3b4b9640f0ddb8cc6521277f166b8a8c181c898079"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"codespan-reporting",
|
||||
"half",
|
||||
"hashbrown 0.16.1",
|
||||
"hexf-parse",
|
||||
"indexmap",
|
||||
"libm",
|
||||
"log",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
"thiserror",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "profiling"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "renderdoc-sys"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "wgpu"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb3feacc458f7bee8bc1737149b42b6c731aa461039a4264a67bb6681646b250"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"hashbrown 0.16.1",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"smallvec",
|
||||
"static_assertions",
|
||||
"wgpu-core",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02da3ad1b568337f25513b317870960ef87073ea0945502e44b864b67a8c77b7"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"cfg_aliases",
|
||||
"document-features",
|
||||
"hashbrown 0.16.1",
|
||||
"indexmap",
|
||||
"log",
|
||||
"naga",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"portable-atomic",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"wgpu-core-deps-windows-linux-android",
|
||||
"wgpu-hal",
|
||||
"wgpu-naga-bridge",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-core-deps-windows-linux-android"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb01076d0aa08b0ba9bd741e178b5cc440f5abe99d9581323a4c8b5d1a1916"
|
||||
dependencies = [
|
||||
"wgpu-hal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-hal"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31f8e1a9e7a8512f276f7c62e018c7fa8d60954303fed2e5750114332049193f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libloading",
|
||||
"log",
|
||||
"naga",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"raw-window-handle",
|
||||
"renderdoc-sys",
|
||||
"thiserror",
|
||||
"wgpu-naga-bridge",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-naga-bridge"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59c654c483f058800972c3645e95388a7eca31bf9fe1933bc20e036588a0be02"
|
||||
dependencies = [
|
||||
"naga",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wgpu-types"
|
||||
version = "29.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9bcc31518a0e9735aefebedb5f7a9ef3ed1c42549c9f4c882fa9060ceaac639"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"log",
|
||||
"raw-window-handle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.50"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "fluxer_gpu_rebuild"
|
||||
version = "0.0.0"
|
||||
edition = "2024"
|
||||
license = "AGPL-3.0-or-later"
|
||||
publish = false
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
||||
[lib]
|
||||
crate-type = ["rlib"]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
wgpu = ["dep:wgpu"]
|
||||
|
||||
[dependencies]
|
||||
parking_lot = "0.12"
|
||||
wgpu = { version = "29", optional = true, default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
parking_lot = "0.12"
|
||||
@@ -0,0 +1,677 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
#![deny(clippy::too_many_lines)]
|
||||
#![deny(clippy::unwrap_used)]
|
||||
#![deny(clippy::panic)]
|
||||
#![deny(warnings)]
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub const MAX_REGISTERED_OWNERS: usize = 1024;
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub type GpuDevice = wgpu::Device;
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub type GpuQueue = wgpu::Queue;
|
||||
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
#[derive(Debug)]
|
||||
pub struct GpuDevice {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
#[derive(Debug)]
|
||||
pub struct GpuQueue {
|
||||
pub id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum GpuRebuildError {
|
||||
DeviceRejected { reason: &'static str },
|
||||
ResourceCreateFailed { reason: &'static str },
|
||||
OwnerInvariantBroken { reason: &'static str },
|
||||
Other { code: u32 },
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GpuRebuildError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::DeviceRejected { reason } => write!(f, "device rejected: {reason}"),
|
||||
Self::ResourceCreateFailed { reason } => write!(f, "resource create failed: {reason}"),
|
||||
Self::OwnerInvariantBroken { reason } => write!(f, "owner invariant broken: {reason}"),
|
||||
Self::Other { code } => write!(f, "other rebuild error code={code}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for GpuRebuildError {}
|
||||
|
||||
pub type OwnerId = u64;
|
||||
|
||||
pub trait GpuLossCallback: Send {
|
||||
fn release(&mut self);
|
||||
fn rebuild(&mut self, device: &GpuDevice, queue: &GpuQueue) -> Result<(), GpuRebuildError>;
|
||||
fn is_ready(&self) -> bool;
|
||||
fn debug_label(&self) -> &'static str {
|
||||
"<unlabelled-gpu-owner>"
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum RebuildOutcome {
|
||||
Rebuilt {
|
||||
owner_id: OwnerId,
|
||||
label: &'static str,
|
||||
},
|
||||
Failed {
|
||||
owner_id: OwnerId,
|
||||
label: &'static str,
|
||||
error: GpuRebuildError,
|
||||
},
|
||||
Vacant {
|
||||
owner_id: OwnerId,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RebuildReport {
|
||||
pub released_count: u32,
|
||||
pub rebuilt_count: u32,
|
||||
pub failed_count: u32,
|
||||
pub vacant_count: u32,
|
||||
pub outcomes: Vec<RebuildOutcome>,
|
||||
}
|
||||
|
||||
impl RebuildReport {
|
||||
pub fn is_total_success(&self) -> bool {
|
||||
self.failed_count == 0 && self.rebuilt_count > 0
|
||||
}
|
||||
|
||||
pub fn is_empty_pass(&self) -> bool {
|
||||
self.released_count == 0
|
||||
&& self.rebuilt_count == 0
|
||||
&& self.failed_count == 0
|
||||
&& self.vacant_count == 0
|
||||
}
|
||||
}
|
||||
|
||||
struct Slot {
|
||||
owner_id: OwnerId,
|
||||
callback: Option<Box<dyn GpuLossCallback>>,
|
||||
}
|
||||
|
||||
struct RegistryInner {
|
||||
slots: Vec<Slot>,
|
||||
next_owner_id: OwnerId,
|
||||
registration_order_monotonic_floor: OwnerId,
|
||||
}
|
||||
|
||||
impl RegistryInner {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
slots: Vec::with_capacity(MAX_REGISTERED_OWNERS),
|
||||
next_owner_id: 1,
|
||||
registration_order_monotonic_floor: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn deregister(&mut self, owner_id: OwnerId) {
|
||||
assert!(owner_id > 0, "owner_id must be positive");
|
||||
assert!(
|
||||
owner_id < self.next_owner_id,
|
||||
"owner_id must come from a real registration"
|
||||
);
|
||||
let before = self.slots.len();
|
||||
self.slots.retain(|slot| slot.owner_id != owner_id);
|
||||
let after = self.slots.len();
|
||||
assert!(after <= before, "deregister must not grow slots");
|
||||
assert!(
|
||||
after >= before.saturating_sub(1),
|
||||
"deregister removes at most one slot"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GpuLossRegistry {
|
||||
inner: Arc<Mutex<RegistryInner>>,
|
||||
}
|
||||
|
||||
impl GpuLossRegistry {
|
||||
pub fn new() -> Self {
|
||||
let inner = Arc::new(Mutex::new(RegistryInner::new()));
|
||||
let registry = Self { inner };
|
||||
assert_eq!(registry.len(), 0, "fresh registry must be empty");
|
||||
assert!(registry.is_empty(), "fresh registry must report empty");
|
||||
registry
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
let guard = self.inner.lock();
|
||||
guard.slots.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn register(&self, owner: Box<dyn GpuLossCallback>) -> RegistrationGuard {
|
||||
let mut guard = self.inner.lock();
|
||||
assert!(
|
||||
guard.slots.len() < MAX_REGISTERED_OWNERS,
|
||||
"registry exceeds MAX_REGISTERED_OWNERS",
|
||||
);
|
||||
let id = guard.next_owner_id;
|
||||
assert!(
|
||||
id > guard.registration_order_monotonic_floor,
|
||||
"ids must be monotonic"
|
||||
);
|
||||
guard.registration_order_monotonic_floor = id;
|
||||
guard.next_owner_id = id.checked_add(1).unwrap_or(OwnerId::MAX);
|
||||
guard.slots.push(Slot {
|
||||
owner_id: id,
|
||||
callback: Some(owner),
|
||||
});
|
||||
drop(guard);
|
||||
RegistrationGuard {
|
||||
owner_id: id,
|
||||
registry: Arc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_device_lost(
|
||||
&self,
|
||||
new_device: &GpuDevice,
|
||||
new_queue: &GpuQueue,
|
||||
) -> RebuildReport {
|
||||
let mut guard = self.inner.lock();
|
||||
assert!(
|
||||
guard.slots.len() <= MAX_REGISTERED_OWNERS,
|
||||
"slots within cap before walk",
|
||||
);
|
||||
let released_count = release_in_reverse_order(&mut guard.slots);
|
||||
let outcomes = rebuild_in_forward_order(&mut guard.slots, new_device, new_queue);
|
||||
let report = summarize_outcomes(outcomes, released_count);
|
||||
assert_pair_total_accounting(&guard.slots, &report);
|
||||
assert_pair_post_rebuild_ready(&guard.slots, &report);
|
||||
report
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for GpuLossRegistry {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegistrationGuard {
|
||||
owner_id: OwnerId,
|
||||
registry: Arc<Mutex<RegistryInner>>,
|
||||
}
|
||||
|
||||
impl RegistrationGuard {
|
||||
pub fn owner_id(&self) -> OwnerId {
|
||||
assert!(self.owner_id > 0, "guard must hold a valid id");
|
||||
assert!(
|
||||
Arc::strong_count(&self.registry) >= 1,
|
||||
"registry must outlive guard"
|
||||
);
|
||||
self.owner_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RegistrationGuard {
|
||||
fn drop(&mut self) {
|
||||
let owner_id = self.owner_id;
|
||||
if owner_id == 0 {
|
||||
return;
|
||||
}
|
||||
let mut guard = self.registry.lock();
|
||||
guard.deregister(owner_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn release_in_reverse_order(slots: &mut [Slot]) -> u32 {
|
||||
let total = slots.len();
|
||||
assert!(
|
||||
total <= MAX_REGISTERED_OWNERS,
|
||||
"slots within cap on release"
|
||||
);
|
||||
let mut released: u32 = 0;
|
||||
for slot in slots.iter_mut().rev() {
|
||||
if let Some(callback) = slot.callback.as_mut() {
|
||||
callback.release();
|
||||
released = released.saturating_add(1);
|
||||
}
|
||||
}
|
||||
assert!(released as usize <= total, "released cannot exceed total");
|
||||
released
|
||||
}
|
||||
|
||||
fn rebuild_in_forward_order(
|
||||
slots: &mut [Slot],
|
||||
device: &GpuDevice,
|
||||
queue: &GpuQueue,
|
||||
) -> Vec<RebuildOutcome> {
|
||||
let total = slots.len();
|
||||
assert!(
|
||||
total <= MAX_REGISTERED_OWNERS,
|
||||
"slots within cap on rebuild"
|
||||
);
|
||||
let mut outcomes: Vec<RebuildOutcome> = Vec::with_capacity(total);
|
||||
for slot in slots.iter_mut() {
|
||||
let owner_id = slot.owner_id;
|
||||
assert!(owner_id > 0, "rebuild slot must have non-zero id");
|
||||
let outcome = rebuild_single_slot(slot, device, queue);
|
||||
outcomes.push(outcome);
|
||||
}
|
||||
assert_eq!(outcomes.len(), total, "one outcome per slot");
|
||||
outcomes
|
||||
}
|
||||
|
||||
fn rebuild_single_slot(slot: &mut Slot, device: &GpuDevice, queue: &GpuQueue) -> RebuildOutcome {
|
||||
let owner_id = slot.owner_id;
|
||||
let callback = match slot.callback.as_mut() {
|
||||
Some(cb) => cb,
|
||||
None => return RebuildOutcome::Vacant { owner_id },
|
||||
};
|
||||
let label = callback.debug_label();
|
||||
match callback.rebuild(device, queue) {
|
||||
Ok(()) => RebuildOutcome::Rebuilt { owner_id, label },
|
||||
Err(error) => RebuildOutcome::Failed {
|
||||
owner_id,
|
||||
label,
|
||||
error,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn summarize_outcomes(outcomes: Vec<RebuildOutcome>, released_count: u32) -> RebuildReport {
|
||||
let mut rebuilt_count: u32 = 0;
|
||||
let mut failed_count: u32 = 0;
|
||||
let mut vacant_count: u32 = 0;
|
||||
for outcome in outcomes.iter() {
|
||||
match outcome {
|
||||
RebuildOutcome::Rebuilt { .. } => rebuilt_count = rebuilt_count.saturating_add(1),
|
||||
RebuildOutcome::Failed { .. } => failed_count = failed_count.saturating_add(1),
|
||||
RebuildOutcome::Vacant { .. } => vacant_count = vacant_count.saturating_add(1),
|
||||
}
|
||||
}
|
||||
let report = RebuildReport {
|
||||
released_count,
|
||||
rebuilt_count,
|
||||
failed_count,
|
||||
vacant_count,
|
||||
outcomes,
|
||||
};
|
||||
assert_eq!(
|
||||
report.outcomes.len() as u32,
|
||||
report.rebuilt_count + report.failed_count + report.vacant_count,
|
||||
"outcome totals must match",
|
||||
);
|
||||
report
|
||||
}
|
||||
|
||||
fn assert_pair_total_accounting(slots: &[Slot], report: &RebuildReport) {
|
||||
let total = slots.len() as u32;
|
||||
assert_eq!(
|
||||
total,
|
||||
report.rebuilt_count + report.failed_count + report.vacant_count,
|
||||
"report covers every slot",
|
||||
);
|
||||
let mut last_id: OwnerId = 0;
|
||||
for slot in slots.iter() {
|
||||
assert!(
|
||||
slot.owner_id > last_id,
|
||||
"registration ids must be monotonic"
|
||||
);
|
||||
last_id = slot.owner_id;
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_pair_post_rebuild_ready(slots: &[Slot], report: &RebuildReport) {
|
||||
assert_eq!(
|
||||
slots.len(),
|
||||
report.outcomes.len(),
|
||||
"slot count must match report"
|
||||
);
|
||||
for (slot, outcome) in slots.iter().zip(report.outcomes.iter()) {
|
||||
match outcome {
|
||||
RebuildOutcome::Rebuilt { owner_id, .. } => {
|
||||
assert_eq!(*owner_id, slot.owner_id, "owner id alignment");
|
||||
if let Some(cb) = slot.callback.as_ref() {
|
||||
assert!(cb.is_ready(), "rebuilt owner must report ready");
|
||||
}
|
||||
}
|
||||
RebuildOutcome::Failed { owner_id, .. } => {
|
||||
assert_eq!(*owner_id, slot.owner_id, "failed owner id alignment");
|
||||
}
|
||||
RebuildOutcome::Vacant { owner_id } => {
|
||||
assert_eq!(*owner_id, slot.owner_id, "vacant owner id alignment");
|
||||
assert!(slot.callback.is_none(), "vacant slot must have no callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(feature = "wgpu")))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
use std::thread;
|
||||
|
||||
fn make_device_queue() -> (GpuDevice, GpuQueue) {
|
||||
(GpuDevice { id: 42 }, GpuQueue { id: 42 })
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Counters {
|
||||
release_calls: AtomicU32,
|
||||
rebuild_calls: AtomicU32,
|
||||
release_seq: AtomicU32,
|
||||
rebuild_seq: AtomicU32,
|
||||
}
|
||||
|
||||
struct MockOwner {
|
||||
counters: Arc<Counters>,
|
||||
ready: bool,
|
||||
fail_rebuild: bool,
|
||||
release_order: Arc<Mutex<Vec<u32>>>,
|
||||
rebuild_order: Arc<Mutex<Vec<u32>>>,
|
||||
slot_marker: u32,
|
||||
label: &'static str,
|
||||
}
|
||||
|
||||
impl GpuLossCallback for MockOwner {
|
||||
fn release(&mut self) {
|
||||
self.ready = false;
|
||||
let n = self.counters.release_calls.fetch_add(1, Ordering::SeqCst);
|
||||
self.counters.release_seq.store(n + 1, Ordering::SeqCst);
|
||||
let mut order = self.release_order.lock();
|
||||
order.push(self.slot_marker);
|
||||
}
|
||||
|
||||
fn rebuild(
|
||||
&mut self,
|
||||
_device: &GpuDevice,
|
||||
_queue: &GpuQueue,
|
||||
) -> Result<(), GpuRebuildError> {
|
||||
let n = self.counters.rebuild_calls.fetch_add(1, Ordering::SeqCst);
|
||||
self.counters.rebuild_seq.store(n + 1, Ordering::SeqCst);
|
||||
let mut order = self.rebuild_order.lock();
|
||||
order.push(self.slot_marker);
|
||||
if self.fail_rebuild {
|
||||
return Err(GpuRebuildError::ResourceCreateFailed {
|
||||
reason: "mock fail",
|
||||
});
|
||||
}
|
||||
self.ready = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.ready
|
||||
}
|
||||
|
||||
fn debug_label(&self) -> &'static str {
|
||||
self.label
|
||||
}
|
||||
}
|
||||
|
||||
fn fresh_counters() -> Arc<Counters> {
|
||||
Arc::new(Counters::default())
|
||||
}
|
||||
|
||||
fn make_owner(
|
||||
marker: u32,
|
||||
counters: Arc<Counters>,
|
||||
release_order: Arc<Mutex<Vec<u32>>>,
|
||||
rebuild_order: Arc<Mutex<Vec<u32>>>,
|
||||
) -> Box<MockOwner> {
|
||||
Box::new(MockOwner {
|
||||
counters,
|
||||
ready: true,
|
||||
fail_rebuild: false,
|
||||
release_order,
|
||||
rebuild_order,
|
||||
slot_marker: marker,
|
||||
label: "mock",
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_owners_release_lifo_rebuild_fifo() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut guards = Vec::new();
|
||||
for i in 0..5u32 {
|
||||
let owner = make_owner(
|
||||
i,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
);
|
||||
guards.push(registry.register(owner));
|
||||
}
|
||||
assert_eq!(registry.len(), 5);
|
||||
let (device, queue) = make_device_queue();
|
||||
let report = registry.handle_device_lost(&device, &queue);
|
||||
assert_eq!(report.released_count, 5);
|
||||
assert_eq!(report.rebuilt_count, 5);
|
||||
assert_eq!(report.failed_count, 0);
|
||||
assert_eq!(report.vacant_count, 0);
|
||||
let rel = release_order.lock().clone();
|
||||
assert_eq!(rel, vec![4, 3, 2, 1, 0]);
|
||||
let reb = rebuild_order.lock().clone();
|
||||
assert_eq!(reb, vec![0, 1, 2, 3, 4]);
|
||||
drop(guards);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn owner_rebuild_failure_does_not_abort_other_owners() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut guards = Vec::new();
|
||||
for i in 0..4u32 {
|
||||
let mut owner = make_owner(
|
||||
i,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
);
|
||||
if i == 1 {
|
||||
owner.fail_rebuild = true;
|
||||
}
|
||||
guards.push(registry.register(owner));
|
||||
}
|
||||
let (device, queue) = make_device_queue();
|
||||
let report = registry.handle_device_lost(&device, &queue);
|
||||
assert_eq!(report.released_count, 4);
|
||||
assert_eq!(report.rebuilt_count, 3);
|
||||
assert_eq!(report.failed_count, 1);
|
||||
let failed = report
|
||||
.outcomes
|
||||
.iter()
|
||||
.filter(|o| matches!(o, RebuildOutcome::Failed { .. }))
|
||||
.count();
|
||||
assert_eq!(failed, 1);
|
||||
drop(guards);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registration_guard_drop_deregisters() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let owner = make_owner(
|
||||
7,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
);
|
||||
let guard = registry.register(owner);
|
||||
assert_eq!(registry.len(), 1);
|
||||
drop(guard);
|
||||
assert_eq!(registry.len(), 0);
|
||||
assert!(registry.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_registry_device_loss_is_noop() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let (device, queue) = make_device_queue();
|
||||
let report = registry.handle_device_lost(&device, &queue);
|
||||
assert!(report.is_empty_pass());
|
||||
assert!(!report.is_total_success());
|
||||
assert_eq!(report.outcomes.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concurrent_registration_is_safe() {
|
||||
let registry = Arc::new(GpuLossRegistry::new());
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut threads = Vec::new();
|
||||
let guards_collector: Arc<Mutex<Vec<RegistrationGuard>>> = Arc::new(Mutex::new(Vec::new()));
|
||||
for i in 0..16u32 {
|
||||
let registry = Arc::clone(®istry);
|
||||
let counters = Arc::clone(&counters);
|
||||
let release_order = Arc::clone(&release_order);
|
||||
let rebuild_order = Arc::clone(&rebuild_order);
|
||||
let guards_collector = Arc::clone(&guards_collector);
|
||||
threads.push(thread::spawn(move || {
|
||||
let owner = make_owner(i, counters, release_order, rebuild_order);
|
||||
let guard = registry.register(owner);
|
||||
let mut store = guards_collector.lock();
|
||||
store.push(guard);
|
||||
}));
|
||||
}
|
||||
for t in threads {
|
||||
assert!(t.join().is_ok());
|
||||
}
|
||||
assert_eq!(registry.len(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pair_asserted_invariants_under_stress() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let mut guards = Vec::new();
|
||||
for i in 0..64u32 {
|
||||
let mut owner = make_owner(
|
||||
i,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
);
|
||||
if i % 7 == 0 {
|
||||
owner.fail_rebuild = true;
|
||||
}
|
||||
guards.push(registry.register(owner));
|
||||
}
|
||||
let (device, queue) = make_device_queue();
|
||||
for _ in 0..5 {
|
||||
release_order.lock().clear();
|
||||
rebuild_order.lock().clear();
|
||||
let _report = registry.handle_device_lost(&device, &queue);
|
||||
}
|
||||
let rel = release_order.lock().clone();
|
||||
let reb = rebuild_order.lock().clone();
|
||||
assert_eq!(rel.len(), 64);
|
||||
assert_eq!(reb.len(), 64);
|
||||
for i in 0..64u32 {
|
||||
assert_eq!(rel[i as usize], 63 - i);
|
||||
assert_eq!(reb[i as usize], i);
|
||||
}
|
||||
drop(guards);
|
||||
assert!(registry.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mock_callback_counters_are_deterministic() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let guard = registry.register(make_owner(
|
||||
1,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
));
|
||||
let (device, queue) = make_device_queue();
|
||||
for _ in 0..3 {
|
||||
registry.handle_device_lost(&device, &queue);
|
||||
}
|
||||
assert_eq!(counters.release_calls.load(Ordering::SeqCst), 3);
|
||||
assert_eq!(counters.rebuild_calls.load(Ordering::SeqCst), 3);
|
||||
drop(guard);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deregister_middle_owner_preserves_order() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let g0 = registry.register(make_owner(
|
||||
0,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
));
|
||||
let g1 = registry.register(make_owner(
|
||||
1,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
));
|
||||
let g2 = registry.register(make_owner(
|
||||
2,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
));
|
||||
assert_eq!(registry.len(), 3);
|
||||
drop(g1);
|
||||
assert_eq!(registry.len(), 2);
|
||||
let (device, queue) = make_device_queue();
|
||||
let report = registry.handle_device_lost(&device, &queue);
|
||||
assert_eq!(report.rebuilt_count, 2);
|
||||
let rel = release_order.lock().clone();
|
||||
assert_eq!(rel, vec![2, 0]);
|
||||
let reb = rebuild_order.lock().clone();
|
||||
assert_eq!(reb, vec![0, 2]);
|
||||
drop(g0);
|
||||
drop(g2);
|
||||
assert!(registry.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rebuild_report_total_success_flag() {
|
||||
let registry = GpuLossRegistry::new();
|
||||
let counters = fresh_counters();
|
||||
let release_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let rebuild_order = Arc::new(Mutex::new(Vec::new()));
|
||||
let guard = registry.register(make_owner(
|
||||
0,
|
||||
Arc::clone(&counters),
|
||||
Arc::clone(&release_order),
|
||||
Arc::clone(&rebuild_order),
|
||||
));
|
||||
let (device, queue) = make_device_queue();
|
||||
let report = registry.handle_device_lost(&device, &queue);
|
||||
assert!(report.is_total_success());
|
||||
assert!(!report.is_empty_pass());
|
||||
drop(guard);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user