Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 61176a9

Browse files
authored
Merge pull request #5243 from pks-t/pks/config-optimize-mem
Memory optimizations for config entries
2 parents 0b5540b + b7dcea0 commit 61176a9

File tree

3 files changed

+40056
-74
lines changed

3 files changed

+40056
-74
lines changed

src/config_entries.c

Lines changed: 36 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ typedef struct config_entry_list {
1111
struct config_entry_list *next;
1212
struct config_entry_list *last;
1313
git_config_entry *entry;
14+
bool first;
1415
} config_entry_list;
1516

1617
typedef struct config_entries_iterator {
@@ -25,31 +26,6 @@ struct git_config_entries {
2526
config_entry_list *list;
2627
};
2728

28-
static void config_entry_list_free(config_entry_list *list)
29-
{
30-
config_entry_list *next;
31-
32-
while (list != NULL) {
33-
next = list->next;
34-
35-
git__free((char*) list->entry->name);
36-
git__free((char *) list->entry->value);
37-
git__free(list->entry);
38-
git__free(list);
39-
40-
list = next;
41-
};
42-
}
43-
44-
static void config_entry_list_append(config_entry_list **list, config_entry_list *entry)
45-
{
46-
if (*list)
47-
(*list)->last->next = entry;
48-
else
49-
*list = entry;
50-
(*list)->last = entry;
51-
}
52-
5329
int git_config_entries_new(git_config_entries **out)
5430
{
5531
git_config_entries *entries;
@@ -127,12 +103,15 @@ static void config_entries_free(git_config_entries *entries)
127103
{
128104
config_entry_list *list = NULL, *next;
129105

130-
git_strmap_foreach_value(entries->map, list, config_entry_list_free(list));
131106
git_strmap_free(entries->map);
132107

133108
list = entries->list;
134109
while (list != NULL) {
135110
next = list->next;
111+
if (list->first)
112+
git__free((char *) list->entry->name);
113+
git__free((char *) list->entry->value);
114+
git__free(list->entry);
136115
git__free(list);
137116
list = next;
138117
}
@@ -148,71 +127,54 @@ void git_config_entries_free(git_config_entries *entries)
148127

149128
int git_config_entries_append(git_config_entries *entries, git_config_entry *entry)
150129
{
151-
config_entry_list *existing, *var;
152-
int error = 0;
153-
154-
var = git__calloc(1, sizeof(config_entry_list));
155-
GIT_ERROR_CHECK_ALLOC(var);
156-
var->entry = entry;
157-
158-
if ((existing = git_strmap_get(entries->map, entry->name)) == NULL) {
159-
/*
160-
* We only ever inspect `last` from the first config
161-
* entry in a multivar. In case where this new entry is
162-
* the first one in the entry map, it will also be the
163-
* last one at the time of adding it, which is
164-
* why we set `last` here to itself. Otherwise we
165-
* do not have to set `last` and leave it set to
166-
* `NULL`.
167-
*/
168-
var->last = var;
169-
170-
error = git_strmap_set(entries->map, entry->name, var);
130+
config_entry_list *existing, *head;
131+
132+
head = git__calloc(1, sizeof(config_entry_list));
133+
GIT_ERROR_CHECK_ALLOC(head);
134+
head->entry = entry;
135+
136+
/*
137+
* This is a micro-optimization for configuration files
138+
* with a lot of same keys. As for multivars the entry's
139+
* key will be the same for all entries, we can just free
140+
* all except the first entry's name and just re-use it.
141+
*/
142+
if ((existing = git_strmap_get(entries->map, entry->name)) != NULL) {
143+
git__free((char *) entry->name);
144+
entry->name = existing->entry->name;
171145
} else {
172-
config_entry_list_append(&existing, var);
146+
head->first = 1;
173147
}
174148

175-
var = git__calloc(1, sizeof(config_entry_list));
176-
GIT_ERROR_CHECK_ALLOC(var);
177-
var->entry = entry;
178-
config_entry_list_append(&entries->list, var);
179-
180-
return error;
181-
}
182-
183-
int config_entry_get(config_entry_list **out, git_config_entries *entries, const char *key)
184-
{
185-
config_entry_list *list;
186-
187-
if ((list = git_strmap_get(entries->map, key)) == NULL)
188-
return GIT_ENOTFOUND;
149+
if (entries->list)
150+
entries->list->last->next = head;
151+
else
152+
entries->list = head;
153+
entries->list->last = head;
189154

190-
*out = list;
155+
if (git_strmap_set(entries->map, entry->name, head) < 0)
156+
return -1;
191157

192158
return 0;
193159
}
194160

195161
int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key)
196162
{
197163
config_entry_list *entry;
198-
int error;
199-
200-
if ((error = config_entry_get(&entry, entries, key)) < 0)
201-
return error;
202-
*out = entry->last->entry;
203-
164+
if ((entry = git_strmap_get(entries->map, key)) == NULL)
165+
return GIT_ENOTFOUND;
166+
*out = entry->entry;
204167
return 0;
205168
}
206169

207170
int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key)
208171
{
209172
config_entry_list *entry;
210-
int error;
211173

212-
if ((error = config_entry_get(&entry, entries, key)) < 0)
213-
return error;
174+
if ((entry = git_strmap_get(entries->map, key)) == NULL)
175+
return GIT_ENOTFOUND;
214176

215-
if (entry->next != NULL) {
177+
if (!entry->first) {
216178
git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar");
217179
return -1;
218180
}
@@ -227,14 +189,14 @@ int git_config_entries_get_unique(git_config_entry **out, git_config_entries *en
227189
return 0;
228190
}
229191

230-
void config_iterator_free(git_config_iterator *iter)
192+
static void config_iterator_free(git_config_iterator *iter)
231193
{
232194
config_entries_iterator *it = (config_entries_iterator *) iter;
233195
git_config_entries_free(it->entries);
234196
git__free(it);
235197
}
236198

237-
int config_iterator_next(
199+
static int config_iterator_next(
238200
git_config_entry **entry,
239201
git_config_iterator *iter)
240202
{

tests/config/stress.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,23 @@ void test_config_stress__foreach_refreshes_snapshot(void)
176176
git_config_free(config);
177177
git__free(value);
178178
}
179+
180+
void test_config_stress__huge_section_with_many_values(void)
181+
{
182+
git_config *config;
183+
184+
/*
185+
* The config file is structured in such a way that is
186+
* has a section header that is approximately 500kb of
187+
* size followed by 40k entries. While the resulting
188+
* configuration file itself is roughly 650kb in size and
189+
* thus considered to be rather small, in the past we'd
190+
* balloon to more than 20GB of memory (20000x500kb)
191+
* while parsing the file. It thus was a trivial way to
192+
* cause an out-of-memory situation and thus cause denial
193+
* of service, e.g. via gitmodules.
194+
*/
195+
cl_git_pass(git_config_open_ondisk(&config, cl_fixture("config/config-oom")));
196+
197+
git_config_free(config);
198+
}

tests/resources/config/config-oom

Lines changed: 40000 additions & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)