From c2f8abecfbaf6b6388e7746b733b7f22cbb7a750 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 7 Oct 2023 12:56:11 -0700 Subject: [PATCH] Port from mio to polling This patch replaces the mio crate with the polling. Now that smol-rs/polling#96 has been merged, we should be at full feature parity with mio v0.6 now. Fixes #7104. Fixes #6486. --- Cargo.lock | 349 ++++++------------ alacritty/src/window_context.rs | 2 +- alacritty_terminal/Cargo.toml | 10 +- alacritty_terminal/src/event_loop.rs | 323 ++++++++-------- alacritty_terminal/src/tty/mod.rs | 22 +- alacritty_terminal/src/tty/unix.rs | 129 ++++--- .../src/tty/windows/blocking.rs | 276 ++++++++++++++ alacritty_terminal/src/tty/windows/child.rs | 65 ++-- alacritty_terminal/src/tty/windows/conpty.rs | 9 +- alacritty_terminal/src/tty/windows/mod.rs | 112 ++---- 10 files changed, 711 insertions(+), 586 deletions(-) create mode 100644 alacritty_terminal/src/tty/windows/blocking.rs diff --git a/Cargo.lock b/Cargo.lock index 2a90cf5c..9faa0d38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,7 +14,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "getrandom", "once_cell", "version_check", @@ -53,7 +53,7 @@ dependencies = [ "notify", "objc", "once_cell", - "parking_lot 0.12.1", + "parking_lot", "png", "serde", "serde_json", @@ -100,18 +100,16 @@ dependencies = [ "home", "libc", "log", - "mio 0.6.23", - "mio-anonymous-pipes", - "mio-extras", - "miow 0.3.7", + "miow", "nix 0.26.2", - "parking_lot 0.12.1", + "parking_lot", + "piper", + "polling", "regex-automata", "serde", "serde_json", "serde_yaml", "signal-hook", - "signal-hook-mio", "toml 0.7.4", "unicode-width", "vte", @@ -336,12 +334,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -421,7 +413,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fdf5e01086b6be750428ba4a40619f847eb2e95756eee84b18e06e5f0b50342" dependencies = [ "lazy-bytes-cast", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -480,6 +472,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "copypasta" version = "0.8.2" @@ -553,7 +554,7 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -562,7 +563,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "crossbeam-utils", ] @@ -572,7 +573,7 @@ version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", ] [[package]] @@ -595,7 +596,7 @@ dependencies = [ "once_cell", "pkg-config", "servo-fontconfig", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -659,7 +660,7 @@ dependencies = [ "libc", "serde", "serde_derive", - "winapi 0.3.9", + "winapi", "wio", ] @@ -707,6 +708,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "fdeflate" version = "0.3.0" @@ -722,7 +729,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall 0.2.16", "windows-sys 0.48.0", @@ -818,20 +825,10 @@ dependencies = [ ] [[package]] -name = "fuchsia-zircon" -version = "0.3.3" +name = "futures-io" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags 1.3.2", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "gethostname" @@ -840,7 +837,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -850,7 +847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb65d4ba3173c56a500b555b532f72c42e8d1fe64962b518897f8959fae2c177" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -859,7 +856,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi", ] @@ -995,15 +992,6 @@ dependencies = [ "libc", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - [[package]] name = "io-lifetimes" version = "1.0.11" @@ -1015,15 +1003,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "is-terminal" version = "0.4.7" @@ -1049,7 +1028,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if 1.0.0", + "cfg-if", "combine", "jni-sys", "log", @@ -1082,16 +1061,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "khronos_api" version = "3.1.0" @@ -1130,12 +1099,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - [[package]] name = "libc" version = "0.2.147" @@ -1148,8 +1111,8 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", + "cfg-if", + "winapi", ] [[package]] @@ -1247,25 +1210,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.8.8" @@ -1278,61 +1222,13 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mio-anonymous-pipes" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc513025fe5005a3aa561b50fdb2cda5a150b84800ae02acd8aa9ed62ca1a6b" -dependencies = [ - "mio 0.6.23", - "miow 0.3.7", - "parking_lot 0.11.2", - "spsc-buffer", - "winapi 0.3.9", -] - -[[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" -dependencies = [ - "lazycell", - "log", - "mio 0.6.23", - "slab", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio 0.6.23", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", -] - [[package]] name = "miow" version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1365,17 +1261,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "net2" -version = "0.2.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d0df99cfcd2530b2e694f6e17e7f37b8e26bb23983ac530c0c97408837c631" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "nix" version = "0.24.3" @@ -1383,7 +1268,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" dependencies = [ "bitflags 1.3.2", - "cfg-if 1.0.0", + "cfg-if", "libc", "memoffset 0.6.5", ] @@ -1396,7 +1281,7 @@ checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" dependencies = [ "autocfg", "bitflags 1.3.2", - "cfg-if 1.0.0", + "cfg-if", "libc", "memoffset 0.6.5", ] @@ -1408,7 +1293,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags 1.3.2", - "cfg-if 1.0.0", + "cfg-if", "libc", "memoffset 0.7.1", "static_assertions", @@ -1437,7 +1322,7 @@ dependencies = [ "inotify", "kqueue", "libc", - "mio 0.8.8", + "mio", "walkdir", "windows-sys 0.45.0", ] @@ -1561,17 +1446,6 @@ dependencies = [ "redox_syscall 0.3.5", ] -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -1579,21 +1453,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if 1.0.0", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi 0.3.9", + "parking_lot_core", ] [[package]] @@ -1602,7 +1462,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "redox_syscall 0.3.5", "smallvec", @@ -1615,6 +1475,23 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "piper" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkg-config" version = "0.3.27" @@ -1634,6 +1511,20 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polling" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62a79e457c9898100b4298d57d69ec53d06f9a6ed352431ce5f377e082d2e846" +dependencies = [ + "cfg-if", + "concurrent-queue", + "pin-project-lite", + "rustix 0.38.8", + "tracing", + "windows-sys 0.48.0", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1748,9 +1639,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.4" +version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" +checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ "bitflags 2.3.3", "errno", @@ -1887,18 +1778,6 @@ dependencies = [ "signal-hook-registry", ] -[[package]] -name = "signal-hook-mio" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" -dependencies = [ - "libc", - "mio 0.6.23", - "mio-uds", - "signal-hook", -] - [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -1914,15 +1793,6 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - [[package]] name = "slotmap" version = "1.0.6" @@ -1997,12 +1867,6 @@ dependencies = [ "serde", ] -[[package]] -name = "spsc-buffer" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be6c3f39c37a4283ee4b43d1311c828f2e1fb0541e76ea0cb1a2abd9ef2f5b3b" - [[package]] name = "static_assertions" version = "1.1.0" @@ -2061,7 +1925,7 @@ dependencies = [ "arrayref", "arrayvec", "bytemuck", - "cfg-if 1.0.0", + "cfg-if", "log", "tiny-skia-path", ] @@ -2120,6 +1984,23 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" + [[package]] name = "unicode-ident" version = "1.0.9" @@ -2222,7 +2103,7 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "wasm-bindgen-macro", ] @@ -2247,7 +2128,7 @@ version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "js-sys", "wasm-bindgen", "web-sys", @@ -2463,12 +2344,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -2479,12 +2354,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -2497,7 +2366,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2506,7 +2375,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2676,7 +2545,7 @@ dependencies = [ "percent-encoding", "raw-window-handle", "redox_syscall 0.3.5", - "rustix 0.38.4", + "rustix 0.38.8", "sctk-adwaita", "serde", "smithay-client-toolkit 0.17.0", @@ -2710,7 +2579,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2719,17 +2588,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] @@ -2760,7 +2619,7 @@ checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507" dependencies = [ "gethostname 0.2.3", "nix 0.24.3", - "winapi 0.3.9", + "winapi", "winapi-wsapoll", "x11rb-protocol 0.10.0", ] @@ -2777,7 +2636,7 @@ dependencies = [ "libloading", "nix 0.26.2", "once_cell", - "winapi 0.3.9", + "winapi", "winapi-wsapoll", "x11rb-protocol 0.12.0", ] diff --git a/alacritty/src/window_context.rs b/alacritty/src/window_context.rs index 06dd68d6..6a536ca4 100644 --- a/alacritty/src/window_context.rs +++ b/alacritty/src/window_context.rs @@ -569,6 +569,6 @@ impl WindowContext { impl Drop for WindowContext { fn drop(&mut self) { // Shutdown the terminal's PTY. - let _ = self.notifier.0.send(Msg::Shutdown); + self.notifier.0.send(Msg::Shutdown); } } diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml index dc164413..7498afab 100644 --- a/alacritty_terminal/Cargo.toml +++ b/alacritty_terminal/Cargo.toml @@ -23,9 +23,8 @@ bitflags = { version = "2.2.1", features = ["serde"] } home = "0.5.5" libc = "0.2" log = "0.4" -mio = "0.6.20" -mio-extras = "2" parking_lot = "0.12.0" +polling = "3.0.0" regex-automata = "0.3.6" serde = { version = "1", features = ["derive", "rc"] } serde_yaml = "0.8" @@ -36,12 +35,11 @@ vte = { version = "0.12.0", default-features = false, features = ["ansi", "serde [target.'cfg(unix)'.dependencies] nix = { version = "0.26.2", default-features = false, features = ["term"] } signal-hook = "0.3.10" -signal-hook-mio = { version = "0.2.1", features = ["support-v0_6"] } [target.'cfg(windows)'.dependencies] -mio-anonymous-pipes = "0.2" -miow = "0.3" -windows-sys = { version = "0.48", features = [ +piper = "0.2.1" +miow = "0.3.0" +windows-sys = { version = "0.48.0", features = [ "Win32_System_Console", "Win32_Foundation", "Win32_Security", diff --git a/alacritty_terminal/src/event_loop.rs b/alacritty_terminal/src/event_loop.rs index 61dc69bc..de22e49d 100644 --- a/alacritty_terminal/src/event_loop.rs +++ b/alacritty_terminal/src/event_loop.rs @@ -5,15 +5,14 @@ use std::collections::VecDeque; use std::fs::File; use std::io::{self, ErrorKind, Read, Write}; use std::marker::Send; +use std::num::NonZeroUsize; +use std::sync::mpsc::{self, Receiver, Sender, TryRecvError}; use std::sync::Arc; use std::thread::JoinHandle; use std::time::Instant; use log::error; -#[cfg(not(windows))] -use mio::unix::UnixReady; -use mio::{self, Events, PollOpt, Ready}; -use mio_extras::channel::{self, Receiver, Sender}; +use polling::{Event as PollingEvent, Events, PollMode}; use crate::event::{self, Event, EventListener, WindowSize}; use crate::sync::FairMutex; @@ -21,7 +20,7 @@ use crate::term::Term; use crate::{ansi, thread, tty}; /// Max bytes to read from the PTY before forced terminal synchronization. -const READ_BUFFER_SIZE: usize = 0x10_0000; +pub(crate) const READ_BUFFER_SIZE: usize = 0x10_0000; /// Max bytes to read from the PTY while the terminal is locked. const MAX_LOCKED_READ: usize = u16::MAX as usize; @@ -39,14 +38,14 @@ pub enum Msg { Resize(WindowSize), } -/// The main event!.. loop. +/// The main event loop. /// /// Handles all the PTY I/O and runs the PTY parser which updates terminal /// state. pub struct EventLoop { - poll: mio::Poll, + poll: Arc, pty: T, - rx: Receiver, + rx: PeekableReceiver, tx: Sender, terminal: Arc>>, event_proxy: U, @@ -54,97 +53,6 @@ pub struct EventLoop { ref_test: bool, } -/// Helper type which tracks how much of a buffer has been written. -struct Writing { - source: Cow<'static, [u8]>, - written: usize, -} - -pub struct Notifier(pub Sender); - -impl event::Notify for Notifier { - fn notify(&self, bytes: B) - where - B: Into>, - { - let bytes = bytes.into(); - // terminal hangs if we send 0 bytes through. - if bytes.len() == 0 { - return; - } - - let _ = self.0.send(Msg::Input(bytes)); - } -} - -impl event::OnResize for Notifier { - fn on_resize(&mut self, window_size: WindowSize) { - let _ = self.0.send(Msg::Resize(window_size)); - } -} - -/// All of the mutable state needed to run the event loop. -/// -/// Contains list of items to write, current write state, etc. Anything that -/// would otherwise be mutated on the `EventLoop` goes here. -#[derive(Default)] -pub struct State { - write_list: VecDeque>, - writing: Option, - parser: ansi::Processor, -} - -impl State { - #[inline] - fn ensure_next(&mut self) { - if self.writing.is_none() { - self.goto_next(); - } - } - - #[inline] - fn goto_next(&mut self) { - self.writing = self.write_list.pop_front().map(Writing::new); - } - - #[inline] - fn take_current(&mut self) -> Option { - self.writing.take() - } - - #[inline] - fn needs_write(&self) -> bool { - self.writing.is_some() || !self.write_list.is_empty() - } - - #[inline] - fn set_current(&mut self, new: Option) { - self.writing = new; - } -} - -impl Writing { - #[inline] - fn new(c: Cow<'static, [u8]>) -> Writing { - Writing { source: c, written: 0 } - } - - #[inline] - fn advance(&mut self, n: usize) { - self.written += n; - } - - #[inline] - fn remaining_bytes(&self) -> &[u8] { - &self.source[self.written..] - } - - #[inline] - fn finished(&self) -> bool { - self.written >= self.source.len() - } -} - impl EventLoop where T: tty::EventedPty + event::OnResize + Send + 'static, @@ -158,12 +66,12 @@ where hold: bool, ref_test: bool, ) -> EventLoop { - let (tx, rx) = channel::channel(); + let (tx, rx) = mpsc::channel(); EventLoop { - poll: mio::Poll::new().expect("create mio Poll"), + poll: polling::Poller::new().expect("create Poll").into(), pty, tx, - rx, + rx: PeekableReceiver::new(rx), terminal, event_proxy, hold, @@ -171,15 +79,15 @@ where } } - pub fn channel(&self) -> Sender { - self.tx.clone() + pub fn channel(&self) -> EventLoopSender { + EventLoopSender { sender: self.tx.clone(), poller: self.poll.clone() } } /// Drain the channel. /// /// Returns `false` when a shutdown message was received. fn drain_recv_channel(&mut self, state: &mut State) -> bool { - while let Ok(msg) = self.rx.try_recv() { + while let Some(msg) = self.rx.recv() { match msg { Msg::Input(input) => state.write_list.push_back(input), Msg::Resize(window_size) => self.pty.on_resize(window_size), @@ -190,20 +98,6 @@ where true } - /// Returns a `bool` indicating whether or not the event loop should continue running. - #[inline] - fn channel_event(&mut self, token: mio::Token, state: &mut State) -> bool { - if !self.drain_recv_channel(state) { - return false; - } - - self.poll - .reregister(&self.rx, token, Ready::readable(), PollOpt::edge() | PollOpt::oneshot()) - .unwrap(); - - true - } - #[inline] fn pty_read( &mut self, @@ -313,17 +207,15 @@ where let mut state = State::default(); let mut buf = [0u8; READ_BUFFER_SIZE]; - let mut tokens = (0..).map(Into::into); - - let poll_opts = PollOpt::edge() | PollOpt::oneshot(); - - let channel_token = tokens.next().unwrap(); - self.poll.register(&self.rx, channel_token, Ready::readable(), poll_opts).unwrap(); + let poll_opts = PollMode::Level; + let mut interest = PollingEvent::readable(0); // Register TTY through EventedRW interface. - self.pty.register(&self.poll, &mut tokens, Ready::readable(), poll_opts).unwrap(); + unsafe { + self.pty.register(&self.poll, interest, poll_opts).unwrap(); + } - let mut events = Events::with_capacity(1024); + let mut events = Events::with_capacity(NonZeroUsize::new(1024).unwrap()); let mut pipe = if self.ref_test { Some(File::create("./alacritty.recording").expect("create alacritty recording")) @@ -337,7 +229,8 @@ where let timeout = handler.sync_timeout().map(|st| st.saturating_duration_since(Instant::now())); - if let Err(err) = self.poll.poll(&mut events, timeout) { + events.clear(); + if let Err(err) = self.poll.wait(&mut events, timeout) { match err.kind() { ErrorKind::Interrupted => continue, _ => panic!("EventLoop polling error: {err:?}"), @@ -345,21 +238,20 @@ where } // Handle synchronized update timeout. - if events.is_empty() { + if events.is_empty() && self.rx.peek().is_none() { state.parser.stop_sync(&mut *self.terminal.lock()); self.event_proxy.send_event(Event::Wakeup); continue; } - for event in events.iter() { - match event.token() { - token if token == channel_token => { - if !self.channel_event(channel_token, &mut state) { - break 'event_loop; - } - }, + // Handle channel events, if there are any. + if !self.drain_recv_channel(&mut state) { + break; + } - token if token == self.pty.child_event_token() => { + for event in events.iter() { + match event.key { + tty::PTY_CHILD_EVENT_TOKEN => { if let Some(tty::ChildEvent::Exited) = self.pty.next_child_event() { if self.hold { // With hold enabled, make sure the PTY is drained. @@ -374,17 +266,13 @@ where } }, - token - if token == self.pty.read_token() - || token == self.pty.write_token() => - { - #[cfg(unix)] - if UnixReady::from(event.readiness()).is_hup() { + tty::PTY_READ_WRITE_TOKEN => { + if event.is_interrupt() { // Don't try to do I/O on a dead PTY. continue; } - if event.readiness().is_readable() { + if event.readable { if let Err(err) = self.pty_read(&mut state, &mut buf, pipe.as_mut()) { // On Linux, a `read` on the master side of a PTY can fail @@ -402,7 +290,7 @@ where } } - if event.readiness().is_writable() { + if event.writable { if let Err(err) = self.pty_write(&mut state) { error!("Error writing to PTY in event loop: {}", err); break 'event_loop; @@ -414,19 +302,152 @@ where } // Register write interest if necessary. - let mut interest = Ready::readable(); - if state.needs_write() { - interest.insert(Ready::writable()); + let needs_write = state.needs_write(); + if needs_write != interest.writable { + interest.writable = needs_write; + + // Re-register with new interest. + self.pty.reregister(&self.poll, interest, poll_opts).unwrap(); } - // Reregister with new interest. - self.pty.reregister(&self.poll, interest, poll_opts).unwrap(); } // The evented instances are not dropped here so deregister them explicitly. - let _ = self.poll.deregister(&self.rx); let _ = self.pty.deregister(&self.poll); (self, state) }) } } + +/// Helper type which tracks how much of a buffer has been written. +struct Writing { + source: Cow<'static, [u8]>, + written: usize, +} + +pub struct Notifier(pub EventLoopSender); + +impl event::Notify for Notifier { + fn notify(&self, bytes: B) + where + B: Into>, + { + let bytes = bytes.into(); + // Terminal hangs if we send 0 bytes through. + if bytes.len() == 0 { + return; + } + + self.0.send(Msg::Input(bytes)); + } +} + +impl event::OnResize for Notifier { + fn on_resize(&mut self, window_size: WindowSize) { + self.0.send(Msg::Resize(window_size)); + } +} + +pub struct EventLoopSender { + sender: Sender, + poller: Arc, +} + +impl EventLoopSender { + pub fn send(&self, msg: Msg) { + let _ = self.sender.send(msg); + let _ = self.poller.notify(); + } +} + +/// All of the mutable state needed to run the event loop. +/// +/// Contains list of items to write, current write state, etc. Anything that +/// would otherwise be mutated on the `EventLoop` goes here. +#[derive(Default)] +pub struct State { + write_list: VecDeque>, + writing: Option, + parser: ansi::Processor, +} + +impl State { + #[inline] + fn ensure_next(&mut self) { + if self.writing.is_none() { + self.goto_next(); + } + } + + #[inline] + fn goto_next(&mut self) { + self.writing = self.write_list.pop_front().map(Writing::new); + } + + #[inline] + fn take_current(&mut self) -> Option { + self.writing.take() + } + + #[inline] + fn needs_write(&self) -> bool { + self.writing.is_some() || !self.write_list.is_empty() + } + + #[inline] + fn set_current(&mut self, new: Option) { + self.writing = new; + } +} + +impl Writing { + #[inline] + fn new(c: Cow<'static, [u8]>) -> Writing { + Writing { source: c, written: 0 } + } + + #[inline] + fn advance(&mut self, n: usize) { + self.written += n; + } + + #[inline] + fn remaining_bytes(&self) -> &[u8] { + &self.source[self.written..] + } + + #[inline] + fn finished(&self) -> bool { + self.written >= self.source.len() + } +} + +struct PeekableReceiver { + rx: Receiver, + peeked: Option, +} + +impl PeekableReceiver { + fn new(rx: Receiver) -> Self { + Self { rx, peeked: None } + } + + fn peek(&mut self) -> Option<&T> { + if self.peeked.is_none() { + self.peeked = self.rx.try_recv().ok(); + } + + self.peeked.as_ref() + } + + fn recv(&mut self) -> Option { + if self.peeked.is_some() { + self.peeked.take() + } else { + match self.rx.try_recv() { + Err(TryRecvError::Disconnected) => panic!("event loop channel closed"), + res => res.ok(), + } + } + } +} diff --git a/alacritty_terminal/src/tty/mod.rs b/alacritty_terminal/src/tty/mod.rs index 4ce277b3..315f008c 100644 --- a/alacritty_terminal/src/tty/mod.rs +++ b/alacritty_terminal/src/tty/mod.rs @@ -1,10 +1,13 @@ //! TTY related functionality. use std::path::PathBuf; +use std::sync::Arc; use std::{env, io}; use crate::config::Config; +use polling::{Event, PollMode, Poller}; + #[cfg(not(windows))] mod unix; #[cfg(not(windows))] @@ -22,20 +25,15 @@ pub trait EventedReadWrite { type Reader: io::Read; type Writer: io::Write; - fn register( - &mut self, - _: &mio::Poll, - _: &mut dyn Iterator, - _: mio::Ready, - _: mio::PollOpt, - ) -> io::Result<()>; - fn reregister(&mut self, _: &mio::Poll, _: mio::Ready, _: mio::PollOpt) -> io::Result<()>; - fn deregister(&mut self, _: &mio::Poll) -> io::Result<()>; + /// # Safety + /// + /// The underlying sources must outlive their registration in the `Poller`. + unsafe fn register(&mut self, _: &Arc, _: Event, _: PollMode) -> io::Result<()>; + fn reregister(&mut self, _: &Arc, _: Event, _: PollMode) -> io::Result<()>; + fn deregister(&mut self, _: &Arc) -> io::Result<()>; fn reader(&mut self) -> &mut Self::Reader; - fn read_token(&self) -> mio::Token; fn writer(&mut self) -> &mut Self::Writer; - fn write_token(&self) -> mio::Token; } /// Events concerning TTY child processes. @@ -51,8 +49,6 @@ pub enum ChildEvent { /// notified if the PTY child process does something we care about (other than writing to the TTY). /// In particular, this allows for race-free child exit notification on UNIX (cf. `SIGCHLD`). pub trait EventedPty: EventedReadWrite { - fn child_event_token(&self) -> mio::Token; - /// Tries to retrieve an event. /// /// Returns `Some(event)` on success, or `None` if there are no events to retrieve. diff --git a/alacritty_terminal/src/tty/unix.rs b/alacritty_terminal/src/tty/unix.rs index fd99edee..4523666e 100644 --- a/alacritty_terminal/src/tty/unix.rs +++ b/alacritty_terminal/src/tty/unix.rs @@ -2,26 +2,34 @@ use std::ffi::CStr; use std::fs::File; -use std::io::{Error, ErrorKind, Result}; +use std::io::{Error, ErrorKind, Read, Result}; use std::mem::MaybeUninit; use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; +use std::os::unix::net::UnixStream; use std::os::unix::process::CommandExt; use std::process::{Child, Command, Stdio}; +use std::sync::Arc; use std::{env, ptr}; use libc::{self, c_int, winsize, TIOCSCTTY}; use log::error; -use mio::unix::EventedFd; use nix::pty::openpty; #[cfg(any(target_os = "linux", target_os = "macos"))] use nix::sys::termios::{self, InputFlags, SetArg}; +use polling::{Event, PollMode, Poller}; use signal_hook::consts as sigconsts; -use signal_hook_mio::v0_6::Signals; +use signal_hook::low_level::pipe as signal_pipe; use crate::config::PtyConfig; use crate::event::{OnResize, WindowSize}; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; +// Interest in PTY read/writes. +pub(crate) const PTY_READ_WRITE_TOKEN: usize = 0; + +// Interest in new child events. +pub(crate) const PTY_CHILD_EVENT_TOKEN: usize = 1; + macro_rules! die { ($($arg:tt)*) => {{ error!($($arg)*); @@ -103,9 +111,7 @@ fn get_pw_entry(buf: &mut [i8; 1024]) -> Result> { pub struct Pty { child: Child, file: File, - token: mio::Token, - signals: Signals, - signals_token: mio::Token, + signals: UnixStream, } impl Pty { @@ -254,7 +260,14 @@ pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: u64) -> Resul } // Prepare signal handling before spawning child. - let signals = Signals::new([sigconsts::SIGCHLD]).expect("error preparing signal handling"); + let signals = { + let (sender, recv) = UnixStream::pair()?; + + // Register the recv end of the pipe for SIGCHLD. + signal_pipe::register(sigconsts::SIGCHLD, sender)?; + recv.set_nonblocking(true)?; + recv + }; match builder.spawn() { Ok(child) => { @@ -264,13 +277,7 @@ pub fn new(config: &PtyConfig, window_size: WindowSize, window_id: u64) -> Resul set_nonblocking(master); } - let mut pty = Pty { - child, - file: unsafe { File::from_raw_fd(master) }, - token: mio::Token::from(0), - signals, - signals_token: mio::Token::from(0), - }; + let mut pty = Pty { child, file: unsafe { File::from_raw_fd(master) }, signals }; pty.on_resize(window_size); Ok(pty) }, @@ -300,46 +307,47 @@ impl EventedReadWrite for Pty { type Writer = File; #[inline] - fn register( + unsafe fn register( &mut self, - poll: &mio::Poll, - token: &mut dyn Iterator, - interest: mio::Ready, - poll_opts: mio::PollOpt, + poll: &Arc, + mut interest: Event, + poll_opts: PollMode, ) -> Result<()> { - self.token = token.next().unwrap(); - poll.register(&EventedFd(&self.file.as_raw_fd()), self.token, interest, poll_opts)?; + interest.key = PTY_READ_WRITE_TOKEN; + unsafe { + poll.add_with_mode(&self.file, interest, poll_opts)?; + } - self.signals_token = token.next().unwrap(); - poll.register( - &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), - ) + unsafe { + poll.add_with_mode( + &self.signals, + Event::readable(PTY_CHILD_EVENT_TOKEN), + PollMode::Level, + ) + } } #[inline] fn reregister( &mut self, - poll: &mio::Poll, - interest: mio::Ready, - poll_opts: mio::PollOpt, + poll: &Arc, + mut interest: Event, + poll_opts: PollMode, ) -> Result<()> { - poll.reregister(&EventedFd(&self.file.as_raw_fd()), self.token, interest, poll_opts)?; + interest.key = PTY_READ_WRITE_TOKEN; + poll.modify_with_mode(&self.file, interest, poll_opts)?; - poll.reregister( + poll.modify_with_mode( &self.signals, - self.signals_token, - mio::Ready::readable(), - mio::PollOpt::level(), + Event::readable(PTY_CHILD_EVENT_TOKEN), + PollMode::Level, ) } #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> Result<()> { - poll.deregister(&EventedFd(&self.file.as_raw_fd()))?; - poll.deregister(&self.signals) + fn deregister(&mut self, poll: &Arc) -> Result<()> { + poll.delete(&self.file)?; + poll.delete(&self.signals) } #[inline] @@ -347,44 +355,33 @@ impl EventedReadWrite for Pty { &mut self.file } - #[inline] - fn read_token(&self) -> mio::Token { - self.token - } - #[inline] fn writer(&mut self) -> &mut File { &mut self.file } - - #[inline] - fn write_token(&self) -> mio::Token { - self.token - } } impl EventedPty for Pty { #[inline] fn next_child_event(&mut self) -> Option { - self.signals.pending().next().and_then(|signal| { - if signal != sigconsts::SIGCHLD { - return None; + // See if there has been a SIGCHLD. + let mut buf = [0u8; 1]; + if let Err(err) = self.signals.read(&mut buf) { + if err.kind() != ErrorKind::WouldBlock { + error!("Error reading from signal pipe: {}", err); } + return None; + } - match self.child.try_wait() { - Err(e) => { - error!("Error checking child process termination: {}", e); - None - }, - Ok(None) => None, - Ok(_) => Some(ChildEvent::Exited), - } - }) - } - - #[inline] - fn child_event_token(&self) -> mio::Token { - self.signals_token + // Match on the child process. + match self.child.try_wait() { + Err(err) => { + error!("Error checking child process termination: {}", err); + None + }, + Ok(None) => None, + Ok(_) => Some(ChildEvent::Exited), + } } } diff --git a/alacritty_terminal/src/tty/windows/blocking.rs b/alacritty_terminal/src/tty/windows/blocking.rs new file mode 100644 index 00000000..3c74be4a --- /dev/null +++ b/alacritty_terminal/src/tty/windows/blocking.rs @@ -0,0 +1,276 @@ +//! Code for running a reader/writer on another thread while driving it through `polling`. + +use std::io::prelude::*; +use std::marker::PhantomData; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Wake, Waker}; +use std::{io, thread}; + +use piper::{pipe, Reader, Writer}; +use polling::os::iocp::{CompletionPacket, PollerIocpExt}; +use polling::{Event, PollMode, Poller}; + +use crate::thread::spawn_named; + +struct Registration { + interest: Mutex>, + end: PipeEnd, +} + +#[derive(Copy, Clone)] +enum PipeEnd { + Reader, + Writer, +} + +struct Interest { + /// The event to send about completion. + event: Event, + + /// The poller to send the event to. + poller: Arc, + + /// The mode that we are in. + mode: PollMode, +} + +/// Poll a reader in another thread. +pub struct UnblockedReader { + /// The event to send about completion. + interest: Arc, + + /// The pipe that we are reading from. + pipe: Reader, + + /// Is this the first time registering? + first_register: bool, + + /// We logically own the reader, but we don't actually use it. + _reader: PhantomData, +} + +impl UnblockedReader { + /// Spawn a new unblocked reader. + pub fn new(mut source: R, pipe_capacity: usize) -> Self { + // Create a new pipe. + let (reader, mut writer) = pipe(pipe_capacity); + let interest = Arc::new(Registration { + interest: Mutex::>::new(None), + end: PipeEnd::Reader, + }); + + // Spawn the reader thread. + spawn_named("alacritty-tty-reader-thread", move || { + let waker = Waker::from(Arc::new(ThreadWaker(thread::current()))); + let mut context = Context::from_waker(&waker); + + loop { + // Read from the reader into the pipe. + match writer.poll_fill(&mut context, &mut source) { + Poll::Ready(Ok(0)) => { + // Either the pipe is closed or the reader is at its EOF. + // In any case, we are done. + return; + }, + + Poll::Ready(Ok(_)) => { + // Keep reading. + continue; + }, + + Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::Interrupted => { + // We were interrupted; continue. + continue; + }, + + Poll::Ready(Err(e)) => { + log::error!("error writing to pipe: {}", e); + return; + }, + + Poll::Pending => { + // We are now waiting on the other end to advance. Park the + // thread until they do. + thread::park(); + }, + } + } + }); + + Self { interest, pipe: reader, first_register: true, _reader: PhantomData } + } + + /// Register interest in the reader. + pub fn register(&mut self, poller: &Arc, event: Event, mode: PollMode) { + let mut interest = self.interest.interest.lock().unwrap(); + *interest = Some(Interest { event, poller: poller.clone(), mode }); + + // Send the event to start off with if we have any data. + if (!self.pipe.is_empty() && event.readable) || self.first_register { + self.first_register = false; + poller.post(CompletionPacket::new(event)).ok(); + } + } + + /// Deregister interest in the reader. + pub fn deregister(&self) { + let mut interest = self.interest.interest.lock().unwrap(); + *interest = None; + } + + /// Try to read from the reader. + pub fn try_read(&mut self, buf: &mut [u8]) -> usize { + let waker = Waker::from(self.interest.clone()); + + match self.pipe.poll_drain_bytes(&mut Context::from_waker(&waker), buf) { + Poll::Pending => 0, + Poll::Ready(n) => n, + } + } +} + +impl Read for UnblockedReader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + Ok(self.try_read(buf)) + } +} + +/// Poll a writer in another thread. +pub struct UnblockedWriter { + /// The interest to send about completion. + interest: Arc, + + /// The pipe that we are writing to. + pipe: Writer, + + /// We logically own the writer, but we don't actually use it. + _reader: PhantomData, +} + +impl UnblockedWriter { + /// Spawn a new unblocked writer. + pub fn new(mut sink: W, pipe_capacity: usize) -> Self { + // Create a new pipe. + let (mut reader, writer) = pipe(pipe_capacity); + let interest = Arc::new(Registration { + interest: Mutex::>::new(None), + end: PipeEnd::Writer, + }); + + // Spawn the writer thread. + spawn_named("alacritty-tty-writer-thread", move || { + let waker = Waker::from(Arc::new(ThreadWaker(thread::current()))); + let mut context = Context::from_waker(&waker); + + loop { + // Write from the pipe into the writer. + match reader.poll_drain(&mut context, &mut sink) { + Poll::Ready(Ok(0)) => { + // Either the pipe is closed or the writer is full. + // In any case, we are done. + return; + }, + + Poll::Ready(Ok(_)) => { + // Keep writing. + continue; + }, + + Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::Interrupted => { + // We were interrupted; continue. + continue; + }, + + Poll::Ready(Err(e)) => { + log::error!("error writing to pipe: {}", e); + return; + }, + + Poll::Pending => { + // We are now waiting on the other end to advance. Park the + // thread until they do. + thread::park(); + }, + } + } + }); + + Self { interest, pipe: writer, _reader: PhantomData } + } + + /// Register interest in the writer. + pub fn register(&self, poller: &Arc, event: Event, mode: PollMode) { + let mut interest = self.interest.interest.lock().unwrap(); + *interest = Some(Interest { event, poller: poller.clone(), mode }); + + // Send the event to start off with if we have room for data. + if !self.pipe.is_full() && event.writable { + poller.post(CompletionPacket::new(event)).ok(); + } + } + + /// Deregister interest in the writer. + pub fn deregister(&self) { + let mut interest = self.interest.interest.lock().unwrap(); + *interest = None; + } + + /// Try to write to the writer. + pub fn try_write(&mut self, buf: &[u8]) -> usize { + let waker = Waker::from(self.interest.clone()); + + match self.pipe.poll_fill_bytes(&mut Context::from_waker(&waker), buf) { + Poll::Pending => 0, + Poll::Ready(n) => n, + } + } +} + +impl Write for UnblockedWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + Ok(self.try_write(buf)) + } + + fn flush(&mut self) -> io::Result<()> { + // Nothing to flush. + Ok(()) + } +} + +struct ThreadWaker(thread::Thread); + +impl Wake for ThreadWaker { + fn wake(self: Arc) { + self.0.unpark(); + } + + fn wake_by_ref(self: &Arc) { + self.0.unpark(); + } +} + +impl Wake for Registration { + fn wake(self: Arc) { + self.wake_by_ref(); + } + + fn wake_by_ref(self: &Arc) { + let mut interest_lock = self.interest.lock().unwrap(); + if let Some(interest) = interest_lock.as_ref() { + // Send the event to the poller. + let send_event = match self.end { + PipeEnd::Reader => interest.event.readable, + PipeEnd::Writer => interest.event.writable, + }; + + if send_event { + interest.poller.post(CompletionPacket::new(interest.event)).ok(); + + // Clear the event if we're in oneshot mode. + if matches!(interest.mode, PollMode::Oneshot | PollMode::EdgeOneshot) { + *interest_lock = None; + } + } + } + } +} diff --git a/alacritty_terminal/src/tty/windows/child.rs b/alacritty_terminal/src/tty/windows/child.rs index 19c8a195..6bc9ed20 100644 --- a/alacritty_terminal/src/tty/windows/child.rs +++ b/alacritty_terminal/src/tty/windows/child.rs @@ -1,8 +1,10 @@ use std::ffi::c_void; use std::io::Error; use std::sync::atomic::{AtomicPtr, Ordering}; +use std::sync::{mpsc, Arc, Mutex}; -use mio_extras::channel::{channel, Receiver, Sender}; +use polling::os::iocp::{CompletionPacket, PollerIocpExt}; +use polling::{Event, Poller}; use windows_sys::Win32::Foundation::{BOOLEAN, HANDLE}; use windows_sys::Win32::System::Threading::{ @@ -12,27 +14,43 @@ use windows_sys::Win32::System::Threading::{ use crate::tty::ChildEvent; +struct Interest { + poller: Arc, + event: Event, +} + +struct ChildExitSender { + sender: mpsc::Sender, + interest: Arc>>, +} + /// WinAPI callback to run when child process exits. extern "system" fn child_exit_callback(ctx: *mut c_void, timed_out: BOOLEAN) { if timed_out != 0 { return; } - let event_tx: Box<_> = unsafe { Box::from_raw(ctx as *mut Sender) }; - let _ = event_tx.send(ChildEvent::Exited); + let event_tx: Box<_> = unsafe { Box::from_raw(ctx as *mut ChildExitSender) }; + let _ = event_tx.sender.send(ChildEvent::Exited); + let interest = event_tx.interest.lock().unwrap(); + if let Some(interest) = interest.as_ref() { + interest.poller.post(CompletionPacket::new(interest.event)).ok(); + } } pub struct ChildExitWatcher { wait_handle: AtomicPtr, - event_rx: Receiver, + event_rx: mpsc::Receiver, + interest: Arc>>, } impl ChildExitWatcher { pub fn new(child_handle: HANDLE) -> Result { - let (event_tx, event_rx) = channel::(); + let (event_tx, event_rx) = mpsc::channel(); let mut wait_handle: HANDLE = 0; - let sender_ref = Box::new(event_tx); + let interest = Arc::new(Mutex::new(None)); + let sender_ref = Box::new(ChildExitSender { sender: event_tx, interest: interest.clone() }); let success = unsafe { RegisterWaitForSingleObject( @@ -51,13 +69,22 @@ impl ChildExitWatcher { Ok(ChildExitWatcher { wait_handle: AtomicPtr::from(wait_handle as *mut c_void), event_rx, + interest, }) } } - pub fn event_rx(&self) -> &Receiver { + pub fn event_rx(&self) -> &mpsc::Receiver { &self.event_rx } + + pub fn register(&self, poller: &Arc, event: Event) { + *self.interest.lock().unwrap() = Some(Interest { poller: poller.clone(), event }); + } + + pub fn deregister(&self) { + *self.interest.lock().unwrap() = None; + } } impl Drop for ChildExitWatcher { @@ -72,36 +99,28 @@ impl Drop for ChildExitWatcher { mod tests { use std::os::windows::io::AsRawHandle; use std::process::Command; + use std::sync::Arc; use std::time::Duration; - use mio::{Events, Poll, PollOpt, Ready, Token}; - + use super::super::PTY_CHILD_EVENT_TOKEN; use super::*; #[test] pub fn event_is_emitted_when_child_exits() { const WAIT_TIMEOUT: Duration = Duration::from_millis(200); + let poller = Arc::new(Poller::new().unwrap()); + let mut child = Command::new("cmd.exe").spawn().unwrap(); let child_exit_watcher = ChildExitWatcher::new(child.as_raw_handle() as HANDLE).unwrap(); - - let mut events = Events::with_capacity(1); - let poll = Poll::new().unwrap(); - let child_events_token = Token::from(0usize); - - poll.register( - child_exit_watcher.event_rx(), - child_events_token, - Ready::readable(), - PollOpt::oneshot(), - ) - .unwrap(); + child_exit_watcher.register(&poller, Event::readable(PTY_CHILD_EVENT_TOKEN)); child.kill().unwrap(); // Poll for the event or fail with timeout if nothing has been sent. - poll.poll(&mut events, Some(WAIT_TIMEOUT)).unwrap(); - assert_eq!(events.iter().next().unwrap().token(), child_events_token); + let mut events = polling::Events::new(); + poller.wait(&mut events, Some(WAIT_TIMEOUT)).unwrap(); + assert_eq!(events.iter().next().unwrap().key, PTY_CHILD_EVENT_TOKEN); // Verify that at least one `ChildEvent::Exited` was received. assert_eq!(child_exit_watcher.event_rx().try_recv(), Ok(ChildEvent::Exited)); } diff --git a/alacritty_terminal/src/tty/windows/conpty.rs b/alacritty_terminal/src/tty/windows/conpty.rs index c9ed631e..12189371 100644 --- a/alacritty_terminal/src/tty/windows/conpty.rs +++ b/alacritty_terminal/src/tty/windows/conpty.rs @@ -3,8 +3,6 @@ use std::io::Error; use std::os::windows::io::IntoRawHandle; use std::{mem, ptr}; -use mio_anonymous_pipes::{EventedAnonRead, EventedAnonWrite}; - use windows_sys::core::{HRESULT, PWSTR}; use windows_sys::Win32::Foundation::{HANDLE, S_OK}; use windows_sys::Win32::System::Console::{ @@ -21,9 +19,12 @@ use windows_sys::Win32::System::Threading::{ use crate::config::PtyConfig; use crate::event::{OnResize, WindowSize}; +use crate::tty::windows::blocking::{UnblockedReader, UnblockedWriter}; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::windows::{cmdline, win32_string, Pty}; +const PIPE_CAPACITY: usize = crate::event_loop::READ_BUFFER_SIZE; + /// Load the pseudoconsole API from conpty.dll if possible, otherwise use the /// standard Windows API. /// @@ -220,8 +221,8 @@ pub fn new(config: &PtyConfig, window_size: WindowSize) -> Option { } } - let conin = EventedAnonWrite::new(conin); - let conout = EventedAnonRead::new(conout); + let conin = UnblockedWriter::new(conin, PIPE_CAPACITY); + let conout = UnblockedReader::new(conout, PIPE_CAPACITY); let child_watcher = ChildExitWatcher::new(proc_info.hProcess).unwrap(); let conpty = Conpty { handle: pty_handle as HPCON, api }; diff --git a/alacritty_terminal/src/tty/windows/mod.rs b/alacritty_terminal/src/tty/windows/mod.rs index 57925f4c..080f6e83 100644 --- a/alacritty_terminal/src/tty/windows/mod.rs +++ b/alacritty_terminal/src/tty/windows/mod.rs @@ -3,17 +3,27 @@ use std::io::{self, Error, ErrorKind, Result}; use std::iter::once; use std::os::windows::ffi::OsStrExt; use std::sync::mpsc::TryRecvError; +use std::sync::Arc; use crate::config::{Program, PtyConfig}; use crate::event::{OnResize, WindowSize}; use crate::tty::windows::child::ChildExitWatcher; use crate::tty::{ChildEvent, EventedPty, EventedReadWrite}; +mod blocking; mod child; mod conpty; +use blocking::{UnblockedReader, UnblockedWriter}; use conpty::Conpty as Backend; -use mio_anonymous_pipes::{EventedAnonRead as ReadPipe, EventedAnonWrite as WritePipe}; +use miow::pipe::{AnonRead, AnonWrite}; +use polling::{Event, Poller}; + +pub const PTY_CHILD_EVENT_TOKEN: usize = 1; +pub const PTY_READ_WRITE_TOKEN: usize = 2; + +type ReadPipe = UnblockedReader; +type WritePipe = UnblockedWriter; pub struct Pty { // XXX: Backend is required to be the first field, to ensure correct drop order. Dropping @@ -21,9 +31,6 @@ pub struct Pty { backend: Backend, conout: ReadPipe, conin: WritePipe, - read_token: mio::Token, - write_token: mio::Token, - child_event_token: mio::Token, child_watcher: ChildExitWatcher, } @@ -39,51 +46,29 @@ impl Pty { conin: impl Into, child_watcher: ChildExitWatcher, ) -> Self { - Self { - backend: backend.into(), - conout: conout.into(), - conin: conin.into(), - read_token: 0.into(), - write_token: 0.into(), - child_event_token: 0.into(), - child_watcher, - } + Self { backend: backend.into(), conout: conout.into(), conin: conin.into(), child_watcher } } } +fn with_key(mut event: Event, key: usize) -> Event { + event.key = key; + event +} + impl EventedReadWrite for Pty { type Reader = ReadPipe; type Writer = WritePipe; #[inline] - fn register( + unsafe fn register( &mut self, - poll: &mio::Poll, - token: &mut dyn Iterator, - interest: mio::Ready, - poll_opts: mio::PollOpt, + poll: &Arc, + interest: polling::Event, + poll_opts: polling::PollMode, ) -> io::Result<()> { - self.read_token = token.next().unwrap(); - self.write_token = token.next().unwrap(); - - if interest.is_readable() { - poll.register(&self.conout, self.read_token, mio::Ready::readable(), poll_opts)? - } else { - poll.register(&self.conout, self.read_token, mio::Ready::empty(), poll_opts)? - } - if interest.is_writable() { - poll.register(&self.conin, self.write_token, mio::Ready::writable(), poll_opts)? - } else { - poll.register(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)? - } - - self.child_event_token = token.next().unwrap(); - poll.register( - self.child_watcher.event_rx(), - self.child_event_token, - mio::Ready::readable(), - poll_opts, - )?; + self.conin.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); + self.conout.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); + self.child_watcher.register(poll, with_key(interest, PTY_CHILD_EVENT_TOKEN)); Ok(()) } @@ -91,36 +76,23 @@ impl EventedReadWrite for Pty { #[inline] fn reregister( &mut self, - poll: &mio::Poll, - interest: mio::Ready, - poll_opts: mio::PollOpt, + poll: &Arc, + interest: polling::Event, + poll_opts: polling::PollMode, ) -> io::Result<()> { - if interest.is_readable() { - poll.reregister(&self.conout, self.read_token, mio::Ready::readable(), poll_opts)?; - } else { - poll.reregister(&self.conout, self.read_token, mio::Ready::empty(), poll_opts)?; - } - if interest.is_writable() { - poll.reregister(&self.conin, self.write_token, mio::Ready::writable(), poll_opts)?; - } else { - poll.reregister(&self.conin, self.write_token, mio::Ready::empty(), poll_opts)?; - } - - poll.reregister( - self.child_watcher.event_rx(), - self.child_event_token, - mio::Ready::readable(), - poll_opts, - )?; + self.conin.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); + self.conout.register(poll, with_key(interest, PTY_READ_WRITE_TOKEN), poll_opts); + self.child_watcher.register(poll, with_key(interest, PTY_CHILD_EVENT_TOKEN)); Ok(()) } #[inline] - fn deregister(&mut self, poll: &mio::Poll) -> io::Result<()> { - poll.deregister(&self.conout)?; - poll.deregister(&self.conin)?; - poll.deregister(self.child_watcher.event_rx())?; + fn deregister(&mut self, _poll: &Arc) -> io::Result<()> { + self.conin.deregister(); + self.conout.deregister(); + self.child_watcher.deregister(); + Ok(()) } @@ -129,27 +101,13 @@ impl EventedReadWrite for Pty { &mut self.conout } - #[inline] - fn read_token(&self) -> mio::Token { - self.read_token - } - #[inline] fn writer(&mut self) -> &mut Self::Writer { &mut self.conin } - - #[inline] - fn write_token(&self) -> mio::Token { - self.write_token - } } impl EventedPty for Pty { - fn child_event_token(&self) -> mio::Token { - self.child_event_token - } - fn next_child_event(&mut self) -> Option { match self.child_watcher.event_rx().try_recv() { Ok(ev) => Some(ev),