2003-04-27 Havoc Pennington <hp@pobox.com>
[platform/upstream/dbus.git] / bus / activation.c
index 54ddd94..3682eec 100644 (file)
@@ -45,6 +45,10 @@ struct BusActivation
   DBusHashTable *pending_activations;
   char *server_address;
   BusContext *context;
+  int n_pending_activations; /**< This is in fact the number of BusPendingActivationEntry,
+                              * i.e. number of pending activation requests, not pending
+                              * activations per se
+                              */
 };
 
 typedef struct
@@ -63,9 +67,11 @@ struct BusPendingActivationEntry
 
 typedef struct
 {
+  int refcount;
   BusActivation *activation;
   char *service_name;
   DBusList *entries;
+  int n_entries;
   DBusBabysitter *babysitter;
   DBusTimeout *timeout;
   unsigned int timeout_added : 1;
@@ -90,22 +96,35 @@ handle_timeout_callback (DBusTimeout   *timeout,
   BusPendingActivation *pending_activation = data;
 
   while (!dbus_timeout_handle (pending_activation->timeout))
-    bus_wait_for_memory ();
+    _dbus_wait_for_memory ();
+}
+
+static void
+bus_pending_activation_ref (BusPendingActivation *pending_activation)
+{
+  _dbus_assert (pending_activation->refcount > 0);
+  pending_activation->refcount += 1;
 }
 
 static void
-bus_pending_activation_free (BusPendingActivation *pending_activation)
+bus_pending_activation_unref (BusPendingActivation *pending_activation)
 {
   DBusList *link;
   
   if (pending_activation == NULL) /* hash table requires this */
     return;
 
+  _dbus_assert (pending_activation->refcount > 0);
+  pending_activation->refcount -= 1;
+
+  if (pending_activation->refcount > 0)
+    return;
+  
   if (pending_activation->timeout_added)
     {
-      bus_loop_remove_timeout (bus_context_get_loop (pending_activation->activation->context),
-                               pending_activation->timeout,
-                               handle_timeout_callback, pending_activation);
+      _dbus_loop_remove_timeout (bus_context_get_loop (pending_activation->activation->context),
+                                 pending_activation->timeout,
+                                 handle_timeout_callback, pending_activation);
       pending_activation->timeout_added = FALSE;
     }
 
@@ -136,6 +155,11 @@ bus_pending_activation_free (BusPendingActivation *pending_activation)
       link = _dbus_list_get_next_link (&pending_activation->entries, link);
     }
   _dbus_list_clear (&pending_activation->entries);
+
+  pending_activation->activation->n_pending_activations -=
+    pending_activation->n_entries;
+
+  _dbus_assert (pending_activation->activation->n_pending_activations >= 0);
   
   dbus_free (pending_activation);
 }
@@ -380,6 +404,7 @@ bus_activation_new (BusContext        *context,
   
   activation->refcount = 1;
   activation->context = context;
+  activation->n_pending_activations = 0;
   
   if (!_dbus_string_copy_data (address, &activation->server_address))
     {
@@ -396,7 +421,7 @@ bus_activation_new (BusContext        *context,
     }
 
   activation->pending_activations = _dbus_hash_table_new (DBUS_HASH_STRING, NULL,
-                                                         (DBusFreeFunction)bus_pending_activation_free);
+                                                         (DBusFreeFunction)bus_pending_activation_unref);
 
   if (activation->pending_activations == NULL)
     {
@@ -466,6 +491,75 @@ child_setup (void *data)
     }
 }
 
