diff --git a/ci/compat-Cargo.lock b/ci/compat-Cargo.lock index bd0456e5e..2e98c07b0 100644 --- a/ci/compat-Cargo.lock +++ b/ci/compat-Cargo.lock @@ -2,20 +2,20 @@ # It is not intended for manual editing. [[package]] name = "addr2line" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)", + "gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "adler32" -version = "1.1.0" +name = "adler" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "aho-corasick" -version = "0.7.10" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -64,13 +64,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.49" +version = "0.3.50" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -102,7 +102,7 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.54" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -115,7 +115,7 @@ name = "cgl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -138,15 +138,15 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.20.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -156,7 +156,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -166,13 +166,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "core-graphics" -version = "0.19.0" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -182,11 +182,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -207,7 +216,7 @@ dependencies = [ "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -236,9 +245,9 @@ name = "derivative" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -266,18 +275,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "downcast-rs" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "either" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -328,13 +337,13 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "gimli" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -343,7 +352,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -353,7 +362,7 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "xml-rs 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -362,13 +371,13 @@ name = "glium" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)", "fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.24.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "takeable-option 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -379,9 +388,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "cgl 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cocoa 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cocoa 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", "glutin_egl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "glutin_emscripten_sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "glutin_gles2_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -389,12 +398,12 @@ dependencies = [ "glutin_wgl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-client 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "winit 0.22.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -404,7 +413,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -440,15 +449,15 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "instant" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -456,7 +465,7 @@ name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -485,12 +494,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazycell" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "libc" -version = "0.2.71" +version = "0.2.74" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -498,8 +507,8 @@ name = "libloading" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -507,7 +516,7 @@ name = "libloading" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -528,7 +537,7 @@ dependencies = [ [[package]] name = "log" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -539,7 +548,7 @@ name = "malloc_buf" version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -557,13 +566,13 @@ name = "memmap" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "memoffset" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -571,10 +580,10 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.3.7" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -587,8 +596,8 @@ dependencies = [ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "net2 0.2.34 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -600,8 +609,8 @@ name = "mio-extras" version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -634,8 +643,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "android_log-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "ndk 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ndk-sys 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -651,8 +660,8 @@ version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -661,9 +670,9 @@ version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -742,8 +751,8 @@ name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "hermit-abi 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -760,10 +769,10 @@ name = "num_enum_derive" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -781,7 +790,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ordered-float" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -811,10 +820,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -824,7 +833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "pkg-config" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -834,7 +843,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro-crate" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -850,10 +859,10 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -869,7 +878,7 @@ name = "quote" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -878,7 +887,7 @@ version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -887,7 +896,7 @@ dependencies = [ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -896,7 +905,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -970,9 +979,9 @@ name = "rand_jitter" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -982,10 +991,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1018,7 +1027,7 @@ name = "raw-window-handle" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1028,23 +1037,24 @@ dependencies = [ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon-core 1.7.1", - "serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rayon-core" version = "1.7.1" dependencies = [ + "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1061,14 +1071,14 @@ dependencies = [ "fixedbitset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "glium 0.27.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "num 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.3.1", "regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1081,7 +1091,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1089,7 +1099,7 @@ name = "regex" version = "1.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1119,7 +1129,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ordered-float 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1143,20 +1153,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.112" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.112" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1165,7 +1175,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1175,7 +1185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1208,12 +1218,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "syn" -version = "1.0.31" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1234,7 +1244,7 @@ name = "toml" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1244,7 +1254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-xid" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1258,7 +1268,7 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1274,8 +1284,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "calloop 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", - "downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "downcast-rs 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-commons 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1329,7 +1339,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1351,7 +1361,7 @@ name = "winapi-util" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1365,15 +1375,15 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "cocoa 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cocoa 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)", "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)", "core-video-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "dispatch 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "instant 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "instant 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)", "mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "ndk 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1385,7 +1395,7 @@ dependencies = [ "raw-window-handle 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "smithay-client-toolkit 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "wayland-client 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "x11-dl 2.18.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1404,9 +1414,9 @@ version = "2.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)", "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1420,30 +1430,31 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum addr2line 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" -"checksum adler32 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" -"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +"checksum addr2line 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +"checksum adler 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" +"checksum aho-corasick 0.7.13 (registry+https://github.com/rust-lang/crates.io-index)" = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" "checksum andrew 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9b7f09f89872c2b6b29e319377b1fbe91c6f5947df19a25596e121cf19a7b35e" "checksum android_glue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" "checksum android_log-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" "checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" "checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" "checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" -"checksum backtrace 0.3.49 (registry+https://github.com/rust-lang/crates.io-index)" = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" +"checksum backtrace 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)" = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" "checksum block 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" "checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" "checksum calloop 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160" -"checksum cc 1.0.54 (registry+https://github.com/rust-lang/crates.io-index)" = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311" +"checksum cc 1.0.58 (registry+https://github.com/rust-lang/crates.io-index)" = "f9a06fb2e53271d7c279ec1efea6ab691c35a2ae67ec0d91d7acec0caf13b518" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cgl 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" "checksum cgmath 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "283944cdecc44bf0b8dd010ec9af888d3b4f142844fdbe026c20ef68148d6fe7" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -"checksum cocoa 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8f7b6f3f7f4f0b3ec5c5039aaa9e8c3cef97a7a480a400fd62944841314f293d" +"checksum cocoa 0.20.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" "checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" "checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" -"checksum core-graphics 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "59e78b2e0aaf43f08e7ae0d6bc96895ef72ff0921c7d4ff4762201b2dba376dd" +"checksum core-graphics 0.19.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" "checksum core-video-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061" "checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" "checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" "checksum crossbeam-queue 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" @@ -1453,8 +1464,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum dlib 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b11f15d1e3268f140f68d390637d5e76d849782d971ae7063e0da69fe9709a76" "checksum doc-comment 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" "checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" -"checksum downcast-rs 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52ba6eb47c2131e784a38b726eb54c1e1484904f013e576a25354d0124161af6" -"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" +"checksum downcast-rs 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +"checksum either 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f" "checksum fixedbitset 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4fcacf5cd3681968f6524ea159383132937739c6c40dabab9e37ed515911b" "checksum fnv 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" "checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" @@ -1463,7 +1474,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -"checksum gimli 0.21.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" +"checksum gimli 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" "checksum gl_generator 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ca98bbde17256e02d17336a6bdb5a50f7d0ccacee502e191d3e3d0ec2f96f84a" "checksum gl_generator 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" "checksum glium 0.27.0 (registry+https://github.com/rust-lang/crates.io-index)" = "030bb23a12fac7e589b002c5e131e89348df88f91b56e3f3dbc4249527eeebf9" @@ -1473,26 +1484,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum glutin_gles2_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07e853d96bebcb8e53e445225c3009758c6f5960d44f2543245f6f07b567dae0" "checksum glutin_glx_sys 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "08c243de74d6cf5ea100c788826d2fb9319de315485dd4b310811a663b3809c3" "checksum glutin_wgl_sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a93dba7ee3a0feeac0f437141ff25e71ce2066bcf1a706acab1559ffff94eb6a" -"checksum hermit-abi 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" -"checksum instant 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7777a24a1ce5de49fcdde84ec46efa487c3af49d5b6e6e0a50367cc5c1096182" +"checksum hermit-abi 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +"checksum instant 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum jni-sys 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum khronos_api 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum lazycell 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" -"checksum libc 0.2.71 (registry+https://github.com/rust-lang/crates.io-index)" = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" +"checksum lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +"checksum libc 0.2.74 (registry+https://github.com/rust-lang/crates.io-index)" = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" "checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum libloading 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2cadb8e769f070c45df05c78c7520eb4cd17061d4ab262e43cfc68b4d00ac71c" "checksum line_drawing 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5cc7ad3d82c845bdb5dde34ffdcc7a5fb4d2996e1e1ee0f19c33bc80e15196b9" "checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" "checksum malloc_buf 0.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" "checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" "checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" -"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" -"checksum miniz_oxide 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +"checksum memoffset 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +"checksum miniz_oxide 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" "checksum mio 0.6.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" "checksum mio-extras 2.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" @@ -1513,16 +1524,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num_enum_derive 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" "checksum objc 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" "checksum object 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" -"checksum ordered-float 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "18869315e81473c951eb56ad5558bbc56978562d3ecfb87abb7a1e944cea4518" +"checksum ordered-float 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3741934be594d77de1c8461ebcbbe866f585ea616a9753aa78f2bdc69f0e4579" "checksum osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88cfece6e95d2e717e0872a7f53a8684712ad13822a7979bc760b9c77ec0013b" "checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e" "checksum parking_lot_core 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +"checksum pkg-config 0.3.18 (registry+https://github.com/rust-lang/crates.io-index)" = "d36492546b6af1463394d46f0c834346f31548646f6ba10849802c9c9a27ac33" "checksum ppv-lite86 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" -"checksum proc-macro-crate 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e10d4b51f154c8a7fb96fd6dad097cb74b863943ec010ac94b9fd1be8861fe1e" +"checksum proc-macro-crate 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)" = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" @@ -1542,7 +1553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77d416b86801d23dde1aa643023b775c3a462efc0ed96443add11546cdf1dca8" "checksum raw-window-handle 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0a441a7a6c80ad6473bd4b74ec1c9a4c951794285bf941c2126f607c72e48211" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" +"checksum redox_syscall 0.1.57 (registry+https://github.com/rust-lang/crates.io-index)" = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" "checksum regex 1.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" "checksum regex-syntax 0.6.18 (registry+https://github.com/rust-lang/crates.io-index)" = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" @@ -1551,20 +1562,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum same-file 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" "checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" "checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -"checksum serde 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)" = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243" -"checksum serde_derive 1.0.112 (registry+https://github.com/rust-lang/crates.io-index)" = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57" +"checksum serde 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)" = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +"checksum serde_derive 1.0.115 (registry+https://github.com/rust-lang/crates.io-index)" = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" "checksum shared_library 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5a9e7e0f2bfae24d8a5b5a66c5b257a83c7412304311512a0c054cd5e619da11" "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -"checksum smallvec 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" +"checksum smallvec 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" "checksum smithay-client-toolkit 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "421c8dc7acf5cb205b88160f8b4cc2c5cfabe210e43b2f80f009f4c1ef910f1d" "checksum stb_truetype 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f77b6b07e862c66a9f3e62a07588fee67cd90a9135a2b942409f195507b4fb51" "checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" -"checksum syn 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" +"checksum syn 1.0.38 (registry+https://github.com/rust-lang/crates.io-index)" = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" "checksum takeable-option 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "36ae8932fcfea38b7d3883ae2ab357b0d57a02caaa18ebb4f5ece08beaec4aa0" "checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" "checksum toml 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum walkdir 2.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" "checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" @@ -1574,7 +1585,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum wayland-scanner 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "93b02247366f395b9258054f964fe293ddd019c3237afba9be2ccbe9e1651c3d" "checksum wayland-sys 0.23.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d94e89a86e6d6d7c7c9b19ebf48a03afaac4af6bc22ae570e9a24124b75358f4" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +"checksum winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" diff --git a/rayon-core/Cargo.toml b/rayon-core/Cargo.toml index 03cd56dbb..fd72d1263 100644 --- a/rayon-core/Cargo.toml +++ b/rayon-core/Cargo.toml @@ -18,6 +18,7 @@ categories = ["concurrency"] [dependencies] num_cpus = "1.2" lazy_static = "1" +crossbeam-channel = "0.4.2" crossbeam-deque = "0.7.2" crossbeam-queue = "0.2" crossbeam-utils = "0.7" diff --git a/rayon-core/src/join/mod.rs b/rayon-core/src/join/mod.rs index 295f97bc2..d72c7e61c 100644 --- a/rayon-core/src/join/mod.rs +++ b/rayon-core/src/join/mod.rs @@ -1,6 +1,5 @@ use crate::job::StackJob; -use crate::latch::{LatchProbe, SpinLatch}; -use crate::log::Event::*; +use crate::latch::SpinLatch; use crate::registry::{self, WorkerThread}; use crate::unwind; use std::any::Any; @@ -131,14 +130,10 @@ where } registry::in_worker(|worker_thread, injected| unsafe { - log!(Join { - worker: worker_thread.index() - }); - // Create virtual wrapper for task b; this all has to be // done here so that the stack frame can keep it all live // long enough. - let job_b = StackJob::new(call_b(oper_b), SpinLatch::new()); + let job_b = StackJob::new(call_b(oper_b), SpinLatch::new(worker_thread)); let job_b_ref = job_b.as_job_ref(); worker_thread.push(job_b_ref); @@ -160,23 +155,14 @@ where // Found it! Let's run it. // // Note that this could panic, but it's ok if we unwind here. - log!(PoppedRhs { - worker: worker_thread.index() - }); let result_b = job_b.run_inline(injected); return (result_a, result_b); } else { - log!(PoppedJob { - worker: worker_thread.index() - }); worker_thread.execute(job); } } else { // Local deque is empty. Time to steal from other // threads. - log!(LostJob { - worker: worker_thread.index() - }); worker_thread.wait_until(&job_b.latch); debug_assert!(job_b.latch.probe()); break; @@ -193,7 +179,7 @@ where #[cold] // cold path unsafe fn join_recover_from_panic( worker_thread: &WorkerThread, - job_b_latch: &SpinLatch, + job_b_latch: &SpinLatch<'_>, err: Box, ) -> ! { worker_thread.wait_until(job_b_latch); diff --git a/rayon-core/src/latch.rs b/rayon-core/src/latch.rs index fb6e44b00..0965bb95a 100644 --- a/rayon-core/src/latch.rs +++ b/rayon-core/src/latch.rs @@ -1,7 +1,8 @@ -use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Condvar, Mutex}; +use std::usize; -use crate::registry::Registry; +use crate::registry::{Registry, WorkerThread}; /// We define various kinds of latches, which are all a primitive signaling /// mechanism. A latch starts as false. Eventually someone calls `set()` and @@ -29,43 +30,189 @@ use crate::registry::Registry; /// - Once `set()` occurs, the next `probe()` *will* observe it. This /// typically requires a seq-cst ordering. See [the "tickle-then-get-sleepy" scenario in the sleep /// README](/src/sleep/README.md#tickle-then-get-sleepy) for details. -pub(super) trait Latch: LatchProbe { +pub(super) trait Latch { /// Set the latch, signalling others. + /// + /// # WARNING + /// + /// Setting a latch triggers other threads to wake up and (in some + /// cases) complete. This may, in turn, cause memory to be + /// allocated and so forth. One must be very careful about this, + /// and it's typically better to read all the fields you will need + /// to access *before* a latch is set! fn set(&self); } -pub(super) trait LatchProbe { - /// Test if the latch is set. - fn probe(&self) -> bool; +pub(super) trait AsCoreLatch { + fn as_core_latch(&self) -> &CoreLatch; } +/// Latch is not set, owning thread is awake +const UNSET: usize = 0; + +/// Latch is not set, owning thread is going to sleep on this latch +/// (but has not yet fallen asleep). +const SLEEPY: usize = 1; + +/// Latch is not set, owning thread is asleep on this latch and +/// must be awoken. +const SLEEPING: usize = 2; + +/// Latch is set. +const SET: usize = 3; + /// Spin latches are the simplest, most efficient kind, but they do /// not support a `wait()` operation. They just have a boolean flag /// that becomes true when `set()` is called. -pub(super) struct SpinLatch { - b: AtomicBool, +#[derive(Debug)] +pub(super) struct CoreLatch { + state: AtomicUsize, } -impl SpinLatch { +impl CoreLatch { + #[inline] + fn new() -> Self { + Self { + state: AtomicUsize::new(0), + } + } + + /// Returns the address of this core latch as an integer. Used + /// for logging. + #[inline] + pub(super) fn addr(&self) -> usize { + self as *const CoreLatch as usize + } + + /// Invoked by owning thread as it prepares to sleep. Returns true + /// if the owning thread may proceed to fall asleep, false if the + /// latch was set in the meantime. + #[inline] + pub(super) fn get_sleepy(&self) -> bool { + self.state + .compare_exchange(UNSET, SLEEPY, Ordering::SeqCst, Ordering::Relaxed) + .is_ok() + } + + /// Invoked by owning thread as it falls asleep sleep. Returns + /// true if the owning thread should block, or false if the latch + /// was set in the meantime. + #[inline] + pub(super) fn fall_asleep(&self) -> bool { + self.state + .compare_exchange(SLEEPY, SLEEPING, Ordering::SeqCst, Ordering::Relaxed) + .is_ok() + } + + /// Invoked by owning thread as it falls asleep sleep. Returns + /// true if the owning thread should block, or false if the latch + /// was set in the meantime. + #[inline] + pub(super) fn wake_up(&self) { + if !self.probe() { + let _ = + self.state + .compare_exchange(SLEEPING, UNSET, Ordering::SeqCst, Ordering::Relaxed); + } + } + + /// Set the latch. If this returns true, the owning thread was sleeping + /// and must be awoken. + /// + /// This is private because, typically, setting a latch involves + /// doing some wakeups; those are encapsulated in the surrounding + /// latch code. + #[inline] + fn set(&self) -> bool { + let old_state = self.state.swap(SET, Ordering::AcqRel); + old_state == SLEEPING + } + + /// Test if this latch has been set. + #[inline] + pub(super) fn probe(&self) -> bool { + self.state.load(Ordering::Acquire) == SET + } +} + +/// Spin latches are the simplest, most efficient kind, but they do +/// not support a `wait()` operation. They just have a boolean flag +/// that becomes true when `set()` is called. +pub(super) struct SpinLatch<'r> { + core_latch: CoreLatch, + registry: &'r Arc, + target_worker_index: usize, + cross: bool, +} + +impl<'r> SpinLatch<'r> { + /// Creates a new spin latch that is owned by `thread`. This means + /// that `thread` is the only thread that should be blocking on + /// this latch -- it also means that when the latch is set, we + /// will wake `thread` if it is sleeping. + #[inline] + pub(super) fn new(thread: &'r WorkerThread) -> SpinLatch<'r> { + SpinLatch { + core_latch: CoreLatch::new(), + registry: thread.registry(), + target_worker_index: thread.index(), + cross: false, + } + } + + /// Creates a new spin latch for cross-threadpool blocking. Notably, we + /// need to make sure the registry is kept alive after setting, so we can + /// safely call the notification. #[inline] - pub(super) fn new() -> SpinLatch { + pub(super) fn cross(thread: &'r WorkerThread) -> SpinLatch<'r> { SpinLatch { - b: AtomicBool::new(false), + cross: true, + ..SpinLatch::new(thread) } } + + #[inline] + pub(super) fn probe(&self) -> bool { + self.core_latch.probe() + } } -impl LatchProbe for SpinLatch { +impl<'r> AsCoreLatch for SpinLatch<'r> { #[inline] - fn probe(&self) -> bool { - self.b.load(Ordering::SeqCst) + fn as_core_latch(&self) -> &CoreLatch { + &self.core_latch } } -impl Latch for SpinLatch { +impl<'r> Latch for SpinLatch<'r> { #[inline] fn set(&self) { - self.b.store(true, Ordering::SeqCst); + let cross_registry; + + let registry = if self.cross { + // Ensure the registry stays alive while we notify it. + // Otherwise, it would be possible that we set the spin + // latch and the other thread sees it and exits, causing + // the registry to be deallocated, all before we get a + // chance to invoke `registry.notify_worker_latch_is_set`. + cross_registry = Arc::clone(self.registry); + &cross_registry + } else { + // If this is not a "cross-registry" spin-latch, then the + // thread which is performing `set` is itself ensuring + // that the registry stays alive. + self.registry + }; + let target_worker_index = self.target_worker_index; + + // NOTE: Once we `set`, the target may proceed and invalidate `&self`! + if self.core_latch.set() { + // Subtle: at this point, we can no longer read from + // `self`, because the thread owning this spin latch may + // have awoken and deallocated the latch. Therefore, we + // only use fields whose values we already read. + registry.notify_worker_latch_is_set(target_worker_index); + } } } @@ -103,15 +250,6 @@ impl LockLatch { } } -impl LatchProbe for LockLatch { - #[inline] - fn probe(&self) -> bool { - // Not particularly efficient, but we don't really use this operation - let guard = self.m.lock().unwrap(); - *guard - } -} - impl Latch for LockLatch { #[inline] fn set(&self) { @@ -126,8 +264,19 @@ impl Latch for LockLatch { /// necessarily make the latch be considered `set()`; instead, it just /// decrements the counter. The latch is only "set" (in the sense that /// `probe()` returns true) once the counter reaches zero. +/// +/// Note: like a `SpinLatch`, count laches are always associated with +/// some registry that is probing them, which must be tickled when +/// they are set. *Unlike* a `SpinLatch`, they don't themselves hold a +/// reference to that registry. This is because in some cases the +/// registry owns the count-latch, and that would create a cycle. So a +/// `CountLatch` must be given a reference to its owning registry when +/// it is set. For this reason, it does not implement the `Latch` +/// trait (but it doesn't have to, as it is not used in those generic +/// contexts). #[derive(Debug)] pub(super) struct CountLatch { + core_latch: CoreLatch, counter: AtomicUsize, } @@ -135,77 +284,47 @@ impl CountLatch { #[inline] pub(super) fn new() -> CountLatch { CountLatch { + core_latch: CoreLatch::new(), counter: AtomicUsize::new(1), } } #[inline] pub(super) fn increment(&self) { - debug_assert!(!self.probe()); + debug_assert!(!self.core_latch.probe()); self.counter.fetch_add(1, Ordering::Relaxed); } -} - -impl LatchProbe for CountLatch { - #[inline] - fn probe(&self) -> bool { - // Need to acquire any memory reads before latch was set: - self.counter.load(Ordering::SeqCst) == 0 - } -} -impl Latch for CountLatch { - /// Set the latch to true, releasing all threads who are waiting. + /// Decrements the latch counter by one. If this is the final + /// count, then the latch is **set**, and calls to `probe()` will + /// return true. Returns whether the latch was set. This is an + /// internal operation, as it does not tickle, and to fail to + /// tickle would lead to deadlock. #[inline] - fn set(&self) { - self.counter.fetch_sub(1, Ordering::SeqCst); - } -} - -/// A tickling latch wraps another latch type, and will also awaken a thread -/// pool when it is set. This is useful for jobs injected between thread pools, -/// so the source pool can continue processing its own work while waiting. -pub(super) struct TickleLatch<'a, L: Latch> { - inner: L, - registry: &'a Arc, -} - -impl<'a, L: Latch> TickleLatch<'a, L> { - #[inline] - pub(super) fn new(latch: L, registry: &'a Arc) -> Self { - registry.increment_terminate_count(); - TickleLatch { - inner: latch, - registry, + fn set(&self) -> bool { + if self.counter.fetch_sub(1, Ordering::SeqCst) == 1 { + self.core_latch.set(); + true + } else { + false } } -} -impl<'a, L: Latch> LatchProbe for TickleLatch<'a, L> { + /// Decrements the latch counter by one and possibly set it. If + /// the latch is set, then the specific worker thread is tickled, + /// which should be the one that owns this latch. #[inline] - fn probe(&self) -> bool { - self.inner.probe() + pub(super) fn set_and_tickle_one(&self, registry: &Registry, target_worker_index: usize) { + if self.set() { + registry.notify_worker_latch_is_set(target_worker_index); + } } } -impl<'a, L: Latch> Latch for TickleLatch<'a, L> { +impl AsCoreLatch for CountLatch { #[inline] - fn set(&self) { - // Ensure the registry stays alive while we tickle it. - let registry = Arc::clone(self.registry); - - // NOTE: Once we `set`, the target may proceed and invalidate `&self`! - self.inner.set(); - registry.tickle(); - } -} - -impl<'a, L> LatchProbe for &'a L -where - L: LatchProbe, -{ - fn probe(&self) -> bool { - L::probe(self) + fn as_core_latch(&self) -> &CoreLatch { + &self.core_latch } } @@ -213,6 +332,7 @@ impl<'a, L> Latch for &'a L where L: Latch, { + #[inline] fn set(&self) { L::set(self); } diff --git a/rayon-core/src/log.rs b/rayon-core/src/log.rs index 687370d0b..e1ff827df 100644 --- a/rayon-core/src/log.rs +++ b/rayon-core/src/log.rs @@ -1,116 +1,423 @@ //! Debug Logging //! -//! To use in a debug build, set the env var `RAYON_LOG=1`. In a -//! release build, logs are compiled out. You will have to change -//! `DUMP_LOGS` to be `true`. +//! To use in a debug build, set the env var `RAYON_LOG` as +//! described below. In a release build, logs are compiled out by +//! default unless Rayon is built with `--cfg rayon_rs_log` (try +//! `RUSTFLAGS="--cfg rayon_rs_log"`). //! -//! **Old environment variable:** `RAYON_LOG` is a one-to-one -//! replacement of the now deprecated `RAYON_RS_LOG` environment -//! variable, which is still supported for backwards compatibility. +//! Note that logs are an internally debugging tool and their format +//! is considered unstable, as are the details of how to enable them. +//! +//! # Valid values for RAYON_LOG +//! +//! The `RAYON_LOG` variable can take on the following values: +//! +//! * `tail:` -- dumps the last 10,000 events into the given file; +//! useful for tracking down deadlocks +//! * `profile:` -- dumps only those events needed to reconstruct how +//! many workers are active at a given time +//! * `all:` -- dumps every event to the file; useful for debugging -#[cfg(debug_assertions)] +use crossbeam_channel::{self, Receiver, Sender}; +use std::collections::VecDeque; use std::env; +use std::fs::File; +use std::io::{self, BufWriter, Write}; + +/// True if logs are compiled in. +pub(super) const LOG_ENABLED: bool = cfg!(any(rayon_rs_log, debug_assertions)); -#[cfg_attr(debug_assertions, derive(Debug))] -#[cfg_attr(not(debug_assertions), allow(dead_code))] +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] pub(super) enum Event { - Tickle { - worker: usize, - old_state: usize, - }, - GetSleepy { - worker: usize, - state: usize, - }, - GotSleepy { - worker: usize, - old_state: usize, - new_state: usize, - }, - GotAwoken { - worker: usize, - }, - FellAsleep { - worker: usize, - }, - GotInterrupted { - worker: usize, - }, - FoundWork { - worker: usize, - yields: usize, - }, - DidNotFindWork { - worker: usize, - yields: usize, - }, - StoleWork { - worker: usize, - victim: usize, - }, - UninjectedWork { - worker: usize, - }, - WaitUntil { - worker: usize, - }, - LatchSet { - worker: usize, - }, - InjectJobs { - count: usize, - }, - Join { - worker: usize, - }, - PoppedJob { - worker: usize, - }, - PoppedRhs { + /// Flushes events to disk, used to terminate benchmarking. + Flush, + + /// Indicates that a worker thread started execution. + ThreadStart { worker: usize, + terminate_addr: usize, }, - LostJob { + + /// Indicates that a worker thread started execution. + ThreadTerminate { worker: usize }, + + /// Indicates that a worker thread became idle, blocked on `latch_addr`. + ThreadIdle { worker: usize, latch_addr: usize }, + + /// Indicates that an idle worker thread found work to do, after + /// yield rounds. It should no longer be considered idle. + ThreadFoundWork { worker: usize, yields: u32 }, + + /// Indicates that a worker blocked on a latch observed that it was set. + /// + /// Internal debugging event that does not affect the state + /// machine. + ThreadSawLatchSet { worker: usize, latch_addr: usize }, + + /// Indicates that an idle worker is getting sleepy. `sleepy_counter` is the internal + /// sleep state that we saw at the time. + ThreadSleepy { worker: usize, jobs_counter: usize }, + + /// Indicates that the thread's attempt to fall asleep was + /// interrupted because the latch was set. (This is not, in and of + /// itself, a change to the thread state.) + ThreadSleepInterruptedByLatch { worker: usize, latch_addr: usize }, + + /// Indicates that the thread's attempt to fall asleep was + /// interrupted because a job was posted. (This is not, in and of + /// itself, a change to the thread state.) + ThreadSleepInterruptedByJob { worker: usize }, + + /// Indicates that an idle worker has gone to sleep. + ThreadSleeping { worker: usize, latch_addr: usize }, + + /// Indicates that a sleeping worker has awoken. + ThreadAwoken { worker: usize, latch_addr: usize }, + + /// Indicates that the given worker thread was notified it should + /// awaken. + ThreadNotify { worker: usize }, + + /// The given worker has pushed a job to its local deque. + JobPushed { worker: usize }, + + /// The given worker has popped a job from its local deque. + JobPopped { worker: usize }, + + /// The given worker has stolen a job from the deque of another. + JobStolen { worker: usize, victim: usize }, + + /// N jobs were injected into the global queue. + JobsInjected { count: usize }, + + /// A job was removed from the global queue. + JobUninjected { worker: usize }, + + /// When announcing a job, this was the value of the counters we observed. + /// + /// No effect on thread state, just a debugging event. + JobThreadCounts { worker: usize, - }, - JobCompletedOk { - owner_thread: usize, - }, - JobPanickedErrorStored { - owner_thread: usize, - }, - JobPanickedErrorNotStored { - owner_thread: usize, - }, - ScopeCompletePanicked { - owner_thread: usize, - }, - ScopeCompleteNoPanic { - owner_thread: usize, + num_idle: u16, + num_sleepers: u16, }, } -#[cfg(debug_assertions)] -lazy_static::lazy_static! { - pub(super) static ref LOG_ENV: bool = - env::var("RAYON_LOG").is_ok() || env::var("RAYON_RS_LOG").is_ok(); +/// Handle to the logging thread, if any. You can use this to deliver +/// logs. You can also clone it freely. +#[derive(Clone)] +pub(super) struct Logger { + sender: Option>, } -#[cfg(debug_assertions)] -macro_rules! log { - ($event:expr) => { - if *$crate::log::LOG_ENV { - eprintln!("{:?}", $event); +impl Logger { + pub(super) fn new(num_workers: usize) -> Logger { + if !LOG_ENABLED { + return Self::disabled(); } - }; + + // see the doc comment for the format + let env_log = match env::var("RAYON_LOG") { + Ok(s) => s, + Err(_) => return Self::disabled(), + }; + + let (sender, receiver) = crossbeam_channel::unbounded(); + + if env_log.starts_with("tail:") { + let filename = env_log["tail:".len()..].to_string(); + ::std::thread::spawn(move || { + Self::tail_logger_thread(num_workers, filename, 10_000, receiver) + }); + } else if env_log == "all" { + ::std::thread::spawn(move || Self::all_logger_thread(num_workers, receiver)); + } else if env_log.starts_with("profile:") { + let filename = env_log["profile:".len()..].to_string(); + ::std::thread::spawn(move || { + Self::profile_logger_thread(num_workers, filename, 10_000, receiver) + }); + } else { + panic!("RAYON_LOG should be 'tail:' or 'profile:'"); + } + + return Logger { + sender: Some(sender), + }; + } + + fn disabled() -> Logger { + Logger { sender: None } + } + + #[inline] + pub(super) fn log(&self, event: impl FnOnce() -> Event) { + if !LOG_ENABLED { + return; + } + + if let Some(sender) = &self.sender { + sender.send(event()).unwrap(); + } + } + + fn profile_logger_thread( + num_workers: usize, + log_filename: String, + capacity: usize, + receiver: Receiver, + ) { + let file = File::create(&log_filename) + .unwrap_or_else(|err| panic!("failed to open `{}`: {}", log_filename, err)); + + let mut writer = BufWriter::new(file); + let mut events = Vec::with_capacity(capacity); + let mut state = SimulatorState::new(num_workers); + let timeout = std::time::Duration::from_secs(30); + + loop { + loop { + match receiver.recv_timeout(timeout) { + Ok(event) => { + if let Event::Flush = event { + break; + } else { + events.push(event); + } + } + + Err(_) => break, + } + + if events.len() == capacity { + break; + } + } + + for event in events.drain(..) { + if state.simulate(&event) { + state.dump(&mut writer, &event).unwrap(); + } + } + + writer.flush().unwrap(); + } + } + + fn tail_logger_thread( + num_workers: usize, + log_filename: String, + capacity: usize, + receiver: Receiver, + ) { + let file = File::create(&log_filename) + .unwrap_or_else(|err| panic!("failed to open `{}`: {}", log_filename, err)); + + let mut writer = BufWriter::new(file); + let mut events: VecDeque = VecDeque::with_capacity(capacity); + let mut state = SimulatorState::new(num_workers); + let timeout = std::time::Duration::from_secs(30); + let mut skipped = false; + + loop { + loop { + match receiver.recv_timeout(timeout) { + Ok(event) => { + if let Event::Flush = event { + // We ignore Flush events in tail mode -- + // we're really just looking for + // deadlocks. + continue; + } else { + if events.len() == capacity { + let event = events.pop_front().unwrap(); + state.simulate(&event); + skipped = true; + } + + events.push_back(event); + } + } + + Err(_) => break, + } + } + + if skipped { + write!(writer, "...\n").unwrap(); + skipped = false; + } + + for event in events.drain(..) { + // In tail mode, we dump *all* events out, whether or + // not they were 'interesting' to the state machine. + state.simulate(&event); + state.dump(&mut writer, &event).unwrap(); + } + + writer.flush().unwrap(); + } + } + + fn all_logger_thread(num_workers: usize, receiver: Receiver) { + let stderr = std::io::stderr(); + let mut state = SimulatorState::new(num_workers); + + for event in receiver { + let mut writer = BufWriter::new(stderr.lock()); + state.simulate(&event); + state.dump(&mut writer, &event).unwrap(); + writer.flush().unwrap(); + } + } +} + +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +enum State { + Working, + Idle, + Notified, + Sleeping, + Terminated, } -#[cfg(not(debug_assertions))] -macro_rules! log { - ($event:expr) => { - if false { - // Expand `$event` so it still appears used, but without - // any of the formatting code to be optimized away. - $event; +impl State { + fn letter(&self) -> char { + match self { + State::Working => 'W', + State::Idle => 'I', + State::Notified => 'N', + State::Sleeping => 'S', + State::Terminated => 'T', } - }; + } +} + +struct SimulatorState { + local_queue_size: Vec, + thread_states: Vec, + injector_size: usize, +} + +impl SimulatorState { + fn new(num_workers: usize) -> Self { + Self { + local_queue_size: (0..num_workers).map(|_| 0).collect(), + thread_states: (0..num_workers).map(|_| State::Working).collect(), + injector_size: 0, + } + } + + fn simulate(&mut self, event: &Event) -> bool { + match *event { + Event::ThreadIdle { worker, .. } => { + assert_eq!(self.thread_states[worker], State::Working); + self.thread_states[worker] = State::Idle; + true + } + + Event::ThreadStart { worker, .. } | Event::ThreadFoundWork { worker, .. } => { + self.thread_states[worker] = State::Working; + true + } + + Event::ThreadTerminate { worker, .. } => { + self.thread_states[worker] = State::Terminated; + true + } + + Event::ThreadSleeping { worker, .. } => { + assert_eq!(self.thread_states[worker], State::Idle); + self.thread_states[worker] = State::Sleeping; + true + } + + Event::ThreadAwoken { worker, .. } => { + assert_eq!(self.thread_states[worker], State::Notified); + self.thread_states[worker] = State::Idle; + true + } + + Event::JobPushed { worker } => { + self.local_queue_size[worker] += 1; + true + } + + Event::JobPopped { worker } => { + self.local_queue_size[worker] -= 1; + true + } + + Event::JobStolen { victim, .. } => { + self.local_queue_size[victim] -= 1; + true + } + + Event::JobsInjected { count } => { + self.injector_size += count; + true + } + + Event::JobUninjected { .. } => { + self.injector_size -= 1; + true + } + + Event::ThreadNotify { worker } => { + // Currently, this log event occurs while holding the + // thread lock, so we should *always* see it before + // the worker awakens. + assert_eq!(self.thread_states[worker], State::Sleeping); + self.thread_states[worker] = State::Notified; + true + } + + // remaining events are no-ops from pov of simulating the + // thread state + _ => false, + } + } + + fn dump(&mut self, w: &mut impl Write, event: &Event) -> io::Result<()> { + let num_idle_threads = self + .thread_states + .iter() + .filter(|s| **s == State::Idle) + .count(); + + let num_sleeping_threads = self + .thread_states + .iter() + .filter(|s| **s == State::Sleeping) + .count(); + + let num_notified_threads = self + .thread_states + .iter() + .filter(|s| **s == State::Notified) + .count(); + + let num_pending_jobs: usize = self.local_queue_size.iter().sum(); + + write!(w, "{:2},", num_idle_threads)?; + write!(w, "{:2},", num_sleeping_threads)?; + write!(w, "{:2},", num_notified_threads)?; + write!(w, "{:4},", num_pending_jobs)?; + write!(w, "{:4},", self.injector_size)?; + + let event_str = format!("{:?}", event); + write!(w, r#""{:60}","#, event_str)?; + + for ((i, state), queue_size) in (0..).zip(&self.thread_states).zip(&self.local_queue_size) { + write!(w, " T{:02},{}", i, state.letter(),)?; + + if *queue_size > 0 { + write!(w, ",{:03},", queue_size)?; + } else { + write!(w, ", ,")?; + } + } + + write!(w, "\n")?; + Ok(()) + } } diff --git a/rayon-core/src/registry.rs b/rayon-core/src/registry.rs index 5ef77e09d..53032cca3 100644 --- a/rayon-core/src/registry.rs +++ b/rayon-core/src/registry.rs @@ -1,6 +1,7 @@ use crate::job::{JobFifo, JobRef, StackJob}; -use crate::latch::{CountLatch, Latch, LatchProbe, LockLatch, SpinLatch, TickleLatch}; +use crate::latch::{AsCoreLatch, CoreLatch, CountLatch, Latch, LockLatch, SpinLatch}; use crate::log::Event::*; +use crate::log::Logger; use crate::sleep::Sleep; use crate::unwind; use crate::util::leak; @@ -132,6 +133,7 @@ where } pub(super) struct Registry { + logger: Logger, thread_infos: Vec, sleep: Sleep, injected_jobs: SegQueue, @@ -152,7 +154,7 @@ pub(super) struct Registry { // - when `join()` or `scope()` is invoked, similarly, no adjustments are needed. // These are always owned by some other job (e.g., one injected by `ThreadPool::install()`) // and that job will keep the pool alive. - terminate_latch: CountLatch, + terminate_count: AtomicUsize, } /// //////////////////////////////////////////////////////////////////////// @@ -233,11 +235,13 @@ impl Registry { }) .unzip(); + let logger = Logger::new(n_threads); let registry = Arc::new(Registry { + logger: logger.clone(), thread_infos: stealers.into_iter().map(ThreadInfo::new).collect(), - sleep: Sleep::new(), + sleep: Sleep::new(logger, n_threads), injected_jobs: SegQueue::new(), - terminate_latch: CountLatch::new(), + terminate_count: AtomicUsize::new(1), panic_handler: builder.take_panic_handler(), start_handler: builder.take_start_handler(), exit_handler: builder.take_exit_handler(), @@ -311,6 +315,11 @@ impl Registry { } } + #[inline] + pub(super) fn log(&self, event: impl FnOnce() -> crate::log::Event) { + self.logger.log(event) + } + pub(super) fn num_threads(&self) -> usize { self.thread_infos.len() } @@ -374,8 +383,8 @@ impl Registry { /// whatever worker has nothing to do. Use this is you know that /// you are not on a worker of this registry. pub(super) fn inject(&self, injected_jobs: &[JobRef]) { - log!(InjectJobs { - count: injected_jobs.len() + self.log(|| JobsInjected { + count: injected_jobs.len(), }); // It should not be possible for `state.terminate` to be true @@ -383,22 +392,31 @@ impl Registry { // drops) a `ThreadPool`; and, in that case, they cannot be // calling `inject()` later, since they dropped their // `ThreadPool`. - assert!( - !self.terminate_latch.probe(), + debug_assert_ne!( + self.terminate_count.load(Ordering::Acquire), + 0, "inject() sees state.terminate as true" ); + let queue_was_empty = self.injected_jobs.is_empty(); + for &job_ref in injected_jobs { self.injected_jobs.push(job_ref); } - self.sleep.tickle(usize::MAX); + + self.sleep + .new_injected_jobs(usize::MAX, injected_jobs.len() as u32, queue_was_empty); + } + + fn has_injected_job(&self) -> bool { + !self.injected_jobs.is_empty() } fn pop_injected_job(&self, worker_index: usize) -> Option { let job = self.injected_jobs.pop().ok(); if job.is_some() { - log!(UninjectedWork { - worker: worker_index + self.log(|| JobUninjected { + worker: worker_index, }); } job @@ -450,6 +468,10 @@ impl Registry { ); self.inject(&[job.as_job_ref()]); job.latch.wait_and_reset(); // Make sure we can use the same latch again next time. + + // flush accumulated logs as we exit the thread + self.logger.log(|| Flush); + job.into_result() }) } @@ -463,7 +485,7 @@ impl Registry { // This thread is a member of a different pool, so let it process // other work while waiting for this `op` to complete. debug_assert!(current_thread.registry().id() != self.id()); - let latch = TickleLatch::new(SpinLatch::new(), current_thread.registry()); + let latch = SpinLatch::cross(current_thread); let job = StackJob::new( |injected| { let worker_thread = WorkerThread::current(); @@ -498,19 +520,28 @@ impl Registry { /// terminate count and is responsible for invoking `terminate()` /// when finished. pub(super) fn increment_terminate_count(&self) { - self.terminate_latch.increment(); + let previous = self.terminate_count.fetch_add(1, Ordering::AcqRel); + debug_assert!(previous != 0, "registry ref count incremented from zero"); + assert!( + previous != std::usize::MAX, + "overflow in registry ref count" + ); } /// Signals that the thread-pool which owns this registry has been /// dropped. The worker threads will gradually terminate, once any /// extant work is completed. pub(super) fn terminate(&self) { - self.terminate_latch.set(); - self.sleep.tickle(usize::MAX); + if self.terminate_count.fetch_sub(1, Ordering::AcqRel) == 1 { + for (i, thread_info) in self.thread_infos.iter().enumerate() { + thread_info.terminate.set_and_tickle_one(self, i); + } + } } - pub(super) fn tickle(&self) { - self.sleep.tickle(usize::MAX); + /// Notify the worker that the latch they are sleeping on has been "set". + pub(super) fn notify_worker_latch_is_set(&self, target_worker_index: usize) { + self.sleep.notify_worker_latch_is_set(target_worker_index); } } @@ -529,6 +560,15 @@ struct ThreadInfo { /// until workers have stopped; only used for tests. stopped: LockLatch, + /// The latch used to signal that terminated has been requested. + /// This latch is *set* by the `terminate` method on the + /// `Registry`, once the registry's main "terminate" counter + /// reaches zero. + /// + /// NB. We use a `CountLatch` here because it has no lifetimes and is + /// meant for async use, but the count never gets higher than one. + terminate: CountLatch, + /// the "stealer" half of the worker's deque stealer: Stealer, } @@ -538,6 +578,7 @@ impl ThreadInfo { ThreadInfo { primed: LockLatch::new(), stopped: LockLatch::new(), + terminate: CountLatch::new(), stealer, } } @@ -599,10 +640,16 @@ impl WorkerThread { } /// Returns the registry that owns this worker thread. + #[inline] pub(super) fn registry(&self) -> &Arc { &self.registry } + #[inline] + pub(super) fn log(&self, event: impl FnOnce() -> crate::log::Event) { + self.registry.logger.log(event) + } + /// Our index amongst the worker threads (ranges from `0..self.num_threads()`). #[inline] pub(super) fn index(&self) -> usize { @@ -611,8 +658,12 @@ impl WorkerThread { #[inline] pub(super) unsafe fn push(&self, job: JobRef) { + self.log(|| JobPushed { worker: self.index }); + let queue_was_empty = self.worker.is_empty(); self.worker.push(job); - self.registry.sleep.tickle(self.index); + self.registry + .sleep + .new_internal_jobs(self.index, 1, queue_was_empty); } #[inline] @@ -631,21 +682,27 @@ impl WorkerThread { /// bottom. #[inline] pub(super) unsafe fn take_local_job(&self) -> Option { - self.worker.pop() + let popped_job = self.worker.pop(); + + if popped_job.is_some() { + self.log(|| JobPopped { worker: self.index }); + } + + popped_job } /// Wait until the latch is set. Try to keep busy by popping and /// stealing tasks as necessary. #[inline] - pub(super) unsafe fn wait_until(&self, latch: &L) { - log!(WaitUntil { worker: self.index }); + pub(super) unsafe fn wait_until(&self, latch: &L) { + let latch = latch.as_core_latch(); if !latch.probe() { self.wait_until_cold(latch); } } #[cold] - unsafe fn wait_until_cold(&self, latch: &L) { + unsafe fn wait_until_cold(&self, latch: &CoreLatch) { // the code below should swallow all panics and hence never // unwind; but if something does wrong, we want to abort, // because otherwise other code in rayon may assume that the @@ -653,7 +710,7 @@ impl WorkerThread { // accesses, which would be *very bad* let abort_guard = unwind::AbortIfPanic; - let mut yields = 0; + let mut idle_state = self.registry.sleep.start_looking(self.index, latch); while !latch.probe() { // Try to find some work to do. We give preference first // to things in our local deque, then in other workers @@ -665,30 +722,31 @@ impl WorkerThread { .or_else(|| self.steal()) .or_else(|| self.registry.pop_injected_job(self.index)) { - yields = self.registry.sleep.work_found(self.index, yields); + self.registry.sleep.work_found(idle_state); self.execute(job); + idle_state = self.registry.sleep.start_looking(self.index, latch); } else { - yields = self.registry.sleep.no_work_found(self.index, yields); + self.registry + .sleep + .no_work_found(&mut idle_state, latch, || self.registry.has_injected_job()) } } // If we were sleepy, we are not anymore. We "found work" -- // whatever the surrounding thread was doing before it had to // wait. - self.registry.sleep.work_found(self.index, yields); + self.registry.sleep.work_found(idle_state); - log!(LatchSet { worker: self.index }); + self.log(|| ThreadSawLatchSet { + worker: self.index, + latch_addr: latch.addr(), + }); mem::forget(abort_guard); // successful execution, do not abort } + #[inline] pub(super) unsafe fn execute(&self, job: JobRef) { job.execute(); - - // Subtle: executing this job will have `set()` some of its - // latches. This may mean that a sleepy (or sleeping) worker - // can now make progress. So we have to tickle them to let - // them know. - self.registry.sleep.tickle(self.index); } /// Try to steal a single job and return it. @@ -715,9 +773,9 @@ impl WorkerThread { match victim.stealer.steal() { Steal::Empty => return None, Steal::Success(d) => { - log!(StoleWork { + self.log(|| JobStolen { worker: self.index, - victim: victim_index + victim: victim_index, }); return Some(d); } @@ -760,7 +818,12 @@ unsafe fn main_loop(worker: Worker, registry: Arc, index: usiz } } - worker_thread.wait_until(®istry.terminate_latch); + let my_terminate_latch = ®istry.thread_infos[index].terminate; + worker_thread.log(|| ThreadStart { + worker: index, + terminate_addr: my_terminate_latch.as_core_latch().addr(), + }); + worker_thread.wait_until(my_terminate_latch); // Should not be any work left in our queue. debug_assert!(worker_thread.take_local_job().is_none()); @@ -771,6 +834,8 @@ unsafe fn main_loop(worker: Worker, registry: Arc, index: usiz // Normal termination, do not abort. mem::forget(abort_guard); + worker_thread.log(|| ThreadTerminate { worker: index }); + // Inform a user callback that we exited a thread. if let Some(ref handler) = registry.exit_handler { let registry = registry.clone(); diff --git a/rayon-core/src/scope/mod.rs b/rayon-core/src/scope/mod.rs index 7c1333d00..7d274920b 100644 --- a/rayon-core/src/scope/mod.rs +++ b/rayon-core/src/scope/mod.rs @@ -5,8 +5,7 @@ //! [`join()`]: ../join/join.fn.html use crate::job::{HeapJob, JobFifo}; -use crate::latch::{CountLatch, Latch}; -use crate::log::Event::*; +use crate::latch::CountLatch; use crate::registry::{in_worker, Registry, WorkerThread}; use crate::unwind; use std::any::Any; @@ -580,24 +579,16 @@ impl<'scope> ScopeBase<'scope> { .compare_exchange(nil, &mut *err, Ordering::Release, Ordering::Relaxed) .is_ok() { - log!(JobPanickedErrorStored { - owner_thread: self.owner_thread_index - }); mem::forget(err); // ownership now transferred into self.panic - } else { - log!(JobPanickedErrorNotStored { - owner_thread: self.owner_thread_index - }); } - self.job_completed_latch.set(); + self.job_completed_latch + .set_and_tickle_one(&self.registry, self.owner_thread_index); } unsafe fn job_completed_ok(&self) { - log!(JobCompletedOk { - owner_thread: self.owner_thread_index - }); - self.job_completed_latch.set(); + self.job_completed_latch + .set_and_tickle_one(&self.registry, self.owner_thread_index); } unsafe fn steal_till_jobs_complete(&self, owner_thread: &WorkerThread) { @@ -609,15 +600,8 @@ impl<'scope> ScopeBase<'scope> { // ordering: let panic = self.panic.swap(ptr::null_mut(), Ordering::Relaxed); if !panic.is_null() { - log!(ScopeCompletePanicked { - owner_thread: owner_thread.index() - }); let value: Box> = mem::transmute(panic); unwind::resume_unwinding(*value); - } else { - log!(ScopeCompleteNoPanic { - owner_thread: owner_thread.index() - }); } } } diff --git a/rayon-core/src/sleep/README.md b/rayon-core/src/sleep/README.md index bc2af869f..c62c3975d 100644 --- a/rayon-core/src/sleep/README.md +++ b/rayon-core/src/sleep/README.md @@ -1,388 +1,219 @@ # Introduction: the sleep module The code in this module governs when worker threads should go to -sleep. This is a tricky topic -- the work-stealing algorithm relies on -having active worker threads running around stealing from one -another. But, if there isn't a lot of work, this can be a bit of a -drag, because it requires high CPU usage. - -The code in this module takes a fairly simple approach to the -problem. It allows worker threads to fall asleep if they have failed -to steal work after various thresholds; however, whenever new work -appears, they will wake up briefly and try to steal again. There are some -shortcomings in this current approach: - -- it can (to some extent) scale *down* the amount of threads, but they - can never be scaled *up*. The latter might be useful in the case of - user tasks that must (perhaps very occasionally and unpredictably) - block for long periods of time. - - however, the preferred approach to this is for users to adopt futures - instead (and indeed this sleeping work is intended to enable future - integration). -- we have no way to wake up threads in a fine-grained or targeted - manner. The current system wakes up *all* sleeping threads whenever - *any* of them might be interested in an event. This means that while - we can scale CPU usage down, we do is in a fairly "bursty" manner, - where everyone comes online, then some of them go back offline. - -# The interface for workers - -Workers interact with the sleep module by invoking three methods: - -- `work_found()`: signals that the worker found some work and is about - to execute it. -- `no_work_found()`: signals that the worker searched all available sources - for work and found none. - - It is important for the coherence of the algorithm that if work - was available **before the search started**, it would have been - found. If work was made available during the search, then it's ok that - it might have been overlooked. -- `tickle()`: indicates that new work is available (e.g., a job has - been pushed to the local deque) or that some other blocking - condition has been resolved (e.g., a latch has been set). Wakes up any - sleeping workers. - -When in a loop searching for work, Workers also have to maintain an -integer `yields` that they provide to the `sleep` module (which will -return a new value for the next time). Thus the basic worker "find -work" loop looks like this (this is `wait_until()`, basically): - -```rust -let mut yields = 0; -while /* not done */ { - if let Some(job) = search_for_work() { - yields = work_found(self.index, yields); - } else { - yields = no_work_found(self.index, yields); - } -} -``` - -# Getting sleepy and falling asleep - -The basic idea here is that every worker goes through three states: - -- **Awake:** actively hunting for tasks. -- **Sleepy:** still actively hunting for tasks, but we have signaled that - we might go to sleep soon if we don't find any. -- **Asleep:** actually asleep (blocked on a condition variable). - -At any given time, only **one** worker can be in the sleepy -state. This allows us to coordinate the entire sleep protocol using a -single `AtomicUsize` and without the need of epoch counters or other -things that might rollover and so forth. - -Whenever a worker invokes `work_found()`, it transitions back to the -**awake** state. In other words, if it was sleepy, it stops being -sleepy. (`work_found()` cannot be invoked when the worker is asleep, -since then it is not doing anything.) - -On the other hand, whenever a worker invokes `no_work_found()`, it -*may* transition to a more sleepy state. To track this, we use the -counter `yields` that is maintained by the worker's steal loop. This -counter starts at 0. Whenever work is found, the counter is returned -to 0. But each time that **no** work is found, the counter is -incremented. Eventually it will reach a threshold -`ROUNDS_UNTIL_SLEEPY`. At this point, the worker will try to become -the sleepy one. It does this by executing a CAS into the global -registry state (details on this below). If that attempt is successful, -then the counter is incremented again, so that it is equal to -`ROUNDS_UNTIL_SLEEPY + 1`. Otherwise, the counter stays the same (and -hence we will keep trying to become sleepy until either work is found -or we are successful). - -Becoming sleepy does not put us to sleep immediately. Instead, we keep -iterating and looking for work for some further number of rounds. If -during this search we **do** find work, then we will return the -counter to 0 and also reset the global state to indicate we are no -longer sleepy. - -But if again no work is found, `yields` will eventually reach the -value `ROUNDS_UNTIL_ASLEEP`. At that point, we will try to transition -from **sleepy** to **asleep**. This is done by the helper fn -`sleep()`, which executes another CAS on the global state that removes -our worker as the sleepy worker and instead sets a flag to indicate -that there are sleeping workers present (the flag may already have -been set, that's ok). Assuming that CAS succeeds, we will block on a -condition variable. - -# Tickling workers - -Of course, while all the stuff in the previous section is happening, -other workers are (hopefully) producing new work. There are three kinds of -events that can allow a blocked worker to make progress: - -1. A new task is pushed onto a worker's deque. This task could be stolen. -2. A new task is injected into the thread-pool from the outside. This - task could be uninjected and executed. -3. A latch is set. One of the sleeping workers might have been waiting for - that before it could go on. - -Whenever one of these things happens, the worker (or thread, more generally) -responsible must invoke `tickle()`. Tickle will basically wake up **all** -the workers: - -- If any worker was the sleepy one, then the global state is changed - so that there is no sleepy worker. The sleepy one will notice this - when it next invokes `no_work_found()` and return to the *awake* state - (with a yield counter of zero). -- If any workers were actually **asleep**, then we invoke - `notify_all()` on the condition variable, which will cause them to - awaken and start over from the awake state (with a yield counter of - zero). - -Because `tickle()` is invoked very frequently -- and hopefully most of -the time it is not needed, because the workers are already actively -stealing -- it is important that it be very cheap. The current design -requires, in the case where nobody is even sleepy, just a load and a -compare. If there are sleepy workers, a swap is needed. If there -workers *asleep*, we must naturally acquire the lock and signal the -condition variable. - -# The global state - -We manage all of the above state transitions using a small bit of global -state (well, global to the registry). This is stored in the `Sleep` struct. -The primary thing is a single `AtomicUsize`. The value in this usize packs -in two pieces of information: - -1. **Are any workers asleep?** This is just one bit (yes or no). -2. **Which worker is the sleepy worker, if any?** This is a worker id. - -We use bit 0 to indicate whether any workers are asleep. So if `state -& 1` is zero, then no workers are sleeping. But if `state & 1` is 1, -then some workers are either sleeping or on their way to falling -asleep (i.e., they have acquired the lock). - -The remaining bits are used to store if there is a sleepy worker. We -want `0` to indicate that there is no sleepy worker. If there a sleepy -worker with index `worker_index`, we would store `(worker_index + 1) -<< 1` . The `+1` is there because worker indices are 0-based, so this -ensures that the value is non-zero, and the shift skips over the -sleepy bit. - -Some examples: - -- `0`: everyone is awake, nobody is sleepy -- `1`: some workers are asleep, no sleepy worker -- `2`: no workers are asleep, but worker 0 is sleepy (`(0 + 1) << 1 == 2`). -- `3`: some workers are asleep, and worker 0 is sleepy. - -# Correctness level 1: avoiding deadlocks etc - -In general, we do not want to miss wakeups. Two bad things could happen: - -- **Suboptimal performance**: If this is a wakeup about a new job being - pushed into a local deque, it won't deadlock, but it will cause - things to run slowly. The reason that it won't deadlock is that we - know at least one thread is active (the one doing the pushing), and - it will (sooner or later) try to pop this item from its own local - deque. -- **Deadlocks:** If this is a wakeup about an injected job or a latch that got set, however, - this can cause deadlocks. In the former case, if a job is injected but no thread ever - wakes to process it, the injector will likely block forever. In the latter case, - imagine this scenario: - - thread A calls join, forking a task T1, then executing task T2 - - thread B steals T1, forks a task T3, and executes T4. - - thread A completes task T2 and blocks on T1 - - thread A steals task T3 from thread B - - thread B finishes T4 and goes to sleep, blocking on T3 - - thread A completes task T3 and makes a wakeup, but it gets lost - At this point, thread B is still asleep and will never signal T2, so thread A will itself - go to sleep. Bad. - -It turns out that guaranteeing we don't miss a wakeup while retaining -good performance is fairly tricky. This is because of some details of -the C++11 memory model. But let's ignore those for now and generally -assume sequential consistency. In that case, our scheme should work -perfectly. - -Even if you assume seqcst, though, ensuring that you don't miss -wakeups can be fairly tricky in the absence of a central queue. For -example, consider the simplest scheme: imagine we just had a boolean -flag indicating whether anyone was asleep. Then you could imagine that -when workers find no work, they flip this flag to true. When work is -published, if the flag is true, we issue a wakeup. - -The problem here is that checking for new work is not an atomic -action. So it's possible that worker 1 could start looking for work -and (say) see that worker 0's queue is empty and then search workers -2..N. While that searching is taking place, worker 0 publishes some -new work. At the time when the new work is published, the "anyone -sleeping?" flag is still false, so nothing happens. Then worker 1, who -failed to find any work, goes to sleep --- completely missing the wakeup! - -We use the "sleepy worker" idea to sidestep this problem. Under our -scheme, instead of going right to sleep at the end, worker 1 would -become sleepy. Worker 1 would then do **at least** one additional -scan. During this scan, they should find the work published by worker -0, so they will stop being sleepy and go back to work (here of course -we are assuming that no one else has stolen the worker 0 work yet; if -someone else stole it, worker 1 may still go to sleep, but that's ok, -since there is no more work to be had). - -Now you may be wondering -- how does being sleepy help? What if, -instead of publishing its job right before worker 1 became sleepy, -worker 0 wait until right before worker 1 was going to go to sleep? In -other words, the sequence was like this: - -- worker 1 gets sleepy -- worker 1 starts its scan, scanning worker 0's deque -- worker 0 publishes its job, but nobody is sleeping yet, so no wakeups occur -- worker 1 finshes its scan, goes to sleep, missing the wakeup - -The reason that this doesn't occur is because, when worker 0 publishes -its job, it will see that there is a sleepy worker. It will clear the -global state to 0. Then, when worker 1 its scan, it will notice that -it is no longer sleepy, and hence it will not go to sleep. Instead it -will awaken and keep searching for work. - -The sleepy worker phase thus also serves as a cheap way to signal that -work is around: instead of doing the whole dance of acquiring a lock -and issuing notifications, when we publish work we can just swap a -single atomic counter and let the sleepy worker notice that on their -own. - -## Beyond seq-cst - -Unfortunately, the C++11 memory model doesn't generally guarantee -seq-cst. And, somewhat annoyingly, it's not easy for the sleep module -**in isolation** to guarantee the properties the need. The key -challenge has to do with the *synchronized-with* relation. Typically, -we try to use acquire-release reasoning, and in that case the idea is -that **if** a load observes a store, it will also observe those writes -that preceded the store. But nothing says that the load **must** -observe the store -- at least not right away. - -The place that this is most relevant is the load in the `tickle()` -routine. The routine begins by reading from the global state. If it -sees anything other than 0, it then does a swap and -- if necessary -- -acquires a lock and does a notify. This load is a seq-cst load (as are -the other accesses in tickle). This ensures that it is sensible to -talk about a tickle happening *before* a worker gets sleepy and so -forth. - -It turns out that to get things right, if we use the current tickle -routine, we have to use seq-cst operations **both in the sleep module -and when publishing work**. We'll walk through two scenarios to -show what I mean. - -### Scenario 1: get-sleepy-then-get-tickled - -This scenario shows why the operations in sleep must be seq-cst. We -want to ensure that once a worker gets sleepy, any other worker that -does a tickle will observe that. In other words, we want to ensure -that the following scenario **cannot happen**: - -1. worker 1 is blocked on latch L -2. worker 1 becomes sleepy - - becoming sleepy involves a CAS on the global state to set it to 4 ("worker 1 is sleepy") -3. worker 0 sets latch L -4. worker 0 tickles **but does not see that worker 0 is sleepy** - -Let's diagram this. The notation `read_xxx(A) = V` means that a read -of location `A` was executed with the result `V`. The `xxx` is the -ordering and the location `A` is either `L` (latch) or `S` (global -state). I will leave the ordering on the latch as `xxx` as it is not -relevant here. The numbers correspond to the steps above. - -``` - worker 0 worker 1 - | +- 2: cas_sc(S, 4) -s| 3: write_xxx(L) + -b| 4: read_sc(S) = ??? <-sc-+ - v -``` - -Clearly, this cannot happen with sc orderings, because read 4 will -always return `4` here. However, if we tried to use acquire-release -orderings on the global state, then there would be **no guarantee** -that the tickle will observe that a sleepy worker occurred. We would -be guaranteed only that worker 0 would **eventually** observe that -worker 1 had become sleepy (and, at that time, that it would see other -writes). But it could take time -- and if we indeed miss that worker 1 -is sleepy, it could lead to deadlock or loss of efficiency, as -explained earlier. - -### Scenario 2: tickle-then-get-sleepy - - - -This scenario shows why latch operations must *also* be seq-cst (and, -more generally, any operations that publish work before a tickle). We -wish to ensure that this ordering of events **cannot occur**: - -1. worker 1 is blocked on latch L -2. worker 1 reads latch L, sees false, starts searching for work -3. worker 0 sets latch L -4. worker 0 tickles - - the tickle reads from the global state, sees 0 -5. worker 1 finishes searching, becomes sleepy - - becoming sleepy involves a CAS on the global state to set it to 4 ("worker 1 is sleepy") -6. worker 1 reads latch L **but does not see that worker 0 set it** -7. worker 1 may then proceed to become sleepy - -In other words, we want to ensure that if worker 0 sets a latch and -does a tickle *before worker 1 gets sleepy*, then worker 1 will -observe that latch as set when it calls probe. We'll see that, with -the current scheme, this implies that the latch memory orderings must -be seq-cst as well. - -Here is the diagram: - -``` - worker 0 worker 1 - | 2: read_xxx(L) = false -s| 3: write_xxx(L, true) -b| 4: read_sc(S) = 0 -+ - | +-sc---> 5: cas_sc(S, 4) - v 6: read_xxx(L) = ??? -``` - -The diagram shows that each thread's actions are related by -*sequenced-before* (sb). Moreover the read and write of `S` are -related by `sc` (the seq-cst ordering). However, and this is crucial, -this **does not** imply that oper 4 *synchronizes-with* oper 5. This -is because a read never synchronizes-with a store, only the -reverse. Hence, if the latch were using acq-rel orderings, it would be -legal for oper 6 to return false. But if the latch were to use an -**sc** ordering itself, then we know that oper 6 must return true, -since `3 -sc-> 4 -sc-> 5 -sc-> 6`. - -**Note** that this means that, before we tickle, we must execute some -seq-cst stores to publish our work (and during the scan we must load -from those same locations) **if we wish to guarantee that the work we -published WILL be seen by the other threads** (as opposed to -*may*). This is true for setting a latch -- if a latch is set but -another thread misses it, then the system could deadlock. However, in -the case of pushing new work to a deque, we choose not to use a seqcst -ordering. This is for several reasons: - -- If we miss a wakeup, the consequences are less dire: we simply run - less efficiently (after all, the current thread will eventually - complete its current task and pop the new task off the deque). -- It is inconvenient: The deque code is beyond our control (it lies in another package). However, - we could create a dummy `AtomicBool` for each deque and do a seqcst write to it - (with whatever value) after we push to the deque, and a seqcst load whenever - we steal from the deque. -- The cost of using a dummy variable was found to be quite high for some benchmarks: - - 8-10% overhead on nbody-parreduce - - 15% overhead on increment-all - - 40% overhead on join-recursively - -### Alternative solutions - -In both cases above, our problems arose because tickle is merely -performing a seq-cst read. If we instead had tickle perform a release -*swap*, that would be a write action of the global state. No matter -the ordering mode, all writes to the same memory location have a total -ordering, and hence we would not have to worry about others storing a -value that we fail to read (as in scenario 1). Similarly, as a release -write, a swap during tickle would synchronize-with a later cas and so -scenario 2 should be averted. So you might wonder why we don't do -that. The simple reason was that it didn't perform as well! In my -measurements, many benchmarks were unaffected by using a swap, but -some of them were hit hard: - - 8-10% overhead on nbody-parreduce - - 35% overhead on increment-all - - 245% overhead on join-recursively +sleep. The system used in this code was introduced in [Rayon RFC #5]. +There is also a [video walkthrough] available. Both of those may be +valuable resources to understanding the code, though naturally they +will also grow stale over time. The comments in this file are +extracted from the RFC and meant to be kept up to date. + +[Rayon RFC #5]: https://github.com/rayon-rs/rfcs/pull/5 +[video walkthrough]: https://youtu.be/HvmQsE5M4cY + +# The `Sleep` struct + +The `Sleep` struct is embedded into each registry. It performs several functions: + +* It tracks when workers are awake or asleep. +* It decides how long a worker should look for work before it goes to sleep, + via a callback that is invoked periodically from the worker's search loop. +* It is notified when latches are set, jobs are published, or other + events occur, and it will go and wake the appropriate threads if + they are sleeping. + +# Thread states + +There are three main thread states: + +* An **active** thread is one that is actively executing a job. +* An **idle** thread is one that is searching for work to do. It will be + trying to steal work or pop work from the global injector queue. +* A **sleeping** thread is one that is blocked on a condition variable, + waiting to be awoken. + +We sometimes refer to the final two states collectively as **inactive**. +Threads begin as idle but transition to idle and finally sleeping when +they're unable to find work to do. + +## Sleepy threads + +There is one other special state worth mentioning. During the idle state, +threads can get **sleepy**. A sleepy thread is still idle, in that it is still +searching for work, but it is *about* to go to sleep after it does one more +search (or some other number, potentially). When a thread enters the sleepy +state, it signals (via the **jobs event counter**, described below) that it is +about to go to sleep. If new work is published, this will lead to the counter +being adjusted. When the thread actually goes to sleep, it will (hopefully, but +not guaranteed) see that the counter has changed and elect not to sleep, but +instead to search again. See the section on the **jobs event counter** for more +details. + +# The counters + +One of the key structs in the sleep module is `AtomicCounters`, found in +`counters.rs`. It packs three counters into one atomically managed value: + +* Two **thread counters**, which track the number of threads in a particular state. +* The **jobs event counter**, which is used to signal when new work is available. + It (sort of) tracks the number of jobs posted, but not quite, and it can rollover. + +## Thread counters + +There are two thread counters, one that tracks **inactive** threads and one that +tracks **sleeping** threads. From this, one can deduce the number of threads +that are idle by subtracting sleeping threads from inactive threads. We track +the counters in this way because it permits simpler atomic operations. One can +increment the number of sleeping threads (and thus decrease the number of idle +threads) simply by doing one atomic increment, for example. Similarly, one can +decrease the number of sleeping threads (and increase the number of idle +threads) through one atomic decrement. + +These counters are adjusted as follows: + +* When a thread enters the idle state: increment the inactive thread counter. +* When a thread enters the sleeping state: increment the sleeping thread counter. +* When a thread awakens a sleeping thread: decrement the sleeping thread counter. + * Subtle point: the thread that *awakens* the sleeping thread decrements the + counter, not the thread that is *sleeping*. This is because there is a delay + between siganling a thread to wake and the thread actually waking: + decrementing the counter when awakening the thread means that other threads + that may be posting work will see the up-to-date value that much faster. +* When a thread finds work, exiting the idle state: decrement the inactive + thread counter. + +## Jobs event counter + +The final counter is the **jobs event counter**. The role of this counter is to +help sleepy threads detect when new work is posted in a lightweight fashion. In +its simplest form, we would simply have a counter that gets incremented each +time a new job is posted. This way, when a thread gets sleepy, it could read the +counter, and then compare to see if the value has changed before it actually +goes to sleep. But this [turns out to be too expensive] in practice, so we use a +somewhat more complex scheme. + +[turns out to be too expensive]: https://github.com/rayon-rs/rayon/pull/746#issuecomment-624802747 + +The idea is that the counter toggles between two states, depending on whether +its value is even or odd (or, equivalently, on the value of its low bit): + +* Even -- If the low bit is zero, then it means that there has been no new work + since the last thread got sleepy. +* Odd -- If the low bit is one, then it means that new work was posted since + the last thread got sleepy. + +### New work is posted + +When new work is posted, we check the value of the counter: if it is even, +then we increment it by one, so that it becomes odd. + +### Worker thread gets sleepy + +When a worker thread gets sleepy, it will read the value of the counter. If the +counter is odd, it will increment the counter so that it is even. Either way, it +remembers the final value of the counter. The final value will be used later, +when the thread is going to sleep. If at that time the counter has not changed, +then we can assume no new jobs have been posted (though note the remote +possibility of rollover, discussed in detail below). + +# Protocol for a worker thread to post work + +The full protocol for a thread to post work is as follows + +* If the work is posted into the injection queue, then execute a seq-cst fence (see below). +* Load the counters, incrementing the JEC if it is even so that it is odd. +* Check if there are idle threads available to handle this new job. If not, + and there are sleeping threads, then wake one or more threads. + +# Protocol for a worker thread to fall asleep + +The full protocol for a thread to fall asleep is as follows: + +* After completing all its jobs, the worker goes idle and begins to + search for work. As it searches, it counts "rounds". In each round, + it searches all other work threads' queues, plus the 'injector queue' for + work injected from the outside. If work is found in this search, the thread + becomes active again and hence restarts this protocol from the top. +* After a certain number of rounds, the thread "gets sleepy" and executes `get_sleepy` + above, remembering the `final_value` of the JEC. It does one more search for work. +* If no work is found, the thread atomically: + * Checks the JEC to see that it has not changed from `final_value`. + * If it has, then the thread goes back to searchin for work. We reset to + just before we got sleepy, so that we will do one more search + before attending to sleep again (rather than searching for many rounds). + * Increments the number of sleeping threads by 1. +* The thread then executes a seq-cst fence operation (see below). +* The thread then does one final check for injected jobs (see below). If any + are available, it returns to the 'pre-sleepy' state as if the JEC had changed. +* The thread waits to be signaled. Once signaled, it returns to the idle state. + +# The jobs event counter and deadlock + +As described in the section on the JEC, the main concern around going to sleep +is avoiding a race condition wherein: + +* Thread A looks for work, finds none. +* Thread B posts work but sees no sleeping threads. +* Thread A goes to sleep. + +The JEC protocol largely prevents this, but due to rollover, this prevention is +not complete. It is possible -- if unlikely -- that enough activity occurs for +Thread A to observe the same JEC value that it saw when getting sleepy. If the +new work being published came from *inside* the thread-pool, then this race +condition isn't too harmful. It means that we have fewer workers processing the +work then we should, but we won't deadlock. This seems like an acceptable risk +given that this is unlikely in practice. + +However, if the work was posted as an *external* job, that is a problem. In that +case, it's possible that all of our workers could go to sleep, and the external +job would never get processed. To prevent that, the sleeping protocol includes +one final check to see if the injector queue is empty before fully falling +asleep. Note that this final check occurs **after** the number of sleeping +threads has been incremented. We are not concerned therefore with races against +injections that occur after that increment, only before. + +Unfortunately, there is one rather subtle point concerning this final check: +we wish to avoid the possibility that: + +* work is pushed into the injection queue by an outside thread X, +* the sleepy thread S sees the JEC but it has rolled over and is equal +* the sleepy thread S reads the injection queue but does not see the work posted by X. + +This is possible because the C++ memory model typically offers guarantees of the +form "if you see the access A, then you must see those other accesses" -- but it +doesn't guarantee that you will see the access A (i.e., if you think of +processors with independent caches, you may be operating on very out of date +cache state). + +## Using seq-cst fences to prevent deadlock + +To overcome this problem, we have inserted two sequentially consistent fence +operations into the protocols above: + +* One fence occurs after work is posted into the injection queue, but before the + counters are read (including the number of sleeping threads). + * Note that no fence is needed for work posted to internal queues, since it is ok + to overlook work in that case. +* One fence occurs after the number of sleeping threads is incremented, but + before the injection queue is read. + +### Proof sketch + +What follows is a "proof sketch" that the protocol is deadlock free. We model +two relevant bits of memory, the job injector queue J and the atomic counters C. + +Consider the actions of the injecting thread: + +* PushJob: Job is injected, which can be modeled as an atomic write to J with release semantics. +* PushFence: A sequentially consistent fence is executed. +* ReadSleepers: The counters C are read (they may also be incremented, but we just consider the read that comes first). + +Meanwhile, the sleepy thread does the following: + +* IncSleepers: The number of sleeping threads is incremented, which is atomic exchange to C. +* SleepFence: A sequentially consistent fence is executed. +* ReadJob: We look to see if the queue is empty, which is a read of J with acquire semantics. + +Either PushFence or SleepFence must come first: + +* If PushFence comes first, then PushJob must be visible to ReadJob. +* If SleepFence comes first, then IncSleepers is visible to ReadSleepers. \ No newline at end of file diff --git a/rayon-core/src/sleep/counters.rs b/rayon-core/src/sleep/counters.rs new file mode 100644 index 000000000..626e24f1e --- /dev/null +++ b/rayon-core/src/sleep/counters.rs @@ -0,0 +1,269 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +pub(super) struct AtomicCounters { + /// Packs together a number of counters. The counters are ordered as + /// follows, from least to most significant bits (here, we assuming + /// that [`THREADS_BITS`] is equal to 10): + /// + /// * Bits 0..10: Stores the number of **sleeping threads** + /// * Bits 10..20: Stores the number of **inactive threads** + /// * Bits 20..: Stores the **job event counter** (JEC) + /// + /// This uses 10 bits ([`THREADS_BITS`]) to encode the number of threads. Note + /// that the total number of bits (and hence the number of bits used for the + /// JEC) will depend on whether we are using a 32- or 64-bit architecture. + value: AtomicUsize, +} + +#[derive(Copy, Clone)] +pub(super) struct Counters { + word: usize, +} + +/// A value read from the **Jobs Event Counter**. +/// See the [`README.md`](README.md) for more +/// coverage of how the jobs event counter works. +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub(super) struct JobsEventCounter(usize); + +impl JobsEventCounter { + pub(super) const DUMMY: JobsEventCounter = JobsEventCounter(std::usize::MAX); + + #[inline] + pub(super) fn as_usize(self) -> usize { + self.0 + } + + /// The JEC "is sleepy" if the last thread to increment it was in the + /// process of becoming sleepy. This is indicated by its value being *even*. + /// When new jobs are posted, they check if the JEC is sleepy, and if so + /// they incremented it. + #[inline] + pub(super) fn is_sleepy(self) -> bool { + (self.as_usize() & 1) == 0 + } + + /// The JEC "is active" if the last thread to increment it was posting new + /// work. This is indicated by its value being *odd*. When threads get + /// sleepy, they will check if the JEC is active, and increment it. + #[inline] + pub(super) fn is_active(self) -> bool { + !self.is_sleepy() + } +} + +/// Number of bits used for the thread counters. +const THREADS_BITS: usize = 10; + +/// Bits to shift to select the sleeping threads +/// (used with `select_bits`). +const SLEEPING_SHIFT: usize = 0 * THREADS_BITS; + +/// Bits to shift to select the inactive threads +/// (used with `select_bits`). +const INACTIVE_SHIFT: usize = 1 * THREADS_BITS; + +/// Bits to shift to select the JEC +/// (use JOBS_BITS). +const JEC_SHIFT: usize = 2 * THREADS_BITS; + +/// Max value for the thread counters. +const THREADS_MAX: usize = (1 << THREADS_BITS) - 1; + +/// Constant that can be added to add one sleeping thread. +const ONE_SLEEPING: usize = 1; + +/// Constant that can be added to add one inactive thread. +/// An inactive thread is either idle, sleepy, or sleeping. +const ONE_INACTIVE: usize = 1 << INACTIVE_SHIFT; + +/// Constant that can be added to add one to the JEC. +const ONE_JEC: usize = 1 << JEC_SHIFT; + +impl AtomicCounters { + #[inline] + pub(super) fn new() -> AtomicCounters { + AtomicCounters { + value: AtomicUsize::new(0), + } + } + + /// Load and return the current value of the various counters. + /// This value can then be given to other method which will + /// attempt to update the counters via compare-and-swap. + #[inline] + pub(super) fn load(&self, ordering: Ordering) -> Counters { + Counters::new(self.value.load(ordering)) + } + + #[inline] + fn try_exchange(&self, old_value: Counters, new_value: Counters, ordering: Ordering) -> bool { + self.value + .compare_exchange(old_value.word, new_value.word, ordering, Ordering::Relaxed) + .is_ok() + } + + /// Adds an inactive thread. This cannot fail. + /// + /// This should be invoked when a thread enters its idle loop looking + /// for work. It is decremented when work is found. Note that it is + /// not decremented if the thread transitions from idle to sleepy or sleeping; + /// so the number of inactive threads is always greater-than-or-equal + /// to the number of sleeping threads. + #[inline] + pub(super) fn add_inactive_thread(&self) { + self.value.fetch_add(ONE_INACTIVE, Ordering::SeqCst); + } + + /// Increments the jobs event counter if `increment_when`, when applied to + /// the current value, is true. Used to toggle the JEC from even (sleepy) to + /// odd (active) or vice versa. Returns the final value of the counters, for + /// which `increment_when` is guaranteed to return false. + pub(super) fn increment_jobs_event_counter_if( + &self, + increment_when: impl Fn(JobsEventCounter) -> bool, + ) -> Counters { + loop { + let old_value = self.load(Ordering::SeqCst); + if increment_when(old_value.jobs_counter()) { + let new_value = old_value.plus(ONE_JEC); + if self.try_exchange(old_value, new_value, Ordering::SeqCst) { + return new_value; + } + } else { + return old_value; + } + } + } + + /// Subtracts an inactive thread. This cannot fail. It is invoked + /// when a thread finds work and hence becomes active. It returns the + /// number of sleeping threads to wake up (if any). + /// + /// See `add_inactive_thread`. + #[inline] + pub(super) fn sub_inactive_thread(&self) -> usize { + let old_value = Counters::new(self.value.fetch_sub(ONE_INACTIVE, Ordering::SeqCst)); + debug_assert!( + old_value.inactive_threads() > 0, + "sub_inactive_thread: old_value {:?} has no inactive threads", + old_value, + ); + debug_assert!( + old_value.sleeping_threads() <= old_value.inactive_threads(), + "sub_inactive_thread: old_value {:?} had {} sleeping threads and {} inactive threads", + old_value, + old_value.sleeping_threads(), + old_value.inactive_threads(), + ); + + // Current heuristic: whenever an inactive thread goes away, if + // there are any sleeping threads, wake 'em up. + let sleeping_threads = old_value.sleeping_threads(); + std::cmp::min(sleeping_threads, 2) + } + + /// Subtracts a sleeping thread. This cannot fail, but it is only + /// safe to do if you you know the number of sleeping threads is + /// non-zero (i.e., because you have just awoken a sleeping + /// thread). + #[inline] + pub(super) fn sub_sleeping_thread(&self) { + let old_value = Counters::new(self.value.fetch_sub(ONE_SLEEPING, Ordering::SeqCst)); + debug_assert!( + old_value.sleeping_threads() > 0, + "sub_sleeping_thread: old_value {:?} had no sleeping threads", + old_value, + ); + debug_assert!( + old_value.sleeping_threads() <= old_value.inactive_threads(), + "sub_sleeping_thread: old_value {:?} had {} sleeping threads and {} inactive threads", + old_value, + old_value.sleeping_threads(), + old_value.inactive_threads(), + ); + } + + #[inline] + pub(super) fn try_add_sleeping_thread(&self, old_value: Counters) -> bool { + debug_assert!( + old_value.inactive_threads() > 0, + "try_add_sleeping_thread: old_value {:?} has no inactive threads", + old_value, + ); + debug_assert!( + old_value.sleeping_threads() < THREADS_MAX, + "try_add_sleeping_thread: old_value {:?} has too many sleeping threads", + old_value, + ); + + let mut new_value = old_value; + new_value.word += ONE_SLEEPING; + + self.try_exchange(old_value, new_value, Ordering::SeqCst) + } +} + +#[inline] +fn select_thread(word: usize, shift: usize) -> usize { + ((word >> shift) as usize) & THREADS_MAX +} + +#[inline] +fn select_jec(word: usize) -> usize { + (word >> JEC_SHIFT) as usize +} + +impl Counters { + #[inline] + fn new(word: usize) -> Counters { + Counters { word } + } + + #[inline] + fn plus(self, word: usize) -> Counters { + Counters { + word: self.word + word, + } + } + + #[inline] + pub(super) fn jobs_counter(self) -> JobsEventCounter { + JobsEventCounter(select_jec(self.word)) + } + + /// The number of threads that are not actively + /// executing work. They may be idle, sleepy, or asleep. + #[inline] + pub(super) fn inactive_threads(self) -> usize { + select_thread(self.word, INACTIVE_SHIFT) + } + + #[inline] + pub(super) fn awake_but_idle_threads(self) -> usize { + debug_assert!( + self.sleeping_threads() <= self.inactive_threads(), + "sleeping threads: {} > raw idle threads {}", + self.sleeping_threads(), + self.inactive_threads() + ); + self.inactive_threads() - self.sleeping_threads() + } + + #[inline] + pub(super) fn sleeping_threads(self) -> usize { + select_thread(self.word, SLEEPING_SHIFT) + } +} + +impl std::fmt::Debug for Counters { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let word = format!("{:016x}", self.word); + fmt.debug_struct("Counters") + .field("word", &word) + .field("jobs", &self.jobs_counter().0) + .field("inactive", &self.inactive_threads()) + .field("sleeping", &self.sleeping_threads()) + .finish() + } +} diff --git a/rayon-core/src/sleep/mod.rs b/rayon-core/src/sleep/mod.rs index 002dc483f..2aa262c51 100644 --- a/rayon-core/src/sleep/mod.rs +++ b/rayon-core/src/sleep/mod.rs @@ -1,281 +1,392 @@ //! Code that decides when workers should go to sleep. See README.md //! for an overview. +use crate::latch::CoreLatch; use crate::log::Event::*; -use std::sync::atomic::{AtomicUsize, Ordering}; +use crate::log::Logger; +use crossbeam_utils::CachePadded; +use std::sync::atomic::Ordering; use std::sync::{Condvar, Mutex}; use std::thread; use std::usize; +mod counters; +use self::counters::{AtomicCounters, JobsEventCounter}; + +/// The `Sleep` struct is embedded into each registry. It governs the waking and sleeping +/// of workers. It has callbacks that are invoked periodically at significant events, +/// such as when workers are looping and looking for work, when latches are set, or when +/// jobs are published, and it either blocks threads or wakes them in response to these +/// events. See the [`README.md`] in this module for more details. +/// +/// [`README.md`] README.md pub(super) struct Sleep { - state: AtomicUsize, - data: Mutex<()>, - tickle: Condvar, + logger: Logger, + + /// One "sleep state" per worker. Used to track if a worker is sleeping and to have + /// them block. + worker_sleep_states: Vec>, + + counters: AtomicCounters, } -const AWAKE: usize = 0; -const SLEEPING: usize = 1; +/// An instance of this struct is created when a thread becomes idle. +/// It is consumed when the thread finds work, and passed by `&mut` +/// reference for operations that preserve the idle state. (In other +/// words, producing one of these structs is evidence the thread is +/// idle.) It tracks state such as how long the thread has been idle. +pub(super) struct IdleState { + /// What is worker index of the idle thread? + worker_index: usize, -const ROUNDS_UNTIL_SLEEPY: usize = 32; -const ROUNDS_UNTIL_ASLEEP: usize = 64; + /// How many rounds have we been circling without sleeping? + rounds: u32, -impl Sleep { - pub(super) fn new() -> Sleep { - Sleep { - state: AtomicUsize::new(AWAKE), - data: Mutex::new(()), - tickle: Condvar::new(), - } - } + /// Once we become sleepy, what was the sleepy counter value? + /// Set to `INVALID_SLEEPY_COUNTER` otherwise. + jobs_counter: JobsEventCounter, +} - fn anyone_sleeping(&self, state: usize) -> bool { - state & SLEEPING != 0 - } +/// The "sleep state" for an individual worker. +#[derive(Default)] +struct WorkerSleepState { + /// Set to true when the worker goes to sleep; set to false when + /// the worker is notified or when it wakes. + is_blocked: Mutex, - fn any_worker_is_sleepy(&self, state: usize) -> bool { - (state >> 1) != 0 - } + condvar: Condvar, +} - fn worker_is_sleepy(&self, state: usize, worker_index: usize) -> bool { - (state >> 1) == (worker_index + 1) - } +const ROUNDS_UNTIL_SLEEPY: u32 = 32; +const ROUNDS_UNTIL_SLEEPING: u32 = ROUNDS_UNTIL_SLEEPY + 1; - fn with_sleepy_worker(&self, state: usize, worker_index: usize) -> usize { - debug_assert!(state == AWAKE || state == SLEEPING); - ((worker_index + 1) << 1) + state +impl Sleep { + pub(super) fn new(logger: Logger, n_threads: usize) -> Sleep { + Sleep { + logger, + worker_sleep_states: (0..n_threads).map(|_| Default::default()).collect(), + counters: AtomicCounters::new(), + } } #[inline] - pub(super) fn work_found(&self, worker_index: usize, yields: usize) -> usize { - log!(FoundWork { + pub(super) fn start_looking(&self, worker_index: usize, latch: &CoreLatch) -> IdleState { + self.logger.log(|| ThreadIdle { worker: worker_index, - yields, + latch_addr: latch.addr(), }); - if yields > ROUNDS_UNTIL_SLEEPY { - // FIXME tickling here is a bit extreme; mostly we want to "release the lock" - // from us being sleepy, we don't necessarily need to wake others - // who are sleeping - self.tickle(worker_index); + + self.counters.add_inactive_thread(); + + IdleState { + worker_index, + rounds: 0, + jobs_counter: JobsEventCounter::DUMMY, } - 0 } #[inline] - pub(super) fn no_work_found(&self, worker_index: usize, yields: usize) -> usize { - log!(DidNotFindWork { - worker: worker_index, - yields, + pub(super) fn work_found(&self, idle_state: IdleState) { + self.logger.log(|| ThreadFoundWork { + worker: idle_state.worker_index, + yields: idle_state.rounds, }); - if yields < ROUNDS_UNTIL_SLEEPY { + + // If we were the last idle thread and other threads are still sleeping, + // then we should wake up another thread. + let threads_to_wake = self.counters.sub_inactive_thread(); + self.wake_any_threads(threads_to_wake as u32); + } + + #[inline] + pub(super) fn no_work_found( + &self, + idle_state: &mut IdleState, + latch: &CoreLatch, + has_injected_jobs: impl FnOnce() -> bool, + ) { + if idle_state.rounds < ROUNDS_UNTIL_SLEEPY { thread::yield_now(); - yields + 1 - } else if yields == ROUNDS_UNTIL_SLEEPY { + idle_state.rounds += 1; + } else if idle_state.rounds == ROUNDS_UNTIL_SLEEPY { + idle_state.jobs_counter = self.announce_sleepy(idle_state.worker_index); + idle_state.rounds += 1; thread::yield_now(); - if self.get_sleepy(worker_index) { - yields + 1 - } else { - yields - } - } else if yields < ROUNDS_UNTIL_ASLEEP { + } else if idle_state.rounds < ROUNDS_UNTIL_SLEEPING { + idle_state.rounds += 1; thread::yield_now(); - if self.still_sleepy(worker_index) { - yields + 1 - } else { - log!(GotInterrupted { - worker: worker_index - }); - 0 - } } else { - debug_assert_eq!(yields, ROUNDS_UNTIL_ASLEEP); - self.sleep(worker_index); - 0 - } - } - - pub(super) fn tickle(&self, worker_index: usize) { - // As described in README.md, this load must be SeqCst so as to ensure that: - // - if anyone is sleepy or asleep, we *definitely* see that now (and not eventually); - // - if anyone after us becomes sleepy or asleep, they see memory events that - // precede the call to `tickle()`, even though we did not do a write. - let old_state = self.state.load(Ordering::SeqCst); - if old_state != AWAKE { - self.tickle_cold(worker_index); + debug_assert_eq!(idle_state.rounds, ROUNDS_UNTIL_SLEEPING); + self.sleep(idle_state, latch, has_injected_jobs); } } #[cold] - fn tickle_cold(&self, worker_index: usize) { - // The `Release` ordering here suffices. The reasoning is that - // the atomic's own natural ordering ensure that any attempt - // to become sleepy/asleep either will come before/after this - // swap. If it comes *after*, then Release is good because we - // want it to see the action that generated this tickle. If it - // comes *before*, then we will see it here (but not other - // memory writes from that thread). If the other worker was - // becoming sleepy, the other writes don't matter. If they - // were were going to sleep, we will acquire lock and hence - // acquire their reads. - let old_state = self.state.swap(AWAKE, Ordering::Release); - log!(Tickle { + fn announce_sleepy(&self, worker_index: usize) -> JobsEventCounter { + let counters = self + .counters + .increment_jobs_event_counter_if(JobsEventCounter::is_active); + let jobs_counter = counters.jobs_counter(); + self.logger.log(|| ThreadSleepy { worker: worker_index, - old_state, + jobs_counter: jobs_counter.as_usize(), }); - if self.anyone_sleeping(old_state) { - let _data = self.data.lock().unwrap(); - self.tickle.notify_all(); - } + jobs_counter } - fn get_sleepy(&self, worker_index: usize) -> bool { - loop { - // Acquire ordering suffices here. If some other worker - // was sleepy but no longer is, we will eventually see - // that, and until then it doesn't hurt to spin. - // Otherwise, we will do a compare-exchange which will - // assert a stronger order and acquire any reads etc that - // we must see. - let state = self.state.load(Ordering::Acquire); - log!(GetSleepy { + #[cold] + fn sleep( + &self, + idle_state: &mut IdleState, + latch: &CoreLatch, + has_injected_jobs: impl FnOnce() -> bool, + ) { + let worker_index = idle_state.worker_index; + + if !latch.get_sleepy() { + self.logger.log(|| ThreadSleepInterruptedByLatch { worker: worker_index, - state, + latch_addr: latch.addr(), }); - if self.any_worker_is_sleepy(state) { - // somebody else is already sleepy, so we'll just wait our turn - debug_assert!( - !self.worker_is_sleepy(state, worker_index), - "worker {} called `is_sleepy()`, \ - but they are already sleepy (state={})", - worker_index, - state - ); - return false; - } else { - // make ourselves the sleepy one - let new_state = self.with_sleepy_worker(state, worker_index); - - // This must be SeqCst on success because we want to - // ensure: - // - // - That we observe any writes that preceded - // some prior tickle, and that tickle may have only - // done a SeqCst load on `self.state`. - // - That any subsequent tickle *definitely* sees this store. - // - // See the section on "Ensuring Sequentially - // Consistency" in README.md for more details. - // - // The failure ordering doesn't matter since we are - // about to spin around and do a fresh load. - if self - .state - .compare_exchange(state, new_state, Ordering::SeqCst, Ordering::Relaxed) - .is_ok() - { - log!(GotSleepy { - worker: worker_index, - old_state: state, - new_state, - }); - return true; - } + + return; + } + + let sleep_state = &self.worker_sleep_states[worker_index]; + let mut is_blocked = sleep_state.is_blocked.lock().unwrap(); + debug_assert!(!*is_blocked); + + // Our latch was signalled. We should wake back up fully as we + // wil have some stuff to do. + if !latch.fall_asleep() { + self.logger.log(|| ThreadSleepInterruptedByLatch { + worker: worker_index, + latch_addr: latch.addr(), + }); + + idle_state.wake_fully(); + return; + } + + loop { + let counters = self.counters.load(Ordering::SeqCst); + + // Check if the JEC has changed since we got sleepy. + debug_assert!(idle_state.jobs_counter.is_sleepy()); + if counters.jobs_counter() != idle_state.jobs_counter { + // JEC has changed, so a new job was posted, but for some reason + // we didn't see it. We should return to just before the SLEEPY + // state so we can do another search and (if we fail to find + // work) go back to sleep. + self.logger.log(|| ThreadSleepInterruptedByJob { + worker: worker_index, + }); + + idle_state.wake_partly(); + latch.wake_up(); + return; + } + + // Otherwise, let's move from IDLE to SLEEPING. + if self.counters.try_add_sleeping_thread(counters) { + break; } } + + // Successfully registered as asleep. + + self.logger.log(|| ThreadSleeping { + worker: worker_index, + latch_addr: latch.addr(), + }); + + // We have one last check for injected jobs to do. This protects against + // deadlock in the very unlikely event that + // + // - an external job is being injected while we are sleepy + // - that job triggers the rollover over the JEC such that we don't see it + // - we are the last active worker thread + std::sync::atomic::fence(Ordering::SeqCst); + if has_injected_jobs() { + // If we see an externally injected job, then we have to 'wake + // ourselves up'. (Ordinarily, `sub_sleeping_thread` is invoked by + // the one that wakes us.) + self.counters.sub_sleeping_thread(); + } else { + // If we don't see an injected job (the normal case), then flag + // ourselves as asleep and wait till we are notified. + // + // (Note that `is_blocked` is held under a mutex and the mutex was + // acquired *before* we incremented the "sleepy counter". This means + // that whomever is coming to wake us will have to wait until we + // release the mutex in the call to `wait`, so they will see this + // boolean as true.) + *is_blocked = true; + while *is_blocked { + is_blocked = sleep_state.condvar.wait(is_blocked).unwrap(); + } + } + + // Update other state: + idle_state.wake_fully(); + latch.wake_up(); + + self.logger.log(|| ThreadAwoken { + worker: worker_index, + latch_addr: latch.addr(), + }); } - fn still_sleepy(&self, worker_index: usize) -> bool { - let state = self.state.load(Ordering::SeqCst); - self.worker_is_sleepy(state, worker_index) + /// Notify the given thread that it should wake up (if it is + /// sleeping). When this method is invoked, we typically know the + /// thread is asleep, though in rare cases it could have been + /// awoken by (e.g.) new work having been posted. + pub(super) fn notify_worker_latch_is_set(&self, target_worker_index: usize) { + self.wake_specific_thread(target_worker_index); } - fn sleep(&self, worker_index: usize) { - loop { - // Acquire here suffices. If we observe that the current worker is still - // sleepy, then in fact we know that no writes have occurred, and anyhow - // we are going to do a CAS which will synchronize. - // - // If we observe that the state has changed, it must be - // due to a tickle, and then the Acquire means we also see - // any events that occured before that. - let state = self.state.load(Ordering::Acquire); - if self.worker_is_sleepy(state, worker_index) { - // It is important that we hold the lock when we do - // the CAS. Otherwise, if we were to CAS first, then - // the following sequence of events could occur: - // - // - Thread A (us) sets state to SLEEPING. - // - Thread B sets state to AWAKE. - // - Thread C sets state to SLEEPY(C). - // - Thread C sets state to SLEEPING. - // - Thread A reawakens, acquires lock, and goes to sleep. - // - // Now we missed the wake-up from thread B! But since - // we have the lock when we set the state to sleeping, - // that cannot happen. Note that the swap `tickle()` - // is not part of the lock, though, so let's play that - // out: - // - // # Scenario 1 - // - // - A loads state and see SLEEPY(A) - // - B swaps to AWAKE. - // - A locks, fails CAS - // - // # Scenario 2 - // - // - A loads state and see SLEEPY(A) - // - A locks, performs CAS - // - B swaps to AWAKE. - // - A waits (releasing lock) - // - B locks, notifies - // - // In general, acquiring the lock inside the loop - // seems like it could lead to bad performance, but - // actually it should be ok. This is because the only - // reason for the `compare_exchange` to fail is if an - // awaken comes, in which case the next cycle around - // the loop will just return. - let data = self.data.lock().unwrap(); - - // This must be SeqCst on success because we want to - // ensure: - // - // - That we observe any writes that preceded - // some prior tickle, and that tickle may have only - // done a SeqCst load on `self.state`. - // - That any subsequent tickle *definitely* sees this store. - // - // See the section on "Ensuring Sequentially - // Consistency" in README.md for more details. - // - // The failure ordering doesn't matter since we are - // about to spin around and do a fresh load. - if self - .state - .compare_exchange(state, SLEEPING, Ordering::SeqCst, Ordering::Relaxed) - .is_ok() - { - // Don't do this in a loop. If we do it in a loop, we need - // some way to distinguish the ABA scenario where the pool - // was awoken but before we could process it somebody went - // to sleep. Note that if we get a false wakeup it's not a - // problem for us, we'll just loop around and maybe get - // sleepy again. - log!(FellAsleep { - worker: worker_index - }); - drop(self.tickle.wait(data).unwrap()); - log!(GotAwoken { - worker: worker_index - }); - return; + /// Signals that `num_jobs` new jobs were injected into the thread + /// pool from outside. This function will ensure that there are + /// threads available to process them, waking threads from sleep + /// if necessary. + /// + /// # Parameters + /// + /// - `source_worker_index` -- index of the thread that did the + /// push, or `usize::MAX` if this came from outside the thread + /// pool -- it is used only for logging. + /// - `num_jobs` -- lower bound on number of jobs available for stealing. + /// We'll try to get at least one thread per job. + #[inline] + pub(super) fn new_injected_jobs( + &self, + source_worker_index: usize, + num_jobs: u32, + queue_was_empty: bool, + ) { + // This fence is needed to guarantee that threads + // as they are about to fall asleep, observe any + // new jobs that may have been injected. + std::sync::atomic::fence(Ordering::SeqCst); + + self.new_jobs(source_worker_index, num_jobs, queue_was_empty) + } + + /// Signals that `num_jobs` new jobs were pushed onto a thread's + /// local deque. This function will try to ensure that there are + /// threads available to process them, waking threads from sleep + /// if necessary. However, this is not guaranteed: under certain + /// race conditions, the function may fail to wake any new + /// threads; in that case the existing thread should eventually + /// pop the job. + /// + /// # Parameters + /// + /// - `source_worker_index` -- index of the thread that did the + /// push, or `usize::MAX` if this came from outside the thread + /// pool -- it is used only for logging. + /// - `num_jobs` -- lower bound on number of jobs available for stealing. + /// We'll try to get at least one thread per job. + #[inline] + pub(super) fn new_internal_jobs( + &self, + source_worker_index: usize, + num_jobs: u32, + queue_was_empty: bool, + ) { + self.new_jobs(source_worker_index, num_jobs, queue_was_empty) + } + + /// Common helper for `new_injected_jobs` and `new_internal_jobs`. + #[inline] + fn new_jobs(&self, source_worker_index: usize, num_jobs: u32, queue_was_empty: bool) { + // Read the counters and -- if sleepy workers have announced themselves + // -- announce that there is now work available. The final value of `counters` + // with which we exit the loop thus corresponds to a state when + let counters = self + .counters + .increment_jobs_event_counter_if(JobsEventCounter::is_sleepy); + let num_awake_but_idle = counters.awake_but_idle_threads(); + let num_sleepers = counters.sleeping_threads(); + + self.logger.log(|| JobThreadCounts { + worker: source_worker_index, + num_idle: num_awake_but_idle as u16, + num_sleepers: num_sleepers as u16, + }); + + if num_sleepers == 0 { + // nobody to wake + return; + } + + // Promote from u16 to u32 so we can interoperate with + // num_jobs more easily. + let num_awake_but_idle = num_awake_but_idle as u32; + let num_sleepers = num_sleepers as u32; + + // If the queue is non-empty, then we always wake up a worker + // -- clearly the existing idle jobs aren't enough. Otherwise, + // check to see if we have enough idle workers. + if !queue_was_empty { + let num_to_wake = std::cmp::min(num_jobs, num_sleepers); + self.wake_any_threads(num_to_wake); + } else if num_awake_but_idle < num_jobs { + let num_to_wake = std::cmp::min(num_jobs - num_awake_but_idle, num_sleepers); + self.wake_any_threads(num_to_wake); + } + } + + #[cold] + fn wake_any_threads(&self, mut num_to_wake: u32) { + if num_to_wake > 0 { + for i in 0..self.worker_sleep_states.len() { + if self.wake_specific_thread(i) { + num_to_wake -= 1; + if num_to_wake == 0 { + return; + } } - } else { - log!(GotInterrupted { - worker: worker_index - }); - return; } } } + + fn wake_specific_thread(&self, index: usize) -> bool { + let sleep_state = &self.worker_sleep_states[index]; + + let mut is_blocked = sleep_state.is_blocked.lock().unwrap(); + if *is_blocked { + *is_blocked = false; + sleep_state.condvar.notify_one(); + + // When the thread went to sleep, it will have incremented + // this value. When we wake it, its our job to decrement + // it. We could have the thread do it, but that would + // introduce a delay between when the thread was + // *notified* and when this counter was decremented. That + // might mislead people with new work into thinking that + // there are sleeping threads that they should try to + // wake, when in fact there is nothing left for them to + // do. + self.counters.sub_sleeping_thread(); + + self.logger.log(|| ThreadNotify { worker: index }); + + true + } else { + false + } + } +} + +impl IdleState { + fn wake_fully(&mut self) { + self.rounds = 0; + self.jobs_counter = JobsEventCounter::DUMMY; + } + + fn wake_partly(&mut self) { + self.rounds = ROUNDS_UNTIL_SLEEPY; + self.jobs_counter = JobsEventCounter::DUMMY; + } } diff --git a/rayon-demo/src/life/mod.rs b/rayon-demo/src/life/mod.rs index b95083883..e2676a1aa 100644 --- a/rayon-demo/src/life/mod.rs +++ b/rayon-demo/src/life/mod.rs @@ -1,6 +1,6 @@ const USAGE: &str = " -Usage: life bench [--size N] [--gens N] - life play [--size N] [--gens N] [--fps N] +Usage: life bench [--size N] [--gens N] [--skip-bridge] + life play [--size N] [--gens N] [--fps N] [--skip-bridge] life --help Conway's Game of Life. @@ -11,6 +11,7 @@ Options: --size N Size of the game board (N x N) [default: 200] --gens N Simulate N generations [default: 100] --fps N Maximum frame rate [default: 60] + --skip-bridge Skips the tests with par-bridge, as it is much slower. -h, --help Show this message. "; @@ -37,6 +38,7 @@ pub struct Args { flag_size: usize, flag_gens: usize, flag_fps: usize, + flag_skip_bridge: bool, } #[derive(PartialEq, Eq, Clone, Debug)] @@ -277,12 +279,14 @@ pub fn main(args: &[String]) { serial as f64 / parallel as f64 ); - let par_bridge = measure(par_bridge_generations, &args).as_nanos(); - println!( - "par_bridge: {:10} ns -> {:.2}x speedup", - par_bridge, - serial as f64 / par_bridge as f64 - ); + if !args.flag_skip_bridge { + let par_bridge = measure(par_bridge_generations, &args).as_nanos(); + println!( + "par_bridge: {:10} ns -> {:.2}x speedup", + par_bridge, + serial as f64 / par_bridge as f64 + ); + } } if args.cmd_play { @@ -298,10 +302,12 @@ pub fn main(args: &[String]) { println!(" cpu usage: {:.1}%", cpu_usage); } - let par_bridge = measure_cpu(par_bridge_generations_limited, &args); - println!("par_bridge: {:.2} fps", par_bridge.actual_fps); - if let Some(cpu_usage) = par_bridge.cpu_usage_percent { - println!(" cpu usage: {:.1}%", cpu_usage); + if !args.flag_skip_bridge { + let par_bridge = measure_cpu(par_bridge_generations_limited, &args); + println!("par_bridge: {:.2} fps", par_bridge.actual_fps); + if let Some(cpu_usage) = par_bridge.cpu_usage_percent { + println!(" cpu usage: {:.1}%", cpu_usage); + } } } }