Commit dac16048 by Russell Belfer

Add mutex around index entries changes

This surrounds any function that mutates the entries vector with
a mutex so it can be safely snapshotted.
parent 54edbb98
...@@ -325,6 +325,28 @@ static unsigned int index_merge_mode( ...@@ -325,6 +325,28 @@ static unsigned int index_merge_mode(
return git_index__create_mode(mode); return git_index__create_mode(mode);
} }
static int index_sort_if_needed(git_index *index, bool need_lock)
{
/* not truly threadsafe because between when this checks and/or
* sorts the array another thread could come in and unsort it
*/
if (git_vector_is_sorted(&index->entries))
return 0;
if (need_lock && git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Unable to lock index");
return -1;
}
git_vector_sort(&index->entries);
if (need_lock)
git_mutex_unlock(&index->lock);
return 0;
}
void git_index__set_ignore_case(git_index *index, bool ignore_case) void git_index__set_ignore_case(git_index *index, bool ignore_case)
{ {
index->ignore_case = ignore_case; index->ignore_case = ignore_case;
...@@ -334,7 +356,7 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case) ...@@ -334,7 +356,7 @@ void git_index__set_ignore_case(git_index *index, bool ignore_case)
index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path;
git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp); git_vector_set_cmp(&index->entries, ignore_case ? index_icmp : index_cmp);
git_vector_sort(&index->entries); index_sort_if_needed(index, true);
index->reuc_search = ignore_case ? reuc_isrch : reuc_srch; index->reuc_search = ignore_case ? reuc_isrch : reuc_srch;
...@@ -352,6 +374,12 @@ int git_index_open(git_index **index_out, const char *index_path) ...@@ -352,6 +374,12 @@ int git_index_open(git_index **index_out, const char *index_path)
index = git__calloc(1, sizeof(git_index)); index = git__calloc(1, sizeof(git_index));
GITERR_CHECK_ALLOC(index); GITERR_CHECK_ALLOC(index);
if (git_mutex_init(&index->lock)) {
giterr_set(GITERR_OS, "Failed to initialize lock");
git__free(index);
return -1;
}
if (index_path != NULL) { if (index_path != NULL) {
index->index_file_path = git__strdup(index_path); index->index_file_path = git__strdup(index_path);
if (!index->index_file_path) if (!index->index_file_path)
...@@ -364,7 +392,8 @@ int git_index_open(git_index **index_out, const char *index_path) ...@@ -364,7 +392,8 @@ int git_index_open(git_index **index_out, const char *index_path)
if (git_vector_init(&index->entries, 32, index_cmp) < 0 || if (git_vector_init(&index->entries, 32, index_cmp) < 0 ||
git_vector_init(&index->names, 32, conflict_name_cmp) < 0 || git_vector_init(&index->names, 32, conflict_name_cmp) < 0 ||
git_vector_init(&index->reuc, 32, reuc_cmp) < 0) git_vector_init(&index->reuc, 32, reuc_cmp) < 0 ||
git_vector_init(&index->deleted, 2, index_cmp) < 0)
goto fail; goto fail;
index->entries_cmp_path = index_cmp_path; index->entries_cmp_path = index_cmp_path;
...@@ -392,12 +421,19 @@ int git_index_new(git_index **out) ...@@ -392,12 +421,19 @@ int git_index_new(git_index **out)
static void index_free(git_index *index) static void index_free(git_index *index)
{ {
/* index iterators increment the refcount of the index, so if we
* get here then there should be no outstanding iterators.
*/
assert(!git_atomic_get(&index->readers));
git_index_clear(index); git_index_clear(index);
git_vector_free(&index->entries); git_vector_free(&index->entries);
git_vector_free(&index->names); git_vector_free(&index->names);
git_vector_free(&index->reuc); git_vector_free(&index->reuc);
git_vector_free(&index->deleted);
git__free(index->index_file_path); git__free(index->index_file_path);
git_mutex_free(&index->lock);
git__memzero(index, sizeof(*index)); git__memzero(index, sizeof(*index));
git__free(index); git__free(index);
...@@ -411,30 +447,74 @@ void git_index_free(git_index *index) ...@@ -411,30 +447,74 @@ void git_index_free(git_index *index)
GIT_REFCOUNT_DEC(index, index_free); GIT_REFCOUNT_DEC(index, index_free);
} }
static void index_entries_free(git_vector *entries) /* call with locked index */
static void index_free_deleted(git_index *index)
{ {
size_t i; size_t i;
for (i = 0; i < entries->length; ++i) if (git_atomic_get(&index->readers) > 0)
index_entry_free(git__swap(entries->contents[i], NULL)); return;
for (i = 0; i < index->deleted.length; ++i)
index_entry_free(git__swap(index->deleted.contents[i], NULL));
git_vector_clear(&index->deleted);
}
static int index_remove_entry(git_index *index, size_t pos, bool need_lock)
{
int error = 0;
git_index_entry *entry = git_vector_get(&index->entries, pos);
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
if (need_lock && git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Unable to lock index");
return -1;
}
error = git_vector_remove(&index->entries, pos);
if (!error) {
if (!git_atomic_get(&index->readers))
index_entry_free(entry);
else
error = git_vector_insert(&index->deleted, entry);
}
if (need_lock)
git_mutex_unlock(&index->lock);
git_vector_clear(entries); return error;
} }
int git_index_clear(git_index *index) int git_index_clear(git_index *index)
{ {
int error = 0;
assert(index); assert(index);
git_tree_cache_free(index->tree); git_tree_cache_free(index->tree);
index->tree = NULL; index->tree = NULL;
index_entries_free(&index->entries); if (git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Failed to lock index");
return -1;
}
while (!error && index->entries.length > 0)
error = index_remove_entry(index, index->entries.length - 1, false);
index_free_deleted(index);
git_mutex_unlock(&index->lock);
git_index_reuc_clear(index); git_index_reuc_clear(index);
git_index_name_clear(index); git_index_name_clear(index);
git_futils_filestamp_set(&index->stamp, NULL); git_futils_filestamp_set(&index->stamp, NULL);
return 0; return error;
} }
static int create_index_error(int error, const char *msg) static int create_index_error(int error, const char *msg)
...@@ -545,7 +625,8 @@ int git_index_write(git_index *index) ...@@ -545,7 +625,8 @@ int git_index_write(git_index *index)
return create_index_error(-1, return create_index_error(-1,
"Failed to read index: The index is in-memory only"); "Failed to read index: The index is in-memory only");
git_vector_sort(&index->entries); if (index_sort_if_needed(index, true) < 0)
return -1;
git_vector_sort(&index->reuc); git_vector_sort(&index->reuc);
if ((error = git_filebuf_open( if ((error = git_filebuf_open(
...@@ -593,7 +674,8 @@ int git_index_write_tree(git_oid *oid, git_index *index) ...@@ -593,7 +674,8 @@ int git_index_write_tree(git_oid *oid, git_index *index)
return git_tree__write_index(oid, index, repo); return git_tree__write_index(oid, index, repo);
} }
int git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo) int git_index_write_tree_to(
git_oid *oid, git_index *index, git_repository *repo)
{ {
assert(oid && index && repo); assert(oid && index && repo);
return git_tree__write_index(oid, index, repo); return git_tree__write_index(oid, index, repo);
...@@ -609,7 +691,8 @@ const git_index_entry *git_index_get_byindex( ...@@ -609,7 +691,8 @@ const git_index_entry *git_index_get_byindex(
git_index *index, size_t n) git_index *index, size_t n)
{ {
assert(index); assert(index);
git_vector_sort(&index->entries); if (index_sort_if_needed(index, true) < 0)
return NULL;
return git_vector_get(&index->entries, n); return git_vector_get(&index->entries, n);
} }
...@@ -695,22 +778,6 @@ static int index_entry_init( ...@@ -695,22 +778,6 @@ static int index_entry_init(
return 0; return 0;
} }
static int index_remove_entry(git_index *index, size_t pos)
{
int error = 0;
git_index_entry *entry = git_vector_get(&index->entries, pos);
if (entry != NULL)
git_tree_cache_invalidate_path(index->tree, entry->path);
error = git_vector_remove(&index->entries, pos);
if (!error)
index_entry_free(entry);
return error;
}
static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, static int index_entry_reuc_init(git_index_reuc_entry **reuc_out,
const char *path, const char *path,
int ancestor_mode, const git_oid *ancestor_oid, int ancestor_mode, const git_oid *ancestor_oid,
...@@ -793,7 +860,7 @@ static int has_file_name(git_index *index, ...@@ -793,7 +860,7 @@ static int has_file_name(git_index *index,
if (!ok_to_replace) if (!ok_to_replace)
break; break;
if (index_remove_entry(index, --pos) < 0) if (index_remove_entry(index, --pos, true) < 0)
break; break;
} }
return retval; return retval;
...@@ -827,7 +894,7 @@ static int has_dir_name(git_index *index, ...@@ -827,7 +894,7 @@ static int has_dir_name(git_index *index,
if (!ok_to_replace) if (!ok_to_replace)
break; break;
if (index_remove_entry(index, position) < 0) if (index_remove_entry(index, position, true) < 0)
break; break;
continue; continue;
} }
...@@ -924,7 +991,12 @@ static int index_insert( ...@@ -924,7 +991,12 @@ static int index_insert(
/* if replacing is not requested or no existing entry exists, just /* if replacing is not requested or no existing entry exists, just
* insert entry at the end; the index is no longer sorted * insert entry at the end; the index is no longer sorted
*/ */
error = git_vector_insert(&index->entries, entry); if ((error = git_mutex_lock(&index->lock)) < 0) {
giterr_set(GITERR_OS, "Unable to acquire index lock");
} else {
error = git_vector_insert(&index->entries, entry);
git_mutex_unlock(&index->lock);
}
done: done:
if (error < 0) { if (error < 0) {
...@@ -1020,7 +1092,7 @@ int git_index_remove(git_index *index, const char *path, int stage) ...@@ -1020,7 +1092,7 @@ int git_index_remove(git_index *index, const char *path, int stage)
return GIT_ENOTFOUND; return GIT_ENOTFOUND;
} }
return index_remove_entry(index, position); return index_remove_entry(index, position, true);
} }
int git_index_remove_directory(git_index *index, const char *dir, int stage) int git_index_remove_directory(git_index *index, const char *dir, int stage)
...@@ -1045,7 +1117,7 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) ...@@ -1045,7 +1117,7 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage)
continue; continue;
} }
if ((error = index_remove_entry(index, pos)) < 0) if ((error = index_remove_entry(index, pos, true)) < 0)
break; break;
/* removed entry at 'pos' so we don't need to increment it */ /* removed entry at 'pos' so we don't need to increment it */
...@@ -1072,7 +1144,8 @@ int git_index__find( ...@@ -1072,7 +1144,8 @@ int git_index__find(
{ {
assert(index && path); assert(index && path);
git_vector_sort(&index->entries); if (index_sort_if_needed(index, true) < 0)
return -1;
return git_index__find_in_entries( return git_index__find_in_entries(
out, &index->entries, index->entries_search, path, path_len, stage); out, &index->entries, index->entries_search, path, path_len, stage);
...@@ -1244,7 +1317,7 @@ int git_index_conflict_remove(git_index *index, const char *path) ...@@ -1244,7 +1317,7 @@ int git_index_conflict_remove(git_index *index, const char *path)
continue; continue;
} }
if ((error = index_remove_entry(index, pos)) < 0) if ((error = index_remove_entry(index, pos, true)) < 0)
break; break;
} }
...@@ -1256,12 +1329,9 @@ static int index_conflicts_match(const git_vector *v, size_t idx, void *p) ...@@ -1256,12 +1329,9 @@ static int index_conflicts_match(const git_vector *v, size_t idx, void *p)
git_index *index = p; git_index *index = p;
git_index_entry *entry = git_vector_get(v, idx); git_index_entry *entry = git_vector_get(v, idx);
GIT_UNUSED(index); if (GIT_IDXENTRY_STAGE(entry) > 0 &&
!index_remove_entry(index, idx, false))
if (GIT_IDXENTRY_STAGE(entry) > 0) {
index_entry_free(entry);
return 1; return 1;
}
return 0; return 0;
} }
...@@ -1269,7 +1339,12 @@ static int index_conflicts_match(const git_vector *v, size_t idx, void *p) ...@@ -1269,7 +1339,12 @@ static int index_conflicts_match(const git_vector *v, size_t idx, void *p)
void git_index_conflict_cleanup(git_index *index) void git_index_conflict_cleanup(git_index *index)
{ {
assert(index); assert(index);
if (git_mutex_lock(&index->lock) < 0)
return;
git_vector_remove_matching(&index->entries, index_conflicts_match, index); git_vector_remove_matching(&index->entries, index_conflicts_match, index);
index_free_deleted(index);
git_mutex_unlock(&index->lock);
} }
int git_index_has_conflicts(const git_index *index) int git_index_has_conflicts(const git_index *index)
...@@ -1792,6 +1867,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) ...@@ -1792,6 +1867,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
seek_forward(INDEX_HEADER_SIZE); seek_forward(INDEX_HEADER_SIZE);
if (git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Unable to acquire index lock");
return -1;
}
git_vector_clear(&index->entries); git_vector_clear(&index->entries);
/* Parse all the entries */ /* Parse all the entries */
...@@ -1860,9 +1940,10 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) ...@@ -1860,9 +1940,10 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size)
* in-memory index is supposed to be case-insensitive * in-memory index is supposed to be case-insensitive
*/ */
git_vector_set_sorted(&index->entries, !index->ignore_case); git_vector_set_sorted(&index->entries, !index->ignore_case);
git_vector_sort(&index->entries); error = index_sort_if_needed(index, false);
done: done:
git_mutex_unlock(&index->lock);
return error; return error;
} }
...@@ -1951,6 +2032,11 @@ static int write_entries(git_index *index, git_filebuf *file) ...@@ -1951,6 +2032,11 @@ static int write_entries(git_index *index, git_filebuf *file)
git_vector case_sorted, *entries; git_vector case_sorted, *entries;
git_index_entry *entry; git_index_entry *entry;
if (git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Failed to lock index");
return -1;
}
/* If index->entries is sorted case-insensitively, then we need /* If index->entries is sorted case-insensitively, then we need
* to re-sort it case-sensitively before writing */ * to re-sort it case-sensitively before writing */
if (index->ignore_case) { if (index->ignore_case) {
...@@ -1965,6 +2051,8 @@ static int write_entries(git_index *index, git_filebuf *file) ...@@ -1965,6 +2051,8 @@ static int write_entries(git_index *index, git_filebuf *file)
if ((error = write_disk_entry(file, entry)) < 0) if ((error = write_disk_entry(file, entry)) < 0)
break; break;
git_mutex_unlock(&index->lock);
if (index->ignore_case) if (index->ignore_case)
git_vector_free(&case_sorted); git_vector_free(&case_sorted);
...@@ -2136,7 +2224,7 @@ int git_index_entry_stage(const git_index_entry *entry) ...@@ -2136,7 +2224,7 @@ int git_index_entry_stage(const git_index_entry *entry)
typedef struct read_tree_data { typedef struct read_tree_data {
git_vector *old_entries; git_vector *old_entries;
git_vector *new_entries; git_vector *new_entries;
git_vector_cmp entries_search; git_vector_cmp entry_cmp;
} read_tree_data; } read_tree_data;
static int read_tree_cb( static int read_tree_cb(
...@@ -2145,6 +2233,7 @@ static int read_tree_cb( ...@@ -2145,6 +2233,7 @@ static int read_tree_cb(
read_tree_data *data = payload; read_tree_data *data = payload;
git_index_entry *entry = NULL, *old_entry; git_index_entry *entry = NULL, *old_entry;
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
size_t pos;
if (git_tree_entry__is_tree(tentry)) if (git_tree_entry__is_tree(tentry))
return 0; return 0;
...@@ -2159,23 +2248,15 @@ static int read_tree_cb( ...@@ -2159,23 +2248,15 @@ static int read_tree_cb(
entry->id = tentry->oid; entry->id = tentry->oid;
/* look for corresponding old entry and copy data to new entry */ /* look for corresponding old entry and copy data to new entry */
if (data->old_entries) { if (data->old_entries != NULL &&
size_t pos; !git_index__find_in_entries(
struct entry_srch_key skey; &pos, data->old_entries, data->entry_cmp, path.ptr, 0, 0) &&
(old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
skey.path = path.ptr; entry->mode == old_entry->mode &&
skey.path_len = strlen(path.ptr); git_oid_equal(&entry->id, &old_entry->id))
skey.stage = 0; {
memcpy(entry, old_entry, sizeof(*entry));
if (!git_vector_bsearch2( entry->flags_extended = 0;
&pos, data->old_entries, data->entries_search, &skey) &&
(old_entry = git_vector_get(data->old_entries, pos)) != NULL &&
entry->mode == old_entry->mode &&
git_oid_equal(&entry->id, &old_entry->id))
{
memcpy(entry, old_entry, sizeof(*entry));
entry->flags_extended = 0;
}
} }
if (path.size < GIT_IDXENTRY_NAMEMASK) if (path.size < GIT_IDXENTRY_NAMEMASK)
...@@ -2204,9 +2285,10 @@ int git_index_read_tree(git_index *index, const git_tree *tree) ...@@ -2204,9 +2285,10 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
data.old_entries = &index->entries; data.old_entries = &index->entries;
data.new_entries = &entries; data.new_entries = &entries;
data.entries_search = index->entries_search; data.entry_cmp = index->entries_search;
git_vector_sort(&index->entries); if (index_sort_if_needed(index, true) < 0)
return -1;
error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data);
...@@ -2215,8 +2297,13 @@ int git_index_read_tree(git_index *index, const git_tree *tree) ...@@ -2215,8 +2297,13 @@ int git_index_read_tree(git_index *index, const git_tree *tree)
if ((error = git_index_clear(index)) < 0) if ((error = git_index_clear(index)) < 0)
/* well, this isn't good */; /* well, this isn't good */;
else else if (git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Unable to acquire index lock");
error = -1;
} else {
git_vector_swap(&entries, &index->entries); git_vector_swap(&entries, &index->entries);
git_mutex_unlock(&index->lock);
}
} }
git_vector_free(&entries); git_vector_free(&entries);
...@@ -2453,9 +2540,19 @@ int git_index__snapshot(git_vector *entries, git_index *index) ...@@ -2453,9 +2540,19 @@ int git_index__snapshot(git_vector *entries, git_index *index)
int error; int error;
GIT_REFCOUNT_INC(index); GIT_REFCOUNT_INC(index);
if (git_mutex_lock(&index->lock) < 0) {
giterr_set(GITERR_OS, "Failed to lock index");
return -1;
}
git_atomic_inc(&index->readers);
git_vector_sort(&index->entries); git_vector_sort(&index->entries);
error = git_vector_dup(entries, &index->entries, index->entries._cmp); error = git_vector_dup(entries, &index->entries, index->entries._cmp);
git_mutex_unlock(&index->lock);
if (error < 0) if (error < 0)
git_index_free(index); git_index_free(index);
...@@ -2464,5 +2561,11 @@ int git_index__snapshot(git_vector *entries, git_index *index) ...@@ -2464,5 +2561,11 @@ int git_index__snapshot(git_vector *entries, git_index *index)
void git_index__release_snapshot(git_index *index) void git_index__release_snapshot(git_index *index)
{ {
git_atomic_dec(&index->readers);
git_index_free(index); git_index_free(index);
if (!git_mutex_lock(&index->lock)) {
index_free_deleted(index); /* try to free pending deleted items */
git_mutex_unlock(&index->lock);
}
} }
...@@ -21,12 +21,15 @@ struct git_index { ...@@ -21,12 +21,15 @@ struct git_index {
git_refcount rc; git_refcount rc;
char *index_file_path; char *index_file_path;
git_futils_filestamp stamp; git_futils_filestamp stamp;
git_vector entries; git_vector entries;
unsigned int on_disk:1; git_mutex lock; /* lock held while entries is being changed */
git_vector deleted; /* deleted entries if readers > 0 */
git_atomic readers; /* number of active iterators */
unsigned int on_disk:1;
unsigned int ignore_case:1; unsigned int ignore_case:1;
unsigned int distrust_filemode:1; unsigned int distrust_filemode:1;
unsigned int no_symlinks:1; unsigned int no_symlinks:1;
......
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