Skip to content

Commit c69dc92

Browse files
stefanbellergitster
authored andcommitted
attr: convert to new threadsafe API
This revamps the API of the attr subsystem to be thread safe. Before we had the question and its results in one struct type. The typical usage of the API was static struct git_attr_check; if (!check) check = git_attr_check_initl("text", NULL); git_check_attr(path, check); act_on(check->value[0]); This has a couple of issues when it comes to thread safety: * the initialization is racy in this implementation. To make it thread safe, we need to acquire a mutex, such that only one thread is executing the code in git_attr_check_initl. As we do not want to introduce a mutex at each call site, this is best done in the attr code. However to do so, we need to have access to the `check` variable, i.e. the API has to look like git_attr_check_initl(struct git_attr_check*, ...); Then one of the threads calling git_attr_check_initl will acquire the mutex and init the `check`, while all other threads will wait on the mutex just to realize they're late to the party and they'll return with no work done. * While the check for attributes to be questioned only need to be initalized once as that part will be read only after its initialisation, the answer may be different for each path. Because of that we need to decouple the check and the answer, such that each thread can obtain an answer for the path it is currently processing. This commit only changes the API, i.e. this doesn't implement actual thread safety. However a later commit that introduces thread safety needs to touch code in attr.c only and doesn't have to add changes all over the place. Signed-off-by: Stefan Beller <[email protected]>
1 parent e2a175f commit c69dc92

File tree

10 files changed

+230
-150
lines changed

10 files changed

+230
-150
lines changed

Documentation/technical/api-gitattributes.txt

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@ attributes to set of paths.
88
Data Structure
99
--------------
1010

11+
extern struct git_attr *git_attr(const char *);
12+
13+
/*
14+
* Return the name of the attribute represented by the argument. The
15+
* return value is a pointer to a null-delimited string that is part
16+
* of the internal data structure; it should not be modified or freed.
17+
*/
18+
extern const char *git_attr_name(const struct git_attr *);
19+
20+
extern int attr_name_valid(const char *name, size_t namelen);
21+
extern void invalid_attr_name_message(struct strbuf *, const char *, int);
22+
1123
`struct git_attr`::
1224

1325
An attribute is an opaque object that is identified by its name.
@@ -16,15 +28,17 @@ Data Structure
1628
of no interest to the calling programs. The name of the
1729
attribute can be retrieved by calling `git_attr_name()`.
1830

19-
`struct git_attr_check_elem`::
20-
21-
This structure represents one attribute and its value.
22-
2331
`struct git_attr_check`::
2432

25-
This structure represents a collection of `git_attr_check_elem`.
33+
This structure represents a collection of `struct git_attrs`.
2634
It is passed to `git_check_attr()` function, specifying the
27-
attributes to check, and receives their values.
35+
attributes to check, and receives their values into a corresponding
36+
`struct git_attr_result`.
37+
38+
`struct git_attr_result`::
39+
40+
This structure represents a collection of results to its
41+
corresponding `struct git_attr_check`, that has the same order.
2842

2943

3044
Attribute Values
@@ -56,16 +70,22 @@ Querying Specific Attributes
5670
* Prepare `struct git_attr_check` using git_attr_check_initl()
5771
function, enumerating the names of attributes whose values you are
5872
interested in, terminated with a NULL pointer. Alternatively, an
59-
empty `struct git_attr_check` can be prepared by calling
60-
`git_attr_check_alloc()` function and then attributes you want to
61-
ask about can be added to it with `git_attr_check_append()`
62-
function.
73+
empty `struct git_attr_check` as alloced by git_attr_check_alloc()
74+
can be prepared by calling `git_attr_check_alloc()` function and
75+
then attributes you want to ask about can be added to it with
76+
`git_attr_check_append()` function.
77+
git_attr_check_initl is thread safe, i.e. you can call it
78+
from different threads at the same time; internally however only one
79+
call at a time is processed. If the calls from different threads have
80+
the same arguments, the returned `git_attr_check` may be the same.
6381

64-
* Call `git_check_attr()` to check the attributes for the path.
82+
* Call `git_check_attr()` to check the attributes for the path,
83+
the returned `git_attr_result` contains the result.
6584

