patch 8.2.3941: SIGTSTP is not handled

Problem:    SIGTSTP is not handled.
Solution:   Handle SIGTSTP like pressing CTRL-Z. (closes #9422)
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index e620f5c..c1aee0e 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -1279,8 +1279,8 @@
 	:autocmd VimResume * checktime
 <							*VimSuspend*
 VimSuspend			When the Vim instance is suspended.  Only when
-				CTRL-Z was typed inside Vim, not when the
-				SIGSTOP or SIGTSTP signal was sent to Vim.
+				CTRL-Z was typed inside Vim, or when the SIGTSTP
+				signal was sent to Vim, but not for SIGSTOP.
 							*WinClosed*
 WinClosed			After closing a window.  The pattern is
 				matched against the |window-ID|.  Both
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
index 8f4f4ae..12824a9 100644
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -109,7 +109,6 @@
 # define ex_pedit		ex_ni
 #endif
 static void	ex_hide(exarg_T *eap);
-static void	ex_stop(exarg_T *eap);
 static void	ex_exit(exarg_T *eap);
 static void	ex_print(exarg_T *eap);
 #ifdef FEAT_BYTEOFF
@@ -6200,7 +6199,7 @@
 /*
  * ":stop" and ":suspend": Suspend Vim.
  */
-    static void
+    void
 ex_stop(exarg_T *eap)
 {
     /*
diff --git a/src/os_unix.c b/src/os_unix.c
index 77f35db..e24c09f 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -157,6 +157,11 @@
 #if defined(SIGWINCH)
 static RETSIGTYPE sig_winch SIGPROTOARG;
 #endif
+#if defined(SIGTSTP)
+static RETSIGTYPE sig_tstp SIGPROTOARG;
+// volatile because it is used in signal handler sig_tstp() and sigcont_handler().
+static volatile sig_atomic_t in_mch_suspend = FALSE;
+#endif
 #if defined(SIGINT)
 static RETSIGTYPE catch_sigint SIGPROTOARG;
 #endif
@@ -197,6 +202,8 @@
 
 // volatile because it is used in signal handler sig_winch().
 static volatile sig_atomic_t do_resize = FALSE;
+// volatile because it is used in signal handler sig_tstp().
+static volatile sig_atomic_t got_tstp = FALSE;
 static char_u	*extra_shell_arg = NULL;
 static int	show_shell_mess = TRUE;
 // volatile because it is used in signal handler deathtrap().
@@ -851,6 +858,24 @@
 }
 #endif
 
+#if defined(SIGTSTP)
+    static RETSIGTYPE
+sig_tstp SIGDEFARG(sigarg)
+{
+    // Second time we get called we actually need to suspend
+    if (in_mch_suspend)
+    {
+	signal(SIGTSTP, ignore_sigtstp ? SIG_IGN : SIG_DFL);
+	raise(sigarg);
+    }
+
+    // this is not required on all systems, but it doesn't hurt anybody
+    signal(SIGTSTP, (RETSIGTYPE (*)())sig_tstp);
+    got_tstp = TRUE;
+    SIGRETURN;
+}
+#endif
+
 #if defined(SIGINT)
     static RETSIGTYPE
 catch_sigint SIGDEFARG(sigarg)
@@ -1158,7 +1183,6 @@
 
 #if defined(SIGCONT)
 static RETSIGTYPE sigcont_handler SIGPROTOARG;
-static volatile sig_atomic_t in_mch_suspend = FALSE;
 
 /*
  * With multi-threading, suspending might not work immediately.  Catch the
@@ -1353,7 +1377,7 @@
 
 #ifdef SIGTSTP
     // See mch_init() for the conditions under which we ignore SIGTSTP.
-    signal(SIGTSTP, ignore_sigtstp ? SIG_IGN : SIG_DFL);
+    signal(SIGTSTP, ignore_sigtstp ? SIG_IGN : (RETSIGTYPE (*)())sig_tstp);
 #endif
 #if defined(SIGCONT)
     signal(SIGCONT, sigcont_handler);
@@ -6386,6 +6410,15 @@
 # ifdef EINTR
 	if (ret == -1 && errno == EINTR)
 	{
+	    // Check whether the EINTR is caused by SIGTSTP
+	    if (got_tstp && !in_mch_suspend)
+	    {
+		exarg_T ea;
+		ea.forceit = TRUE;
+		ex_stop(&ea);
+		got_tstp = FALSE;
+	    }
+
 	    // Check whether window has been resized, EINTR may be caused by
 	    // SIGWINCH.
 	    if (do_resize)
@@ -7176,7 +7209,7 @@
 	    // we are going to suspend or starting an external process
 	    // so we shouldn't  have problem with this
 # ifdef SIGTSTP
-	    signal(SIGTSTP, restricted ? SIG_IGN : SIG_DFL);
+	    signal(SIGTSTP, restricted ? SIG_IGN : (RETSIGTYPE (*)())sig_tstp);
 # endif
 	    return 1; // succeed
 	}
diff --git a/src/proto/ex_docmd.pro b/src/proto/ex_docmd.pro
index d88b8a3..0fed2f0 100644
--- a/src/proto/ex_docmd.pro
+++ b/src/proto/ex_docmd.pro
@@ -40,6 +40,7 @@
 void ex_quit(exarg_T *eap);
 void tabpage_close(int forceit);
 void tabpage_close_other(tabpage_T *tp, int forceit);
+void ex_stop(exarg_T *eap);
 void handle_drop(int filec, char_u **filev, int split, void (*callback)(void *), void *cookie);
 void handle_any_postponed_drop(void);
 void ex_splitview(exarg_T *eap);
diff --git a/src/testdir/test_signals.vim b/src/testdir/test_signals.vim
index 72fd024..d3a4d89 100644
--- a/src/testdir/test_signals.vim
+++ b/src/testdir/test_signals.vim
@@ -105,6 +105,58 @@
   call StopVimInTerminal(buf)
 endfunc
 
+" Test signal TSTP. Handler sets got_tstp.
+func Test_signal_TSTP()
+  CheckRunVimInTerminal
+  if !HasSignal('TSTP')
+    throw 'Skipped: TSTP signal not supported'
+  endif
+
+  " Skip the test when running with valgrind as signal TSTP is not received
+  " somehow by Vim when running with valgrind.
+  let cmd = GetVimCommand()
+  if cmd =~ 'valgrind'
+    throw 'Skipped: cannot test signal TSTP with valgrind'
+  endif
+
+  " If test fails once, it can leave temporary files and trying to rerun
+  " the test would then fail again if they are not deleted first.
+  call delete('.Xsig_TERM.swp')
+  call delete('XsetupAucmd')
+  call delete('XautoOut')
+  let lines =<< trim END
+    au VimSuspend * call writefile(["VimSuspend triggered"], "XautoOut", "as")
+    au VimResume * call writefile(["VimResume triggered"], "XautoOut", "as")
+  END
+  call writefile(lines, 'XsetupAucmd')
+
+  let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6})
+  let pid_vim = term_getjob(buf)->job_info().process
+
+  call term_sendkeys(buf, ":call setline(1, 'foo')\n")
+  call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))})
+
+  call assert_false(filereadable('Xsig_TERM'))
+
+  " After TSTP the file is not saved (same function as ^Z)
+  exe 'silent !kill -s TSTP ' .. pid_vim
+  call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))})
+
+  " We resume after the suspend
+  exe 'silent !kill -s CONT ' .. pid_vim
+  exe 'silent !sleep 0.006'
+
+  call StopVimInTerminal(buf)
+
+  let result = readfile('XautoOut')
+  call assert_equal(["VimSuspend triggered", "VimResume triggered"], result)
+
+  %bwipe!
+  call delete('.Xsig_TERM.swp')
+  call delete('XsetupAucmd')
+  call delete('XautoOut')
+endfunc
+
 " Test a deadly signal.
 "
 " There are several deadly signals: SISEGV, SIBUS, SIGTERM...
diff --git a/src/version.c b/src/version.c
index ee3a29f..2a5260a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -750,6 +750,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3941,
+/**/
     3940,
 /**/
     3939,