diff --git a/gdb/Makefile.in b/gdb/Makefile.in
index 195f3a2e2d1..bc6156d247f 100644
--- a/gdb/Makefile.in
+++ b/gdb/Makefile.in
@@ -1028,6 +1028,7 @@ COMMON_SFILES = \
 	break-catch-sig.c \
 	break-catch-syscall.c \
 	break-catch-throw.c \
+	break-cond-parse.c \
 	breakpoint.c \
 	bt-utils.c \
 	btrace.c \
@@ -1296,6 +1297,7 @@ HFILES_NO_SRCDIR = \
 	bfd-target.h \
 	bfin-tdep.h \
 	block.h \
+	break-cond-parse.h \
 	breakpoint.h \
 	bsd-kvm.h \
 	bsd-uthread.h \
diff --git a/gdb/NEWS b/gdb/NEWS
index 4358494a6b6..6f27f7b20c2 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -9,6 +9,10 @@
 * GDB index now contains information about the main function.  This speeds up
   startup when it is being used for some large binaries.
 
+* For breakpoints that are created in the 'pending' state, any
+  'thread' or 'task' keywords are parsed at the time the breakpoint is
+  created, rather than at the time the breakpoint becomes non-pending.
+
 * Changed commands
 
 disassemble
diff --git a/gdb/break-cond-parse.c b/gdb/break-cond-parse.c
new file mode 100644
index 00000000000..eff738f2a7c
--- /dev/null
+++ b/gdb/break-cond-parse.c
@@ -0,0 +1,701 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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 "defs.h"
+#include "gdbsupport/gdb_assert.h"
+#include "gdbsupport/selftest.h"
+#include "test-target.h"
+#include "scoped-mock-context.h"
+#include "break-cond-parse.h"
+#include "tid-parse.h"
+#include "ada-lang.h"
+
+/* When parsing tokens from a string, which direction are we parsing?
+
+   Given the following string and pointer 'ptr':
+
+	ABC DEF GHI JKL
+	       ^
+	       ptr
+
+   Parsing 'forward' will return the token 'GHI' and update 'ptr' to point
+   between GHI and JKL.  Parsing 'backward' will return the token 'DEF' and
+   update 'ptr' to point between ABC and DEF.
+*/
+
+enum class parse_direction
+{
+  /* Parse the next token forwards.  */
+  forward,
+
+  /* Parse the previous token backwards.  */
+  backward
+};
+
+/* Find the next token in DIRECTION from *CURR.  */
+
+static std::string_view
+find_next_token (const char **curr, parse_direction direction)
+{
+  const char *tok_start, *tok_end;
+
+  gdb_assert (**curr != '\0');
+
+  if (direction == parse_direction::forward)
+    {
+      *curr = skip_spaces (*curr);
+      tok_start = *curr;
+      *curr = skip_to_space (*curr);
+      tok_end = *curr - 1;
+    }
+  else
+    {
+      gdb_assert (direction == parse_direction::backward);
+
+      while (isspace (**curr))
+	--(*curr);
+
+      tok_end = *curr;
+
+      while (!isspace (**curr))
+	--(*curr);
+
+      tok_start = (*curr) + 1;
+    }
+
+  return std::string_view (tok_start, tok_end - tok_start + 1);
+}
+
+/* A class that represents a complete parsed token.  Each token has a type
+   and a std::string_view into the original breakpoint condition string.  */
+
+struct token
+{
+  /* The types a token might take.  */
+  enum class type
+  {
+    /* These are the token types for the 'if', 'thread', 'inferior', and
+       'task' keywords.  The m_content for these token types is the value
+       passed to the keyword, not the keyword itself.  */
+    CONDITION,
+    THREAD,
+    INFERIOR,
+    TASK,
+
+    /* This is the token used when we find unknown content, the m_content
+       for this token is the rest of the input string.  */
+    REST,
+
+    /* This is the token for the -force-condition token, the m_content for
+       this token contains the keyword itself.  */
+    FORCE
+  };
+
+  token (enum type type, std::string_view content)
+    : m_type (type),
+      m_content (std::move (content))
+  {
+    /* Nothing.  */
+  }
+
+  /* Return a string representing this token.  Only used for debug.  */
+  std::string to_string () const
+  {
+    switch (m_type)
+      {
+      case type::CONDITION:
+	return string_printf ("{ CONDITION: \"%s\" }",
+			      std::string (m_content).c_str ());
+      case type::THREAD:
+	return string_printf ("{ THREAD: \"%s\" }",
+			      std::string (m_content).c_str ());
+      case type::INFERIOR:
+	return string_printf ("{ INFERIOR: \"%s\" }",
+			      std::string (m_content).c_str ());
+      case type::TASK:
+	return string_printf ("{ TASK: \"%s\" }",
+			      std::string (m_content).c_str ());
+      case type::REST:
+	return string_printf ("{ REST: \"%s\" }",
+			      std::string (m_content).c_str ());
+      case type::FORCE:
+	return string_printf ("{ FORCE }");
+      default:
+	return "** unknown **";
+      }
+  }
+
+  /* The type of this token.  */
+  const type &get_type () const
+  {
+    return m_type;
+  }
+
+  /* Return the value of this token.  */
+  const std::string_view &get_value () const
+  {
+    gdb_assert (m_content.size () > 0);
+    return m_content;
+  }
+
+  /* Extend this token with the contents of OTHER.  This only makes sense
+     if OTHER is the next token after this one in the original string,
+     however, enforcing that restriction is left to the caller of this
+     function.
+
+     When OTHER is a keyword/value token, e.g. 'thread 1', the m_content
+     for OTHER will only point to the '1'.  However, as the m_content is a
+     std::string_view, then when we merge the m_content of OTHER into this
+     token we automatically merge in the 'thread' part too, as it
+     naturally sits between this token and OTHER.  */
+
+  void
+  extend (const token &other)
+  {
+    m_content = std::string_view (this->m_content.data (),
+				  (other.m_content.data ()
+				   - this->m_content.data ()
+				   + other.m_content.size ()));
+  }
+
+private:
+  /* The type of this token.  */
+  type m_type;
+
+  /* The important content part of this token.  The extend member function
+     depends on this being a std::string_view.  */
+  std::string_view m_content;
+};
+
+/* Split STR, a breakpoint condition string, into a vector of tokens where
+   each token represents a component of the condition.  Tokens are first
+   parsed from the front of STR until we encounter an 'if' token.  At this
+   point tokens are parsed from the end of STR until we encounter an
+   unknown token, which we assume is the other end of the 'if' condition.
+   If when scanning forward we encounter an unknown token then the
+   remainder of STR is placed into a 'rest' token (the rest of the
+   string), and no backward scan is performed.  */
+
+static std::vector<token>
+parse_all_tokens (const char *str)
+{
+  gdb_assert (str != nullptr);
+
+  std::vector<token> forward_results;
+  std::vector<token> backward_results;
+
+  const char *cond_start = nullptr;
+  const char *cond_end = nullptr;
+  parse_direction direction = parse_direction::forward;
+  std::vector<token> *curr_results = &forward_results;
+  while (*str != '\0')
+    {
+      /* Find the next token.  If moving backward and this token starts at
+	 the same location as the condition then we must have found the
+	 other end of the condition string -- we're done.  */
+      std::string_view t = find_next_token (&str, direction);
+      if (direction == parse_direction::backward && t.data () <= cond_start)
+	{
+	  cond_end = &t.back ();
+	  break;
+	}
+
+      /* We only have a single flag option to check for.  All the other
+	 options take a value so require an additional token to be found.
+	 Additionally, we require that this flag be at least '-f', we
+	 don't allow it to be abbreviated to '-'.  */
+      if (t.length () > 1 && startswith ("-force-condition", t))
+	{
+	  curr_results->emplace_back (token::type::FORCE, t);
+	  continue;
+	}
+
+      /* Maybe the first token was the last token in the string.  If this
+	 is the case then we definitely can't try to extract a value
+	 token.  This also means that the token T is meaningless.  Reset
+	 TOK to point at the start of the unknown content and break out of
+	 the loop.  We'll record the unknown part of the string outside of
+	 the scanning loop (below).  */
+      if (direction == parse_direction::forward && *str == '\0')
+	{
+	  str = t.data ();
+	  break;
+	}
+
+      /* As before, find the next token and, if we are scanning backwards,
+	 check that we have not reached the start of the condition string.  */
+      std::string_view v = find_next_token (&str, direction);
+      if (direction == parse_direction::backward && v.data () <= cond_start)
+	{
+	  /* Use token T here as that must also be part of the condition
+	     string.  */
+	  cond_end = &t.back ();
+	  break;
+	}
+
+      /* When moving backward we will first parse the value token then the
+	 keyword token, so swap them now.  */
+      if (direction == parse_direction::backward)
+	std::swap (t, v);
+
+      /* Check for valid option in token T.  If we find a valid option then
+	 parse the value from the token V.  Except for 'if', that's handled
+	 differently.
+
+	 For the 'if' token we need to capture the entire condition
+	 string, so record the start of the condition string and then
+	 start scanning backwards looking for the end of the condition
+	 string.
+
+	 The order of these checks is important, at least the check for
+	 'thread' must occur before the check for 'task'.  We accept
+	 abbreviations of these token names, and 't' should resolve to
+	 'thread', which will only happen if we check 'thread' first.  */
+      if (direction == parse_direction::forward && startswith ("if", t))
+	{
+	  cond_start = v.data ();
+	  str = str + strlen (str);
+	  gdb_assert (*str == '\0');
+	  --str;
+	  direction = parse_direction::backward;
+	  curr_results = &backward_results;
+	  continue;
+	}
+      else if (startswith ("thread", t))
+	curr_results->emplace_back (token::type::THREAD, v);
+      else if (startswith ("inferior", t))
+	curr_results->emplace_back (token::type::INFERIOR, v);
+      else if (startswith ("task", t))
+	curr_results->emplace_back (token::type::TASK, v);
+      else
+	{
+	  /* An unknown token.  If we are scanning forward then reset TOK
+	     to point at the start of the unknown content, we record this
+	     outside of the scanning loop (below).
+
+	     If we are scanning backward then unknown content is assumed to
+	     be the other end of the condition string, obviously, this is
+	     just a heuristic, we could be looking at a mistyped command
+	     line, but this will be spotted when the condition is
+	     eventually evaluated.
+
+	     Either way, no more scanning is required after this.  */
+	  if (direction == parse_direction::forward)
+	    str = t.data ();
+	  else
+	    {
+	      gdb_assert (direction == parse_direction::backward);
+	      cond_end = &v.back ();
+	    }
+	  break;
+	}
+    }
+
+  if (cond_start != nullptr)
+    {
+      /* If we found the start of a condition string then we should have
+	 switched to backward scan mode, and found the end of the condition
+	 string.  Capture the whole condition string into COND_STRING
+	 now.  */
+      gdb_assert (direction == parse_direction::backward);
+      gdb_assert (cond_end != nullptr);
+
+      std::string_view v (cond_start, cond_end - cond_start + 1);
+
+      forward_results.emplace_back (token::type::CONDITION, v);
+    }
+  else if (*str != '\0')
+    {
+      /* If we didn't have a condition start pointer then we should still
+	 be in forward scanning mode.  If we didn't reach the end of the
+	 input string (TOK is not at the null character) then the rest of
+	 the input string is garbage that we didn't understand.
+
+	 Record the unknown content into REST.  The caller of this function
+	 will report this as an error later on.  We could report the error
+	 here, but we prefer to allow the caller to run other checks, and
+	 prioritise other errors before reporting this problem.  */
+      gdb_assert (direction == parse_direction::forward);
+      gdb_assert (cond_end == nullptr);
+
+      std::string_view v (str, strlen (str));
+
+      forward_results.emplace_back (token::type::REST, v);
+    }
+
+  /* If we have tokens in the BACKWARD_RESULTS vector then this means that
+     we found an 'if' condition (which will be the last thing in the
+     FORWARD_RESULTS vector), and then we started a backward scan.
+
+     The last tokens from the input string (those after the 'if' condition)
+     will be the first tokens added to the BACKWARD_RESULTS vector, so the
+     last items in the BACKWARD_RESULTS vector are those next to the 'if'
+     condition.
+
+     Check the tokens in the BACKWARD_RESULTS vector from back to front.
+     If the tokens look invalid then we assume that they are actually part
+     of the 'if' condition, and merge the token with the 'if' condition.
+     If it turns out that this was incorrect and that instead the user just
+     messed up entering the token value, then this will show as an error
+     when parsing the 'if' condition.
+
+     Doing this allows us to handle things like:
+
+       break function if ( variable == thread )
+
+     Where 'thread' is a local variable within 'function'.  When parsing
+     this we will initially see 'thread )' as a thread token with ')' as
+     the value.  However, the following code will spot that ')' is not a
+     valid thread-id, and so we merge 'thread )' into the 'if' condition
+     string.
+
+     This code also handles the special treatment for '-force-condition',
+     which exists for backwards compatibility reasons.  Traditionally this
+     flag, if it occurred immediately after the 'if' condition, would be
+     treated as part of the 'if' condition.  When the breakpoint condition
+     parsing code was rewritten, this behaviour was retained.  */
+  gdb_assert (backward_results.empty ()
+	      || (forward_results.back ().get_type ()
+		  == token::type::CONDITION));
+  while (!backward_results.empty ())
+    {
+      token &t = backward_results.back ();
+
+      if (t.get_type () == token::type::FORCE)
+	forward_results.back ().extend (std::move (t));
+      else if (t.get_type () == token::type::THREAD)
+	{
+	  const char *end;
+	  std::string v (t.get_value ());
+	  if (is_thread_id (v.c_str (), &end) && *end == '\0')
+	    break;
+	  forward_results.back ().extend (std::move (t));
+	}
+      else if (t.get_type () == token::type::INFERIOR
+	       || t.get_type () == token::type::TASK)
+	{
+	  /* Place the token's value into a null-terminated string, parse
+	     the string as a number and check that the entire string was
+	     parsed.  If this is true then this looks like a valid inferior
+	     or task number, otherwise, assume an invalid id, and merge
+	     this token with the 'if' token.  */
+	  char *end;
+	  std::string v (t.get_value ());
+	  (void) strtol (v.c_str (), &end, 0);
+	  if (end > v.c_str () && *end == '\0')
+	    break;
+	  forward_results.back ().extend (std::move (t));
+	}
+      else
+	gdb_assert_not_reached ("unexpected token type");
+
+      /* If we found an actual valid token above then we will have broken
+	 out of the loop.  We only get here if the token was merged with
+	 the 'if' condition, in which case we can discard the last token
+	 and then check the token before that.  */
+      backward_results.pop_back ();
+    }
+
+  /* If after the above checks we still have some tokens in the
+     BACKWARD_RESULTS vector, then these need to be appended to the
+     FORWARD_RESULTS vector.  However, we first reverse the order so that
+     FORWARD_RESULTS retains the tokens in the order they appeared in the
+     input string.  */
+  if (!backward_results.empty ())
+    forward_results.insert (forward_results.end (),
+			    backward_results.rbegin (),
+			    backward_results.rend ());
+
+  return forward_results;
+}
+
+/* Called when the global debug_breakpoint is true.  Prints VEC to the
+   debug output stream.  */
+
+static void
+dump_condition_tokens (const std::vector<token> &vec)
+{
+  gdb_assert (debug_breakpoint);
+
+  bool first = true;
+  std::string str = "Tokens: ";
+  for (const token &t : vec)
+    {
+      if (!first)
+	str += " ";
+      first = false;
+      str += t.to_string ();
+    }
+  breakpoint_debug_printf ("%s", str.c_str ());
+}
+
+/* See break-cond-parse.h.  */
+
+void
+create_breakpoint_parse_arg_string
+  (const char *str, gdb::unique_xmalloc_ptr<char> *cond_string_ptr,
+   int *thread_ptr, int *inferior_ptr, int *task_ptr,
+   gdb::unique_xmalloc_ptr<char> *rest_ptr, bool *force_ptr)
+{
+  /* Set up the defaults.  */
+  cond_string_ptr->reset ();
+  rest_ptr->reset ();
+  *thread_ptr = -1;
+  *inferior_ptr = -1;
+  *task_ptr = -1;
+  *force_ptr = false;
+
+  if (str == nullptr)
+    return;
+
+  /* Split STR into a series of tokens.  */
+  std::vector<token> tokens = parse_all_tokens (str);
+  if (debug_breakpoint)
+    dump_condition_tokens (tokens);
+
+  /* Temporary variables.  Initialised to the default state, then updated
+     as we parse TOKENS.  If all of TOKENS is parsed successfully then the
+     state from these variables is copied into the output arguments before
+     the function returns.  */
+  int thread = -1, inferior = -1, task = -1;
+  bool force = false;
+  gdb::unique_xmalloc_ptr<char> cond_string, rest;
+
+  for (const token &t : tokens)
+    {
+      switch (t.get_type ())
+	{
+	case token::type::FORCE:
+	  force = true;
+	  break;
+	case token::type::THREAD:
+	  {
+	    if (thread != -1)
+	      error ("You can specify only one thread.");
+	    if (task != -1 || inferior != -1)
+	      error ("You can specify only one of thread, inferior, or task.");
+	    const char *tmptok;
+	    thread_info *thr
+	      = parse_thread_id (std::string (t.get_value ()).c_str (),
+				 &tmptok);
+	    gdb_assert (*tmptok == '\0');
+	    thread = thr->global_num;
+	  }
+	  break;
+	case token::type::INFERIOR:
+	  {
+	    if (inferior != -1)
+	      error ("You can specify only one inferior.");
+	    if (task != -1 || thread != -1)
+	      error ("You can specify only one of thread, inferior, or task.");
+	    char *tmptok;
+	    long inferior_id
+	      = strtol (std::string (t.get_value ()).c_str (), &tmptok, 0);
+	    if (*tmptok != '\0')
+	      error (_("Junk '%s' after inferior keyword."), tmptok);
+	    if (inferior_id > INT_MAX)
+	      error (_("No inferior number '%ld'"), inferior_id);
+	    inferior = static_cast<int> (inferior_id);
+	    struct inferior *inf = find_inferior_id (inferior);
+	    if (inf == nullptr)
+	      error (_("No inferior number '%d'"), inferior);
+	  }
+	  break;
+	case token::type::TASK:
+	  {
+	    if (task != -1)
+	      error ("You can specify only one task.");
+	    if (inferior != -1 || thread != -1)
+	      error ("You can specify only one of thread, inferior, or task.");
+	    char *tmptok;
+	    long task_id
+	      = strtol (std::string (t.get_value ()).c_str (), &tmptok, 0);
+	    if (*tmptok != '\0')
+	      error (_("Junk '%s' after task keyword."), tmptok);
+	    if (task_id > INT_MAX)
+	      error (_("Unknown task %ld"), task_id);
+	    task = static_cast<int> (task_id);
+	    if (!valid_task_id (task))
+	      error (_("Unknown task %d."), task);
+	  }
+	  break;
+	case token::type::CONDITION:
+	  cond_string.reset (savestring (t.get_value ().data (),
+					 t.get_value ().size ()));
+	  break;
+	case token::type::REST:
+	  rest.reset (savestring (t.get_value ().data (),
+				  t.get_value ().size ()));
+	  break;
+	}
+    }
+
+  /* Move results into the output locations.  */
+  *force_ptr = force;
+  *thread_ptr = thread;
+  *inferior_ptr = inferior;
+  *task_ptr = task;
+  rest_ptr->reset (rest.release ());
+  cond_string_ptr->reset (cond_string.release ());
+}
+
+#if GDB_SELF_TEST
+
+namespace selftests {
+
+/* Run a single test of the create_breakpoint_parse_arg_string function.
+   INPUT is passed to create_breakpoint_parse_arg_string while all other
+   arguments are the expected output from
+   create_breakpoint_parse_arg_string.  */
+
+static void
+test (const char *input, const char *condition, int thread = -1,
+      int inferior = -1, int task = -1, bool force = false,
+      const char *rest = nullptr, const char *error_msg = nullptr)
+{
+  gdb::unique_xmalloc_ptr<char> extracted_condition;
+  gdb::unique_xmalloc_ptr<char> extracted_rest;
+  int extracted_thread, extracted_inferior, extracted_task;
+  bool extracted_force_condition;
+  std::string exception_msg, error_str;
+
+  if (error_msg != nullptr)
+    error_str = std::string (error_msg) + "\n";
+
+  try
+    {
+      create_breakpoint_parse_arg_string (input, &extracted_condition,
+					  &extracted_thread,
+					  &extracted_inferior,
+					  &extracted_task, &extracted_rest,
+					  &extracted_force_condition);
+    }
+  catch (const gdb_exception_error &ex)
+    {
+      string_file buf;
+
+      exception_print (&buf, ex);
+      exception_msg = buf.release ();
+    }
+
+  if ((condition == nullptr) != (extracted_condition.get () == nullptr)
+      || (condition != nullptr
+	  && strcmp (condition, extracted_condition.get ()) != 0)
+      || (rest == nullptr) != (extracted_rest.get () == nullptr)
+      || (rest != nullptr && strcmp (rest, extracted_rest.get ()) != 0)
+      || thread != extracted_thread
+      || inferior != extracted_inferior
+      || task != extracted_task
+      || force != extracted_force_condition
+      || exception_msg != error_str)
+    {
+      if (run_verbose ())
+	{
+	  debug_printf ("input: '%s'\n", input);
+	  debug_printf ("condition: '%s'\n", extracted_condition.get ());
+	  debug_printf ("rest: '%s'\n", extracted_rest.get ());
+	  debug_printf ("thread: %d\n", extracted_thread);
+	  debug_printf ("inferior: %d\n", extracted_inferior);
+	  debug_printf ("task: %d\n", extracted_task);
+	  debug_printf ("forced: %s\n",
+			extracted_force_condition ? "true" : "false");
+	  debug_printf ("exception: '%s'\n", exception_msg.c_str ());
+	}
+
+      /* Report the failure.  */
+      SELF_CHECK (false);
+    }
+}
+
+/* Wrapper for test function.  Pass through the default values for all
+   parameters, except the last parameter, which indicates that we expect
+   INPUT to trigger an error.  */
+
+static void
+test_error (const char *input, const char *error_msg)
+{
+  test (input, nullptr, -1, -1, -1, false, nullptr, error_msg);
+}
+
+/* Test the create_breakpoint_parse_arg_string function.  Just wraps
+   multiple calls to the test function above.  */
+
+static void
+create_breakpoint_parse_arg_string_tests ()
+{
+  gdbarch *arch = current_inferior ()->arch ();
+  scoped_restore_current_pspace_and_thread restore;
+  scoped_mock_context<test_target_ops> mock_target (arch);
+
+  int global_thread_num = mock_target.mock_thread.global_num;
+
+  /* Test parsing valid breakpoint condition strings.  */
+  test ("  if blah  ", "blah");
+  test (" if blah thread 1", "blah", global_thread_num);
+  test (" if blah inferior 1", "blah", -1, 1);
+  test (" if blah thread 1  ", "blah", global_thread_num);
+  test ("thread 1 woof", nullptr, global_thread_num, -1, -1, false, "woof");
+  test ("thread 1 X", nullptr, global_thread_num, -1, -1, false, "X");
+  test (" if blah thread 1 -force-condition", "blah", global_thread_num,
+	-1, -1, true);
+  test (" -force-condition if blah thread 1", "blah", global_thread_num,
+	-1, -1, true);
+  test (" -force-condition if blah thread 1  ", "blah", global_thread_num,
+	-1, -1, true);
+  test ("thread 1 -force-condition if blah", "blah", global_thread_num,
+	-1, -1, true);
+  test ("if (A::outer::func ())", "(A::outer::func ())");
+  test ("if ( foo == thread )", "( foo == thread )");
+  test ("if ( foo == thread ) inferior 1", "( foo == thread )", -1, 1);
+  test ("if ( foo == thread ) thread 1", "( foo == thread )",
+	global_thread_num);
+  test ("if foo == thread", "foo == thread");
+  test ("if foo == thread 1", "foo ==", global_thread_num);
+
+  /* Test parsing some invalid breakpoint condition strings.  */
+  test_error ("thread 1 if foo == 123 thread 1",
+	      "You can specify only one thread.");
+  test_error ("thread 1 if foo == 123 inferior 1",
+	      "You can specify only one of thread, inferior, or task.");
+  test_error ("thread 1 if foo == 123 task 1",
+	      "You can specify only one of thread, inferior, or task.");
+  test_error ("inferior 1 if foo == 123 inferior 1",
+	      "You can specify only one inferior.");
+  test_error ("inferior 1 if foo == 123 thread 1",
+	      "You can specify only one of thread, inferior, or task.");
+  test_error ("inferior 1 if foo == 123 task 1",
+	      "You can specify only one of thread, inferior, or task.");
+  test_error ("thread 1.2.3", "Invalid thread ID: 1.2.3");
+  test_error ("thread 1/2", "Invalid thread ID: 1/2");
+  test_error ("thread 1xxx", "Invalid thread ID: 1xxx");
+  test_error ("inferior 1xxx", "Junk 'xxx' after inferior keyword.");
+  test_error ("task 1xxx", "Junk 'xxx' after task keyword.");
+}
+
+} // namespace selftests
+#endif /* GDB_SELF_TEST */
+
+void _initialize_break_cond_parse ();
+void
+_initialize_break_cond_parse ()
+{
+#if GDB_SELF_TEST
+  selftests::register_test
+    ("create_breakpoint_parse_arg_string",
+     selftests::create_breakpoint_parse_arg_string_tests);
+#endif
+}
diff --git a/gdb/break-cond-parse.h b/gdb/break-cond-parse.h
new file mode 100644
index 00000000000..cbee70f4e9e
--- /dev/null
+++ b/gdb/break-cond-parse.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2023 Free Software Foundation, Inc.
+
+   This file is part of GDB.
+
+   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/>.  */
+
+#if !defined (BREAK_COND_PARSE_H)
+#define BREAK_COND_PARSE_H 1
+
+/* Given TOK, a string possibly containing a condition, thread, inferior,
+   task and force-condition flag, as accepted by the 'break' command,
+   extract the condition string, thread, inferior, task number, and the
+   force_condition flag, then set *COND_STRING, *THREAD, *INFERIOR, *TASK,
+   and *FORCE.
+
+   As TOK is parsed, if an unknown keyword is encountered before the 'if'
+   keyword then everything starting from the unknown keyword is placed into
+   *REST.
+
+   Both *COND and *REST are initialized to nullptr.  If no 'if' keyword is
+   found then *COND will be returned as nullptr.  If no unknown content is
+   found then *REST is returned as nullptr.
+
+   If no thread is found, *THREAD is set to -1.  If no inferior is found,
+   *INFERIOR is set to -1.  If no task is found, *TASK is set to -1.  If
+   the -force-condition flag is not found then *FORCE is set to false.
+
+   Due to the free-form nature that the string TOK might take (a 'thread'
+   keyword can appear before or after an 'if' condition) then we end up
+   having to check for keywords from both the start of TOK and the end of
+   TOK.
+
+   If TOK is nullptr, or TOK is the empty string, then the output variables
+   are all given their default values.  */
+
+extern void create_breakpoint_parse_arg_string
+  (const char *tok, gdb::unique_xmalloc_ptr<char> *cond_string,
+   int *thread, int *inferior, int *task,
+   gdb::unique_xmalloc_ptr<char> *rest, bool *force);
+
+#endif
diff --git a/gdb/breakpoint.c b/gdb/breakpoint.c
index 1fdeae731a0..4fe125996f0 100644
--- a/gdb/breakpoint.c
+++ b/gdb/breakpoint.c
@@ -70,6 +70,7 @@
 #include "cli/cli-style.h"
 #include "cli/cli-decode.h"
 #include <unordered_set>
