claim_op (GTlsConnectionBase    *tls,
          GTlsConnectionBaseOp   op,
          gint64                 timeout,
          GCancellable          *cancellable,
          GError               **error)
{
  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);

 try_again:
  if (g_cancellable_set_error_if_cancelled (cancellable, error))
    return FALSE;

  g_mutex_lock (&priv->op_mutex);

  if (((op == G_TLS_CONNECTION_BASE_OP_HANDSHAKE ||
        op == G_TLS_CONNECTION_BASE_OP_READ) &&
       (priv->read_closing || priv->read_closed)) ||
      ((op == G_TLS_CONNECTION_BASE_OP_HANDSHAKE ||
        op == G_TLS_CONNECTION_BASE_OP_WRITE) &&
       (priv->write_closing || priv->write_closed)))
    {
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_CLOSED,
                           _("Connection is closed"));
      g_mutex_unlock (&priv->op_mutex);
      return FALSE;
    }

  if (priv->handshake_error &&
      op != G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH &&
      op != G_TLS_CONNECTION_BASE_OP_CLOSE_READ &&
      op != G_TLS_CONNECTION_BASE_OP_CLOSE_WRITE)
    {
      if (error)
        *error = g_error_copy (priv->handshake_error);
      g_mutex_unlock (&priv->op_mutex);
      return FALSE;
    }

  if (op != G_TLS_CONNECTION_BASE_OP_HANDSHAKE)
    {
      if (op != G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH &&
          op != G_TLS_CONNECTION_BASE_OP_CLOSE_READ &&
          op != G_TLS_CONNECTION_BASE_OP_CLOSE_WRITE &&
          priv->need_handshake && !priv->handshaking)
        {
          priv->handshaking = TRUE;
          if (!do_implicit_handshake (tls, timeout, cancellable, error))
            {
              g_mutex_unlock (&priv->op_mutex);
              return FALSE;
            }
        }

      if (priv->need_finish_handshake &&
          priv->implicit_handshake)
        {
          GError *my_error = NULL;
          gboolean success;

          priv->need_finish_handshake = FALSE;

          g_mutex_unlock (&priv->op_mutex);
          success = finish_handshake (tls, priv->implicit_handshake, &my_error);
          g_clear_object (&priv->implicit_handshake);
          g_clear_pointer (&priv->handshake_context, g_main_context_unref);
          g_mutex_lock (&priv->op_mutex);

          if (op != G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH &&
              op != G_TLS_CONNECTION_BASE_OP_CLOSE_READ &&
              op != G_TLS_CONNECTION_BASE_OP_CLOSE_WRITE &&
              (!success || g_cancellable_set_error_if_cancelled (cancellable, &my_error)))
            {
              g_propagate_error (error, my_error);
              g_mutex_unlock (&priv->op_mutex);
              return FALSE;
            }

          g_clear_error (&my_error);
        }
    }

  if (priv->handshaking &&
      timeout != 0 &&
      g_main_context_is_owner (priv->handshake_context))
    {
      /* Cannot perform a blocking operation during a handshake on the
       * same thread that triggered the handshake. The only way this can
       * occur is if the application is doing something weird in its
       * accept-certificate callback. Allowing a blocking op would stall
       * the handshake (forever, if there's no timeout). Even a close
       * op would deadlock here.
       */
      g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, _("Cannot perform blocking operation during TLS handshake"));
      g_mutex_unlock (&priv->op_mutex);
      return FALSE;
    }

  if ((op != G_TLS_CONNECTION_BASE_OP_WRITE && priv->reading) ||
      (op != G_TLS_CONNECTION_BASE_OP_READ && priv->writing) ||
      (op != G_TLS_CONNECTION_BASE_OP_HANDSHAKE && priv->handshaking))
    {
      GPollFD fds[2];
      int nfds;
      gint64 start_time;
      gint result = 1; /* if the loop is never entered, it's as if we cancelled early */

      g_cancellable_reset (priv->waiting_for_op);

      g_mutex_unlock (&priv->op_mutex);

      if (timeout == 0)
        {
          /* Intentionally not translated because this is not a fatal error to be
           * presented to the user, and to avoid this showing up in profiling. */
          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK, "Operation would block");
          return FALSE;
        }

      g_cancellable_make_pollfd (priv->waiting_for_op, &fds[0]);
      if (g_cancellable_make_pollfd (cancellable, &fds[1]))
        nfds = 2;
      else
        nfds = 1;

      /* Convert from microseconds to milliseconds. */
      if (timeout != -1)
        timeout /= 1000;

      /* Poll until cancellation or the timeout is reached. */
      start_time = g_get_monotonic_time ();

      while (!g_cancellable_is_cancelled (priv->waiting_for_op) &&
             !g_cancellable_is_cancelled (cancellable))
        {
          result = g_poll (fds, nfds, timeout);

          if (result == 0)
            break;
          if (result != -1 || errno != EINTR)
            continue;

          if (timeout != -1)
            {
              timeout -= (g_get_monotonic_time () - start_time) / 1000;
              if (timeout < 0)
                timeout = 0;
            }
        }

      if (nfds > 1)
        g_cancellable_release_fd (cancellable);

      if (result == 0)
        {
          g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
                               _("Socket I/O timed out"));
          return FALSE;
        }

      goto try_again;
    }

  if (op == G_TLS_CONNECTION_BASE_OP_HANDSHAKE)
    priv->handshaking = TRUE;
  if (op == G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH ||
      op == G_TLS_CONNECTION_BASE_OP_CLOSE_READ)
    priv->read_closing = TRUE;
  if (op == G_TLS_CONNECTION_BASE_OP_CLOSE_BOTH ||
      op == G_TLS_CONNECTION_BASE_OP_CLOSE_WRITE)
    priv->write_closing = TRUE;

  if (op != G_TLS_CONNECTION_BASE_OP_WRITE)
    priv->reading = TRUE;
  if (op != G_TLS_CONNECTION_BASE_OP_READ)
    priv->writing = TRUE;

  g_mutex_unlock (&priv->op_mutex);
  return TRUE;
}