dbus-pending-call.c 12.2 KB
Newer Older
1 2 3 4 5
/* -*- mode: C; c-file-style: "gnu" -*- */
/* dbus-pending-call.c Object representing a call in progress.
 *
 * Copyright (C) 2002, 2003 Red Hat Inc.
 *
6
 * Licensed under the Academic Free License version 2.0
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "dbus-internals.h"
25 26
#include "dbus-connection-internal.h"
#include "dbus-pending-call.h"
27 28 29 30 31 32 33 34 35 36 37 38 39 40
#include "dbus-list.h"
#include "dbus-threads.h"
#include "dbus-test.h"

/**
 * @defgroup DBusPendingCallInternals DBusPendingCall implementation details
 * @ingroup DBusInternals
 * @brief DBusPendingCall private implementation details.
 *
 * The guts of DBusPendingCall and its methods.
 *
 * @{
 */

41 42
static dbus_int32_t notify_user_data_slot = -1;

43 44 45 46
/**
 * Creates a new pending reply object.
 *
 * @param connection connection where reply will arrive
47 48
 * @param timeout_milliseconds length of timeout, -1 for default
 * @param timeout_handler timeout handler, takes pending call as data
49 50 51
 * @returns a new #DBusPendingCall or #NULL if no memory.
 */
DBusPendingCall*
52 53 54
_dbus_pending_call_new (DBusConnection    *connection,
                        int                timeout_milliseconds,
                        DBusTimeoutHandler timeout_handler)
55 56
{
  DBusPendingCall *pending;
57
  DBusTimeout *timeout;
58

59 60 61 62
  _dbus_return_val_if_fail (timeout_milliseconds >= 0 || timeout_milliseconds == -1, FALSE);
  
  if (timeout_milliseconds == -1)
    timeout_milliseconds = _DBUS_DEFAULT_TIMEOUT_VALUE;
63 64 65

  if (!dbus_pending_call_allocate_data_slot (&notify_user_data_slot))
    return NULL;
66
  
67
  pending = dbus_new0 (DBusPendingCall, 1);
68 69
  
  if (pending == NULL)
70 71 72 73
    {
      dbus_pending_call_free_data_slot (&notify_user_data_slot);
      return NULL;
    }
74

75 76 77 78 79 80
  timeout = _dbus_timeout_new (timeout_milliseconds,
                               timeout_handler,
			       pending, NULL);  

  if (timeout == NULL)
    {
81
      dbus_pending_call_free_data_slot (&notify_user_data_slot);
82 83 84 85
      dbus_free (pending);
      return NULL;
    }
  
86 87
  pending->refcount.value = 1;
  pending->connection = connection;
88
  pending->timeout = timeout;
89 90

  _dbus_data_slot_list_init (&pending->slot_list);
91
  
92 93 94
  return pending;
}

95 96 97 98 99 100 101 102 103 104 105 106 107
/**
 * Calls notifier function for the pending call
 * and sets the call to completed.
 *
 * @param pending the pending call
 * 
 */
void
_dbus_pending_call_notify (DBusPendingCall *pending)
{
  pending->completed = TRUE;

  if (pending->function)
108 109 110 111 112 113 114
    {
      void *user_data;
      user_data = dbus_pending_call_get_data (pending,
                                              notify_user_data_slot);
      
      (* pending->function) (pending, user_data);
    }
115 116
}

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
/** @} */

/**
 * @defgroup DBusPendingCall DBusPendingCall
 * @ingroup  DBus
 * @brief Pending reply to a method call message
 *
 * A DBusPendingCall is an object representing an
 * expected reply. A #DBusPendingCall can be created
 * when you send a message that should have a reply.
 *
 * @{
 */

/**
 * @typedef DBusPendingCall
 *
 * Opaque data type representing a message pending.
 */

/**
 * Increments the reference count on a pending call.
 *
 * @param pending the pending call object
141
 * @returns the pending call object
142
 */
143
DBusPendingCall *
144 145
dbus_pending_call_ref (DBusPendingCall *pending)
{
146
  _dbus_return_val_if_fail (pending != NULL, NULL);
147 148

  _dbus_atomic_inc (&pending->refcount);
149 150

  return pending;
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
}

/**
 * Decrements the reference count on a pending call,
 * freeing it if the count reaches 0.
 *
 * @param pending the pending call object
 */
