From 17fbe2b702cdc880abd6cbe117e620b6432f42e0 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Tue, 24 Dec 2002 06:37:33 +0000 Subject: 2002-12-24 Havoc Pennington * glib/dbus-gthread.c: fix include * glib/dbus-glib.h: rename DBusMessageHandler for now. I think glib API needs to change, though, as you don't want to use DBusMessageFunction, you want to use the DBusMessageHandler object. Probably dbus_connection_open_with_g_main_loop() and dbus_connection_setup_g_main_loop() or something like that (but think of better names...) that just create a connection that has watch/timeout functions etc. already set up. * dbus/dbus-connection.c (dbus_connection_send_message_with_reply): new function just to show how the message handler helps us deal with replies. * dbus/dbus-list.c (_dbus_list_remove_last): new function * dbus/dbus-string.c (_dbus_string_test): free a string that wasn't * dbus/dbus-hash.c: use memory pools for the hash entries (rebuild_table): be more paranoid about overflow, and shrink table when we can (_dbus_hash_test): reduce number of sprintfs and write valid C89. Add tests for case where we grow and then shrink the hash table. * dbus/dbus-mempool.h, dbus/dbus-mempool.c: memory pools * dbus/dbus-connection.c (dbus_connection_register_handler) (dbus_connection_unregister_handler): new functions * dbus/dbus-message.c (dbus_message_get_name): new * dbus/dbus-list.c: fix docs typo * dbus/dbus-message-handler.h, dbus/dbus-message-handler.c: an object representing a handler for messages. --- ChangeLog | 41 ++++ dbus/Makefile.am | 4 + dbus/dbus-connection-internal.h | 14 ++ dbus/dbus-connection.c | 413 +++++++++++++++++++++++++++++++++++- dbus/dbus-connection.h | 35 +++- dbus/dbus-hash.c | 238 ++++++++++++++++----- dbus/dbus-list.c | 35 +++- dbus/dbus-list.h | 2 + dbus/dbus-mempool.c | 454 ++++++++++++++++++++++++++++++++++++++++ dbus/dbus-mempool.h | 44 ++++ dbus/dbus-message-handler.c | 265 +++++++++++++++++++++++ dbus/dbus-message-handler.h | 59 ++++++ dbus/dbus-message.c | 13 ++ dbus/dbus-message.h | 1 + dbus/dbus-string.c | 4 +- dbus/dbus-test.c | 18 +- dbus/dbus-test.h | 8 +- dbus/dbus.h | 1 + glib/dbus-glib.h | 6 +- glib/dbus-gmain.c | 2 +- glib/dbus-gthread.c | 2 +- 21 files changed, 1587 insertions(+), 72 deletions(-) create mode 100644 dbus/dbus-mempool.c create mode 100644 dbus/dbus-mempool.h create mode 100644 dbus/dbus-message-handler.c create mode 100644 dbus/dbus-message-handler.h diff --git a/ChangeLog b/ChangeLog index da0eac6f..bf0d8846 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,44 @@ +2002-12-24 Havoc Pennington + + * glib/dbus-gthread.c: fix include + + * glib/dbus-glib.h: rename DBusMessageHandler for now. + I think glib API needs to change, though, as you don't + want to use DBusMessageFunction, you want to use the + DBusMessageHandler object. Probably + dbus_connection_open_with_g_main_loop() + and dbus_connection_setup_g_main_loop() or something like that + (but think of better names...) that just create a connection + that has watch/timeout functions etc. already set up. + + * dbus/dbus-connection.c + (dbus_connection_send_message_with_reply): new function just to + show how the message handler helps us deal with replies. + + * dbus/dbus-list.c (_dbus_list_remove_last): new function + + * dbus/dbus-string.c (_dbus_string_test): free a string that + wasn't + + * dbus/dbus-hash.c: use memory pools for the hash entries + (rebuild_table): be more paranoid about overflow, and + shrink table when we can + (_dbus_hash_test): reduce number of sprintfs and write + valid C89. Add tests for case where we grow and then + shrink the hash table. + + * dbus/dbus-mempool.h, dbus/dbus-mempool.c: memory pools + + * dbus/dbus-connection.c (dbus_connection_register_handler) + (dbus_connection_unregister_handler): new functions + + * dbus/dbus-message.c (dbus_message_get_name): new + + * dbus/dbus-list.c: fix docs typo + + * dbus/dbus-message-handler.h, dbus/dbus-message-handler.c: + an object representing a handler for messages. + 2002-12-16 Anders Carlsson * glib/dbus-glib.h: diff --git a/dbus/Makefile.am b/dbus/Makefile.am index b5071f0b..74b5dfbb 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -11,6 +11,7 @@ dbusinclude_HEADERS= \ dbus-macros.h \ dbus-memory.h \ dbus-message.h \ + dbus-message-handler.h \ dbus-server.h \ dbus-threads.h \ dbus-types.h @@ -21,6 +22,7 @@ libdbus_1_la_SOURCES= \ dbus-errors.c \ dbus-memory.c \ dbus-message.c \ + dbus-message-handler.c \ dbus-message-internal.h \ dbus-server.c \ dbus-server-protected.h \ @@ -51,6 +53,8 @@ libdbus_convenience_la_SOURCES= \ dbus-internals.h \ dbus-list.c \ dbus-list.h \ + dbus-mempool.c \ + dbus-mempool.h \ dbus-string.c \ dbus-string.h \ dbus-sysdeps.c \ diff --git a/dbus/dbus-connection-internal.h b/dbus/dbus-connection-internal.h index 7260bde0..cf932ddd 100644 --- a/dbus/dbus-connection-internal.h +++ b/dbus/dbus-connection-internal.h @@ -59,6 +59,20 @@ void _dbus_connection_do_iteration (DBusConnection *connect void _dbus_connection_transport_error (DBusConnection *connection, DBusResultCode result_code); +void _dbus_connection_handler_destroyed (DBusConnection *connection, + DBusMessageHandler *handler); + + +dbus_bool_t _dbus_message_handler_add_connection (DBusMessageHandler *handler, + DBusConnection *connection); +void _dbus_message_handler_remove_connection (DBusMessageHandler *handler, + DBusConnection *connection); +DBusHandlerResult _dbus_message_handler_handle_message (DBusMessageHandler *handler, + DBusConnection *connection, + DBusMessage *message); + + + DBUS_END_DECLS; #endif /* DBUS_CONNECTION_INTERNAL_H */ diff --git a/dbus/dbus-connection.c b/dbus/dbus-connection.c index 3c6df57f..4a40f0b0 100644 --- a/dbus/dbus-connection.c +++ b/dbus/dbus-connection.c @@ -27,6 +27,7 @@ #include "dbus-watch.h" #include "dbus-connection-internal.h" #include "dbus-list.h" +#include "dbus-hash.h" #include "dbus-message-internal.h" /** @@ -75,6 +76,10 @@ struct DBusConnection DBusConnectionErrorFunction error_function; /**< Callback for errors. */ void *error_data; /**< Data for error callback. */ DBusFreeFunction error_free_data_function; /**< Free function for error callback data. */ + DBusHashTable *handler_table; /**< Table of registered DBusMessageHandler */ + DBusList *filter_list; /**< List of filters. */ + int filters_serial; /**< Increments when the list of filters is changed. */ + int handlers_serial; /**< Increments when the handler table is changed. */ }; /** @@ -270,13 +275,21 @@ _dbus_connection_new_for_transport (DBusTransport *transport) { DBusConnection *connection; DBusWatchList *watch_list; + DBusHashTable *handler_table; watch_list = NULL; connection = NULL; + handler_table = NULL; watch_list = _dbus_watch_list_new (); if (watch_list == NULL) goto error; + + handler_table = + _dbus_hash_table_new (DBUS_HASH_STRING, + dbus_free, NULL); + if (handler_table == NULL) + goto error; connection = dbus_new0 (DBusConnection, 1); if (connection == NULL) @@ -285,7 +298,9 @@ _dbus_connection_new_for_transport (DBusTransport *transport) connection->refcount = 1; connection->transport = transport; connection->watches = watch_list; - + connection->handler_table = handler_table; + connection->filter_list = NULL; + _dbus_transport_ref (transport); _dbus_transport_set_connection (transport, connection); @@ -293,14 +308,56 @@ _dbus_connection_new_for_transport (DBusTransport *transport) error: - _dbus_assert (connection == NULL); + if (connection != NULL) + dbus_free (connection); + if (handler_table) + _dbus_hash_table_unref (handler_table); + if (watch_list) _dbus_watch_list_free (watch_list); return NULL; } +/** + * Used to notify a connection when a DBusMessageHandler is + * destroyed, so the connection can drop any reference + * to the handler. + * + * @param connection the connection + * @param handler the handler + */ +void +_dbus_connection_handler_destroyed (DBusConnection *connection, + DBusMessageHandler *handler) +{ + DBusHashIter iter; + DBusList *link; + + _dbus_hash_iter_init (connection->handler_table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + DBusMessageHandler *h = _dbus_hash_iter_get_value (&iter); + + if (h == handler) + _dbus_hash_iter_remove_entry (&iter); + } + + link = _dbus_list_get_first_link (&connection->filter_list); + while (link != NULL) + { + DBusMessageHandler *h = link->data; + DBusList *next = _dbus_list_get_next_link (&connection->filter_list, link); + + if (h == handler) + _dbus_list_remove_link (&connection->filter_list, + link); + + link = next; + } +} + /** @} */ /** @@ -377,11 +434,38 @@ dbus_connection_unref (DBusConnection *connection) connection->refcount -= 1; if (connection->refcount == 0) { + DBusHashIter iter; + DBusList *link; + /* free error data as a side effect */ dbus_connection_set_error_function (connection, NULL, NULL, NULL); _dbus_watch_list_free (connection->watches); + + _dbus_hash_iter_init (connection->handler_table, &iter); + while (_dbus_hash_iter_next (&iter)) + { + DBusMessageHandler *h = _dbus_hash_iter_get_value (&iter); + + _dbus_message_handler_remove_connection (h, connection); + } + + link = _dbus_list_get_first_link (&connection->filter_list); + while (link != NULL) + { + DBusMessageHandler *h = link->data; + DBusList *next = _dbus_list_get_next_link (&connection->filter_list, link); + + _dbus_message_handler_remove_connection (h, connection); + + link = next; + } + + _dbus_hash_table_unref (connection->handler_table); + connection->handler_table = NULL; + + _dbus_list_clear (&connection->filter_list); _dbus_list_foreach (&connection->outgoing_messages, (DBusForeachFunction) dbus_message_unref, @@ -470,6 +554,66 @@ dbus_connection_send_message (DBusConnection *connection, return TRUE; } +/** + * Queues a message to send, as with dbus_connection_send_message(), + * but also sets up a DBusMessageHandler to receive a reply to the + * message. If no reply is received in the given timeout_milliseconds, + * expires the pending reply and sends the DBusMessageHandler a + * synthetic error reply (generated in-process, not by the remote + * application) indicating that a timeout occurred. + * + * Reply handlers see their replies after message filters see them, + * but before message handlers added with + * dbus_connection_register_handler() see them, regardless of the + * reply message's name. Reply handlers are only handed a single + * message as a reply, after a reply has been seen the handler is + * removed. If a filter filters out the reply before the handler sees + * it, the handler is not removed but the timeout will immediately + * fire again. If a filter was dumb and kept removing the timeout + * reply then we'd get in an infinite loop. + * + * If #NULL is passed for the reply_handler, the timeout reply will + * still be generated and placed into the message queue, but no + * specific message handler will receive the reply. + * + * If -1 is passed for the timeout, a sane default timeout is used. -1 + * is typically the best value for the timeout for this reason, unless + * you want a very short or very long timeout. There is no way to + * avoid a timeout entirely, other than passing INT_MAX for the + * timeout to postpone it indefinitely. + * + * @param connection the connection + * @param message the message to send + * @param reply_handler message handler expecting the reply, or #NULL + * @param timeout_milliseconds timeout in milliseconds or -1 for default + * @param result return location for result code + * @returns #TRUE if the message is successfully queued, #FALSE if no memory. + * + * @todo this function isn't implemented because we need message serials + * and other slightly more rich DBusMessage implementation in order to + * implement it. The basic idea will be to keep a hash of serials we're + * expecting a reply to, and also to add a way to tell GLib or Qt to + * install a timeout. Then install a timeout which is the shortest + * timeout of any pending reply. + * + * @todo implement non-reentrant "block for reply" variant. i.e. send + * a message, block until we get a reply, then pull reply out of + * message queue and return it, *without dispatching any handlers for + * any other messages* - used for non-reentrant "method calls" We can + * block properly for this using _dbus_connection_do_iteration(). + * + */ +dbus_bool_t +dbus_connection_send_message_with_reply (DBusConnection *connection, + DBusMessage *message, + DBusMessageHandler *reply_handler, + int timeout_milliseconds, + DBusResultCode *result) +{ + /* FIXME */ + return dbus_connection_send_message (connection, message, result); +} + /** * Blocks until the outgoing message queue is empty. * @@ -531,6 +675,90 @@ dbus_connection_pop_message (DBusConnection *connection) return NULL; } +/** + * Pops the first-received message from the current incoming message + * queue, runs any handlers for it, then unrefs the message. + * + * @param connection the connection + * @returns #TRUE if the queue is not empty after dispatch + * + * @todo this function is not properly robust against reentrancy, + * that is, if handlers are added/removed while dispatching + * a message, things will get messed up. + */ +dbus_bool_t +dbus_connection_dispatch_message (DBusConnection *connection) +{ + DBusMessage *message; + int filter_serial; + int handler_serial; + DBusList *link; + DBusHandlerResult result; + const char *name; + + dbus_connection_ref (connection); + + message = dbus_connection_pop_message (connection); + if (message == NULL) + return FALSE; + + filter_serial = connection->filters_serial; + handler_serial = connection->handlers_serial; + + result = DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + + link = _dbus_list_get_first_link (&connection->filter_list); + while (link != NULL) + { + DBusMessageHandler *handler = link->data; + DBusList *next = _dbus_list_get_next_link (&connection->filter_list, link); + + result = _dbus_message_handler_handle_message (handler, connection, + message); + + if (result == DBUS_HANDLER_RESULT_REMOVE_MESSAGE) + goto out; + + if (filter_serial != connection->filters_serial) + { + _dbus_warn ("Message filters added or removed while dispatching filters - not currently supported!\n"); + goto out; + } + + link = next; + } + + name = dbus_message_get_name (message); + if (name != NULL) + { + DBusMessageHandler *handler; + + handler = _dbus_hash_table_lookup_string (connection->handler_table, + name); + if (handler != NULL) + { + + result = _dbus_message_handler_handle_message (handler, connection, + message); + + if (result == DBUS_HANDLER_RESULT_REMOVE_MESSAGE) + goto out; + + if (handler_serial != connection->handlers_serial) + { + _dbus_warn ("Message handlers added or removed while dispatching handlers - not currently supported!\n"); + goto out; + } + } + } + + out: + dbus_connection_unref (connection); + dbus_message_unref (message); + + return connection->n_incoming > 0; +} + /** * Sets the error handler function for the connection. * @@ -623,4 +851,185 @@ dbus_connection_handle_watch (DBusConnection *connection, watch, condition); } +/** + * Adds a message filter. Filters are handlers that are run on + * all incoming messages, prior to the normal handlers + * registered with dbus_connection_register_handler(). + * Filters are run in the order that they were added. + * The same handler can be added as a filter more than once, in + * which case it will be run more than once. + * + * @param connection the connection + * @param handler the handler + * @returns #TRUE on success, #FALSE if not enough memory. + */ +dbus_bool_t +dbus_connection_add_filter (DBusConnection *connection, + DBusMessageHandler *handler) +{ + if (!_dbus_message_handler_add_connection (handler, connection)) + return FALSE; + + if (!_dbus_list_append (&connection->filter_list, + handler)) + { + _dbus_message_handler_remove_connection (handler, connection); + return FALSE; + } + + connection->filters_serial += 1; + + return TRUE; +} + +/** + * Removes a previously-added message filter. It is a programming + * error to call this function for a handler that has not + * been added as a filter. If the given handler was added + * more than once, only one instance of it will be removed + * (the most recently-added instance). + * + * @param connection the connection + * @param handler the handler to remove + * + */ +void +dbus_connection_remove_filter (DBusConnection *connection, + DBusMessageHandler *handler) +{ + if (!_dbus_list_remove_last (&connection->filter_list, handler)) + { + _dbus_warn ("Tried to remove a DBusConnection filter that had not been added\n"); + return; + } + + _dbus_message_handler_remove_connection (handler, connection); + + connection->filters_serial += 1; +} + +/** + * Registers a handler for a list of message names. A single handler + * can be registered for any number of message names, but each message + * name can only have one handler at a time. It's not allowed to call + * this function with the name of a message that already has a + * handler. If the function returns #FALSE, the handlers were not + * registered due to lack of memory. + * + * @param connection the connection + * @param handler the handler + * @param messages_to_handle the messages to handle + * @param n_messages the number of message names in messages_to_handle + * @returns #TRUE on success, #FALSE if no memory or another handler already exists + * + **/ +dbus_bool_t +dbus_connection_register_handler (DBusConnection *connection, + DBusMessageHandler *handler, + const char **messages_to_handle, + int n_messages) +{ + int i; + + i = 0; + while (i < n_messages) + { + DBusHashIter iter; + char *key; + + key = _dbus_strdup (messages_to_handle[i]); + if (key == NULL) + goto failed; + + if (!_dbus_hash_iter_lookup (connection->handler_table, + key, TRUE, + &iter)) + { + dbus_free (key); + goto failed; + } + + if (_dbus_hash_iter_get_value (&iter) != NULL) + { + _dbus_warn ("Bug in application: attempted to register a second handler for %s\n", + messages_to_handle[i]); + dbus_free (key); /* won't have replaced the old key with the new one */ + goto failed; + } + + if (!_dbus_message_handler_add_connection (handler, connection)) + { + _dbus_hash_iter_remove_entry (&iter); + /* key has freed on nuking the entry */ + goto failed; + } + + _dbus_hash_iter_set_value (&iter, handler); + + connection->handlers_serial += 1; + + ++i; + } + + return TRUE; + + failed: + /* unregister everything registered so far, + * so we don't fail partially + */ + dbus_connection_unregister_handler (connection, + handler, + messages_to_handle, + i); + + return FALSE; +} + +/** + * Unregisters a handler for a list of message names. The handlers + * must have been previously registered. + * + * @param connection the connection + * @param handler the handler + * @param messages_to_handle the messages to handle + * @param n_messages the number of message names in messages_to_handle + * + **/ +void +dbus_connection_unregister_handler (DBusConnection *connection, + DBusMessageHandler *handler, + const char **messages_to_handle, + int n_messages) +{ + int i; + + i = 0; + while (i < n_messages) + { + DBusHashIter iter; + + if (!_dbus_hash_iter_lookup (connection->handler_table, + (char*) messages_to_handle[i], FALSE, + &iter)) + { + _dbus_warn ("Bug in application: attempted to unregister handler for %s which was not registered\n", + messages_to_handle[i]); + } + else if (_dbus_hash_iter_get_value (&iter) != handler) + { + _dbus_warn ("Bug in application: attempted to unregister handler for %s which was registered by a different handler\n", + messages_to_handle[i]); + } + else + { + _dbus_hash_iter_remove_entry (&iter); + _dbus_message_handler_remove_connection (handler, connection); + } + + ++i; + } + + connection->handlers_serial += 1; +} + /** @} */ diff --git a/dbus/dbus-connection.h b/dbus/dbus-connection.h index ceb5728f..518295b2 100644 --- a/dbus/dbus-connection.h +++ b/dbus/dbus-connection.h @@ -35,6 +35,13 @@ DBUS_BEGIN_DECLS; typedef struct DBusConnection DBusConnection; typedef struct DBusWatch DBusWatch; +typedef struct DBusMessageHandler DBusMessageHandler; + +typedef enum +{ + DBUS_HANDLER_RESULT_REMOVE_MESSAGE, /**< Remove this message, no further processing. */ + DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS /**< Run any additional handlers that are interested on this message. */ +} DBusHandlerResult; typedef enum { @@ -63,15 +70,21 @@ void dbus_connection_ref (DBusConnection *connection); void dbus_connection_unref (DBusConnection *connection); void dbus_connection_disconnect (DBusConnection *connection); dbus_bool_t dbus_connection_get_is_connected (DBusConnection *connection); -dbus_bool_t dbus_connection_send_message (DBusConnection *connection, - DBusMessage *message, - DBusResultCode *result); void dbus_connection_flush (DBusConnection *connection); int dbus_connection_get_n_messages (DBusConnection *connection); DBusMessage* dbus_connection_peek_message (DBusConnection *connection); DBusMessage* dbus_connection_pop_message (DBusConnection *connection); +dbus_bool_t dbus_connection_dispatch_message (DBusConnection *connection); +dbus_bool_t dbus_connection_send_message (DBusConnection *connection, + DBusMessage *message, + DBusResultCode *result); +dbus_bool_t dbus_connection_send_message_with_reply (DBusConnection *connection, + DBusMessage *message, + DBusMessageHandler *reply_handler, + int timeout_milliseconds, + DBusResultCode *result); void dbus_connection_set_error_function (DBusConnection *connection, DBusConnectionErrorFunction error_function, @@ -86,7 +99,6 @@ void dbus_connection_handle_watch (DBusConnection *connectio DBusWatch *watch, unsigned int condition); - int dbus_watch_get_fd (DBusWatch *watch); unsigned int dbus_watch_get_flags (DBusWatch *watch); void* dbus_watch_get_data (DBusWatch *watch); @@ -95,6 +107,21 @@ void dbus_watch_set_data (DBusWatch *watch, DBusFreeFunction free_data_function); +/* Handlers */ +dbus_bool_t dbus_connection_add_filter (DBusConnection *connection, + DBusMessageHandler *handler); +void dbus_connection_remove_filter (DBusConnection *connection, + DBusMessageHandler *handler); + +dbus_bool_t dbus_connection_register_handler (DBusConnection *connection, + DBusMessageHandler *handler, + const char **messages_to_handle, + int n_messages); +void dbus_connection_unregister_handler (DBusConnection *connection, + DBusMessageHandler *handler, + const char **messages_to_handle, + int n_messages); + DBUS_END_DECLS; #endif /* DBUS_CONNECTION_H */ diff --git a/dbus/dbus-hash.c b/dbus/dbus-hash.c index f4a22586..9cec1c1d 100644 --- a/dbus/dbus-hash.c +++ b/dbus/dbus-hash.c @@ -76,6 +76,7 @@ #include "dbus-hash.h" #include "dbus-internals.h" +#include "dbus-mempool.h" /** * @defgroup DBusHashTable Hash table @@ -92,13 +93,6 @@ * * The guts of DBusHashTable. * - * @todo rebuild_table() should be modified to also shrink the hash bucket - * array when appropriate; otherwise if a hash table has been - * very large but is now small, iteration becomes inefficient. - * We should still only shrink when adding hash entries though, not - * when removing them, so that you can still iterate over the hash - * removing entries. So if you added 5000, removed 4000, the - * shrinking would happen next time an entry was added. * @{ */ @@ -114,6 +108,15 @@ * preliminary values that are arbitrarily similar will end up in * different buckets. The hash function was taken from a * random-number generator. (This is used to hash integers.) + * + * The down_shift drops off the high bits of the hash index, and + * decreases as we increase the number of hash buckets (to keep more + * range in the hash index). The mask also strips high bits and strips + * fewer high bits as the number of hash buckets increases. + * I don't understand two things: why is the initial downshift 28 + * to keep 4 bits when the initial mask is 011 to keep 2 bits, + * and why do we have both a mask and a downshift? + * */ #define RANDOM_INDEX(table, i) \ (((((long) (i))*1103515245) >> (table)->down_shift) & (table)->mask) @@ -121,6 +124,7 @@ /** * Initial number of buckets in hash table (hash table statically * allocates its buckets for this size and below). + * The initial mask has to be synced to this. */ #define DBUS_SMALL_HASH_TABLE 4 @@ -176,9 +180,12 @@ struct DBusHashTable { int n_entries; /**< Total number of entries present * in table. */ - int rebuild_size; /**< Enlarge table when numEntries gets + int hi_rebuild_size; /**< Enlarge table when n_entries gets * to be this large. */ + int lo_rebuild_size; /**< Shrink table when n_entries gets + * below this. + */ int down_shift; /**< Shift count used in hashing * function. Designed to use high- * order bits of randomized keys. @@ -193,6 +200,8 @@ struct DBusHashTable { DBusFreeFunction free_key_function; /**< Function to free keys */ DBusFreeFunction free_value_function; /**< Function to free values */ + + DBusMemPool *entry_pool; /**< Memory pool for hash entries */ }; /** @@ -269,22 +278,34 @@ _dbus_hash_table_new (DBusHashType type, DBusFreeFunction value_free_function) { DBusHashTable *table; - + DBusMemPool *entry_pool; + table = dbus_new0 (DBusHashTable, 1); if (table == NULL) return NULL; + + entry_pool = _dbus_mem_pool_new (sizeof (DBusHashEntry), TRUE); + if (entry_pool == NULL) + { + dbus_free (table); + return NULL; + } table->refcount = 1; - + table->entry_pool = entry_pool; + _dbus_assert (DBUS_SMALL_HASH_TABLE == _DBUS_N_ELEMENTS (table->static_buckets)); table->buckets = table->static_buckets; table->n_buckets = DBUS_SMALL_HASH_TABLE; table->n_entries = 0; - table->rebuild_size = DBUS_SMALL_HASH_TABLE * REBUILD_MULTIPLIER; + table->hi_rebuild_size = DBUS_SMALL_HASH_TABLE * REBUILD_MULTIPLIER; + table->lo_rebuild_size = 0; table->down_shift = 28; table->mask = 3; table->key_type = type; + + _dbus_assert (table->mask < table->n_buckets); switch (table->key_type) { @@ -330,12 +351,12 @@ _dbus_hash_table_unref (DBusHashTable *table) if (table->refcount == 0) { +#if 0 DBusHashEntry *entry; DBusHashEntry *next; int i; /* Free the entries in the table. */ - for (i = 0; i < table->n_buckets; i++) { entry = table->buckets[i]; @@ -348,7 +369,11 @@ _dbus_hash_table_unref (DBusHashTable *table) entry = next; } } - +#else + /* We can do this very quickly with memory pools ;-) */ + _dbus_mem_pool_free (table->entry_pool); +#endif + /* Free the bucket array, if it was dynamically allocated. */ if (table->buckets != table->static_buckets) dbus_free (table->buckets); @@ -362,8 +387,8 @@ alloc_entry (DBusHashTable *table) { DBusHashEntry *entry; - entry = dbus_new0 (DBusHashEntry, 1); - + entry = _dbus_mem_pool_alloc (table->entry_pool); + return entry; } @@ -376,7 +401,7 @@ free_entry (DBusHashTable *table, if (table->free_value_function) (* table->free_value_function) (entry->value); - dbus_free (entry); + _dbus_mem_pool_dealloc (table->entry_pool, entry); } static void @@ -631,6 +656,9 @@ _dbus_hash_iter_get_string_key (DBusHashIter *iter) * because create_if_not_found was #FALSE and the entry * did not exist. * + * If create_if_not_found is #TRUE and the entry is created, the hash + * table takes ownership of the key that's passed in. + * * For a hash table of type #DBUS_HASH_INT, cast the int * key to the key parameter using #_DBUS_INT_TO_POINTER(). * @@ -698,8 +726,12 @@ add_entry (DBusHashTable *table, *bucket = b; table->n_entries += 1; - - if (table->n_entries >= table->rebuild_size) + + /* note we ONLY rebuild when ADDING - because you can iterate over a + * table and remove entries safely. + */ + if (table->n_entries >= table->hi_rebuild_size || + table->n_entries < table->lo_rebuild_size) rebuild_table (table); return entry; @@ -814,34 +846,82 @@ static void rebuild_table (DBusHashTable *table) { int old_size; + int new_buckets; DBusHashEntry **old_buckets; DBusHashEntry **old_chain; DBusHashEntry *entry; - + dbus_bool_t growing; + /* * Allocate and initialize the new bucket array, and set up * hashing constants for new array size. */ + + growing = table->n_entries >= table->hi_rebuild_size; old_size = table->n_buckets; old_buckets = table->buckets; - table->n_buckets *= 4; - table->buckets = dbus_new0 (DBusHashEntry*, table->n_buckets); + if (growing) + { + /* overflow paranoia */ + if (table->n_buckets < _DBUS_INT_MAX / 4 && + table->down_shift >= 0) + new_buckets = table->n_buckets * 4; + else + return; /* can't grow anymore */ + } + else + { + new_buckets = table->n_buckets / 4; + if (new_buckets < DBUS_SMALL_HASH_TABLE) + return; /* don't bother shrinking this far */ + } + + table->buckets = dbus_new0 (DBusHashEntry*, new_buckets); if (table->buckets == NULL) { /* out of memory, yay - just don't reallocate, the table will * still work, albeit more slowly. */ - table->n_buckets /= 4; table->buckets = old_buckets; return; } - table->rebuild_size *= 4; - table->down_shift -= 2; - table->mask = (table->mask << 2) + 3; + table->n_buckets = new_buckets; + + if (growing) + { + table->lo_rebuild_size = table->hi_rebuild_size; + table->hi_rebuild_size *= 4; + + table->down_shift -= 2; /* keep 2 more high bits */ + table->mask = (table->mask << 2) + 3; /* keep 2 more high bits */ + } + else + { + table->hi_rebuild_size = table->lo_rebuild_size; + table->lo_rebuild_size /= 4; + + table->down_shift += 2; /* keep 2 fewer high bits */ + table->mask = table->mask >> 2; /* keep 2 fewer high bits */ + } +#if 0 + printf ("%s table to lo = %d hi = %d downshift = %d mask = 0x%x\n", + growing ? "GROW" : "SHRINK", + table->lo_rebuild_size, + table->hi_rebuild_size, + table->down_shift, + table->mask); +#endif + + _dbus_assert (table->lo_rebuild_size >= 0); + _dbus_assert (table->hi_rebuild_size > table->lo_rebuild_size); + _dbus_assert (table->mask != 0); + /* the mask is essentially the max index */ + _dbus_assert (table->mask < table->n_buckets); + /* * Rehash all of the existing entries into the new bucket array. */ @@ -867,7 +947,7 @@ rebuild_table (DBusHashTable *table) _dbus_assert_not_reached ("Unknown hash table type"); break; } - + bucket = &(table->buckets[idx]); entry->next = *bucket; *bucket = entry; @@ -1086,6 +1166,10 @@ _dbus_hash_table_get_n_entries (DBusHashTable *table) #include "dbus-test.h" #include +/* If you're wondering why the hash table test takes + * forever to run, it's because we call this function + * in inner loops thus making things quadratic. + */ static int count_entries (DBusHashTable *table) { @@ -1114,6 +1198,17 @@ _dbus_hash_test (void) DBusHashTable *table1; DBusHashTable *table2; DBusHashIter iter; +#define N_HASH_KEYS 5000 + char keys[N_HASH_KEYS][128]; + + printf ("Computing test hash keys...\n"); + i = 0; + while (i < N_HASH_KEYS) + { + sprintf (keys[i], "Hash key %d", i); + ++i; + } + printf ("... done.\n"); table1 = _dbus_hash_table_new (DBUS_HASH_STRING, dbus_free, dbus_free); @@ -1131,12 +1226,10 @@ _dbus_hash_test (void) i = 0; while (i < 3000) { - char buf[256]; - sprintf (buf, "Hash key %d", i); void *value; char *key; - key = _dbus_strdup (buf); + key = _dbus_strdup (keys[i]); if (key == NULL) return FALSE; value = _dbus_strdup ("Value!"); @@ -1147,7 +1240,7 @@ _dbus_hash_test (void) key, value)) return FALSE; - value = _dbus_strdup (buf); + value = _dbus_strdup (keys[i]); if (value == NULL) return FALSE; @@ -1158,13 +1251,13 @@ _dbus_hash_test (void) _dbus_assert (count_entries (table1) == i + 1); _dbus_assert (count_entries (table2) == i + 1); - value = _dbus_hash_table_lookup_string (table1, buf); + value = _dbus_hash_table_lookup_string (table1, keys[i]); _dbus_assert (value != NULL); _dbus_assert (strcmp (value, "Value!") == 0); value = _dbus_hash_table_lookup_int (table2, i); _dbus_assert (value != NULL); - _dbus_assert (strcmp (value, buf) == 0); + _dbus_assert (strcmp (value, keys[i]) == 0); ++i; } @@ -1172,11 +1265,8 @@ _dbus_hash_test (void) --i; while (i >= 0) { - char buf[256]; - sprintf (buf, "Hash key %d", i); - _dbus_hash_table_remove_string (table1, - buf); + keys[i]); _dbus_hash_table_remove_int (table2, i); @@ -1211,12 +1301,10 @@ _dbus_hash_test (void) i = 0; while (i < 5000) { - char buf[256]; - sprintf (buf, "Hash key %d", i); char *key; void *value; - key = _dbus_strdup (buf); + key = _dbus_strdup (keys[i]); if (key == NULL) return FALSE; value = _dbus_strdup ("Value!"); @@ -1227,7 +1315,7 @@ _dbus_hash_test (void) key, value)) return FALSE; - value = _dbus_strdup (buf); + value = _dbus_strdup (keys[i]); if (value == NULL) return FALSE; @@ -1297,7 +1385,60 @@ _dbus_hash_test (void) _dbus_assert (count_entries (table2) + 1 == i); --i; } - + + /* add/remove interleaved, to check that we grow/shrink the table + * appropriately + */ + i = 0; + while (i < 1000) + { + char *key; + void *value; + + key = _dbus_strdup (keys[i]); + if (key == NULL) + return FALSE; + + value = _dbus_strdup ("Value!"); + if (value == NULL) + return FALSE; + + if (!_dbus_hash_table_insert_string (table1, + key, value)) + return FALSE; + + ++i; + } + + --i; + while (i >= 0) + { + char *key; + void *value; + + key = _dbus_strdup (keys[i]); + if (key == NULL) + return FALSE; + value = _dbus_strdup ("Value!"); + if (value == NULL) + return FALSE; + + if (!_dbus_hash_table_remove_string (table1, keys[i])) + return FALSE; + + if (!_dbus_hash_table_insert_string (table1, + key, value)) + return FALSE; + + if (!_dbus_hash_table_remove_string (table1, keys[i])) + return FALSE; + + _dbus_assert (_dbus_hash_table_get_n_entries (table1) == i); + + --i; + } + + /* nuke these tables */ _dbus_hash_table_unref (table1); _dbus_hash_table_unref (table2); @@ -1318,12 +1459,10 @@ _dbus_hash_test (void) i = 0; while (i < 3000) { - char buf[256]; - sprintf (buf, "Hash key %d", i); void *value; char *key; - key = _dbus_strdup (buf); + key = _dbus_strdup (keys[i]); if (key == NULL) return FALSE; value = _dbus_strdup ("Value!"); @@ -1336,7 +1475,7 @@ _dbus_hash_test (void) _dbus_assert (_dbus_hash_iter_get_value (&iter) == NULL); _dbus_hash_iter_set_value (&iter, value); - value = _dbus_strdup (buf); + value = _dbus_strdup (keys[i]); if (value == NULL) return FALSE; @@ -1349,7 +1488,7 @@ _dbus_hash_test (void) _dbus_assert (count_entries (table1) == i + 1); _dbus_assert (count_entries (table2) == i + 1); - if (!_dbus_hash_iter_lookup (table1, buf, FALSE, &iter)) + if (!_dbus_hash_iter_lookup (table1, keys[i], FALSE, &iter)) return FALSE; value = _dbus_hash_iter_get_value (&iter); @@ -1367,7 +1506,7 @@ _dbus_hash_test (void) value = _dbus_hash_iter_get_value (&iter); _dbus_assert (value != NULL); - _dbus_assert (strcmp (value, buf) == 0); + _dbus_assert (strcmp (value, keys[i]) == 0); /* Iterate just to be sure it works, though * it's a stupid thing to do @@ -1381,10 +1520,7 @@ _dbus_hash_test (void) --i; while (i >= 0) { - char buf[256]; - sprintf (buf, "Hash key %d", i); - - if (!_dbus_hash_iter_lookup (table1, buf, FALSE, &iter)) + if (!_dbus_hash_iter_lookup (table1, keys[i], FALSE, &iter)) _dbus_assert_not_reached ("hash entry should have existed"); _dbus_hash_iter_remove_entry (&iter); diff --git a/dbus/dbus-list.c b/dbus/dbus-list.c index 31e5ae3f..c36246cb 100644 --- a/dbus/dbus-list.c +++ b/dbus/dbus-list.c @@ -133,7 +133,7 @@ link_after (DBusList **list, * while (link != NULL) * { * printf ("value is %p\n", link->data); - * link = _dbus_list_get_next_link (&list); + * link = _dbus_list_get_next_link (&link); * } * @endcode * @@ -155,7 +155,7 @@ link_after (DBusList **list, * while (link != NULL) * { * printf ("value is %p\n", link->data); - * link = _dbus_list_get_prev_link (&list); + * link = _dbus_list_get_prev_link (&link); * } * @endcode * @@ -300,6 +300,37 @@ _dbus_list_remove (DBusList **list, return FALSE; } +/** + * Removes a value from the list. Only removes the + * last value equal to the given data pointer, + * even if multiple values exist which match. + * This is a linear-time operation. + * + * @param list address of the list head. + * @param data the value to remove. + * @returns #TRUE if a value was found to remove. + */ +dbus_bool_t +_dbus_list_remove_last (DBusList **list, + void *data) +{ + DBusList *link; + + link = _dbus_list_get_last_link (list); + while (link != NULL) + { + if (link->data == data) + { + _dbus_list_remove_link (list, link); + return TRUE; + } + + link = _dbus_list_get_prev_link (list, link); + } + + return FALSE; +} + /** * Removes a link from the list. This is a constant-time operation. * diff --git a/dbus/dbus-list.h b/dbus/dbus-list.h index 8de40002..ce8956a4 100644 --- a/dbus/dbus-list.h +++ b/dbus/dbus-list.h @@ -51,6 +51,8 @@ dbus_bool_t _dbus_list_insert_after (DBusList **list, void *data); dbus_bool_t _dbus_list_remove (DBusList **list, void *data); +dbus_bool_t _dbus_list_remove_last (DBusList **list, + void *data); void _dbus_list_remove_link (DBusList **list, DBusList *link); void _dbus_list_clear (DBusList **list); diff --git a/dbus/dbus-mempool.c b/dbus/dbus-mempool.c new file mode 100644 index 00000000..a3aa086d --- /dev/null +++ b/dbus/dbus-mempool.c @@ -0,0 +1,454 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-mempool.h Memory pools + * + * Copyright (C) 2002 Red Hat, Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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-mempool.h" + +/** + * @defgroup DBusMemPool memory pools + * @ingroup DBusInternals + * @brief DBusMemPool object + * + * Types and functions related to DBusMemPool. A memory pool is used + * to decrease memory fragmentation/overhead and increase speed for + * blocks of small uniformly-sized objects. The main point is to avoid + * the overhead of a malloc block for each small object, speed is + * secondary. + */ + +/** + * @defgroup DBusMemPoolInternals Memory pool implementation details + * @ingroup DBusInternals + * @brief DBusMemPool implementation details + * + * The guts of DBusMemPool. + * + * @{ + */ + +/** + * typedef so DBusFreedElement struct can refer to itself. + */ +typedef struct DBusFreedElement DBusFreedElement; + +/** + * struct representing an element on the free list. + * We just cast freed elements to this so we can + * make a list out of them. + */ +struct DBusFreedElement +{ + DBusFreedElement *next; /**< next element of the free list */ +}; + +/** + * The dummy size of the variable-length "elements" + * field in DBusMemBlock + */ +#define ELEMENT_PADDING 4 + +/** + * Typedef for DBusMemBlock so the struct can recursively + * point to itself. + */ +typedef struct DBusMemBlock DBusMemBlock; + +/** + * DBusMemBlock object represents a single malloc()-returned + * block that gets chunked up into objects in the memory pool. + */ +struct DBusMemBlock +{ + DBusMemBlock *next; /**< next block in the list, which is already used up; + * only saved so we can free all the blocks + * when we free the mem pool. + */ + + int used_so_far; /**< bytes of this block already allocated as elements. */ + + unsigned char elements[ELEMENT_PADDING]; /**< the block data, actually allocated to required size */ +}; + +/** + * Internals fields of DBusMemPool + */ +struct DBusMemPool +{ + int element_size; /**< size of a single object in the pool */ + int block_size; /**< size of most recently allocated block */ + unsigned int zero_elements : 1; /**< whether to zero-init allocated elements */ + + DBusFreedElement *free_elements; /**< a free list of elements to recycle */ + DBusMemBlock *blocks; /**< blocks of memory from malloc() */ +}; + +/** @} */ + +/** + * @addtogroup DBusMemPool + * + * @{ + */ + +/** + * @typedef DBusMemPool + * + * Opaque object representing a memory pool. Memory pools allow + * avoiding per-malloc-block memory overhead when allocating a lot of + * small objects that are all the same size. They are slightly + * faster than calling malloc() also. + */ + +/** + * Creates a new memory pool, or returns #NULL on failure. Objects in + * the pool must be at least sizeof(void*) bytes each, due to the way + * memory pools work. To avoid creating 64 bit problems, this means at + * least 8 bytes on all platforms, unless you are 4 bytes on 32-bit + * and 8 bytes on 64-bit. + * + * @param element_size size of an element allocated from the pool. + * @param zero_elements whether to zero-initialize elements + * @returns the new pool or #NULL + */ +DBusMemPool* +_dbus_mem_pool_new (int element_size, + dbus_bool_t zero_elements) +{ + DBusMemPool *pool; + + pool = dbus_new0 (DBusMemPool, 1); + if (pool == NULL) + return NULL; + + /* these assertions are equivalent but the first is more clear + * to programmers that see it fail. + */ + _dbus_assert (element_size >= (int) sizeof (void*)); + _dbus_assert (element_size >= (int) sizeof (DBusFreedElement)); + + pool->element_size = element_size; + pool->zero_elements = zero_elements != FALSE; + + /* pick a size for the first block; it increases + * for each block we need to allocate. This is + * actually half the initial block size + * since _dbus_mem_pool_alloc() unconditionally + * doubles it prior to creating a new block. + */ + pool->block_size = element_size * 8; + + _dbus_assert ((pool->block_size % + pool->element_size) == 0); + + return pool; +} + +/** + * Frees a memory pool (and all elements allocated from it). + * + * @param pool the memory pool. + */ +void +_dbus_mem_pool_free (DBusMemPool *pool) +{ + DBusMemBlock *block; + + block = pool->blocks; + while (block != NULL) + { + DBusMemBlock *next = block->next; + + dbus_free (block); + + block = next; + } + + dbus_free (pool); +} + +/** + * Allocates an object from the memory pool. + * The object must be freed with _dbus_mem_pool_dealloc(). + * + * @param pool the memory pool + * @returns the allocated object or #NULL if no memory. + */ +void* +_dbus_mem_pool_alloc (DBusMemPool *pool) +{ + if (pool->free_elements) + { + DBusFreedElement *element = pool->free_elements; + + pool->free_elements = pool->free_elements->next; + + if (pool->zero_elements) + memset (element, '\0', pool->element_size); + + return element; + } + else + { + void *element; + + if (pool->blocks == NULL || + pool->blocks->used_so_far == pool->block_size) + { + /* Need a new block */ + DBusMemBlock *block; + int alloc_size; + + if (pool->block_size <= _DBUS_INT_MAX / 4) /* avoid overflow */ + { + /* use a larger block size for our next block */ + pool->block_size *= 2; + _dbus_assert ((pool->block_size % + pool->element_size) == 0); + } + + alloc_size = sizeof (DBusMemBlock) - ELEMENT_PADDING + pool->block_size; + + if (pool->zero_elements) + block = dbus_malloc0 (alloc_size); + else + block = dbus_malloc (alloc_size); + + if (block == NULL) + return NULL; + + block->used_so_far = 0; + block->next = pool->blocks; + pool->blocks = block; + } + + element = &pool->blocks->elements[pool->blocks->used_so_far]; + + pool->blocks->used_so_far += pool->element_size; + + return element; + } +} + +/** + * Deallocates an object previously created with + * _dbus_mem_pool_alloc(). The previous object + * must have come from this same pool. + * @param pool the memory pool + * @param element the element earlier allocated. + */ +void +_dbus_mem_pool_dealloc (DBusMemPool *pool, + void *element) +{ + DBusFreedElement *freed; + + freed = element; + freed->next = pool->free_elements; + pool->free_elements = freed; +} + +/** @} */ + +#ifdef DBUS_BUILD_TESTS +#include "dbus-test.h" +#include +#include + +static void +time_for_size (int size) +{ + int i; + int j; + clock_t start; + clock_t end; +#define FREE_ARRAY_SIZE 512 +#define N_ITERATIONS FREE_ARRAY_SIZE * 512 + void *to_free[FREE_ARRAY_SIZE]; + DBusMemPool *pool; + + printf ("Timings for size %d\n", size); + + printf (" malloc\n"); + + start = clock (); + + i = 0; + j = 0; + while (i < N_ITERATIONS) + { + to_free[j] = dbus_malloc (size); + _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */ + + ++j; + + if (j == FREE_ARRAY_SIZE) + { + j = 0; + while (j < FREE_ARRAY_SIZE) + { + dbus_free (to_free[j]); + ++j; + } + + j = 0; + } + + ++i; + } + + end = clock (); + + printf (" created/destroyed %d elements in %g seconds\n", + N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC); + + + + printf (" mempools\n"); + + start = clock (); + + pool = _dbus_mem_pool_new (size, FALSE); + + i = 0; + j = 0; + while (i < N_ITERATIONS) + { + to_free[j] = _dbus_mem_pool_alloc (pool); + _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */ + + ++j; + + if (j == FREE_ARRAY_SIZE) + { + j = 0; + while (j < FREE_ARRAY_SIZE) + { + _dbus_mem_pool_dealloc (pool, to_free[j]); + ++j; + } + + j = 0; + } + + ++i; + } + + _dbus_mem_pool_free (pool); + + end = clock (); + + printf (" created/destroyed %d elements in %g seconds\n", + N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC); + + printf (" zeroed malloc\n"); + + start = clock (); + + i = 0; + j = 0; + while (i < N_ITERATIONS) + { + to_free[j] = dbus_malloc0 (size); + _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */ + + ++j; + + if (j == FREE_ARRAY_SIZE) + { + j = 0; + while (j < FREE_ARRAY_SIZE) + { + dbus_free (to_free[j]); + ++j; + } + + j = 0; + } + + ++i; + } + + end = clock (); + + printf (" created/destroyed %d elements in %g seconds\n", + N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC); + + printf (" zeroed mempools\n"); + + start = clock (); + + pool = _dbus_mem_pool_new (size, TRUE); + + i = 0; + j = 0; + while (i < N_ITERATIONS) + { + to_free[j] = _dbus_mem_pool_alloc (pool); + _dbus_assert (to_free[j] != NULL); /* in a real app of course this is wrong */ + + ++j; + + if (j == FREE_ARRAY_SIZE) + { + j = 0; + while (j < FREE_ARRAY_SIZE) + { + _dbus_mem_pool_dealloc (pool, to_free[j]); + ++j; + } + + j = 0; + } + + ++i; + } + + _dbus_mem_pool_free (pool); + + end = clock (); + + printf (" created/destroyed %d elements in %g seconds\n", + N_ITERATIONS, (end - start) / (double) CLOCKS_PER_SEC); +} + +/** + * @ingroup DBusMemPoolInternals + * Unit test for DBusMemPool + * @returns #TRUE on success. + */ +dbus_bool_t +_dbus_mem_pool_test (void) +{ + int i; + int element_sizes[] = { 4, 8, 16, 50, 124 }; + + i = 0; + while (i < _DBUS_N_ELEMENTS (element_sizes)) + { + time_for_size (element_sizes[i]); + ++i; + } + + return TRUE; +} + +#endif /* DBUS_BUILD_TESTS */ diff --git a/dbus/dbus-mempool.h b/dbus/dbus-mempool.h new file mode 100644 index 00000000..17cfa309 --- /dev/null +++ b/dbus/dbus-mempool.h @@ -0,0 +1,44 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-mempool.h Memory pools + * + * Copyright (C) 2002 Red Hat, Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ + +#ifndef DBUS_MEMPOOL_H +#define DBUS_MEMPOOL_H + +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef struct DBusMemPool DBusMemPool; + +DBusMemPool* _dbus_mem_pool_new (int element_size, + dbus_bool_t zero_elements); +void _dbus_mem_pool_free (DBusMemPool *pool); +void* _dbus_mem_pool_alloc (DBusMemPool *pool); +void _dbus_mem_pool_dealloc (DBusMemPool *pool, + void *element); + +DBUS_END_DECLS; + +#endif /* DBUS_MEMPOOL_H */ diff --git a/dbus/dbus-message-handler.c b/dbus/dbus-message-handler.c new file mode 100644 index 00000000..5f12d238 --- /dev/null +++ b/dbus/dbus-message-handler.c @@ -0,0 +1,265 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-message-handler.c Sender/receiver of messages. + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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" +#include "dbus-message-handler.h" +#include "dbus-list.h" +#include "dbus-connection-internal.h" + +/** + * @defgroup DBusMessageHandlerInternals DBusMessageHandler implementation details + * @ingroup DBusInternals + * @brief DBusMessageHandler private implementation details. + * + * The guts of DBusMessageHandler and its methods. + * + * @{ + */ + + +/** + * @brief Internals of DBusMessageHandler + * + * Object that can send and receive messages. + */ +struct DBusMessageHandler +{ + int refcount; /**< reference count */ + + DBusHandleMessageFunction function; /**< handler function */ + void *user_data; /**< user data for function */ + DBusFreeFunction free_user_data; /**< free the user data */ + + DBusList *connections; /**< connections we're registered with */ +}; + +/** + * Add this connection to the list used by this message handler. + * When the message handler goes away, the connection + * will be notified. + * + * @param handler the message handler + * @param connection the connection + * @returns #FALSE if not enough memory + */ +dbus_bool_t +_dbus_message_handler_add_connection (DBusMessageHandler *handler, + DBusConnection *connection) +{ + /* This is a bit wasteful - we just put the connection in the list + * once per time it's added. :-/ + */ + if (!_dbus_list_prepend (&handler->connections, connection)) + return FALSE; + + return TRUE; +} + +/** + * Reverses the effect of _dbus_message_handler_add_connection(). + * @param handler the message handler + * @param connection the connection + */ +void +_dbus_message_handler_remove_connection (DBusMessageHandler *handler, + DBusConnection *connection) +{ + if (!_dbus_list_remove (&handler->connections, connection)) + _dbus_warn ("Function _dbus_message_handler_remove_connection() called when the connection hadn't been added\n"); +} + + +/** + * Handles the given message, by dispatching the handler function + * for this DBusMessageHandler, if any. + * + * @param handler the handler + * @param connection the connection that received the message + * @param message the message + * + * @returns what to do with the message + */ +DBusHandlerResult +_dbus_message_handler_handle_message (DBusMessageHandler *handler, + DBusConnection *connection, + DBusMessage *message) +{ + /* This function doesn't ref handler/connection/message + * since that's done in dbus_connection_dispatch_message(). + */ + if (handler->function != NULL) + return (* handler->function) (handler, connection, message, handler->user_data); + else + return DBUS_HANDLER_RESULT_ALLOW_MORE_HANDLERS; +} + +/** @} */ + +/** + * @defgroup DBusMessageHandler DBusMessageHandler + * @ingroup DBus + * @brief Message processor + * + * A DBusMessageHandler is an object that can send and receive + * messages. Typically the handler is registered with one or + * more DBusConnection objects and processes some types of + * messages received from the connection. + * + * @{ + */ + +/** + * @typedef DBusMessageHandler + * + * Opaque data type representing a message handler. + */ + +/** + * Creates a new message handler. The handler function + * may be #NULL for a no-op handler or a handler to + * be assigned a function later. + * + * @param function function to call to handle a message + * @param user_data data to pass to the function + * @param free_user_data function to call to free the user data + * @returns a new DBusMessageHandler or #NULL if no memory. + */ +DBusMessageHandler* +dbus_message_handler_new (DBusHandleMessageFunction function, + void *user_data, + DBusFreeFunction free_user_data) +{ + DBusMessageHandler *handler; + + handler = dbus_new (DBusMessageHandler, 1); + + if (handler == NULL) + return NULL; + + handler->refcount = 1; + handler->function = function; + handler->user_data = user_data; + handler->free_user_data = free_user_data; + handler->connections = NULL; + + return handler; +} + +/** + * Increments the reference count on a message handler. + * + * @param handler the handler + */ +void +dbus_message_handler_ref (DBusMessageHandler *handler) +{ + _dbus_assert (handler != NULL); + + handler->refcount += 1; +} + +/** + * Decrements the reference count on a message handler, + * freeing the handler if the count reaches 0. + * + * @param handler the handler + */ +void +dbus_message_handler_unref (DBusMessageHandler *handler) +{ + _dbus_assert (handler != NULL); + _dbus_assert (handler->refcount > 0); + + handler->refcount -= 1; + if (handler->refcount == 0) + { + DBusList *link; + + if (handler->free_user_data) + (* handler->free_user_data) (handler->user_data); + + link = _dbus_list_get_first_link (&handler->connections); + while (link != NULL) + { + DBusConnection *connection = link->data; + + _dbus_connection_handler_destroyed (connection, handler); + + link = _dbus_list_get_next_link (&handler->connections, link); + } + + _dbus_list_clear (&handler->connections); + + dbus_free (handler); + } +} + +/** + * Gets the user data for the handler (the same user data + * passed to the handler function.) + * + * @param handler the handler + * @returns the user data + */ +void* +dbus_message_handler_get_data (DBusMessageHandler *handler) +{ + return handler->user_data; +} + +/** + * Sets the user data for the handler (the same user data + * to be passed to the handler function). Frees any previously-existing + * user data with the previous free_user_data function. + * + * @param handler the handler + * @param user_data the user data + * @param free_user_data free function for the data + */ +void +dbus_message_handler_set_data (DBusMessageHandler *handler, + void *user_data, + DBusFreeFunction free_user_data) +{ + if (handler->free_user_data) + (* handler->free_user_data) (handler->user_data); + + handler->user_data = user_data; + handler->free_user_data = free_user_data; +} + +/** + * Sets the handler function. Call dbus_message_handler_set_data() + * to set the user data for the function. + * + * @param handler the handler + * @param function the function + */ +void +dbus_message_handler_set_function (DBusMessageHandler *handler, + DBusHandleMessageFunction function) +{ + handler->function = function; +} + +/** @} */ diff --git a/dbus/dbus-message-handler.h b/dbus/dbus-message-handler.h new file mode 100644 index 00000000..dac015ac --- /dev/null +++ b/dbus/dbus-message-handler.h @@ -0,0 +1,59 @@ +/* -*- mode: C; c-file-style: "gnu" -*- */ +/* dbus-message-handler.h Sender/receiver of messages. + * + * Copyright (C) 2002 Red Hat Inc. + * + * Licensed under the Academic Free License version 1.2 + * + * 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 + * + */ +#if !defined (DBUS_INSIDE_DBUS_H) && !defined (DBUS_COMPILATION) +#error "Only can be included directly, this file may disappear or change contents." +#endif + +#ifndef DBUS_MESSAGE_HANDLER_H +#define DBUS_MESSAGE_HANDLER_H + +#include +#include +#include + +DBUS_BEGIN_DECLS; + +typedef DBusHandlerResult (* DBusHandleMessageFunction) (DBusMessageHandler *handler, + DBusConnection *connection, + DBusMessage *message, + void *user_data); + +DBusMessageHandler* dbus_message_handler_new (DBusHandleMessageFunction function, + void *user_data, + DBusFreeFunction free_user_data); + + +void dbus_message_handler_ref (DBusMessageHandler *handler); +void dbus_message_handler_unref (DBusMessageHandler *handler); + + +void* dbus_message_handler_get_data (DBusMessageHandler *handler); +void dbus_message_handler_set_data (DBusMessageHandler *handler, + void *data, + DBusFreeFunction free_user_data); +void dbus_message_handler_set_function (DBusMessageHandler *handler, + DBusHandleMessageFunction function); + +DBUS_END_DECLS; + +#endif /* DBUS_MESSAGE_HANDLER_H */ diff --git a/dbus/dbus-message.c b/dbus/dbus-message.c index d86f8318..62ac5eaf 100644 --- a/dbus/dbus-message.c +++ b/dbus/dbus-message.c @@ -200,6 +200,19 @@ dbus_message_unref (DBusMessage *message) } } +/** + * Gets the name of a message. + * @param message the message + * @returns the message name (should not be freed) + */ +const char* +dbus_message_get_name (DBusMessage *message) +{ + /* FIXME */ + + return NULL; +} + /** @} */ /** diff --git a/dbus/dbus-message.h b/dbus/dbus-message.h index 69d74e9c..ae8b84e1 100644 --- a/dbus/dbus-message.h +++ b/dbus/dbus-message.h @@ -39,6 +39,7 @@ DBusMessage* dbus_message_new (void); void dbus_message_ref (DBusMessage *message); void dbus_message_unref (DBusMessage *message); +const char* dbus_message_get_name (DBusMessage *message); DBUS_END_DECLS; diff --git a/dbus/dbus-string.c b/dbus/dbus-string.c index 573ed313..2b8d1349 100644 --- a/dbus/dbus-string.c +++ b/dbus/dbus-string.c @@ -1200,9 +1200,9 @@ _dbus_string_test (void) _dbus_assert (v == 27); _dbus_assert (end == i); - - _dbus_string_set_length (&str, 0); + _dbus_string_free (&str); + if (!_dbus_string_init (&str, _DBUS_INT_MAX)) _dbus_assert_not_reached ("failed to init string"); diff --git a/dbus/dbus-test.c b/dbus/dbus-test.c index c1514246..df840a7c 100644 --- a/dbus/dbus-test.c +++ b/dbus/dbus-test.c @@ -24,23 +24,35 @@ #include "dbus-types.h" #include "dbus-test.h" #include +#include + +static void +die (const char *failure) +{ + fprintf (stderr, "Failed: %s\n", failure); + exit (1); +} int main (int argc, char **argv) { + printf ("%s: running memory pool tests\n", argv[0]); + if (!_dbus_mem_pool_test ()) + die ("memory pools"); + printf ("%s: running string tests\n", argv[0]); if (!_dbus_string_test ()) - return 1; + die ("strings"); printf ("%s: running linked list tests\n", argv[0]); if (!_dbus_list_test ()) - return 1; + die ("lists"); printf ("%s: running hash table tests\n", argv[0]); if (!_dbus_hash_test ()) - return 1; + die ("hash tables"); printf ("%s: completed successfully\n", argv[0]); return 0; diff --git a/dbus/dbus-test.h b/dbus/dbus-test.h index 0df49738..80e0204c 100644 --- a/dbus/dbus-test.h +++ b/dbus/dbus-test.h @@ -26,8 +26,10 @@ #include -dbus_bool_t _dbus_hash_test (void); -dbus_bool_t _dbus_list_test (void); -dbus_bool_t _dbus_string_test (void); +dbus_bool_t _dbus_hash_test (void); +dbus_bool_t _dbus_list_test (void); +dbus_bool_t _dbus_mem_pool_test (void); +dbus_bool_t _dbus_string_test (void); + #endif /* DBUS_TEST_H */ diff --git a/dbus/dbus.h b/dbus/dbus.h index 6dc14dc6..fc4f2557 100644 --- a/dbus/dbus.h +++ b/dbus/dbus.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/glib/dbus-glib.h b/glib/dbus-glib.h index 0afd23bd..6abbc4fe 100644 --- a/glib/dbus-glib.h +++ b/glib/dbus-glib.h @@ -26,9 +26,9 @@ #include #include -typedef void (*DBusMessageHandler) (DBusConnection *connection, - DBusMessage *message, - gpointer data); +typedef void (*DBusMessageFunction) (DBusConnection *connection, + DBusMessage *message, + gpointer data); void dbus_gthread_init (void); diff --git a/glib/dbus-gmain.c b/glib/dbus-gmain.c index 0b9ac9f1..7d22dbdd 100644 --- a/glib/dbus-gmain.c +++ b/glib/dbus-gmain.c @@ -88,7 +88,7 @@ gdbus_connection_dispatch (GSource *source, gpointer user_data) { DBusGSource *dbus_source = (DBusGSource *)source; - DBusMessageHandler handler = (DBusMessageHandler)callback; + DBusMessageFunction handler = (DBusMessageFunction)callback; DBusMessage *message; GList *list; diff --git a/glib/dbus-gthread.c b/glib/dbus-gthread.c index cb4733ff..685f4cd6 100644 --- a/glib/dbus-gthread.c +++ b/glib/dbus-gthread.c @@ -23,7 +23,7 @@ #include #include -#include "dbus-gthread.h" +#include "dbus-glib.h" static DBusMutex * dbus_gmutex_new (void); static void dbus_gmutex_free (DBusMutex *mutex); -- cgit