diff --git a/gdb/infrun.c b/gdb/infrun.c
index ad7892105a4..bfc909415c5 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -5328,7 +5328,8 @@ finish_step_over (struct execution_control_state *ecs)
       /* If we're stepping over a breakpoint with all threads locked,
 	 then only the thread that was stepped should be reporting
 	 back an event.  */
-      gdb_assert (ecs->event_thread->control.trap_expected);
+      gdb_assert (ecs->event_thread->control.trap_expected
+		  || target_can_randomly_thread_switch ());
 
       clear_step_over_info ();
     }
@@ -5567,14 +5568,38 @@ handle_signal_stop (struct execution_control_state *ecs)
 	{
 	  if (single_step_breakpoint_inserted_here_p (aspace, pc))
 	    {
-	      if (debug_infrun)
+	      if (target_can_randomly_thread_switch ())
 		{
-		  fprintf_unfiltered (gdb_stdlog,
-				      "infrun: [%s] hit another thread's "
-				      "single-step breakpoint\n",
-				      target_pid_to_str (ecs->ptid).c_str ());
+		  if (debug_infrun)
+		    fprintf_unfiltered (gdb_stdlog,
+					"infrun: [%s] random thread switch\n",
+					(target_pid_to_str
+					 (ecs->ptid).c_str ()));
+
+		  /* Here the step was intended for some other thread;
+		     but instead we saw a context switch.  This can
+		     happen with "green" threads in Ravenscar, and the
+		     recourse is to pretend we wanted to step the new
+		     thread and got an ordinary stop there.  */
+		  for (thread_info *th : all_threads ())
+		    if (th->control.trap_expected)
+		      {
+			std::swap (th->control, ecs->event_thread->control);
+			break;
+		      }
+		}
+	      else
+		{
+		  if (debug_infrun)
+		    {
+		      fprintf_unfiltered (gdb_stdlog,
+					  "infrun: [%s] hit another thread's "
+					  "single-step breakpoint\n",
+					  (target_pid_to_str
+					   (ecs->ptid).c_str ()));
+		    }
+		  ecs->hit_singlestep_breakpoint = 1;
 		}
-	      ecs->hit_singlestep_breakpoint = 1;
 	    }
 	}
       else
diff --git a/gdb/ravenscar-thread.c b/gdb/ravenscar-thread.c
index f36ee3ea240..4e35026e6e2 100644
--- a/gdb/ravenscar-thread.c
+++ b/gdb/ravenscar-thread.c
@@ -89,6 +89,11 @@ struct ravenscar_thread_target final : public target_ops
   ptid_t wait (ptid_t, struct target_waitstatus *, int) override;
   void resume (ptid_t, int, enum gdb_signal) override;
 
+  bool can_randomly_thread_switch () override
+  {
+    return true;
+  }
+
   void fetch_registers (struct regcache *, int) override;
   void store_registers (struct regcache *, int) override;
 
diff --git a/gdb/target-delegates.c b/gdb/target-delegates.c
index 3654f02e63b..8c0d135d010 100644
--- a/gdb/target-delegates.c
+++ b/gdb/target-delegates.c
@@ -19,6 +19,7 @@ struct dummy_target : public target_ops
   void fetch_registers (struct regcache *arg0, int arg1) override;
   void store_registers (struct regcache *arg0, int arg1) override;
   void prepare_to_store (struct regcache *arg0) override;
+  bool can_randomly_thread_switch () override;
   void files_info () override;
   int insert_breakpoint (struct gdbarch *arg0, struct bp_target_info *arg1) override;
   int remove_breakpoint (struct gdbarch *arg0, struct bp_target_info *arg1, enum remove_bp_reason arg2) override;
@@ -186,6 +187,7 @@ struct debug_target : public target_ops
   void fetch_registers (struct regcache *arg0, int arg1) override;
   void store_registers (struct regcache *arg0, int arg1) override;
   void prepare_to_store (struct regcache *arg0) override;
+  bool can_randomly_thread_switch () override;
   void files_info () override;
   int insert_breakpoint (struct gdbarch *arg0, struct bp_target_info *arg1) override;
   int remove_breakpoint (struct gdbarch *arg0, struct bp_target_info *arg1, enum remove_bp_reason arg2) override;
@@ -551,6 +553,31 @@ debug_target::prepare_to_store (struct regcache *arg0)
   fputs_unfiltered (")\n", gdb_stdlog);
 }
 
+bool
+target_ops::can_randomly_thread_switch ()
+{
+  return this->beneath ()->can_randomly_thread_switch ();
+}
+
+bool
+dummy_target::can_randomly_thread_switch ()
+{
+  return false;
+}
+
+bool
+debug_target::can_randomly_thread_switch ()
+{
+  bool result;
+  fprintf_unfiltered (gdb_stdlog, "-> %s->can_randomly_thread_switch (...)\n", this->beneath ()->shortname ());
+  result = this->beneath ()->can_randomly_thread_switch ();
+  fprintf_unfiltered (gdb_stdlog, "<- %s->can_randomly_thread_switch (", this->beneath ()->shortname ());
+  fputs_unfiltered (") = ", gdb_stdlog);
+  target_debug_print_bool (result);
+  fputs_unfiltered ("\n", gdb_stdlog);
+  return result;
+}
+
 void
 target_ops::files_info ()
 {
diff --git a/gdb/target.h b/gdb/target.h
index 4f7a43e1a8b..ad3a8be4c18 100644
--- a/gdb/target.h
+++ b/gdb/target.h
@@ -491,6 +491,13 @@ struct target_ops
     virtual void prepare_to_store (struct regcache *)
       TARGET_DEFAULT_NORETURN (noprocess ());
 
+    /* True if single-stepping on this target can cause an
+       un-asked-for thread switch.  This is normal on "green" thread
+       targets, like Ravenscar, when single-stepping through the
+       context-switching code.  */
+    virtual bool can_randomly_thread_switch ()
+      TARGET_DEFAULT_RETURN (false);
+
     virtual void files_info ()
       TARGET_DEFAULT_IGNORE ();
     virtual int insert_breakpoint (struct gdbarch *,
@@ -1402,6 +1409,11 @@ extern void target_resume (ptid_t ptid, int step, enum gdb_signal signal);
    coalesce multiple resumption requests in a single vCont packet.  */
 extern void target_commit_resume ();
 
+/* A wrapper for target_ops::can_randomly_thread_switch.  */
+
+#define target_can_randomly_thread_switch()			\
+  (current_top_target ()->can_randomly_thread_switch) ()
+
 /* Setup to defer target_commit_resume calls, and reactivate
    target_commit_resume on destruction, if it was previously
    active.  */