+typedef struct
+{
+  BusPendingActivation *pending_activation;
+  DBusPreallocatedHash *hash_entry;
+} RestorePendingData;
+
+static void
+restore_pending (void *data)
+{
+  RestorePendingData *d = data;
+
+  _dbus_assert (d->pending_activation != NULL);
+  _dbus_assert (d->hash_entry != NULL);
+
+  _dbus_verbose ("Restoring pending activation for service %s, has timeout = %d\n",
+                 d->pending_activation->service_name,
+                 d->pending_activation->timeout_added);
+  
+  _dbus_hash_table_insert_string_preallocated (d->pending_activation->activation->pending_activations,
+                                               d->hash_entry,
+                                               d->pending_activation->service_name, d->pending_activation);
+
+  bus_pending_activation_ref (d->pending_activation);
+  
+  d->hash_entry = NULL;
+}
+
+static void
+free_pending_restore_data (void *data)
+{
+  RestorePendingData *d = data;
+
+  if (d->hash_entry)
+    _dbus_hash_table_free_preallocated_entry (d->pending_activation->activation->pending_activations,
+                                              d->hash_entry);
+
+  bus_pending_activation_unref (d->pending_activation);
+  
+  dbus_free (d);
+}
+
+static dbus_bool_t
+add_restore_pending_to_transaction (BusTransaction       *transaction,
+                                    BusPendingActivation *pending_activation)
+{
+  RestorePendingData *d;
+
+  d = dbus_new (RestorePendingData, 1);
+  if (d == NULL)
+    return FALSE;
+  
+  d->pending_activation = pending_activation;
+  d->hash_entry = _dbus_hash_table_preallocate_entry (d->pending_activation->activation->pending_activations);
+  
+  bus_pending_activation_ref (d->pending_activation);
+  
+  if (d->hash_entry == NULL ||
+      !bus_transaction_add_cancel_hook (transaction, restore_pending, d,
+                                        free_pending_restore_data))
+    {
+      free_pending_restore_data (d);
+      return FALSE;
+    }
+
+  _dbus_verbose ("Saved pending activation to be restored if the transaction fails\n");
+  
+  return TRUE;
+}
+
 dbus_bool_t
 bus_activation_service_created (BusActivation  *activation,
                                const char     *service_name,
@@ -499,8 +593,7 @@ bus_activation_service_created (BusActivation  *activation,
              goto error;
            }
 
-         if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
-              !dbus_message_append_args (message,
+         if (!dbus_message_append_args (message,
                                         DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ACTIVATED,
                                         0))
            {
@@ -509,25 +602,31 @@ bus_activation_service_created (BusActivation  *activation,
              goto error;
            }
           
-         if (!bus_transaction_send_message (transaction, entry->connection, message))
+         if (!bus_transaction_send_from_driver (transaction, entry->connection, message))
            {
              dbus_message_unref (message);
              BUS_SET_OOM (error);
              goto error;
            }
-
+          
           dbus_message_unref (message);
        }
 
       link = next;
     }
+
+  if (!add_restore_pending_to_transaction (transaction, pending_activation))
+    {
+      _dbus_verbose ("Could not add cancel hook to transaction to revert removing pending activation\n");
+      BUS_SET_OOM (error);
+      goto error;
+    }
   
   _dbus_hash_table_remove_string (activation->pending_activations, service_name);
 
   return TRUE;
 
  error:
-  _dbus_hash_table_remove_string (activation->pending_activations, service_name);
   return FALSE;
 }
 
@@ -542,7 +641,6 @@ try_send_activation_failure (BusPendingActivation *pending_activation,
                              const DBusError      *how)
 {
   BusActivation *activation;
-  DBusMessage *message;
   DBusList *link;
   BusTransaction *transaction;
   
@@ -560,27 +658,13 @@ try_send_activation_failure (BusPendingActivation *pending_activation,
       
       if (dbus_connection_get_is_connected (entry->connection))
        {
-         message = dbus_message_new_error_reply (entry->activation_message,
-                                                  how->name,
-                                                  how->message);
-         if (!message)
+          if (!bus_transaction_send_error_reply (transaction,
+                                                 entry->connection,
+                                                 how,
+                                                 entry->activation_message))
             goto error;
-
-         if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS))
-            {
-             dbus_message_unref (message);
-             goto error;
-           }
-          
-         if (!bus_transaction_send_message (transaction, entry->connection, message))
-           {
-             dbus_message_unref (message);
-             goto error;
-           }
-
-          dbus_message_unref (message);
        }
-
+      
       link = next;
     }
 
