diff --git a/gdb/infrun.c b/gdb/infrun.c
index 617b6309e99..f1afc4ba8e2 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -756,13 +756,56 @@ follow_fork ()
 	  if (tp == cur_thr)
 	    continue;
 
-	  if (tp->pending_follow.kind () != TARGET_WAITKIND_SPURIOUS)
+	  /* follow_fork_inferior clears tp->pending_follow, and below
+	     we'll need the value after the follow_fork_inferior
+	     call.  */
+	  target_waitkind kind = tp->pending_follow.kind ();
+
+	  if (kind != TARGET_WAITKIND_SPURIOUS)
 	    {
 	      infrun_debug_printf ("need to follow-fork [%s] first",
 				   tp->ptid.to_string ().c_str ());
 
 	      switch_to_thread (tp);
-	      should_resume = false;
+
+	      /* Set up inferior(s) as specified by the caller, and
+		 tell the target to do whatever is necessary to follow
+		 either parent or child.  */
+	      if (follow_child)
+		{
+		  /* The thread that started the execution command
+		     won't exist in the child.  Abort the command and
+		     immediately stop in this thread, in the child,
+		     inside fork.  */
+		  should_resume = false;
+		}
+	      else
+		{
+		  /* Following the parent, so let the thread fork its
+		     child freely, it won't influence the current
+		     execution command.  */
+		  if (follow_fork_inferior (follow_child, detach_fork))
+		    {
+		      /* Target refused to follow, or there's some
+			 other reason we shouldn't resume.  */
+		      switch_to_thread (cur_thr);
+		      set_last_target_status_stopped (cur_thr);
+		      return false;
+		    }
+
+		  /* If we're following a vfork, when we need to leave
+		     the just-forked thread as selected, as we need to
+		     solo-resume it to collect the VFORK_DONE event.
+		     If we're following a fork, however, switch back
+		     to the original thread that we continue stepping
+		     it, etc.  */
+		  if (kind != TARGET_WAITKIND_VFORKED)
+		    {
+		      gdb_assert (kind == TARGET_WAITKIND_FORKED);
+		      switch_to_thread (cur_thr);
+		    }
+		}
+
 	      break;
 	    }
 	}
@@ -2200,6 +2243,29 @@ user_visible_resume_target (ptid_t resume_ptid)
 	  : current_inferior ()->process_target ());
 }
 
+/* Find a thread from the inferiors that we'll resume that is waiting
+   for a vfork-done event.  */
+
+static thread_info *
+find_thread_waiting_for_vfork_done ()
+{
+  gdb_assert (!target_is_non_stop_p ());
+
+  if (sched_multi)
+    {
+      for (inferior *inf : all_non_exited_inferiors ())
+	if (inf->thread_waiting_for_vfork_done != nullptr)
+	  return inf->thread_waiting_for_vfork_done;
+    }
+  else
+    {
+      inferior *cur_inf = current_inferior ();
+      if (cur_inf->thread_waiting_for_vfork_done != nullptr)
+	return cur_inf->thread_waiting_for_vfork_done;
+    }
+  return nullptr;
+}
+
 /* Return a ptid representing the set of threads that we will resume,
    in the perspective of the target, assuming run control handling
    does not require leaving some threads stopped (e.g., stepping past
@@ -2240,14 +2306,18 @@ internal_resume_ptid (int user_step)
      Since we don't have that flexibility (we can only pass one ptid), just
      resume the first thread waiting for a vfork-done event we find (e.g. thread
      2.1).  */
-  if (sched_multi)
-    {
-      for (inferior *inf : all_non_exited_inferiors ())
-	if (inf->thread_waiting_for_vfork_done != nullptr)
-	  return inf->thread_waiting_for_vfork_done->ptid;
+  thread_info *thr = find_thread_waiting_for_vfork_done ();
+  if (thr != nullptr)
+    {
+      /* If we have a thread that is waiting for a vfork-done event,
+	 then we should have switched to it earlier.  Calling
+	 target_resume with thread scope is only possible when the
+	 current thread matches the thread scope.  */
+      gdb_assert (thr->ptid == inferior_ptid);
+      gdb_assert (thr->inf->process_target ()
+		  == inferior_thread ()->inf->process_target ());
+      return thr->ptid;
     }
-  else if (current_inferior ()->thread_waiting_for_vfork_done != nullptr)
-    return current_inferior ()->thread_waiting_for_vfork_done->ptid;
 
   return user_visible_resume_ptid (user_step);
 }
