do_implicit_handshake (GTlsConnectionBase  *tls,
                       gint64               timeout,
                       GCancellable        *cancellable,
                       GError             **error)
{
  GTlsConnectionBasePrivate *priv = g_tls_connection_base_get_instance_private (tls);
  GTlsConnectionBaseClass *tls_class = G_TLS_CONNECTION_BASE_GET_CLASS (tls);
  gint64 *thread_timeout = NULL;

  /* We have op_mutex */

  g_assert (!priv->handshake_context);
  if (timeout != 0)
    {
      priv->handshake_context = g_main_context_new ();
      g_main_context_push_thread_default (priv->handshake_context);
    }
  else
    {
      priv->handshake_context = g_main_context_ref_thread_default ();
    }

  g_assert (!priv->implicit_handshake);
  priv->implicit_handshake = g_task_new (tls, cancellable,
                                        timeout ? sync_handshake_thread_completed : NULL,
                                        NULL);
  g_task_set_source_tag (priv->implicit_handshake, do_implicit_handshake);
  g_task_set_name (priv->implicit_handshake, "[glib-networking] do_implicit_handshake");

  thread_timeout = g_new0 (gint64, 1);
  g_task_set_task_data (priv->implicit_handshake,
                        thread_timeout, g_free);

  if (tls_class->prepare_handshake)
    tls_class->prepare_handshake (tls, priv->advertised_protocols);

  if (timeout != 0)
    {
      GError *my_error = NULL;
      gboolean success;

      /* In the blocking case, run the handshake operation synchronously in
       * another thread, and delegate handling the timeout to that thread; it
       * should return G_IO_ERROR_TIMED_OUT iff (timeout > 0) and the operation
       * times out. If (timeout < 0) it should block indefinitely until the
       * operation is complete or errors. */
      *thread_timeout = timeout;

      g_mutex_unlock (&priv->op_mutex);

      g_task_set_return_on_cancel (priv->implicit_handshake, TRUE);
      g_task_run_in_thread (priv->implicit_handshake, handshake_thread);

      crank_sync_handshake_context (tls, cancellable);

      success = finish_handshake (tls,
                                  priv->implicit_handshake,
                                  &my_error);

      g_main_context_pop_thread_default (priv->handshake_context);
      g_clear_pointer (&priv->handshake_context, g_main_context_unref);
      g_clear_object (&priv->implicit_handshake);

      yield_op (tls, G_TLS_CONNECTION_BASE_OP_HANDSHAKE,
                G_TLS_CONNECTION_BASE_OK);

      g_mutex_lock (&priv->op_mutex);

      if (my_error)
        g_propagate_error (error, my_error);
      return success;
    }
  else
    {
      /* In the non-blocking case, start the asynchronous handshake operation
       * and return EWOULDBLOCK to the caller, who will handle polling for
       * completion of the handshake and whatever operation they actually cared
       * about. Run the actual operation as blocking in its thread. */
      *thread_timeout = -1; /* blocking */

      g_task_run_in_thread (priv->implicit_handshake,
                            async_handshake_thread);

      /* 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;
    }
}