+#include "break-cond-parse.h"
 
 /* readline include files */
 #include "readline/tilde.h"
@@ -6328,20 +6329,7 @@ print_breakpoint_location (const breakpoint *b, const bp_location *loc)
       uiout->field_stream ("at", stb);
     }
   else
-    {
-      uiout->field_string ("pending", b->locspec->to_string ());
-      /* If extra_string is available, it could be holding a condition
-	 or dprintf arguments.  In either case, make sure it is printed,
-	 too, but only for non-MI streams.  */
-      if (!uiout->is_mi_like_p () && b->extra_string != NULL)
-	{
-	  if (b->type == bp_dprintf)
-	    uiout->text (",");
-	  else
-	    uiout->text (" ");
-	  uiout->text (b->extra_string.get ());
-	}
-    }
+    uiout->field_string ("pending", b->locspec->to_string ());
 
   if (loc && is_breakpoint (b)
       && breakpoint_condition_evaluation_mode () == condition_evaluation_target
@@ -8706,8 +8694,8 @@ code_breakpoint::code_breakpoint (struct gdbarch *gdbarch_,
      command line, otherwise it's an error.  */
   if (type == bp_dprintf)
     update_dprintf_command_list (this);
-  else if (extra_string != nullptr)
-    error (_("Garbage '%s' at end of command"), extra_string.get ());
+  else
+    gdb_assert (extra_string == nullptr);
 
   /* The order of the locations is now stable.  Set the location
      condition using the location's number.  */
@@ -8936,197 +8924,6 @@ check_fast_tracepoint_sals (struct gdbarch *gdbarch,
     }
 }
 
-/* Given TOK, a string specification of condition and thread, as accepted
-   by the 'break' command, extract the condition string into *COND_STRING.
-   If no condition string is found then *COND_STRING is set to nullptr.
-
-   If the breakpoint specification has an associated thread, task, or
-   inferior, these are extracted into *THREAD, *TASK, and *INFERIOR
-   respectively, otherwise these arguments are set to -1 (for THREAD and
-   INFERIOR) or 0 (for TASK).
-
-   PC identifies the context at which the condition should be parsed.  */
-
-static void
-find_condition_and_thread (const char *tok, CORE_ADDR pc,
-			   gdb::unique_xmalloc_ptr<char> *cond_string,
-			   int *thread, int *inferior, int *task,
-			   gdb::unique_xmalloc_ptr<char> *rest)
-{
-  cond_string->reset ();
-  *thread = -1;
-  *inferior = -1;
-  *task = -1;
-  rest->reset ();
-  bool force = false;
-
-  while (tok && *tok)
-    {
-      const char *end_tok;
-      int toklen;
-      const char *cond_start = NULL;
-      const char *cond_end = NULL;
-
-      tok = skip_spaces (tok);
-
-      if ((*tok == '"' || *tok == ',') && rest)
-	{
-	  rest->reset (savestring (tok, strlen (tok)));
-	  break;
-	}
-
-      end_tok = skip_to_space (tok);
-
-      toklen = end_tok - tok;
-
-      if (toklen >= 1 && strncmp (tok, "if", toklen) == 0)
-	{
-	  tok = cond_start = end_tok + 1;
-	  try
-	    {
-	      parse_exp_1 (&tok, pc, block_for_pc (pc), 0);
-	    }
-	  catch (const gdb_exception_error &)
-	    {
-	      if (!force)
-		throw;
-	      else
-		tok = tok + strlen (tok);
-	    }
-	  cond_end = tok;
-	  cond_string->reset (savestring (cond_start, cond_end - cond_start));
-	}
-      else if (toklen >= 1 && strncmp (tok, "-force-condition", toklen) == 0)
-	{
-	  tok = tok + toklen;
-	  force = true;
-	}
-      else if (toklen >= 1 && strncmp (tok, "thread", toklen) == 0)
-	{
-	  const char *tmptok;
-	  struct thread_info *thr;
-
-	  if (*thread != -1)
-	    error(_("You can specify only one thread."));
-
-	  if (*task != -1)
-	    error (_("You can specify only one of thread or task."));
-
-	  if (*inferior != -1)
-	    error (_("You can specify only one of inferior or thread."));
-
-	  tok = end_tok + 1;
-	  thr = parse_thread_id (tok, &tmptok);
-	  if (tok == tmptok)
-	    error (_("Junk after thread keyword."));
-	  *thread = thr->global_num;
-	  tok = tmptok;
-	}
-      else if (toklen >= 1 && strncmp (tok, "inferior", toklen) == 0)
-	{
-	  if (*inferior != -1)
-	    error(_("You can specify only one inferior."));
-
-	  if (*task != -1)
-	    error (_("You can specify only one of inferior or task."));
-
-	  if (*thread != -1)
-	    error (_("You can specify only one of inferior or thread."));
-
-	  char *tmptok;
-	  tok = end_tok + 1;
-	  *inferior = strtol (tok, &tmptok, 0);
-	  if (tok == tmptok)
-	    error (_("Junk after inferior keyword."));
-	  if (!valid_global_inferior_id (*inferior))
-	    error (_("Unknown inferior number %d."), *inferior);
-	  tok = tmptok;
-	}
-      else if (toklen >= 1 && strncmp (tok, "task", toklen) == 0)
-	{
-	  char *tmptok;
-
-	  if (*task != -1)
-	    error(_("You can specify only one task."));
-
-	  if (*thread != -1)
-	    error (_("You can specify only one of thread or task."));
-
-	  if (*inferior != -1)
-	    error (_("You can specify only one of inferior or task."));
-
-	  tok = end_tok + 1;
-	  *task = strtol (tok, &tmptok, 0);
-	  if (tok == tmptok)
-	    error (_("Junk after task keyword."));
-	  if (!valid_task_id (*task))
-	    error (_("Unknown task %d."), *task);
-	  tok = tmptok;
-	}
-      else if (rest)
-	{
-	  rest->reset (savestring (tok, strlen (tok)));
-	  break;
-	}
-      else
-	error (_("Junk at end of arguments."));
-    }
-}
-
-/* Call 'find_condition_and_thread' for each sal in SALS until a parse
-   succeeds.  The parsed values are written to COND_STRING, THREAD,
-   TASK, and REST.  See the comment of 'find_condition_and_thread'
-   for the description of these parameters and INPUT.  */
-
-static void
-find_condition_and_thread_for_sals (const std::vector<symtab_and_line> &sals,
-				    const char *input,
-				    gdb::unique_xmalloc_ptr<char> *cond_string,
-				    int *thread, int *inferior, int *task,
-				    gdb::unique_xmalloc_ptr<char> *rest)
-{
-  int num_failures = 0;
-  for (auto &sal : sals)
-    {
-      gdb::unique_xmalloc_ptr<char> cond;
-      int thread_id = -1;
-      int inferior_id = -1;
-      int task_id = -1;
-      gdb::unique_xmalloc_ptr<char> remaining;
-
-      /* Here we want to parse 'arg' to separate condition from thread
-	 number.  But because parsing happens in a context and the
-	 contexts of sals might be different, try each until there is
-	 success.  Finding one successful parse is sufficient for our
-	 goal.  When setting the breakpoint we'll re-parse the
-	 condition in the context of each sal.  */
-      try
-	{
-	  find_condition_and_thread (input, sal.pc, &cond, &thread_id,
-				     &inferior_id, &task_id, &remaining);
-	  *cond_string = std::move (cond);
-	  /* A value of -1 indicates that these fields are unset.  At most
-	     one of these fields should be set (to a value other than -1)
-	     at this point.  */
-	  gdb_assert (((thread_id == -1 ? 1 : 0)
-		       + (task_id == -1 ? 1 : 0)
-		       + (inferior_id == -1 ? 1 : 0)) >= 2);
-	  *thread = thread_id;
-	  *inferior = inferior_id;
-	  *task = task_id;
-	  *rest = std::move (remaining);
-	  break;
-	}
-      catch (const gdb_exception_error &e)
-	{
-	  num_failures++;
-	  /* If no sal remains, do not continue.  */
-	  if (num_failures == sals.size ())
-	    throw;
-	}
-    }
-}
-
 /* Decode a static tracepoint marker spec.  */
 
 static std::vector<symtab_and_line>
@@ -9243,6 +9040,46 @@ create_breakpoint (struct gdbarch *gdbarch,
 	      ? (extra_string != nullptr && !parse_extra)
 	      : (extra_string == nullptr || parse_extra));
 
+  /* Will hold either copies of the similarly named function argument, or
+     will hold a modified version of the function argument, depending on
+     the value of PARSE_EXTRA.  */
+  gdb::unique_xmalloc_ptr<char> cond_string_copy;
+  gdb::unique_xmalloc_ptr<char> extra_string_copy;
+
+  if (parse_extra)
+    {
+      /* Parse EXTRA_STRING splitting the parts out.  */
+      create_breakpoint_parse_arg_string (extra_string, &cond_string_copy,
+					  &thread, &inferior, &task,
+					  &extra_string_copy,
+					  &force_condition);
+
+      /* We could check that EXTRA_STRING_COPY is empty at this point -- it
+	 should be, as we only get here for things that are not bp_dprintf,
+	 however, we prefer to give the location spec parser a chance to
+	 run first, this means the user will get errors about invalid
+	 location spec instead of an error about garbage at the end of the
+	 command line.
+
+	 We still do the EXTRA_STRING_COPY is empty check, just later in
+	 this function.  */
+
+      gdb_assert (thread == -1 || thread > 0);
+      gdb_assert (task == -1 || task > 0);
+      gdb_assert (inferior == -1 || inferior > 0);
+    }
+  else
+    {
+      if (cond_string != nullptr)
+	cond_string_copy.reset (xstrdup (cond_string));
+      if (extra_string != nullptr)
+	extra_string_copy.reset (xstrdup (extra_string));
+    }
+
+  /* Clear these.  Updated values are now held in the *_copy locals.  */
+  cond_string = nullptr;
+  extra_string = nullptr;
+
   try
     {
       ops->create_sals_from_location_spec (locspec, &canonical);
@@ -9278,6 +9115,13 @@ create_breakpoint (struct gdbarch *gdbarch,
 	throw;
     }
 
+  /* Only bp_dprintf breakpoints should have anything in EXTRA_STRING_COPY
+     by this point.  For all other breakpoints this indicates an error.  We
+     could place this check earlier in the function, but we prefer to see
+     errors from the location spec parser before we see this error message.  */
+  if (type_wanted != bp_dprintf && extra_string_copy.get () != nullptr)
+    error (_("Garbage '%s' at end of command"), extra_string_copy.get ());
+
   if (!pending && canonical.lsals.empty ())
     return 0;
 
@@ -9301,63 +9145,31 @@ create_breakpoint (struct gdbarch *gdbarch,
      breakpoint.  */
   if (!pending)
     {
-      gdb::unique_xmalloc_ptr<char> cond_string_copy;
-      gdb::unique_xmalloc_ptr<char> extra_string_copy;
-
-      if (parse_extra)
+      /* Check the validity of the condition.  We should error out if the
+	 condition is invalid at all of the locations and if it is not
+	 forced.  In the PARSE_EXTRA case above, this check is done when
+	 parsing the EXTRA_STRING.  */
+      if (cond_string_copy.get () != nullptr && !force_condition)
 	{
-	  gdb_assert (type_wanted != bp_dprintf);
-
-	  gdb::unique_xmalloc_ptr<char> rest;
-	  gdb::unique_xmalloc_ptr<char> cond;
-
+	  int num_failures = 0;
 	  const linespec_sals &lsal = canonical.lsals[0];
-
-	  find_condition_and_thread_for_sals (lsal.sals, extra_string,
-					      &cond, &thread, &inferior,
-					      &task, &rest);
-
-	  if (rest.get () != nullptr && *(rest.get ()) != '\0')
-	    error (_("Garbage '%s' at end of command"), rest.get ());
-
-	  cond_string_copy = std::move (cond);
-	  extra_string_copy = std::move (rest);
-	}
-      else
-	{
-	  /* Check the validity of the condition.  We should error out
-	     if the condition is invalid at all of the locations and
-	     if it is not forced.  In the PARSE_EXTRA case above, this
-	     check is done when parsing the EXTRA_STRING.  */
-	  if (cond_string != nullptr && !force_condition)
+	  for (const auto &sal : lsal.sals)
 	    {
-	      int num_failures = 0;
-	      const linespec_sals &lsal = canonical.lsals[0];
-	      for (const auto &sal : lsal.sals)
+	      const char *cond = cond_string_copy.get ();
+	      try
 		{
-		  const char *cond = cond_string;
-		  try
-		    {
-		      parse_exp_1 (&cond, sal.pc, block_for_pc (sal.pc), 0);
-		      /* One success is sufficient to keep going.  */
-		      break;
-		    }
-		  catch (const gdb_exception_error &)
-		    {
-		      num_failures++;
-		      /* If this is the last sal, error out.  */
-		      if (num_failures == lsal.sals.size ())
-			throw;
-		    }
+		  parse_exp_1 (&cond, sal.pc, block_for_pc (sal.pc), 0);
+		  /* One success is sufficient to keep going.  */
+		  break;
+		}
+	      catch (const gdb_exception_error &)
+		{
+		  num_failures++;
+		  /* If this is the last sal, error out.  */
+		  if (num_failures == lsal.sals.size ())
+		    throw;
 		}
 	    }
-
-	  /* Create a private copy of condition string.  */
-	  if (cond_string)
-	    cond_string_copy.reset (xstrdup (cond_string));
-	  /* Create a private copy of any extra string.  */
-	  if (extra_string)
-	    extra_string_copy.reset (xstrdup (extra_string));
 	}
 
       ops->create_breakpoints_sal (gdbarch, &canonical,
@@ -9374,21 +9186,16 @@ create_breakpoint (struct gdbarch *gdbarch,
 								 type_wanted);
       b->locspec = locspec->clone ();
 
-      if (parse_extra)
-	b->cond_string = NULL;
-      else
-	{
-	  /* Create a private copy of condition string.  */
-	  b->cond_string.reset (cond_string != NULL
-				? xstrdup (cond_string)
-				: NULL);
-	  b->thread = thread;
-	}
+      /* Create a private copy of the condition string.  */
+      b->cond_string = std::move (cond_string_copy);
+
+      b->thread = thread;
+      b->task = task;
+      b->inferior = inferior;
 
       /* Create a private copy of any extra string.  */
-      b->extra_string.reset (extra_string != NULL
-			     ? xstrdup (extra_string)
-			     : NULL);
+      b->extra_string = std::move (extra_string_copy);
+
       b->ignore_count = ignore_count;
       b->disposition = tempflag ? disp_del : disp_donttouch;
       b->condition_not_parsed = 1;
@@ -9397,9 +9204,12 @@ create_breakpoint (struct gdbarch *gdbarch,
 	   && type_wanted != bp_hardware_breakpoint) || thread != -1)
 	b->pspace = current_program_space;
 
+      if (b->type == bp_dprintf)
+	update_dprintf_command_list (b.get ());
+
       install_breakpoint (internal, std::move (b), 0);
     }
-  
+
   if (canonical.lsals.size () > 1)
     {
       warning (_("Multiple breakpoints were set.\nUse the "
@@ -13128,24 +12938,6 @@ code_breakpoint::location_spec_to_sals (location_spec *locspec,
     {
       for (auto &sal : sals)
 	resolve_sal_pc (&sal);
-      if (condition_not_parsed && extra_string != NULL)
-	{
-	  gdb::unique_xmalloc_ptr<char> local_cond, local_extra;
-	  int local_thread, local_task, local_inferior;
-
-	  find_condition_and_thread_for_sals (sals, extra_string.get (),
-					      &local_cond, &local_thread,
-					      &local_inferior,
-					      &local_task, &local_extra);
-	  gdb_assert (cond_string == nullptr);
-	  if (local_cond != nullptr)
-	    cond_string = std::move (local_cond);
-	  thread = local_thread;
-	  task = local_task;
-	  if (local_extra != nullptr)
-	    extra_string = std::move (local_extra);
-	  condition_not_parsed = 0;
-	}
 
       if (type == bp_static_tracepoint)
 	{
diff --git a/gdb/testsuite/gdb.ada/tasks.exp b/gdb/testsuite/gdb.ada/tasks.exp
index 603d43f7c36..337ee1b6f07 100644
--- a/gdb/testsuite/gdb.ada/tasks.exp
+++ b/gdb/testsuite/gdb.ada/tasks.exp
@@ -55,11 +55,11 @@ gdb_test "watch j task 1 task 3" "You can specify only one task\\."
 
 # Check that attempting to combine 'task' and 'thread' gives an error.
 gdb_test "break break_me task 1 thread 1" \
-    "You can specify only one of thread or task\\."
+    "You can specify only one of thread, inferior, or task\\."
 gdb_test "break break_me thread 1 task 1" \
-    "You can specify only one of thread or task\\."
+    "You can specify only one of thread, inferior, or task\\."
 gdb_test "break break_me inferior 1 task 1" \
-    "You can specify only one of inferior or task\\."
+    "You can specify only one of thread, inferior, or task\\."
 gdb_test "watch j task 1 thread 1" \
     "You can specify only one of thread or task\\."
 gdb_test "watch j thread 1 task 1" \
diff --git a/gdb/testsuite/gdb.base/condbreak.exp b/gdb/testsuite/gdb.base/condbreak.exp
index a5b2a28701b..5a6a42e073b 100644
--- a/gdb/testsuite/gdb.base/condbreak.exp
+++ b/gdb/testsuite/gdb.base/condbreak.exp
@@ -179,6 +179,10 @@ gdb_test "break -q main if (1==1) thread 999" \
     "Unknown thread 999\\."
 gdb_test "break -q main thread 999 if (1==1)" \
     "Unknown thread 999\\."
+gdb_test "break -q main if (1==1) thread 999 -force-condition" \
+    "Unknown thread 999\\."
+gdb_test "break -q main thread 999 if (1==1) -force-condition" \
+    "Unknown thread 999\\."
 
 # Verify that both if and thread can be distinguished from a breakpoint
 # address expression.
@@ -186,20 +190,71 @@ gdb_test "break *main if (1==1) thread 999" \
     "Unknown thread 999\\."
 gdb_test "break *main thread 999 if (1==1)" \
     "Unknown thread 999\\."
+gdb_test "break *main if (1==1) thread 999 -force-condition" \
+    "Unknown thread 999\\."
+gdb_test "break *main thread 999 if (1==1) -force-condition" \
+    "Unknown thread 999\\."
 
 # Similarly for task.
 gdb_test "break *main if (1==1) task 999" \
     "Unknown task 999\\."
 gdb_test "break *main task 999 if (1==1)" \
     "Unknown task 999\\."
+gdb_test "break *main if (1==1) task 999 -force-condition" \
+    "Unknown task 999\\."
+gdb_test "break *main task 999 if (1==1) -force-condition" \
+    "Unknown task 999\\."
 
-# GDB accepts abbreviations for "thread" and "task".
+# GDB accepts abbreviations for "thread", "task" and
+# "-force-condition", when these keywords appear after
+# the breakpoint condition.
 gdb_test "break *main if (1==1) t 999" \
     "Unknown thread 999\\."
 gdb_test "break *main if (1==1) th 999" \
     "Unknown thread 999\\."
 gdb_test "break *main if (1==1) ta 999" \
     "Unknown task 999\\."
+gdb_test "break *main if (1==1) t 999 -force" \
+    "Unknown thread 999\\."
+gdb_test "break *main if (1==1) th 999 -force" \
+    "Unknown thread 999\\."
+gdb_test "break *main if (1==1) ta 999 -force" \
+    "Unknown task 999\\."
+
+# Check the use of abbreviations before the condition.  This works
+# because, when the location spec starts with '*' GDB is able to
+# figure out that the complete location is '*main'.
+gdb_test "break *main t 999 if (1==1)" \
+    "Unknown thread 999\\."
+gdb_test "break *main th 999 if (1==1)" \
+    "Unknown thread 999\\."
+gdb_test "break *main ta 999 if (1==1)" \
+    "Unknown task 999\\."
+gdb_test "break *main t 999 -force if (1==1)" \
+    "Unknown thread 999\\."
+gdb_test "break *main th 999 -force if (1==1)" \
+    "Unknown thread 999\\."
+gdb_test "break *main ta 999 -force if (1==1)" \
+    "Unknown task 999\\."
+
+# However, when the location spec doesn't start with '*' GDB relies on
+# the linespec parser to spot the keyword which marks the end of the
+# linespec, and this parser doesn't check for abbreviations.
+gdb_test "with breakpoint pending off -- break main t 999 if (1==1)" \
+    "Function \"main t 999\" not defined\\."
+gdb_test "with breakpoint pending off -- break main th 999 if (1==1)" \
+    "Function \"main th 999\" not defined\\."
+gdb_test "with breakpoint pending off -- break main ta 999 if (1==1)" \
+    "Function \"main ta 999\" not defined\\."
+
+# GDB does not treat a "-force-condition" flag that appears
+# immediately after the condition as the flag, but instead treats it
+# as " - force - condition", that is, subtraction of the symbol
+# "force" followed by subtraction of symbol "context".  This is really
+# just a quirk of how this used to be implemented, and should maybe be
+# changed in the future.  However, for now GDB retains this behaviour.
+gdb_test "break *main if (1==1) -force-condition" \
+    "No symbol \"force\" in current context\\."
 
 set test "run until breakpoint at marker3"
 gdb_test_multiple "continue" $test {
diff --git a/gdb/testsuite/gdb.base/pending.exp b/gdb/testsuite/gdb.base/pending.exp
index d7d3735000a..645f63bdb16 100644
--- a/gdb/testsuite/gdb.base/pending.exp
+++ b/gdb/testsuite/gdb.base/pending.exp
@@ -170,7 +170,8 @@ gdb_test "info break" \
 \[\t \]+stop only if k == 1.*
 \[\t \]+print k.*
 \[0-9\]+\[\t \]+breakpoint     keep y.* in main at .*$srcfile:$mainline.*
-\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp2_loc if x > 3.*" \
+\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp2_loc.*
+\\s+stop only if x > 3.*" \
 "multiple pending breakpoints"
 
 
@@ -195,8 +196,10 @@ gdb_test "info break" \
 \[\t \]+stop only if k == 1.*
 \[\t \]+print k.*
 \[0-9\]+\[\t \]+breakpoint     keep y.* in main at .*$srcfile:$mainline.*
-\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp2_loc if x > 3.*
-\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp3_loc.*ignore next 2 hits.*" \
+\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp2_loc.*
+\\s+stop only if x > 3.*
+\[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*pendshr.c:$bp3_loc.*
+\\s+ignore next 2 hits.*" \
 "multiple pending breakpoints 2"
 
 #
@@ -267,3 +270,17 @@ gdb_test "info break" \
 \[0-9\]+\[\t \]+breakpoint     keep y.* in main at .*$srcfile:$mainline.*
 \[0-9\]+\[\t \]+breakpoint     keep y.*PENDING.*imaginary.*" \
 "verify pending breakpoint after restart"
+
+# Test GDB's parsing of pending breakpoint thread and condition.
+
+gdb_test_no_output "set breakpoint pending on"
+gdb_test "break foo if (unknown_var && another_unknown_var) thread 1" \
+    "Breakpoint $decimal \\(foo\\) pending\\."
+set bpnum [get_integer_valueof "\$bpnum" "*INVALID" \
+	       "get number for foo breakpoint"]
+gdb_test "info breakpoints $bpnum" \
+    [multi_line \
+	 "$bpnum\\s+breakpoint\\s+keep\\s+y\\s+<PENDING>\\s+foo" \
+	 "\\s+stop only if \\(unknown_var && another_unknown_var\\)" \
+	 "\\s+stop only in thread 1"] \
+    "check pending breakpoint on foo"
diff --git a/gdb/testsuite/gdb.linespec/explicit.exp b/gdb/testsuite/gdb.linespec/explicit.exp
index 668002d9038..8cbefe204ec 100644
--- a/gdb/testsuite/gdb.linespec/explicit.exp
+++ b/gdb/testsuite/gdb.linespec/explicit.exp
@@ -575,7 +575,7 @@ namespace eval $testfile {
 	      allow-pending]} {
 	fail "set $tst"
     } else {
-	gdb_test "info break" ".*PENDING.*myfunction if foofoofoo == 1.*" $tst
+	gdb_test "info break" ".*PENDING.*myfunction\r\n\\s+stop only if foofoofoo == 1.*" $tst
     }
 
     gdb_exit
@@ -586,7 +586,7 @@ namespace eval $testfile {
 	      allow-pending]} {
 	fail "set $tst"
     } else {
-	gdb_test "info break" ".*PENDING.*myfunction if arg == 0" $tst
+	gdb_test "info break" ".*PENDING.*myfunction\r\n\\s+stop only if arg == 0" $tst
 
 	gdb_load [standard_output_file $exefile]
 	gdb_test "info break" \
diff --git a/gdb/testsuite/gdb.linespec/keywords.exp b/gdb/testsuite/gdb.linespec/keywords.exp
index dc66e32237c..7900e3670a7 100644
--- a/gdb/testsuite/gdb.linespec/keywords.exp
+++ b/gdb/testsuite/gdb.linespec/keywords.exp
@@ -55,7 +55,7 @@ with_test_prefix "trailing whitespace" {
 gdb_test "break thread 123" "Unknown thread 123\\."
 gdb_test "break thread foo" "Invalid thread ID: foo"
 gdb_test "break task 123" "Unknown task 123\\."
-gdb_test "break task foo" "Junk after task keyword\\."
+gdb_test "break task foo" "Junk 'foo' after task keyword\\."
 gdb_breakpoint "thread if 0" "message"
 
 # These are also NULL locations, but using a subsequent keyword
@@ -63,9 +63,9 @@ gdb_breakpoint "thread if 0" "message"
 gdb_test "break thread thread" "Invalid thread ID: thread"
 gdb_test "break thread task" "Invalid thread ID: task"
 gdb_test "break thread if" "Invalid thread ID: if"
-gdb_test "break task task" "Junk after task keyword\\."
-gdb_test "break task thread" "Junk after task keyword\\."
-gdb_test "break task if" "Junk after task keyword\\."
+gdb_test "break task task" "Junk 'task' after task keyword\\."
+gdb_test "break task thread" "Junk 'thread' after task keyword\\."
+gdb_test "break task if" "Junk 'if' after task keyword\\."
 
 # Test locations containing keyword followed by keyword.
 gdb_test "break thread thread 123" "Unknown thread 123\\."
diff --git a/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp b/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp
index 28f52938aeb..bb9987a5572 100644
--- a/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp
+++ b/gdb/testsuite/gdb.mi/mi-dprintf-pending.exp
@@ -50,7 +50,8 @@ set bp_location1 [gdb_get_line_number "set breakpoint 1 here"]
 # Set pending dprintf via MI.
 set bp [mi_make_breakpoint_pending -number "1" -type "dprintf" \
 	    -disp "keep" -enabled "y" -pending "pendfunc1" \
-	    -original-location "pendfunc1"]
+	    -original-location "pendfunc1" \
+	    -script {\["printf \\\"hello\\\""\]}]
 mi_gdb_test "-dprintf-insert -f pendfunc1 \"hello\"" \
     ".*\\^done,$bp" "mi set dprintf"
 
diff --git a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
index 1f6573268da..b8aceabcad6 100644
--- a/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
+++ b/gdb/testsuite/gdb.multi/inferior-specific-bp.exp
@@ -51,9 +51,9 @@ if {![runto_main]} {
 # this should fail.  Try with the keywords in both orders just in case the
 # parser has a bug.
 gdb_test "break foo thread 1.1 inferior 1" \
-    "You can specify only one of inferior or thread\\."
+    "You can specify only one of thread, inferior, or task\\."
 gdb_test "break foo inferior 1 thread 1.1" \
-    "You can specify only one of inferior or thread\\."
+    "You can specify only one of thread, inferior, or task\\."
 
 # Try to create a breakpoint using the 'inferior' keyword multiple times.
 gdb_test "break foo inferior 1 inferior 2" \
diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c b/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c
new file mode 100644
index 00000000000..15d1b9833dd
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp-lib.c
@@ -0,0 +1,22 @@
+/* Copyright 2023 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/>.  */
+
+int global_var = 0;
+
+void
+foo (int arg)
+{
+  global_var = arg;
+}
diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp.c b/gdb/testsuite/gdb.threads/del-pending-thread-bp.c
new file mode 100644
index 00000000000..6fc76dbf08c
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp.c
@@ -0,0 +1,85 @@
+/* Copyright 2023 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 <dlfcn.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+pthread_barrier_t barrier;
+
+static void
+barrier_wait (pthread_barrier_t *b)
+{
+  int res = pthread_barrier_wait (b);
+  if (res != 0 && res != PTHREAD_BARRIER_SERIAL_THREAD)
+    abort ();
+}
+
+static void *
+thread_worker (void *arg)
+{
+  barrier_wait (&barrier);
+  return NULL;
+}
+
+void
+breakpt (void)
+{
+  /* Nothing.  */
+}
+
+int
+main (void)
+{
+  void *handle;
+  void (*func)(int);
+  pthread_t thread;
+
+  if (pthread_barrier_init (&barrier, NULL, 2) != 0)
+    abort ();
+
+  if (pthread_create (&thread, NULL, thread_worker, NULL) != 0)
+    abort ();
+
+  breakpt ();
+
+  /* Allow the worker thread to complete.  */
+  barrier_wait (&barrier);
+
+  if (pthread_join (thread, NULL) != 0)
+    abort ();
+
+  breakpt ();
+
+  /* Now load the shared library.  */
+  handle = dlopen (SHLIB_NAME, RTLD_LAZY);
+  if (handle == NULL)
+    abort ();
+
+  /* Find the function symbol.  */
+  func = (void (*)(int)) dlsym (handle, "foo");
+
+  /* Call the library function.  */
+  func (1);
+
+  /* Unload the shared library.  */
+  if (dlclose (handle) != 0)
+    abort ();
+
+  breakpt ();
+
+  return 0;
+}
+
diff --git a/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp b/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp
new file mode 100644
index 00000000000..4ed05eaa42d
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/del-pending-thread-bp.exp
@@ -0,0 +1,108 @@
+# Copyright 2023 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/>.
+
+# This test checks that pending thread-specific breakpoints are
+# correctly deleted when the thread the breakpoint is for goes out of
+# scope.
+#
+# We also check that we can't create a pending thread-specific
+# breakpoint for a non-existent thread.
+
+require allow_shlib_tests
+
+standard_testfile
+
+set libname $testfile-lib
+set srcfile_lib $srcdir/$subdir/$libname.c
+set binfile_lib [standard_output_file $libname.so]
+
+if { [gdb_compile_shlib $srcfile_lib $binfile_lib {}] != "" } {
+    untested "failed to compile shared library 1"
+    return -1
+}
+
+set binfile_lib_target [gdb_download_shlib $binfile_lib]
+
+if { [prepare_for_testing "failed to prepare" $testfile $srcfile \
+	  [list debug \
+	       additional_flags=-DSHLIB_NAME=\"$binfile_lib_target\" \
+	       shlib_load pthreads]] } {
+    return -1
+}
+
+gdb_locate_shlib $binfile_lib
+
+if ![runto_main] {
+    return 0
+}
+
+# Run until we have two threads.
+gdb_breakpoint "breakpt"
+gdb_continue_to_breakpoint "first breakpt call"
+
+# Confirm that we have a thread '2'.
+gdb_test "info threads" "\r\n\\s+2\\s+\[^\r\n\]+"
+
+# Create a pending, thread-specific, breakpoint on 'foo'.
+gdb_breakpoint "foo thread 2" allow-pending
+set bpnum [get_integer_valueof "\$bpnum" "*INVALID*" \
+	       "get breakpoint number"]
+
+# Check we can't create a pending thread-specific breakpoint for a
+# non-existent thread.
+gdb_test "with breakpoint pending on -- break foo thread 99" \
+    "Unknown thread 99\\."
+
+# Continue to 'breakpt' again.  Don't use gdb_continue_to_breakpoint
+# as we are looking for the thread exited and breakpoint deleted
+# messages.
+set output [list "Continuing\\."]
+
+if {!([target_info exists gdb_protocol]
+      && ([target_info gdb_protocol] == "remote"
+	  || [target_info gdb_protocol] == "extended-remote"))} {
+    # Due to bug PR gdb/30129 we don't see the thread exited messages
+    # for remote targets.  When this bit of this test can be cleaned
+    # up.
+    lappend output "\\\[Thread \[^\r\n\]+ exited\\\]"
+}
+
+lappend output "Thread-specific breakpoint $bpnum deleted - thread 2 no longer in the thread list\\." \
+    "" \
+    "Thread 1 \[^\r\n\]+, breakpt \\(\\) at \[^\r\n\]+" \
+    "$decimal\\s+\[^\r\n\]+"
+
+gdb_test "continue" \
+    [multi_line {*}$output] \
+    "second breakpt call"
+
+# Confirm breakpoint has been deleted.
+gdb_test "info breakpoints $bpnum" \
+    "No breakpoint or watchpoint matching '$bpnum'\\."
+
+# Continue again.  This will pass through 'foo'.  We should not stop
+# in 'foo', the breakpoint has been deleted.  We should next stop in
+# breakpt again.
+gdb_test "continue" \
+    [multi_line \
+	 "Continuing\\." \
+	 "" \
+	 "Thread 1 \[^\r\n\]+ hit Breakpoint $decimal, breakpt \\(\\) at \[^\r\n\]+" \
+	 "$decimal\\s+\[^\r\n\]+"] \
+    "third breakpt call"
+gdb_test "bt 1" \
+    [multi_line \
+	 "#0\\s+breakpt \\(\\) at \[^\r\n\]+" \
+	 "\\(More stack frames follow\\.\\.\\.\\)"]