diff --git a/gdb/testsuite/gdb.threads/foll-fork-other-thread.c b/gdb/testsuite/gdb.threads/foll-fork-other-thread.c
new file mode 100644
index 00000000000..47e8b1c7389
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/foll-fork-other-thread.c
@@ -0,0 +1,84 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2022 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <assert.h>
+#include <limits.h>
+
+/* Set by GDB.  */
+volatile int stop_looping = 0;
+
+static void *
+gdb_forker_thread (void *arg)
+{
+  int ret;
+  int stat;
+  pid_t pid = FORK_FUNC ();
+
+  if (pid == 0)
+    _exit (0);
+
+  assert (pid > 0);
+
+  /* Wait for child to exit.  */
+  do
+    {
+      ret = waitpid (pid, &stat, 0);
+    }
+  while (ret == -1 && errno == EINTR);
+
+  assert (ret == pid);
+  assert (WIFEXITED (stat));
+  assert (WEXITSTATUS (stat) == 0);
+
+  stop_looping = 1;
+
+  return NULL;
+}
+
+static void
+sleep_a_bit (void)
+{
+  usleep (1000 * 50);
+}
+
+int
+main (void)
+{
+  int i;
+  int ret;
+  pthread_t thread;
+
+  alarm (60);
+
+  ret = pthread_create (&thread, NULL, gdb_forker_thread, NULL);
+  assert (ret == 0);
+
+  while (!stop_looping)  /* while loop */
+    {
+      sleep_a_bit ();    /* break here */
+      sleep_a_bit ();    /* other line */
+    }
+
+  pthread_join (thread, NULL);
+
+  return 0; /* exiting here */
+}
diff --git a/gdb/testsuite/gdb.threads/foll-fork-other-thread.exp b/gdb/testsuite/gdb.threads/foll-fork-other-thread.exp
new file mode 100644
index 00000000000..ed62bfc54d9
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/foll-fork-other-thread.exp
@@ -0,0 +1,172 @@
+# Copyright 2022 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Test catching a vfork/fork in one thread, and then doing a "next" in
+# another thread, in different combinations of "set follow-fork
+# parent/child", and other execution modes.
+
+standard_testfile
+
+# Line where to stop the main thread.
+set break_here_line [gdb_get_line_number "break here"]
+
+# Build executables, one for each fork flavor.
+foreach_with_prefix fork_func {fork vfork} {
+    set opts [list debug pthreads additional_flags=-DFORK_FUNC=${fork_func}]
+    if { [build_executable "failed to prepare" \
+	    ${testfile}-${fork_func} ${srcfile} $opts] } {
+	return
+    }
+}
+
+# Run the test with the given parameters:
+#
+#   - FORK_FUNC: fork flavor, "fork" or "vfork".
+#   - FOLLOW: "set follow-fork" value, either "parent" or "child".
+#   - TARGET-NON-STOP: "maintenance set target-non-stop" value, "auto", "on" or
+#     "off".
+#   - NON-STOP: "set non-stop" value, "on" or "off".
+#   - DISPLACED-STEPPING: "set displaced-stepping" value, "auto", "on" or "off".
+
+proc do_test { fork_func follow target-non-stop non-stop displaced-stepping } {
+    save_vars { ::GDBFLAGS } {
+	append ::GDBFLAGS " -ex \"maintenance set target-non-stop ${target-non-stop}\""
+	append ::GDBFLAGS " -ex \"set non-stop ${non-stop}\""
+	clean_restart ${::binfile}-${fork_func}
+    }
+
+    gdb_test_no_output "set displaced-stepping ${displaced-stepping}"
+
+    if { ![runto_main] } {
+	return
+    }
+
+    delete_breakpoints
+
+    gdb_test "catch $fork_func" "Catchpoint .*"
+
+    # Verify that the catchpoint is mentioned in an "info breakpoints",
+    # and further that the catchpoint mentions no process id.
+    gdb_test "info breakpoints" \
+	".*catchpoint.*keep y.*fork\[\r\n\]+" \
+	"info breakpoints before fork"
+
+    gdb_test "continue" \
+	"Catchpoint \[0-9\]* \\(.?forked process \[0-9\]*\\),.*" \
+	"explicit child follow, catch fork"
+
+    # Verify that the catchpoint is mentioned in an "info breakpoints",
+    # and further that the catchpoint managed to capture a process id.
+    gdb_test "info breakpoints" \
+	".*catchpoint.*keep y.*fork, process.*" \
+	"info breakpoints after fork"
+
+    gdb_test "thread 1" "Switching to .*"
+
+    gdb_test_no_output "set scheduler-locking on"
+
+    # Advance the next-ing thread to the point where we'll execute the
+    # next.
+    gdb_test "break $::srcfile:$::break_here_line" "Breakpoint $::decimal at $::hex.*"
+    gdb_test "continue" "hit Breakpoint $::decimal, main.*"
+
+    # Disable schedlock and step.  The pending fork should no longer
+    # be pending afterwards.
+
+    gdb_test "set scheduler-locking off"
+
+    # Make sure GDB doesn't try to step over the breakpoint at PC
+    # first, we want to make sure that GDB doesn't lose focus of the
+    # step/next in this thread.  A breakpoint would make GDB switch
+    # focus anyhow, thus hide a potential bug.
+    delete_breakpoints
+
+    gdb_test_no_output "set follow-fork $follow"
+
+    set any "\[^\r\n\]*"
+
+    if {$follow == "child"} {
+
+	# For fork, GDB detaches from the parent at follow-fork time.
+	# For vfork, GDB detaches from the parent at child exit/exec
+	# time.
+	if {$fork_func == "fork"} {
+	    set detach_parent \
+		[multi_line \
+		     "\\\[Detaching after $fork_func from parent process $any\\\]" \
+		     "\\\[Inferior 1 $any detached\\\]"]
+	} else {
+	    set detach_parent ""
+	}
+
+	gdb_test "next" \
+	    [multi_line \
+		 "\\\[Attaching after $any $fork_func to child $any\\\]" \
+		 "\\\[New inferior 2 $any\\\]" \
+		 "$detach_parent.*warning: Not resuming: switched threads before following fork child\\." \
+		 "\\\[Switching to $any\\\]" \
+		 ".*"] \
+	    "next aborts resumption"
+
+	# The child should be stopped inside the fork implementation
+	# in the runtime.  Exactly at which instruction/function is
+	# system dependent, but we can check that our
+	# "gdb_forker_thread" function appears in the backtrace.
+	gdb_test "bt" " in gdb_forker_thread ${any} at ${any}${::srcfile}:.*"
+
+	# The child is now thread 1.
+	gdb_test "print \$_thread" " = 1"
+
+	if {$fork_func == "fork"} {
+	    gdb_test "continue" \
+		[multi_line \
+		     "Continuing." \
+		     "\\\[Inferior 2 \\\(process $any\\\) exited normally\\\]"] \
+		"continue to exit"
+	} else {
+	    gdb_test "continue" \
+		[multi_line \
+		     "Continuing." \
+		     "\\\[Detaching vfork parent process $any after child exit\\\]" \
+		     "\\\[Inferior 1 \\\(process $any\\\) detached\\\]" \
+		     "\\\[Inferior 2 \\\(process $any\\\) exited normally\\\]"] \
+		"continue to exit"
+	}
+    } else {
+	gdb_test "next" \
+	    "\\\[Detaching after $fork_func from child process ${any}\\\].* other line .*" \
+	    "next to other line"
+
+	gdb_test "print \$_thread" " = 1"
+
+	gdb_test "continue" \
+	    [multi_line \
+		 "Continuing." \
+		 "\\\[Inferior 1 \\\(process $any\\\) exited normally\\\]"] \
+	    "continue to exit"
+    }
+}
+
+foreach_with_prefix fork_func {fork vfork} {
+    foreach_with_prefix follow {child} {
+	foreach_with_prefix target-non-stop {auto on off} {
+	    foreach_with_prefix non-stop {off} {
+		foreach_with_prefix displaced-stepping {auto on off} {
+		    do_test ${fork_func} ${follow} ${target-non-stop} ${non-stop} ${displaced-stepping}
+		}
+	    }
+	}
+    }
+}