void
dbus_pending_call_unref (DBusPendingCall *pending)
{
  dbus_bool_t last_unref;

  _dbus_return_if_fail (pending != NULL);

  last_unref = (_dbus_atomic_dec (&pending->refcount) == 1);

  if (last_unref)
    {
170 171 172 173 174 175 176
      /* If we get here, we should be already detached
       * from the connection, or never attached.
       */
      _dbus_assert (pending->connection == NULL);
      _dbus_assert (!pending->timeout_added);  

      /* this assumes we aren't holding connection lock... */
177 178
      _dbus_data_slot_list_free (&pending->slot_list);
      
179 180 181 182
      if (pending->timeout != NULL)
        _dbus_timeout_unref (pending->timeout);
      
      if (pending->timeout_link)
183
        {
184 185 186
          dbus_message_unref ((DBusMessage *)pending->timeout_link->data);
          _dbus_list_free_link (pending->timeout_link);
          pending->timeout_link = NULL;
187 188 189 190 191 192 193 194 195
        }

      if (pending->reply)
        {
          dbus_message_unref (pending->reply);
          pending->reply = NULL;
        }
      
      dbus_free (pending);
196 197

      dbus_pending_call_free_data_slot (&notify_user_data_slot);
198 199 200 201 202 203 204 205 206 207 208
    }
}

/**
 * Sets a notification function to be called when the reply is
 * received or the pending call times out.
 *
 * @param pending the pending call
 * @param function notifier function
 * @param user_data data to pass to notifier function
 * @param free_user_data function to free the user data
209
 * @returns #FALSE if not enough memory
210
 */
211
dbus_bool_t
212 213 214 215 216
dbus_pending_call_set_notify (DBusPendingCall              *pending,
                              DBusPendingCallNotifyFunction function,
                              void                         *user_data,
                              DBusFreeFunction              free_user_data)
{
217
  _dbus_return_val_if_fail (pending != NULL, FALSE);
218

219 220 221 222 223
  /* could invoke application code! */
  if (!dbus_pending_call_set_data (pending, notify_user_data_slot,
                                   user_data, free_user_data))
    return FALSE;
  
224 225
  pending->function = function;

226
  return TRUE;
227 228
}

229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
/**
 * Cancels the pending call, such that any reply
 * or error received will just be ignored.
 * Drops at least one reference to the #DBusPendingCall
 * so will free the call if nobody else is holding
 * a reference.
 * 
 * @param pending the pending call
 */
void
dbus_pending_call_cancel (DBusPendingCall *pending)
{
  if (pending->connection)
    _dbus_connection_remove_pending_call (pending->connection,
                                          pending);
}
245

246 247 248 249
/**
 * Checks whether the pending call has received a reply
 * yet, or not.
 *
250 251
 * @todo not thread safe? I guess it has to lock though it sucks
 *
252
 * @param pending the pending call
253
 * @returns #TRUE if a reply has been received */
254 255
dbus_bool_t
dbus_pending_call_get_completed (DBusPendingCall *pending)
256
{
257
  return pending->completed;
258 259
}

260 261 262 263 264 265
/**
 * Gets the reply, or returns #NULL if none has been received yet. The
 * reference count is not incremented on the returned message, so you
 * have to keep a reference count on the pending call (or add one
 * to the message).
 *
266 267 268
 * @todo not thread safe? I guess it has to lock though it sucks
 * @todo maybe to make this threadsafe, it should be steal_reply(), i.e. only one thread can ever get the message
 * 
269 270 271 272 273
 * @param pending the pending call
 * @returns the reply message or #NULL.
 */
DBusMessage*
dbus_pending_call_get_reply (DBusPendingCall *pending)
274
{
275
  return pending->reply;
276 277
}

278 279 280 281 282 283
/**
 * Block until the pending call is completed.  The blocking is as with
 * dbus_connection_send_with_reply_and_block(); it does not enter the
 * main loop or process other messages, it simply waits for the reply
 * in question.
 *
284 285 286
 * If the pending call is already completed, this function returns
 * immediately.
 *
287 288 289 290 291 292 293 294 295
 * @todo when you start blocking, the timeout is reset, but it should
 * really only use time remaining since the pending call was created.
 *
 * @param pending the pending call
 */