@@ -604,7 +688,7 @@ pending_activation_failed (BusPendingActivation *pending_activation,
 {
   /* FIXME use preallocated OOM messages instead of bus_wait_for_memory() */
   while (!try_send_activation_failure (pending_activation, how))
-    bus_wait_for_memory ();
+    _dbus_wait_for_memory ();
 
   /* Destroy this pending activation */
   _dbus_hash_table_remove_string (pending_activation->activation->pending_activations,
@@ -624,8 +708,18 @@ babysitter_watch_callback (DBusWatch     *watch,
   
   _dbus_babysitter_ref (babysitter);
   
-  retval = _dbus_babysitter_handle_watch (babysitter, watch, condition);
-
+  retval = dbus_watch_handle (watch, condition);
+
+  /* FIXME this is broken in the same way that
+   * connection watches used to be; there should be
+   * a separate callback for status change, instead
+   * of doing "if we handled a watch status might
+   * have changed"
+   *
+   * Fixing this lets us move dbus_watch_handle
+   * calls into dbus-mainloop.c
+   */
+  
   if (_dbus_babysitter_get_child_exited (babysitter))
     {
       DBusError error;
@@ -650,9 +744,9 @@ add_babysitter_watch (DBusWatch      *watch,
 {
   BusPendingActivation *pending_activation = data;
 
-  return bus_loop_add_watch (bus_context_get_loop (pending_activation->activation->context),
-                             watch, babysitter_watch_callback, pending_activation,
-                             NULL);
+  return _dbus_loop_add_watch (bus_context_get_loop (pending_activation->activation->context),
+                               watch, babysitter_watch_callback, pending_activation,
+                               NULL);
 }
 
 static void
@@ -661,8 +755,8 @@ remove_babysitter_watch (DBusWatch      *watch,
 {
   BusPendingActivation *pending_activation = data;
   
-  bus_loop_remove_watch (bus_context_get_loop (pending_activation->activation->context),
-                         watch, babysitter_watch_callback, pending_activation);
+  _dbus_loop_remove_watch (bus_context_get_loop (pending_activation->activation->context),
+                           watch, babysitter_watch_callback, pending_activation);
 }
 
 static dbus_bool_t
@@ -690,6 +784,45 @@ pending_activation_timed_out (void *data)
   return TRUE;
 }
 
+static void
+cancel_pending (void *data)
+{
+  BusPendingActivation *pending_activation = data;
+
+  _dbus_verbose ("Canceling pending activation of %s\n",
+                 pending_activation->service_name);
+
+  if (pending_activation->babysitter)
+    _dbus_babysitter_kill_child (pending_activation->babysitter);
+  
+  _dbus_hash_table_remove_string (pending_activation->activation->pending_activations,
+                                  pending_activation->service_name);
+}
+
+static void
+free_pending_cancel_data (void *data)
+{
+  BusPendingActivation *pending_activation = data;
+  
+  bus_pending_activation_unref (pending_activation);
+}
+
+static dbus_bool_t
+add_cancel_pending_to_transaction (BusTransaction       *transaction,
+                                   BusPendingActivation *pending_activation)
+{  
+  if (!bus_transaction_add_cancel_hook (transaction, cancel_pending,
+                                        pending_activation,
+                                        free_pending_cancel_data))
+    return FALSE;
+
+  bus_pending_activation_ref (pending_activation); 
+  
+  _dbus_verbose ("Saved pending activation to be canceled if the transaction fails\n");
+  
+  return TRUE;
+}
+
 dbus_bool_t
 bus_activation_activate_service (BusActivation  *activation,
                                 DBusConnection *connection,
@@ -707,6 +840,15 @@ bus_activation_activate_service (BusActivation  *activation,
   dbus_bool_t retval;
 
   _DBUS_ASSERT_ERROR_IS_CLEAR (error);
+
+  if (activation->n_pending_activations >=
+      bus_context_get_max_pending_activations (activation->context))
+    {
+      dbus_set_error (error, DBUS_ERROR_LIMITS_EXCEEDED,
+                     "The maximum number of pending activations has been reached, activation of %s failed",
+                     service_name);
+      return FALSE;
+    }
   
   entry = _dbus_hash_table_lookup_string (activation->entries, service_name);
 
@@ -722,28 +864,34 @@ bus_activation_activate_service (BusActivation  *activation,
   _dbus_string_init_const (&service_str, service_name);
   if (bus_registry_lookup (bus_context_get_registry (activation->context), &service_str) != NULL)
     {
+      _dbus_verbose ("Service \"%s\" is already active\n", service_name);
+      
       message = dbus_message_new_reply (activation_message);
 
       if (!message)
        {
+          _dbus_verbose ("No memory to create reply to activate message\n");
          BUS_SET_OOM (error);
          return FALSE;
        }
 
-      if (!dbus_message_set_sender (message, DBUS_SERVICE_DBUS) ||
-          !dbus_message_append_args (message,
+      if (!dbus_message_append_args (message,
                                     DBUS_TYPE_UINT32, DBUS_ACTIVATION_REPLY_ALREADY_ACTIVE, 
                                     0))
        {
+          _dbus_verbose ("No memory to set args of reply to activate message\n");
          BUS_SET_OOM (error);
          dbus_message_unref (message);
          return FALSE;
        }
 
-      retval = bus_transaction_send_message (transaction, connection, message);
+      retval = bus_transaction_send_from_driver (transaction, connection, message);
       dbus_message_unref (message);
       if (!retval)
-       BUS_SET_OOM (error);
+        {
+          _dbus_verbose ("Failed to send reply\n");
+          BUS_SET_OOM (error);
+        }
 
       return retval;
     }
@@ -751,6 +899,7 @@ bus_activation_activate_service (BusActivation  *activation,
   pending_activation_entry = dbus_new0 (BusPendingActivationEntry, 1);
   if (!pending_activation_entry)
     {
+      _dbus_verbose ("Failed to create pending activation entry\n");
       BUS_SET_OOM (error);
       return FALSE;
     }
@@ -766,29 +915,38 @@ bus_activation_activate_service (BusActivation  *activation,
     {
       if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
        {
+          _dbus_verbose ("Failed to append a new entry to pending activation\n");
+          
          BUS_SET_OOM (error);
          bus_pending_activation_entry_free (pending_activation_entry);
-
          return FALSE;
        }
+
+      pending_activation->n_entries += 1;
+      pending_activation->activation->n_pending_activations += 1;
     }
   else
     {
       pending_activation = dbus_new0 (BusPendingActivation, 1);
       if (!pending_activation)
        {
+          _dbus_verbose ("Failed to create pending activation\n");
+          
          BUS_SET_OOM (error);
          bus_pending_activation_entry_free (pending_activation_entry);   
          return FALSE;
        }
 
       pending_activation->activation = activation;
+      pending_activation->refcount = 1;
       
       pending_activation->service_name = _dbus_strdup (service_name);
       if (!pending_activation->service_name)
        {
+          _dbus_verbose ("Failed to copy service name for pending activation\n");
+          
          BUS_SET_OOM (error);
-         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_unref (pending_activation);
          bus_pending_activation_entry_free (pending_activation_entry);   
          return FALSE;
        }
@@ -800,20 +958,24 @@ bus_activation_activate_service (BusActivation  *activation,
                            NULL);
       if (!pending_activation->timeout)
        {
+          _dbus_verbose ("Failed to create timeout for pending activation\n");
+          
          BUS_SET_OOM (error);
-         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_unref (pending_activation);
          bus_pending_activation_entry_free (pending_activation_entry);   
          return FALSE;
        }
 
-      if (!bus_loop_add_timeout (bus_context_get_loop (activation->context),
-                                 pending_activation->timeout,
-                                 handle_timeout_callback,
-                                 pending_activation,
-                                 NULL))
+      if (!_dbus_loop_add_timeout (bus_context_get_loop (activation->context),
+                                   pending_activation->timeout,
+                                   handle_timeout_callback,
+                                   pending_activation,
+                                   NULL))
        {
+          _dbus_verbose ("Failed to add timeout for pending activation\n");
+          
          BUS_SET_OOM (error);
-         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_unref (pending_activation);
          bus_pending_activation_entry_free (pending_activation_entry);   
          return FALSE;
        }
@@ -822,20 +984,37 @@ bus_activation_activate_service (BusActivation  *activation,
       
       if (!_dbus_list_append (&pending_activation->entries, pending_activation_entry))
        {
+          _dbus_verbose ("Failed to add entry to just-created pending activation\n");
+          
          BUS_SET_OOM (error);
-         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_unref (pending_activation);
          bus_pending_activation_entry_free (pending_activation_entry);   
          return FALSE;
        }
+
+      pending_activation->n_entries += 1;
+      pending_activation->activation->n_pending_activations += 1;
       
       if (!_dbus_hash_table_insert_string (activation->pending_activations,
-                                          pending_activation->service_name, pending_activation))
+                                          pending_activation->service_name,
+                                           pending_activation))
        {
+          _dbus_verbose ("Failed to put pending activation in hash table\n");
+          
          BUS_SET_OOM (error);
-         bus_pending_activation_free (pending_activation);
+         bus_pending_activation_unref (pending_activation);
          return FALSE;
        }
     }
+
+  if (!add_cancel_pending_to_transaction (transaction, pending_activation))
+    {
+      _dbus_verbose ("Failed to add pending activation cancel hook to transaction\n");
+      BUS_SET_OOM (error);
+      _dbus_hash_table_remove_string (activation->pending_activations,
+                                     pending_activation->service_name);
+      return FALSE;
+    }
   
   /* FIXME we need to support a full command line, not just a single
    * argv[0]
@@ -849,8 +1028,8 @@ bus_activation_activate_service (BusActivation  *activation,
                                           child_setup, activation, 
                                           error))
     {
-      _dbus_hash_table_remove_string (activation->pending_activations,
-                                     pending_activation->service_name);
+      _dbus_verbose ("Failed to spawn child\n");
+      _DBUS_ASSERT_ERROR_IS_SET (error);
       return FALSE;
     }
 
@@ -864,9 +1043,7 @@ bus_activation_activate_service (BusActivation  *activation,
                                              NULL))
     {
       BUS_SET_OOM (error);
-      
-      _dbus_hash_table_remove_string (activation->pending_activations,
-                                     pending_activation->service_name);
+      _dbus_verbose ("Failed to set babysitter watch functions\n");
       return FALSE;
     }