Unverified Commit 61176a9b by Patrick Steinhardt Committed by GitHub

Merge pull request #5243 from pks-t/pks/config-optimize-mem

Memory optimizations for config entries
parents 0b5540b9 b7dcea04
...@@ -11,6 +11,7 @@ typedef struct config_entry_list { ...@@ -11,6 +11,7 @@ typedef struct config_entry_list {
struct config_entry_list *next; struct config_entry_list *next;
struct config_entry_list *last; struct config_entry_list *last;
git_config_entry *entry; git_config_entry *entry;
bool first;
} config_entry_list; } config_entry_list;
typedef struct config_entries_iterator { typedef struct config_entries_iterator {
...@@ -25,31 +26,6 @@ struct git_config_entries { ...@@ -25,31 +26,6 @@ struct git_config_entries {
config_entry_list *list; config_entry_list *list;
}; };
static void config_entry_list_free(config_entry_list *list)
{
config_entry_list *next;
while (list != NULL) {
next = list->next;
git__free((char*) list->entry->name);
git__free((char *) list->entry->value);
git__free(list->entry);
git__free(list);
list = next;
};
}
static void config_entry_list_append(config_entry_list **list, config_entry_list *entry)
{
if (*list)
(*list)->last->next = entry;
else
*list = entry;
(*list)->last = entry;
}
int git_config_entries_new(git_config_entries **out) int git_config_entries_new(git_config_entries **out)
{ {
git_config_entries *entries; git_config_entries *entries;
...@@ -127,12 +103,15 @@ static void config_entries_free(git_config_entries *entries) ...@@ -127,12 +103,15 @@ static void config_entries_free(git_config_entries *entries)
{ {
config_entry_list *list = NULL, *next; config_entry_list *list = NULL, *next;
git_strmap_foreach_value(entries->map, list, config_entry_list_free(list));
git_strmap_free(entries->map); git_strmap_free(entries->map);
list = entries->list; list = entries->list;
while (list != NULL) { while (list != NULL) {
next = list->next; next = list->next;
if (list->first)
git__free((char *) list->entry->name);
git__free((char *) list->entry->value);
git__free(list->entry);
git__free(list); git__free(list);
list = next; list = next;
} }
...@@ -148,46 +127,33 @@ void git_config_entries_free(git_config_entries *entries) ...@@ -148,46 +127,33 @@ void git_config_entries_free(git_config_entries *entries)
int git_config_entries_append(git_config_entries *entries, git_config_entry *entry) int git_config_entries_append(git_config_entries *entries, git_config_entry *entry)
{ {
config_entry_list *existing, *var; config_entry_list *existing, *head;
int error = 0;
var = git__calloc(1, sizeof(config_entry_list)); head = git__calloc(1, sizeof(config_entry_list));
GIT_ERROR_CHECK_ALLOC(var); GIT_ERROR_CHECK_ALLOC(head);
var->entry = entry; head->entry = entry;
if ((existing = git_strmap_get(entries->map, entry->name)) == NULL) {
/* /*
* We only ever inspect `last` from the first config * This is a micro-optimization for configuration files
* entry in a multivar. In case where this new entry is * with a lot of same keys. As for multivars the entry's
* the first one in the entry map, it will also be the * key will be the same for all entries, we can just free
* last one at the time of adding it, which is * all except the first entry's name and just re-use it.
* why we set `last` here to itself. Otherwise we
* do not have to set `last` and leave it set to
* `NULL`.
*/ */
var->last = var; if ((existing = git_strmap_get(entries->map, entry->name)) != NULL) {
git__free((char *) entry->name);
error = git_strmap_set(entries->map, entry->name, var); entry->name = existing->entry->name;
} else { } else {
config_entry_list_append(&existing, var); head->first = 1;
} }
var = git__calloc(1, sizeof(config_entry_list)); if (entries->list)
GIT_ERROR_CHECK_ALLOC(var); entries->list->last->next = head;
var->entry = entry; else
config_entry_list_append(&entries->list, var); entries->list = head;
entries->list->last = head;
return error;
}
int config_entry_get(config_entry_list **out, git_config_entries *entries, const char *key)
{
config_entry_list *list;
if ((list = git_strmap_get(entries->map, key)) == NULL)
return GIT_ENOTFOUND;
*out = list; if (git_strmap_set(entries->map, entry->name, head) < 0)
return -1;
return 0; return 0;
} }
...@@ -195,24 +161,20 @@ int config_entry_get(config_entry_list **out, git_config_entries *entries, const ...@@ -195,24 +161,20 @@ int config_entry_get(config_entry_list **out, git_config_entries *entries, const
int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key) int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key)
{ {
config_entry_list *entry; config_entry_list *entry;
int error; if ((entry = git_strmap_get(entries->map, key)) == NULL)
return GIT_ENOTFOUND;
if ((error = config_entry_get(&entry, entries, key)) < 0) *out = entry->entry;
return error;
*out = entry->last->entry;
return 0; return 0;
} }
int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key) int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key)
{ {
config_entry_list *entry; config_entry_list *entry;
int error;
if ((error = config_entry_get(&entry, entries, key)) < 0) if ((entry = git_strmap_get(entries->map, key)) == NULL)
return error; return GIT_ENOTFOUND;
if (entry->next != NULL) { if (!entry->first) {
git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar"); git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar");
return -1; return -1;
} }
...@@ -227,14 +189,14 @@ int git_config_entries_get_unique(git_config_entry **out, git_config_entries *en ...@@ -227,14 +189,14 @@ int git_config_entries_get_unique(git_config_entry **out, git_config_entries *en
return 0; return 0;
} }
void config_iterator_free(git_config_iterator *iter) static void config_iterator_free(git_config_iterator *iter)
{ {
config_entries_iterator *it = (config_entries_iterator *) iter; config_entries_iterator *it = (config_entries_iterator *) iter;
git_config_entries_free(it->entries); git_config_entries_free(it->entries);
git__free(it); git__free(it);
} }
int config_iterator_next( static int config_iterator_next(
git_config_entry **entry, git_config_entry **entry,
git_config_iterator *iter) git_config_iterator *iter)
{ {
......
...@@ -176,3 +176,23 @@ void test_config_stress__foreach_refreshes_snapshot(void) ...@@ -176,3 +176,23 @@ void test_config_stress__foreach_refreshes_snapshot(void)
git_config_free(config); git_config_free(config);
git__free(value); git__free(value);
} }
void test_config_stress__huge_section_with_many_values(void)
{
git_config *config;
/*
* The config file is structured in such a way that is
* has a section header that is approximately 500kb of
* size followed by 40k entries. While the resulting
* configuration file itself is roughly 650kb in size and
* thus considered to be rather small, in the past we'd
* balloon to more than 20GB of memory (20000x500kb)
* while parsing the file. It thus was a trivial way to
* cause an out-of-memory situation and thus cause denial
* of service, e.g. via gitmodules.
*/
cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config-oom")));
git_config_free(config);
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment