/*
 * gcc -o665211 665211.c `pkg-config --cflags --libs gio-2.0`
 * ./665211
 */

#include <gio/gio.h>

#define N_RUNS 100000
#define SLEEP_MIN_USEC 1
#define SLEEP_MAX_USEC 10

/* Can run in any thread */
static void
ensure_connection_works (GDBusConnection *conn)
{
  GVariant *v;
  GError *error = NULL;

  v = g_dbus_connection_call_sync (conn, "org.freedesktop.DBus",
      "/org/freedesktop/DBus", "org.freedesktop.DBus", "GetId", NULL, NULL, 0, -1,
      NULL, &error);
  g_assert_no_error (error);
  g_assert (v != NULL);
  g_assert (g_variant_is_of_type (v, G_VARIANT_TYPE ("(s)")));
  g_variant_unref (v);
}

/*
 * get_sync_in_thread:
 * @data: (type guint): delay in microseconds
 *
 * Sleep for a short time, then get a session bus connection and call
 * a method on it.
 *
 * Runs in a non-main thread.
 *
 * Returns: (transfer full): the connection
 */
static gpointer
get_sync_in_thread (gpointer data)
{
  guint delay = GPOINTER_TO_UINT (data);
  GError *error = NULL;
  GDBusConnection *conn;

  g_usleep (delay);

  conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
  g_assert_no_error (error);

  ensure_connection_works (conn);

  return conn;
}

static guint unref_wins = 0;
static guint get_wins = 0;

/* Runs in the main thread */
static void
compete (guint n)
{
  guint i, j;
  GError *error = NULL;
  GDBusConnection *conn, *new_conn;
  GVariant *v;

  conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
  g_assert_no_error (error);

  for (i = 0; i < n; i++)
    {
      GThread *thread;
      guint unref_delay, get_delay;

      /* We want to be the last ref, so let it finish setting up */
      for (j = 0; j < 100; j++)
        {
          guint r = g_atomic_int_get (&G_OBJECT (conn)->ref_count);

          if (r != 1)
            {
              g_debug ("refcount is %u, sleeping", r);
              g_usleep (1000);
            }
        }

      /* Delay for a random time on each side of the race, to perturb the
       * timing. Ideally, we want each side to win half the races. */
      unref_delay = g_random_int_range (SLEEP_MIN_USEC, SLEEP_MAX_USEC);
      get_delay = g_random_int_range (SLEEP_MIN_USEC, SLEEP_MAX_USEC);

      /* One half of the race is to call g_bus_get_sync... */
      thread = g_thread_create (get_sync_in_thread,
          GUINT_TO_POINTER (get_delay), TRUE, &error);

      /* ... and the other half is to unref the shared connection, which must
       * have exactly one ref at this point
       */
      g_usleep (unref_delay);
      g_object_unref (conn);

      /* Wait for the thread to run; see what it got */
      new_conn = g_thread_join (thread);

      /* If the thread won the race, it will have kept the same connection,
       * and it'll have one ref
       */
      if (new_conn == conn)
        {
          get_wins++;
        }
      else
        {
          unref_wins++;
          /* conn is invalid now, but new_conn is suitable for the
           * next round
           */
          conn = new_conn;
        }

      ensure_connection_works (conn);
    }
}

int
main (void)
{
  g_type_init ();

  compete (N_RUNS);

  g_print ("Unref won %u races; Get won %u races\n", unref_wins, get_wins);
  return 0;
}