void
dbus_pending_call_block (DBusPendingCall *pending)
{
  DBusMessage *message;
296 297 298

  if (dbus_pending_call_get_completed (pending))
    return;
299 300

  /* message may be NULL if no reply */
301 302 303 304 305 306
  message = _dbus_connection_block_for_reply (pending->connection,
                                              pending->reply_serial,
                                              dbus_timeout_get_interval (pending->timeout));

  _dbus_connection_lock (pending->connection);
  _dbus_pending_call_complete_and_unlock (pending, message);
307 308
  if (message)
    dbus_message_unref (message);
309 310
}

311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
static DBusDataSlotAllocator slot_allocator;
_DBUS_DEFINE_GLOBAL_LOCK (pending_call_slots);

/**
 * Allocates an integer ID to be used for storing application-specific
 * data on any DBusPendingCall. The allocated ID may then be used
 * with dbus_pending_call_set_data() and dbus_pending_call_get_data().
 * The passed-in slot must be initialized to -1, and is filled in
 * with the slot ID. If the passed-in slot is not -1, it's assumed
 * to be already allocated, and its refcount is incremented.
 * 
 * The allocated slot is global, i.e. all DBusPendingCall objects will
 * have a slot with the given integer ID reserved.
 *
 * @param slot_p address of a global variable storing the slot
 * @returns #FALSE on failure (no memory)
 */
dbus_bool_t
dbus_pending_call_allocate_data_slot (dbus_int32_t *slot_p)
{
  return _dbus_data_slot_allocator_alloc (&slot_allocator,
                                          _DBUS_LOCK_NAME (pending_call_slots),
                                          slot_p);
}

/**
 * Deallocates a global ID for #DBusPendingCall data slots.
 * dbus_pending_call_get_data() and dbus_pending_call_set_data() may
 * no longer be used with this slot.  Existing data stored on existing
 * DBusPendingCall objects will be freed when the #DBusPendingCall is
 * finalized, but may not be retrieved (and may only be replaced if
 * someone else reallocates the slot).  When the refcount on the
 * passed-in slot reaches 0, it is set to -1.
 *
 * @param slot_p address storing the slot to deallocate
 */
void
dbus_pending_call_free_data_slot (dbus_int32_t *slot_p)
{
  _dbus_return_if_fail (*slot_p >= 0);
  
  _dbus_data_slot_allocator_free (&slot_allocator, slot_p);
}

/**
 * Stores a pointer on a #DBusPendingCall, along
 * with an optional function to be used for freeing
 * the data when the data is set again, or when
 * the pending call is finalized. The slot number
 * must have been allocated with dbus_pending_call_allocate_data_slot().
 *
 * @param pending the pending_call
 * @param slot the slot number
 * @param data the data to store
 * @param free_data_func finalizer function for the data
 * @returns #TRUE if there was enough memory to store the data
 */
dbus_bool_t
dbus_pending_call_set_data (DBusPendingCall  *pending,
                            dbus_int32_t      slot,
                            void             *data,
                            DBusFreeFunction  free_data_func)
{
  DBusFreeFunction old_free_func;
  void *old_data;
  dbus_bool_t retval;

  _dbus_return_val_if_fail (pending != NULL, FALSE);
  _dbus_return_val_if_fail (slot >= 0, FALSE);

  retval = _dbus_data_slot_list_set (&slot_allocator,
                                     &pending->slot_list,
                                     slot, data, free_data_func,
                                     &old_free_func, &old_data);

  if (retval)
    {
      if (old_free_func)
        (* old_free_func) (old_data);
    }

  return retval;
}

/**
 * Retrieves data previously set with dbus_pending_call_set_data().
 * The slot must still be allocated (must not have been freed).
 *
 * @param pending the pending_call
 * @param slot the slot to get data from
 * @returns the data, or #NULL if not found
 */
void*
dbus_pending_call_get_data (DBusPendingCall   *pending,
                            dbus_int32_t       slot)
{
  void *res;

  _dbus_return_val_if_fail (pending != NULL, NULL);

  res = _dbus_data_slot_list_get (&slot_allocator,
                                  &pending->slot_list,
                                  slot);

  return res;
}

418 419 420 421
/** @} */

#ifdef DBUS_BUILD_TESTS

422 423 424 425 426 427 428 429 430 431 432 433 434
/**
 * @ingroup DBusPendingCallInternals
 * Unit test for DBusPendingCall.
 *
 * @returns #TRUE on success.
 */
dbus_bool_t
_dbus_pending_call_test (const char *test_data_dir)
{  

  return TRUE;
}
#endif /* DBUS_BUILD_TESTS */