66-
* Inspect `git_attr_check` structure to see how each of the
67-
attribute in the array is defined for the path.
85+
* Inspect the returned `git_attr_result` structure to see how
86+
each of the attribute in the array is defined for the path.
6887

88+
* Do not free the result as the memory is owned by the attr subsystem.
6989

7090
Example
7191
-------
@@ -89,15 +109,20 @@ static void setup_check(void)
89109

90110
------------
91111
const char *path;
112+
struct git_attr_result *result;
92113

93114
setup_check();
94-
git_check_attr(path, check);
115+
result = git_check_attr(path, check);
95116
------------
96117

97-
. Act on `.value` member of the result, left in `check->check[]`:
118+
The `result` must not be free'd as it is owned by the attr subsystem.
119+
It is reused by the same thread, so a subsequent call to git_check_attr
120+
in the same thread will overwrite the result.
121+
122+
. Act on `result->value[]`:
98123

99124
------------
100-
const char *value = check->check[0].value;
125+
const char *value = result->value[0];
101126

102127
if (ATTR_TRUE(value)) {
103128
The attribute is Set, by listing only the name of the
@@ -123,6 +148,8 @@ the first step in the above would be different.
123148
static struct git_attr_check *check;
124149
static void setup_check(const char **argv)
125150
{
151+
if (check)
152+
return; /* already done */
126153
check = git_attr_check_alloc();
127154
while (*argv) {
128155
struct git_attr *attr = git_attr(*argv);
@@ -138,17 +165,20 @@ Querying All Attributes
138165

139166
To get the values of all attributes associated with a file:
140167

141-
* Prepare an empty `git_attr_check` structure by calling
142-
`git_attr_check_alloc()`.
168+
* Setup a local variables on the stack for both the question
169+
`struct git_attr_check` as well as the result `struct git_attr_result`.
170+
Zero them out via their respective _INIT macro.
143171

144-
* Call `git_all_attrs()`, which populates the `git_attr_check`
145-
with the attributes attached to the path.
172+
* Call `git_all_attrs()`
146173

147-
* Iterate over the `git_attr_check.check[]` array to examine
148-
the attribute names and values. The name of the attribute
149-
described by a `git_attr_check.check[]` object can be retrieved via
150-
`git_attr_name(check->check[i].attr)`. (Please note that no items
174+
* Iterate over the `git_attr_check.attr[]` array to examine the
175+
attribute names. The name of the attribute described by a
176+
`git_attr_check.attr[]` object can be retrieved via
177+
`git_attr_name(check->attr[i])`. (Please note that no items
151178
will be returned for unset attributes, so `ATTR_UNSET()` will return
152179
false for all returned `git_array_check` objects.)
180+
The respective value for an attribute can be found in the same
181+
index position in of `git_attr_result`.
153182

154-
* Free the `git_array_check` by calling `git_attr_check_free()`.
183+
* Clear the local variables by calling `git_attr_check_clear()` and
184+
`git_attr_result_clear()`.

archive.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
111111
struct archiver_args *args = c->args;
112112
write_archive_entry_fn_t write_entry = c->write_entry;
113113
static struct git_attr_check *check;
114+
struct git_attr_result *result;
114115
const char *path_without_prefix;
115116
int err;
116117

@@ -125,11 +126,13 @@ static int write_archive_entry(const unsigned char *sha1, const char *base,
125126
path_without_prefix = path.buf + args->baselen;
126127

127128
if (!check)
128-
check = git_attr_check_initl("export-ignore", "export-subst", NULL);
129-
if (!git_check_attr(path_without_prefix, check)) {
130-
if (ATTR_TRUE(check->check[0].value))
129+
git_attr_check_initl(&check, "export-ignore", "export-subst", NULL);
130+
131+
result = git_check_attr(path_without_prefix, check);
132+
if (result) {
133+
if (ATTR_TRUE(result->value[0]))
131134
return 0;
132-
args->convert = ATTR_TRUE(check->check[1].value);
135+
args->convert = ATTR_TRUE(result->value[1]);
133136
}
134137

135138
if (S_ISDIR(mode) || S_ISGITLINK(mode)) {

attr.c

Lines changed: 60 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ static struct git_attr *(git_attr_hash[HASHSIZE]);
5555
*/
5656
static int cannot_trust_maybe_real;
5757

58+
/*
59+
* Send one or more git_attr_check to git_check_attrs(), and
60+
* each 'value' member tells what its value is.
61+
* Unset one is returned as NULL.
62+
*/
63+
struct git_attr_check_elem {
64+
const struct git_attr *attr;
65+
const char *value;
66+
};
67+
5868
/* NEEDSWORK: This will become per git_attr_check */
5969
static struct git_attr_check_elem *check_all_attr;
6070

@@ -781,7 +791,7 @@ static int macroexpand_one(int nr, int rem)
781791

782792
static int attr_check_is_dynamic(const struct git_attr_check *check)
783793
{
784-
return (void *)(check->check) != (void *)(check + 1);
794+
return (void *)(check->attr) != (void *)(check + 1);
785795
}
786796

787797
static void empty_attr_check_elems(struct git_attr_check *check)
@@ -799,7 +809,8 @@ static void empty_attr_check_elems(struct git_attr_check *check)
799809
* any and all attributes that are visible are collected in it.
800810
*/
801811
static void collect_some_attrs(const char *path, int pathlen,
802-
struct git_attr_check *check, int collect_all)
812+
struct git_attr_check *check,
813+
struct git_attr_result *result, int collect_all)
803814

804815
{
805816
struct attr_stack *stk;
@@ -825,13 +836,11 @@ static void collect_some_attrs(const char *path, int pathlen,
825836
check_all_attr[i].value = ATTR__UNKNOWN;
826837

827838
if (!collect_all && !cannot_trust_maybe_real) {
828-
struct git_attr_check_elem *celem = check->check;
829-
830839
rem = 0;
831840
for (i = 0; i < check->check_nr; i++) {
832-
if (!celem[i].attr->maybe_real) {
841+
if (!check->attr[i]->maybe_real) {
833842
struct git_attr_check_elem *c;
834-
c = check_all_attr + celem[i].attr->attr_nr;
843+
c = check_all_attr + check->attr[i]->attr_nr;
835844
c->value = ATTR__UNSET;
836845
rem++;
837846
}
@@ -845,38 +854,45 @@ static void collect_some_attrs(const char *path, int pathlen,
845854
rem = fill(path, pathlen, basename_offset, stk, rem);
846855

847856
if (collect_all) {
848-
empty_attr_check_elems(check);
849857
for (i = 0; i < attr_nr; i++) {
850858
const struct git_attr *attr = check_all_attr[i].attr;
851859
const char *value = check_all_attr[i].value;
852860
if (value == ATTR__UNSET || value == ATTR__UNKNOWN)
853861
continue;
854-
git_attr_check_append(check, attr)->value = value;
862+
863+
git_attr_check_append(check, attr);
864+
865+
ALLOC_GROW(result->value,
866+
result->check_nr + 1,
867+
result->check_alloc);
868+
result->value[result->check_nr++] = value;
855869
}
856870
}
857871
}
858872

859873
static int git_check_attrs(const char *path, int pathlen,
860-
struct git_attr_check *check)
874+
struct git_attr_check *check,
875+
struct git_attr_result *result)
861876
{
862877
int i;
863-
struct git_attr_check_elem *celem = check->check;
864878

865-
collect_some_attrs(path, pathlen, check, 0);
879+
collect_some_attrs(path, pathlen, check, result, 0);
866880

867881
for (i = 0; i < check->check_nr; i++) {
868-
const char *value = check_all_attr[celem[i].attr->attr_nr].value;
882+
const char *value = check_all_attr[check->attr[i]->attr_nr].value;
869883
if (value == ATTR__UNKNOWN)
870884
value = ATTR__UNSET;
871-
celem[i].value = value;
885+
result->value[i] = value;
872886
}
873887

874888
return 0;
875889
}
876890

877-
void git_all_attrs(const char *path, struct git_attr_check *check)
891+
void git_all_attrs(const char *path,
892+
struct git_attr_check *check,
893+
struct git_attr_result *result)
878894
{
879-
collect_some_attrs(path, strlen(path), check, 1);
895+
collect_some_attrs(path, strlen(path), check, result, 1);
880896
}
881897

882898
void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
@@ -892,36 +908,41 @@ void git_attr_set_direction(enum git_attr_direction new, struct index_state *ist
892908
use_index = istate;
893909
}
894910

895-
static int git_check_attr_counted(const char *path, int pathlen,
896-
struct git_attr_check *check)
911+
struct git_attr_result *git_check_attr(const char *path,
912+
struct git_attr_check *check)
897913
{
898-
check->finalized = 1;
899-
return git_check_attrs(path, pathlen, check);
900-
}
914+
static struct git_attr_result *result;
915+
if (!result)
916+
result = xcalloc(1, sizeof(*result));
901917

902-
int git_check_attr(const char *path, struct git_attr_check *check)
903-
{
904-
return git_check_attr_counted(path, strlen(path), check);
918+
ALLOC_GROW(result->value, check->check_nr, result->check_alloc);
919+
result->check_nr = check->check_nr;
920+
921+
check->finalized = 1;
922+
git_check_attrs(path, strlen(path), check, result);
923+
return result;
905924
}
906925

907-
struct git_attr_check *git_attr_check_initl(const char *one, ...)
926+
extern void git_attr_check_initl(struct git_attr_check **check_,
927+
const char *one, ...)
908928
{
909-
struct git_attr_check *check;
910929
int cnt;
911930
va_list params;
912931
const char *param;
932+
struct git_attr_check *check;
913933

914934
va_start(params, one);
915935
for (cnt = 1; (param = va_arg(params, const char *)) != NULL; cnt++)
916936
;
917937
va_end(params);
938+
918939
check = xcalloc(1,
919-
sizeof(*check) + cnt * sizeof(*(check->check)));
940+
sizeof(*check) + cnt * sizeof(*(check->attr)));
920941
check->check_nr = cnt;
921942
check->finalized = 1;
922-
check->check = (struct git_attr_check_elem *)(check + 1);
943+
check->attr = (const struct git_attr **)(check + 1);
923944

924-
check->check[0].attr = git_attr(one);
945+
check->attr[0] = git_attr(one);
925946
va_start(params, one);
926947
for (cnt = 1; cnt < check->check_nr; cnt++) {
927948
struct git_attr *attr;
@@ -932,42 +953,40 @@ struct git_attr_check *git_attr_check_initl(const char *one, ...)
932953
attr = git_attr(param);
933954
if (!attr)
934955
die("BUG: %s: not a valid attribute name", param);
935-
check->check[cnt].attr = attr;
956+
check->attr[cnt] = attr;
936957
}
937958
va_end(params);
938-
return check;
959+
*check_ = check;
939960
}
940961

941962
struct git_attr_check *git_attr_check_alloc(void)
942963
{
943964
return xcalloc(1, sizeof(struct git_attr_check));
944965
}
945966

946-
struct git_attr_check_elem *git_attr_check_append(struct git_attr_check *check,
947-
const struct git_attr *attr)
967+
void git_attr_check_append(struct git_attr_check *check,
968+
const struct git_attr *attr)
948969
{
949-
struct git_attr_check_elem *elem;
950970
if (check->finalized)
951971
die("BUG: append after git_attr_check structure is finalized");
952972
if (!attr_check_is_dynamic(check))
953973
die("BUG: appending to a statically initialized git_attr_check");
954-
ALLOC_GROW(check->check, check->check_nr + 1, check->check_alloc);
955-
elem = &check->check[check->check_nr++];
956-
elem->attr = attr;
957-
return elem;
974+
ALLOC_GROW(check->attr, check->check_nr + 1, check->check_alloc);
975+
check->attr[check->check_nr++] = attr;
958976
}
959977

960978
void git_attr_check_clear(struct git_attr_check *check)
961979
{
962980
empty_attr_check_elems(check);
963981
if (!attr_check_is_dynamic(check))
964982
die("BUG: clearing a statically initialized git_attr_check");
965-
free(check->check);
983+
free(check->attr);
966984
check->check_alloc = 0;
967985
}
968986

969-
void git_attr_check_free(struct git_attr_check *check)
987+
void git_attr_result_clear(struct git_attr_result *result)
970988
{
971-
git_attr_check_clear(check);
972-
free(check);
989+
free(result->value);
990+
result->check_nr = 0;
991+
result->check_alloc = 0;
973992
}

0 commit comments

Comments
 (0)