Repository: kkawakam/rustyline (rust)

Issue:
rustyline overwrites previously registered SIGWINCH handler
If an application registers a SIGWINCH handler prior to initializing rustyline, rustyline will override it when it installs its own SIGWINCH handler. Installation of the SIGWINCH handler takes place [here](https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs#L1045-L1050).

The [`sigaction`](https://linux.die.net/man/2/sigaction) system call can return the function pointer for the previously registered signal handler. rustyline could store this value and call the function if it is non-NULL. (See [this StackOverflow question](https://stackoverflow.com/questions/17102919/is-it-valid-to-have-multiple-signal-handlers-for-same-signal).) The alternative for the application is to register their signal handler after initializing rustyline and either save the reference to rustyline's signal handler, or leave rustyline's signal handler abandoned, but people probably don't expect rustyline to install a SIGWINCH handler, so they'll only realize this once it causes [a bug](https://github.com/PaulJuliusMartinez/jless/issues/62).


Pull request:
Introduce signal-hook
Try to fix #606
But `signal-hook` doesn't seem to work (as expected).


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 1b74820..1b11bf7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -43,6 +43,7 @@ regex = { version = "1.5.4", optional = true }
 nix = { version = "0.24", default-features = false, features = ["fs", "ioctl", "poll", "signal", "term"] }
 utf8parse = "0.2"
 skim = { version = "0.9", optional = true }
+signal-hook = { version = "0.3", optional = true, default-features = false }
 
 [target.'cfg(windows)'.dependencies]
 winapi = { version = "0.3", features = ["consoleapi", "handleapi", "synchapi", "minwindef", "processenv", "std", "winbase", "wincon", "winuser"] }
diff --git a/README.md b/README.md
index 3aa29bc..ffefda3 100644
--- a/README.md
+++ b/README.md
@@ -20,11 +20,11 @@ Readline implementation in Rust that is based on [Antirez' Linenoise](https://gi
 ## Example
 ```rust
 use rustyline::error::ReadlineError;
-use rustyline::Editor;
+use rustyline::{Editor, Result};
 
-fn main() {
+fn main() -> Result<()> {
     // `()` can be used when no completer is required
-    let mut rl = Editor::<()>::new();
+    let mut rl = Editor::<()>::new()?;
     if rl.load_history("history.txt").is_err() {
         println!("No previous history.");
     }
@@ -49,7 +49,7 @@ fn main() {
             }
         }
     }
-    rl.save_history("history.txt").unwrap();
+    rl.save_history("history.txt")
 }
 ```
 
diff --git a/examples/custom_key_bindings.rs b/examples/custom_key_bindings.rs
index ed7b363..b11d683 100644
--- a/examples/custom_key_bindings.rs
+++ b/examples/custom_key_bindings.rs
@@ -85,7 +85,7 @@ impl ConditionalEventHandler for TabEventHandler {
 }
 
 fn main() -> Result<()> {
-    let mut rl = Editor::<MyHelper>::new();
+    let mut rl = Editor::<MyHelper>::new()?;
     rl.set_helper(Some(MyHelper(HistoryHinter {})));
 
     let ceh = Box::new(CompleteHintHandler);
diff --git a/examples/diy_hints.rs b/examples/diy_hints.rs
index fb3130b..136f7b4 100644
--- a/examples/diy_hints.rs
+++ b/examples/diy_hints.rs
@@ -85,7 +85,7 @@ fn main() -> Result<()> {
     println!("This is a DIY hint hack of rustyline");
     let h = DIYHinter { hints: diy_hints() };
 
-    let mut rl: Editor<DIYHinter> = Editor::new();
+    let mut rl: Editor<DIYHinter> = Editor::new()?;
     rl.set_helper(Some(h));
 
     loop {
diff --git a/examples/example.rs b/examples/example.rs
index 2ab63bd..36eac89 100644
--- a/examples/example.rs
+++ b/examples/example.rs
@@ -62,7 +62,7 @@ fn main() -> rustyline::Result<()> {
         colored_prompt: "".to_owned(),
         validator: MatchingBracketValidator::new(),
     };
-    let mut rl = Editor::with_config(config);
+    let mut rl = Editor::with_config(config)?;
     rl.set_helper(Some(h));
     rl.bind_sequence(KeyEvent::alt('n'), Cmd::HistorySearchForward);
     rl.bind_sequence(KeyEvent::alt('p'), Cmd::HistorySearchBackward);
diff --git a/examples/external_print.rs b/examples/external_print.rs
index f1cfb64..fba845f 100644
--- a/examples/external_print.rs
+++ b/examples/external_print.rs
@@ -6,7 +6,7 @@ use rand::{thread_rng, Rng};
 use rustyline::{Editor, ExternalPrinter, Result};
 
 fn main() -> Result<()> {
-    let mut rl = Editor::<()>::new();
+    let mut rl = Editor::<()>::new()?;
     let mut printer = rl.create_external_printer()?;
     thread::spawn(move || {
         let mut rng = thread_rng();
diff --git a/examples/input_multiline.rs b/examples/input_multiline.rs
index 6a85999..0f216e2 100644
--- a/examples/input_multiline.rs
+++ b/examples/input_multiline.rs
@@ -12,7 +12,7 @@ fn main() -> Result<()> {
     let h = InputValidator {
         brackets: MatchingBracketValidator::new(),
     };
-    let mut rl = Editor::new();
+    let mut rl = Editor::new()?;
     rl.set_helper(Some(h));
 
     let input = rl.readline("> ")?;
diff --git a/examples/input_validation.rs b/examples/input_validation.rs
index c80a34d..536f299 100644
--- a/examples/input_validation.rs
+++ b/examples/input_validation.rs
@@ -22,7 +22,7 @@ impl Validator for InputValidator {
 
 fn main() -> Result<()> {
     let h = InputValidator {};
-    let mut rl = Editor::new();
+    let mut rl = Editor::new()?;
     rl.set_helper(Some(h));
 
     let input = rl.readline("> ")?;
diff --git a/examples/minimal.rs b/examples/minimal.rs
index de84a59..47bc5be 100644
--- a/examples/minimal.rs
+++ b/examples/minimal.rs
@@ -2,7 +2,8 @@ use rustyline::{Editor, Result};
 
 /// Minimal REPL
 fn main() -> Result<()> {
-    let mut rl = Editor::<()>::new();
+    env_logger::init();
+    let mut rl = Editor::<()>::new()?;
     loop {
         let line = rl.readline("> ")?; // read
         println!("Line: {}", line); // eval / print
diff --git a/examples/numeric_input.rs b/examples/numeric_input.rs
index bdb7625..fa3ea2f 100644
--- a/examples/numeric_input.rs
+++ b/examples/numeric_input.rs
@@ -19,7 +19,7 @@ impl ConditionalEventHandler for FilteringEventHandler {
 }
 
 fn main() -> Result<()> {
-    let mut rl = Editor::<()>::new();
+    let mut rl = Editor::<()>::new()?;
 
     rl.bind_sequence(
         Event::Any,
diff --git a/examples/read_password.rs b/examples/read_password.rs
index 435e6e3..3f20cf8 100644
--- a/examples/read_password.rs
+++ b/examples/read_password.rs
@@ -28,7 +28,7 @@ impl Highlighter for MaskingHighlighter {
 fn main() -> Result<()> {
     println!("This is just a hack. Reading passwords securely requires more than that.");
     let h = MaskingHighlighter { masking: false };
-    let mut rl = Editor::new();
+    let mut rl = Editor::new()?;
     rl.set_helper(Some(h));
 
     let username = rl.readline("Username:")?;
diff --git a/src/edit.rs b/src/edit.rs
index 64411cb..9e716cb 100644
--- a/src/edit.rs
+++ b/src/edit.rs
@@ -8,6 +8,7 @@ use unicode_segmentation::UnicodeSegmentation;
 use unicode_width::UnicodeWidthChar;
 
 use super::{Context, Helper, Result};
+use crate::error::ReadlineError;
 use crate::highlight::Highlighter;
 use crate::hint::Hint;
 use crate::history::SearchDirection;
@@ -83,7 +84,8 @@ impl<'out, 'prompt, H: Helper> State<'out, 'prompt, H> {
     ) -> Result<Cmd> {
         loop {
             let rc = input_state.next_cmd(rdr, self, single_esc_abort, ignore_external_print);
-            if rc.is_err() && self.out.sigwinch() {
+            if let Err(ReadlineError::WindowResized) = rc {
+                debug!(target: "rustyline", "SIGWINCH");
                 self.out.update_size();
                 self.prompt_size = self
                     .out
diff --git a/src/error.rs b/src/error.rs
index b5c17e6..46b635c 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,7 +1,7 @@
 //! Contains error type for handling I/O and Errno errors
 #[cfg(windows)]
 use std::char;
-use std::error;
+use std::error::Error;
 use std::fmt;
 use std::io;
 
@@ -21,10 +21,8 @@ pub enum ReadlineError {
     /// Unix Error from syscall
     #[cfg(unix)]
     Errno(nix::Error),
-    /// Error generated on WINDOW_BUFFER_SIZE_EVENT to mimic unix SIGWINCH
-    /// signal
-    #[cfg(windows)]
-    WindowResize,
+    /// Error generated on WINDOW_BUFFER_SIZE_EVENT / SIGWINCH signal
+    WindowResized,
     /// Like Utf8Error on unix
     #[cfg(windows)]
     Decode(char::DecodeUtf16Error),
@@ -41,8 +39,7 @@ impl fmt::Display for ReadlineError {
             ReadlineError::Interrupted => write!(f, "Interrupted"),
             #[cfg(unix)]
             ReadlineError::Errno(ref err) => err.fmt(f),
-            #[cfg(windows)]
-            ReadlineError::WindowResize => write!(f, "WindowResize"),
+            ReadlineError::WindowResized => write!(f, "WindowResized"),
             #[cfg(windows)]
             ReadlineError::Decode(ref err) => err.fmt(f),
             #[cfg(windows)]
@@ -51,10 +48,45 @@ impl fmt::Display for ReadlineError {
     }
 }
 
-impl error::Error for ReadlineError {}
+impl Error for ReadlineError {
+    fn source(&self) -> Option<&(dyn Error + 'static)> {
+        match *self {
+            ReadlineError::Io(ref err) => Some(err),
+            ReadlineError::Eof => None,
+            ReadlineError::Interrupted => None,
+            #[cfg(unix)]
+            ReadlineError::Errno(ref err) => Some(err),
+            ReadlineError::WindowResized => None,
+            #[cfg(windows)]
+            ReadlineError::Decode(ref err) => Some(err),
+            #[cfg(windows)]
+            ReadlineError::SystemError(_) => None,
+        }
+    }
+}
+
+#[cfg(unix)]
+#[derive(Debug)]
+pub(crate) struct WindowResizedError;
+#[cfg(unix)]
+impl fmt::Display for WindowResizedError {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "WindowResized")
+    }
+}
+#[cfg(unix)]
+impl Error for WindowResizedError {}
 
 impl From<io::Error> for ReadlineError {
     fn from(err: io::Error) -> Self {
+        #[cfg(unix)]
+        if err.kind() == io::ErrorKind::Interrupted {
+            if let Some(e) = err.get_ref() {
+                if e.downcast_ref::<WindowResizedError>().is_some() {
+                    return ReadlineError::WindowResized;
+                }
+            }
+        }
         ReadlineError::Io(err)
     }
 }
diff --git a/src/lib.rs b/src/lib.rs
index ed92f62..dfcf63f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -8,12 +8,13 @@
 //! Usage
 //!
 //! ```
-//! let mut rl = rustyline::Editor::<()>::new();
+//! let mut rl = rustyline::Editor::<()>::new()?;
 //! let readline = rl.readline(">> ");
 //! match readline {
 //!     Ok(line) => println!("Line: {:?}", line),
 //!     Err(_) => println!("No input"),
 //! }
+//! # Ok::<(), rustyline::error::ReadlineError>(())
 //! ```
 #![warn(missing_docs)]
 #![cfg_attr(docsrs, feature(doc_cfg))]
@@ -46,13 +47,14 @@ use std::sync::{Arc, Mutex};
 use log::debug;
 use unicode_width::UnicodeWidthStr;
 
-use crate::tty::{RawMode, Renderer, Term, Terminal};
+use crate::tty::{RawMode, RawReader, Renderer, Term, Terminal};
 
 #[cfg(feature = "custom-bindings")]
 pub use crate::binding::{ConditionalEventHandler, Event, EventContext, EventHandler};
 use crate::completion::{longest_common_prefix, Candidate, Completer};
 pub use crate::config::{Behavior, ColorMode, CompletionType, Config, EditMode, HistoryDuplicates};
 use crate::edit::State;
+use crate::error::ReadlineError;
 use crate::highlight::Highlighter;
 use crate::hint::Hinter;
 use crate::history::{History, SearchDirection};
@@ -572,6 +574,7 @@ impl<'h> Context<'h> {
 }
 
 /// Line editor
+#[must_use]
 pub struct Editor<H: Helper> {
     term: Terminal,
     history: History,
@@ -584,29 +587,27 @@ pub struct Editor<H: Helper> {
 #[allow(clippy::new_without_default)]
 impl<H: Helper> Editor<H> {
     /// Create an editor with the default configuration
-    #[must_use]
-    pub fn new() -> Self {
+    pub fn new() -> Result<Self> {
         Self::with_config(Config::default())
     }
 
     /// Create an editor with a specific configuration.
-    #[must_use]
-    pub fn with_config(config: Config) -> Self {
+    pub fn with_config(config: Config) -> Result<Self> {
         let term = Terminal::new(
             config.color_mode(),
             config.behavior(),
             config.tab_stop(),
             config.bell_style(),
             config.enable_bracketed_paste(),
-        );
-        Self {
+        )?;
+        Ok(Self {
             term,
             history: History::with_config(config),
             helper: None,
             kill_ring: Arc::new(Mutex::new(KillRing::new(60))),
             config,
             custom_bindings: Bindings::new(),
-        }
+        })
     }
 
     /// This method will read a line from STDIN and will display a `prompt`.
@@ -687,7 +688,7 @@ impl<H: Helper> Editor<H> {
         let mut rdr = self.term.create_reader(&self.config, term_key_map);
         if self.term.is_output_tty() && self.config.check_cursor_position() {
             if let Err(e) = s.move_cursor_at_leftmost(&mut rdr) {
-                if s.out.sigwinch() {
+                if let ReadlineError::WindowResized = e {
                     s.out.update_size();
                 } else {
                     return Err(e);
@@ -730,6 +731,7 @@ impl<H: Helper> Editor<H> {
                 original_mode.disable_raw_mode()?;
                 tty::suspend()?;
                 let _ = self.term.enable_raw_mode()?; // TODO original_mode may have changed
+                s.out.update_size(); // window may have been resized
                 s.refresh_line()?;
                 continue;
             }
@@ -737,7 +739,6 @@ impl<H: Helper> Editor<H> {
             #[cfg(unix)]
             if cmd == Cmd::QuotedInsert {
                 // Quoted insert
-                use crate::tty::RawReader;
                 let c = rdr.next_char()?;
                 s.edit_insert(c, 1)?;
                 continue;
@@ -745,7 +746,6 @@ impl<H: Helper> Editor<H> {
 
             #[cfg(windows)]
             if cmd == Cmd::PasteFromClipboard {
-                use crate::tty::RawReader;
                 let clipboard = rdr.read_pasted_text()?;
                 s.edit_yank(&input_state, &clipboard[..], Anchor::Before, 1)?;
             }
@@ -849,7 +849,7 @@ impl<H: Helper> Editor<H> {
 
     /// Returns an iterator over edited lines
     /// ```
-    /// let mut rl = rustyline::Editor::<()>::new();
+    /// let mut rl = rustyline::Editor::<()>::new()?;
     /// for readline in rl.iter("> ") {
     ///     match readline {
     ///         Ok(line) => {
@@ -861,6 +861,7 @@ impl<H: Helper> Editor<H> {
     ///         }
     ///     }
     /// }
+    /// # Ok::<(), rustyline::error::ReadlineError>(())
     /// ```
     pub fn iter<'a>(&'a mut self, prompt: &'a str) -> impl Iterator<Item = Result<String>> + 'a {
         Iter {
diff --git a/src/tty/mod.rs b/src/tty/mod.rs
index 2dc583a..24f04f7 100644
--- a/src/tty/mod.rs
+++ b/src/tty/mod.rs
@@ -103,8 +103,6 @@ pub trait Renderer {
     /// Clear rows used by prompt and edited line
     fn clear_rows(&mut self, layout: &Layout) -> Result<()>;
 
-    /// Check if a SIGWINCH signal has been received
-    fn sigwinch(&self) -> bool;
     /// Update the number of columns/rows in the current terminal.
     fn update_size(&mut self);
     /// Get the number of columns in the current terminal.
@@ -157,10 +155,6 @@ impl<'a, R: Renderer + ?Sized> Renderer for &'a mut R {
         (**self).clear_rows(layout)
     }
 
-    fn sigwinch(&self) -> bool {
-        (**self).sigwinch()
-    }
-
     fn update_size(&mut self) {
         (**self).update_size();
     }
@@ -233,7 +227,9 @@ pub trait Term {
         tab_stop: usize,
         bell_style: BellStyle,
         enable_bracketed_paste: bool,
-    ) -> Self;
+    ) -> Result<Self>
+    where
+        Self: Sized;
     /// Check if current terminal can provide a rich line-editing user
     /// interface.
     fn is_unsupported(&self) -> bool;
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
index af20126..34e695e 100644
--- a/src/tty/unix.rs
+++ b/src/tty/unix.rs
@@ -4,17 +4,17 @@ use std::collections::HashMap;
 use std::fs::{File, OpenOptions};
 use std::io::{self, BufReader, ErrorKind, Read, Write};
 use std::os::unix::io::{AsRawFd, IntoRawFd, RawFd};
+use std::os::unix::net::UnixStream;
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::mpsc::{self, SyncSender};
-use std::sync::{self, Arc, Mutex};
+use std::sync::{Arc, Mutex};
 
 use log::{debug, warn};
 use nix::errno::Errno;
 use nix::poll::{self, PollFlags};
 use nix::sys::select::{self, FdSet};
-use nix::sys::signal;
 use nix::sys::termios::{self, SetArg, SpecialCharacterIndices as SCI, Termios};
-use nix::unistd::{close, isatty, write};
+use nix::unistd::{close, isatty, read, write};
 use unicode_segmentation::UnicodeSegmentation;
 use utf8parse::{Parser, Receiver};
 
@@ -24,7 +24,7 @@ use crate::highlight::Highlighter;
 use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
 use crate::layout::{Layout, Position};
 use crate::line_buffer::LineBuffer;
-use crate::{error, Cmd, Result};
+use crate::{error, Cmd, ReadlineError, Result};
 
 /// Unsupported Terminals that don't support RAW mode
 const UNSUPPORTED_TERM: [&str; 3] = ["dumb", "cons25", "emacs"];
@@ -120,6 +120,7 @@ impl RawMode for PosixMode {
 // So we use low-level stuff instead...
 struct TtyIn {
     fd: RawFd,
+    sigwinch_pipe: Option<RawFd>,
 }
 
 impl Read for TtyIn {
@@ -134,7 +135,12 @@ impl Read for TtyIn {
             };
             if res == -1 {
                 let error = io::Error::last_os_error();
-                if error.kind() != ErrorKind::Interrupted || SIGWINCH.load(Ordering::Relaxed) {
+                if error.kind() == ErrorKind::Interrupted && self.sigwinch()? {
+                    return Err(io::Error::new(
+                        ErrorKind::Interrupted,
+                        error::WindowResizedError,
+                    ));
+                } else if error.kind() != ErrorKind::Interrupted {
                     return Err(error);
                 }
             } else {
@@ -145,6 +151,23 @@ impl Read for TtyIn {
     }
 }
 
+impl TtyIn {
+    /// Check if a SIGWINCH signal has been received
+    fn sigwinch(&self) -> nix::Result<bool> {
+        if let Some(pipe) = self.sigwinch_pipe {
+            let mut buf = [0u8; 64];
+            match read(pipe, &mut buf) {
+                Ok(0) => Ok(false),
+                Ok(_) => Ok(true),
+                Err(e) if e == Errno::EWOULDBLOCK || e == Errno::EINTR => Ok(false),
+                Err(e) => Err(e),
+            }
+        } else {
+            Ok(false)
+        }
+    }
+}
+
 // (native receiver with a selectable file descriptor, actual message receiver)
 type PipeReader = Arc<Mutex<(File, mpsc::Receiver<String>)>>;
 // (native sender, actual message sender)
@@ -201,12 +224,13 @@ const RXVT_CTRL_SHIFT: char = '@';
 impl PosixRawReader {
     fn new(
         fd: RawFd,
+        sigwinch_pipe: Option<RawFd>,
         config: &Config,
         key_map: PosixKeyMap,
         pipe_reader: Option<PipeReader>,
     ) -> Self {
         Self {
-            tty_in: BufReader::with_capacity(1024, TtyIn { fd }),
+            tty_in: BufReader::with_capacity(1024, TtyIn { fd, sigwinch_pipe }),
             timeout_ms: config.keyseq_timeout(),
             parser: Parser::new(),
             key_map,
@@ -655,7 +679,7 @@ impl PosixRawReader {
         })
     }
 
-    fn poll(&mut self, timeout_ms: i32) -> nix::Result<i32> {
+    fn poll(&mut self, timeout_ms: i32) -> Result<i32> {
         let n = self.tty_in.buffer().len();
         if n > 0 {
             return Ok(n as i32);
@@ -663,33 +687,35 @@ impl PosixRawReader {
         let mut fds = [poll::PollFd::new(self.as_raw_fd(), PollFlags::POLLIN)];
         let r = poll::poll(&mut fds, timeout_ms);
         match r {
-            Ok(_) => r,
+            Ok(n) => Ok(n),
             Err(Errno::EINTR) => {
-                if SIGWINCH.load(Ordering::Relaxed) {
-                    r
+                if self.tty_in.get_ref().sigwinch()? {
+                    Err(ReadlineError::WindowResized)
                 } else {
                     Ok(0) // Ignore EINTR while polling
                 }
             }
-            Err(_) => r,
+            Err(e) => Err(e.into()),
         }
     }
 
     fn select(&mut self, single_esc_abort: bool) -> Result<Event> {
         let tty_in = self.as_raw_fd();
+        let sigwinch_pipe = self.tty_in.get_ref().sigwinch_pipe;
+        let pipe_reader = self
+            .pipe_reader
+            .as_ref()
+            .map(|pr| pr.lock().unwrap().0.as_raw_fd());
         loop {
             let mut readfds = self.fds;
             readfds.clear();
+            if let Some(sigwinch_pipe) = sigwinch_pipe {
+                readfds.insert(sigwinch_pipe);
+            }
             readfds.insert(tty_in);
-            readfds.insert(
-                self.pipe_reader
-                    .as_ref()
-                    .unwrap()
-                    .lock()
-                    .unwrap()
-                    .0
-                    .as_raw_fd(),
-            );
+            if let Some(pipe_reader) = pipe_reader {
+                readfds.insert(pipe_reader);
+            }
             if let Err(err) = select::select(
                 readfds.highest().map(|h| h + 1),
                 Some(&mut readfds),
@@ -697,17 +723,22 @@ impl PosixRawReader {
                 None,
                 None,
             ) {
-                if err != Errno::EINTR || SIGWINCH.load(Ordering::Relaxed) {
+                if err == Errno::EINTR && self.tty_in.get_ref().sigwinch()? {
+                    return Err(ReadlineError::WindowResized);
+                } else if err != Errno::EINTR {
                     return Err(err.into());
                 } else {
                     continue;
                 }
             };
-            if readfds.contains(tty_in) {
+            if sigwinch_pipe.map_or(false, |fd| readfds.contains(fd)) {
+                self.tty_in.get_ref().sigwinch()?;
+                return Err(ReadlineError::WindowResized);
+            } else if readfds.contains(tty_in) {
                 // prefer user input over external print
                 return self.next_key(single_esc_abort).map(Event::KeyPress);
-            } else {
-                let mut guard = self.pipe_reader.as_ref().unwrap().lock().unwrap();
+            } else if let Some(ref pipe_reader) = self.pipe_reader {
+                let mut guard = pipe_reader.lock().unwrap();
                 let mut buf = [0; 1];
                 guard.0.read_exact(&mut buf)?;
                 if let Ok(msg) = guard.1.try_recv() {
@@ -719,6 +750,7 @@ impl PosixRawReader {
 }
 
 impl RawReader for PosixRawReader {
+    #[cfg(not(feature = "signal-hook"))]
     fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
         match self.pipe_reader {
             Some(_) => self.select(single_esc_abort),
@@ -726,6 +758,11 @@ impl RawReader for PosixRawReader {
         }
     }
 
+    #[cfg(feature = "signal-hook")]
+    fn wait_for_input(&mut self, single_esc_abort: bool) -> Result<Event> {
+        self.select(single_esc_abort)
+    }
+
     fn next_key(&mut self, single_esc_abort: bool) -> Result<KeyEvent> {
         let c = self.next_char()?;
 
@@ -748,7 +785,7 @@ impl RawReader for PosixRawReader {
                     key = self.escape_sequence()?
                 }
                 // Err(ref e) if e.kind() == ErrorKind::Interrupted => continue,
-                Err(e) => return Err(e.into()),
+                Err(e) => return Err(e),
             }
         }
         debug!(target: "rustyline", "c: {:?} => key: {:?}", c, key);
@@ -1026,13 +1063,6 @@ impl Renderer for PosixRenderer {
         Ok(())
     }
 
-    /// Check if a SIGWINCH signal has been received
-    fn sigwinch(&self) -> bool {
-        SIGWINCH
-            .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
-            .unwrap_or(false)
-    }
-
     /// Try to update the number of columns in the current terminal,
     fn update_size(&mut self) {
         let (cols, _) = get_win_size(self.out);
@@ -1109,23 +1139,67 @@ fn write_all(fd: RawFd, buf: &str) -> nix::Result<()> {
     Ok(())
 }
 
-static SIGWINCH_ONCE: sync::Once = sync::Once::new();
-static SIGWINCH: AtomicBool = AtomicBool::new(false);
+#[cfg(not(feature = "signal-hook"))]
+static mut SIGWINCH_PIPE: RawFd = -1;
+#[cfg(not(feature = "signal-hook"))]
+extern "C" fn sigwinch_handler(_: libc::c_int) {
+    let _ = unsafe { write(SIGWINCH_PIPE, &[b's']) };
+}
 
-fn install_sigwinch_handler() {
-    SIGWINCH_ONCE.call_once(|| unsafe {
+#[derive(Clone, Debug)]
+struct SigWinCh {
+    pipe: RawFd,
+    #[cfg(not(feature = "signal-hook"))]
+    original: nix::sys::signal::SigAction,
+    #[cfg(feature = "signal-hook")]
+    id: signal_hook::SigId,
+}
+impl SigWinCh {
+    #[cfg(not(feature = "signal-hook"))]
+    fn install_sigwinch_handler() -> Result<SigWinCh> {
+        use nix::sys::signal;
+        let (pipe, pipe_write) = UnixStream::pair()?;
+        pipe.set_nonblocking(true)?;
+        unsafe { SIGWINCH_PIPE = pipe_write.into_raw_fd() };
         let sigwinch = signal::SigAction::new(
             signal::SigHandler::Handler(sigwinch_handler),
             signal::SaFlags::empty(),
             signal::SigSet::empty(),
         );
-        let _ = signal::sigaction(signal::SIGWINCH, &sigwinch);
-    });
-}
+        let original = unsafe { signal::sigaction(signal::SIGWINCH, &sigwinch)? };
+        Ok(SigWinCh {
+            pipe: pipe.into_raw_fd(),
+            original,
+        })
+    }
 
-extern "C" fn sigwinch_handler(_: libc::c_int) {
-    SIGWINCH.store(true, Ordering::SeqCst);
-    debug!(target: "rustyline", "SIGWINCH");
+    #[cfg(feature = "signal-hook")]
+    fn install_sigwinch_handler() -> Result<SigWinCh> {
+        let (pipe, pipe_write) = UnixStream::pair()?;
+        pipe.set_nonblocking(true)?;
+        let id = signal_hook::low_level::pipe::register(libc::SIGWINCH, pipe_write)?;
+        Ok(SigWinCh {
+            pipe: pipe.into_raw_fd(),
+            id,
+        })
+    }
+
+    #[cfg(not(feature = "signal-hook"))]
+    fn uninstall_sigwinch_handler(self) -> Result<()> {
+        use nix::sys::signal;
+        let _ = unsafe { signal::sigaction(signal::SIGWINCH, &self.original)? };
+        close(self.pipe)?;
+        unsafe { close(SIGWINCH_PIPE)? };
+        unsafe { SIGWINCH_PIPE = -1 };
+        Ok(())
+    }
+
+    #[cfg(feature = "signal-hook")]
+    fn uninstall_sigwinch_handler(self) -> Result<()> {
+        signal_hook::low_level::unregister(self.id);
+        close(self.pipe)?;
+        Ok(())
+    }
 }
 
 fn map_key(key_map: &mut HashMap<KeyEvent, Cmd>, raw: &Termios, index: SCI, name: &str, cmd: Cmd) {
@@ -1155,6 +1229,7 @@ pub struct PosixTerminal {
     pipe_reader: Option<PipeReader>,
     // external print writer
     pipe_writer: Option<PipeWriter>,
+    sigwinch: Option<SigWinCh>,
 }
 
 impl PosixTerminal {
@@ -1180,7 +1255,7 @@ impl Term for PosixTerminal {
         tab_stop: usize,
         bell_style: BellStyle,
         enable_bracketed_paste: bool,
-    ) -> Self {
+    ) -> Result<Self> {
         let (tty_in, is_in_a_tty, tty_out, is_out_a_tty, close_on_drop) =
             if behavior == Behavior::PreferTerm {
                 let tty = OpenOptions::new().read(true).write(true).open("/dev/tty");
@@ -1206,8 +1281,15 @@ impl Term for PosixTerminal {
                     false,
                 )
             };
-        let term = Self {
-            unsupported: is_unsupported_term(),
+        let unsupported = is_unsupported_term();
+        #[allow(unused_variables)]
+        let sigwinch = if !unsupported && is_in_a_tty && is_out_a_tty {
+            Some(SigWinCh::install_sigwinch_handler()?)
+        } else {
+            None
+        };
+        Ok(Self {
+            unsupported,
             tty_in,
             is_in_a_tty,
             tty_out,
@@ -1220,11 +1302,8 @@ impl Term for PosixTerminal {
             raw_mode: Arc::new(AtomicBool::new(false)),
             pipe_reader: None,
             pipe_writer: None,
-        };
-        if !term.unsupported && term.is_in_a_tty && term.is_out_a_tty {
-            install_sigwinch_handler();
-        }
-        term
+            sigwinch,
+        })
     }
 
     // Init checks:
@@ -1310,7 +1389,13 @@ impl Term for PosixTerminal {
 
     /// Create a RAW reader
     fn create_reader(&self, config: &Config, key_map: PosixKeyMap) -> PosixRawReader {
-        PosixRawReader::new(self.tty_in, config, key_map, self.pipe_reader.clone())
+        PosixRawReader::new(
+            self.tty_in,
+            self.sigwinch.as_ref().map(|s| s.pipe),
+            config,
+            key_map,
+            self.pipe_reader.clone(),
+        )
     }
 
     fn create_writer(&self) -> PosixRenderer {
@@ -1364,6 +1449,9 @@ impl Drop for PosixTerminal {
             close(self.tty_in);
             debug_assert_eq!(self.tty_in, self.tty_out);
         }
+        if let Some(sigwinch) = self.sigwinch.take() {
+            sigwinch.uninstall_sigwinch_handler();
+        }
     }
 }
 
@@ -1395,6 +1483,7 @@ impl super::ExternalPrinter for ExternalPrinter {
 
 #[cfg(not(test))]
 pub fn suspend() -> Result<()> {
+    use nix::sys::signal;
     use nix::unistd::Pid;
     // suspend the whole process group
     signal::kill(Pid::from_raw(0), signal::SIGTSTP)?;
diff --git a/src/tty/windows.rs b/src/tty/windows.rs
index b41cc5e..eac18b5 100644
--- a/src/tty/windows.rs
+++ b/src/tty/windows.rs
@@ -185,11 +185,8 @@ fn read_input(handle: HANDLE, max_count: u32) -> Result<KeyEvent> {
         total += count;
 
         if rec.EventType == wincon::WINDOW_BUFFER_SIZE_EVENT {
-            SIGWINCH.store(true, Ordering::SeqCst);
             debug!(target: "rustyline", "SIGWINCH");
-            return Err(error::ReadlineError::WindowResize); // sigwinch +
-                                                            // err => err
-                                                            // ignored
+            return Err(error::ReadlineError::WindowResized);
         } else if rec.EventType != wincon::KEY_EVENT {
             continue;
         }
@@ -513,12 +510,6 @@ impl Renderer for ConsoleRenderer {
         self.clear_old_rows(&info, layout)
     }
 
-    fn sigwinch(&self) -> bool {
-        SIGWINCH
-            .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst)
-            .unwrap_or(false)
-    }
-
     /// Try to get the number of columns in the current terminal,
     /// or assume 80 if it fails.
     fn update_size(&mut self) {
@@ -598,8 +589,6 @@ fn write_all(handle: HANDLE, mut data: &[u16]) -> Result<()> {
     Ok(())
 }
 
-static SIGWINCH: AtomicBool = AtomicBool::new(false);
-
 #[cfg(not(test))]
 pub type Terminal = Console;
 
@@ -644,7 +633,7 @@ impl Term for Console {
         _tab_stop: usize,
         bell_style: BellStyle,
         _enable_bracketed_paste: bool,
-    ) -> Console {
+    ) -> Result<Console> {
         let (conin, conout, close_on_drop) = if behavior == Behavior::PreferTerm {
             if let (Ok(conin), Ok(conout)) = (
                 OpenOptions::new().read(true).write(true).open("CONIN$"),
@@ -685,7 +674,7 @@ impl Term for Console {
             Err(_) => false,
         };
 
-        Console {
+        Ok(Console {
             conin_isatty,
             conin: conin.unwrap_or(ptr::null_mut()),
             conout_isatty,
@@ -697,7 +686,7 @@ impl Term for Console {
             raw_mode: Arc::new(AtomicBool::new(false)),
             pipe_reader: None,
             pipe_writer: None,
-        }
+        })
     }
 
     /// Checking for an unsupported TERM in windows is a no-op


Test patch:
diff --git a/src/test/mod.rs b/src/test/mod.rs
index 903be59..3634a9d 100644
--- a/src/test/mod.rs
+++ b/src/test/mod.rs
@@ -19,7 +19,7 @@ mod vi_insert;
 
 fn init_editor(mode: EditMode, keys: &[KeyEvent]) -> Editor<()> {
     let config = Config::builder().edit_mode(mode).build();
-    let mut editor = Editor::<()>::with_config(config);
+    let mut editor = Editor::<()>::with_config(config).unwrap();
     editor.term.keys.extend(keys.iter().cloned());
     editor
 }
diff --git a/src/tty/test.rs b/src/tty/test.rs
index 50eecbf..88e22a6 100644
--- a/src/tty/test.rs
+++ b/src/tty/test.rs
@@ -122,10 +122,6 @@ impl Renderer for Sink {
         Ok(())
     }
 
-    fn sigwinch(&self) -> bool {
-        false
-    }
-
     fn update_size(&mut self) {}
 
     fn get_columns(&self) -> usize {
@@ -176,13 +172,13 @@ impl Term for DummyTerminal {
         _tab_stop: usize,
         bell_style: BellStyle,
         _enable_bracketed_paste: bool,
-    ) -> DummyTerminal {
-        DummyTerminal {
+    ) -> Result<DummyTerminal> {
+        Ok(DummyTerminal {
             keys: Vec::new(),
             cursor: 0,
             color_mode,
             bell_style,
-        }
+        })
     }
 
     // Init checks: