Skip to content

Commit f572341

Browse files
committed
trace: verify that locking works
Recently, t5552 introduced a pattern where two processes try to write to the same GIT_TRACE file in parallel. This is not safe, as the two processes fighting over who gets to append to the file can cause garbled lines and may even result in data loss on Windows (where buffers are written to before they are flushed). To remedy this, we introduced the lock_or_unlock_fd_for_appending() function. And to make sure that this works, this commit introduces a regression test. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent a53e721 commit f572341

File tree

5 files changed

+139
-0
lines changed

5 files changed

+139
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,7 @@ TEST_BUILTINS_OBJS += test-strcmp-offset.o
729729
TEST_BUILTINS_OBJS += test-string-list.o
730730
TEST_BUILTINS_OBJS += test-submodule-config.o
731731
TEST_BUILTINS_OBJS += test-subprocess.o
732+
TEST_BUILTINS_OBJS += test-trace.o
732733
TEST_BUILTINS_OBJS += test-urlmatch-normalization.o
733734
TEST_BUILTINS_OBJS += test-wildmatch.o
734735
TEST_BUILTINS_OBJS += test-write-cache.o

t/helper/test-tool.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ static struct test_cmd cmds[] = {
3939
{ "string-list", cmd__string_list },
4040
{ "submodule-config", cmd__submodule_config },
4141
{ "subprocess", cmd__subprocess },
42+
{ "trace", cmd__trace },
4243
{ "urlmatch-normalization", cmd__urlmatch_normalization },
4344
{ "wildmatch", cmd__wildmatch },
4445
{ "write-cache", cmd__write_cache },

t/helper/test-tool.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ int cmd__strcmp_offset(int argc, const char **argv);
3333
int cmd__string_list(int argc, const char **argv);
3434
int cmd__submodule_config(int argc, const char **argv);
3535
int cmd__subprocess(int argc, const char **argv);
36+
int cmd__trace(int argc, const char **argv);
3637
int cmd__urlmatch_normalization(int argc, const char **argv);
3738
int cmd__wildmatch(int argc, const char **argv);
3839
int cmd__write_cache(int argc, const char **argv);

t/helper/test-trace.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#include "test-tool.h"
2+
#include "cache.h"
3+
#include "run-command.h"
4+
5+
static struct child_process children[2] = {
6+
CHILD_PROCESS_INIT,
7+
CHILD_PROCESS_INIT,
8+
};
9+
10+
#define SAY(child, what) \
11+
if (write_in_full(children[child].in, \
12+
what "\n", strlen(what) + 1) < 0) \
13+
die("Failed to tell child process #%d to %s", child, what)
14+
15+
#define LISTEN(child, what) \
16+
if (strbuf_getwholeline_fd(&buf, children[child].out, '\n') < 0) \
17+
die("Child process #%d failed to acknowledge %s", child, what)
18+
19+
#define ACK(what) \
20+
if (write_in_full(1, what ": ACK\n", strlen(what) + 6) < 0) \
21+
die_errno("'%s': %s ACK", child_name, what)
22+
23+
static void contention_orchestrator(const char *argv0)
24+
{
25+
struct strbuf buf = STRBUF_INIT;
26+
int i;
27+
28+
/* Spawn two children and simulate write contention */
29+
trace_printf("start");
30+
31+
for (i = 0; i < 2; i++) {
32+
strbuf_reset(&buf);
33+
strbuf_addf(&buf, "child #%d", i);
34+
argv_array_pushl(&children[i].args,
35+
argv0, "trace", "lock", buf.buf, NULL);
36+
children[i].in = children[i].out = -1;
37+
if (start_command(&children[i]) < 0)
38+
die("Could not spawn child process #%d", i);
39+
}
40+
41+
SAY(1, "lock");
42+
LISTEN(1, "lock");
43+
44+
SAY(0, "trace delayed");
45+
SAY(1, "trace while-locked");
46+
LISTEN(1, "trace");
47+
48+
SAY(1, "unlock");
49+
LISTEN(1, "unlock");
50+
LISTEN(0, "trace");
51+
52+
SAY(0, "quit");
53+
SAY(1, "quit");
54+
55+
if (finish_command(&children[0]) < 0 ||
56+
finish_command(&children[1]) < 0)
57+
die("Child process failed to finish");
58+
59+
strbuf_release(&buf);
60+
}
61+
62+
static void child(const char *child_name)
63+
{
64+
struct strbuf buf = STRBUF_INIT;
65+
int fd, locked = 0;
66+
const char *p;
67+
68+
/* This is the child process */
69+
trace_printf("child start: '%s'", child_name);
70+
fd = trace_default_key.fd;
71+
if (fd <= 0)
72+
die("child process: not tracing...");
73+
while (!strbuf_getwholeline_fd(&buf, 0, '\n')) {
74+
strbuf_rtrim(&buf);
75+
if (!strcmp("lock", buf.buf)) {
76+
if (lock_or_unlock_fd_for_appending(fd, 1) < 0 &&
77+
errno != EBADF)
78+
die_errno("'%s': lock", child_name);
79+
ACK("lock");
80+
locked = 1;
81+
} else if (!strcmp("unlock", buf.buf)) {
82+
if (lock_or_unlock_fd_for_appending(fd, 0) < 0 &&
83+
errno != EBADF)
84+
die_errno("'%s': unlock", child_name);
85+
ACK("unlock");
86+
locked = 0;
87+
} else if (skip_prefix(buf.buf, "trace ", &p)) {
88+
if (!locked)
89+
trace_printf("%s: %s", child_name, p);
90+
else {
91+
char *p2 = xstrdup(p);
92+
93+
/* Give the racy process a run for its money. */
94+
sleep_millisec(50);
95+
96+
strbuf_reset(&buf);
97+
strbuf_addf(&buf, "%s: %s\n",
98+
child_name, p2);
99+
free(p2);
100+
101+
if (write_in_full(fd, buf.buf, buf.len) < 0)
102+
die_errno("'%s': trace", child_name);
103+
}
104+
ACK("trace");
105+
} else if (!strcmp("quit", buf.buf)) {
106+
close(0);
107+
close(1);
108+
break;
109+
} else
110+
die("Unhandled command: '%s'", buf.buf);
111+
112+
}
113+
114+
strbuf_release(&buf);
115+
}
116+
117+
int cmd__trace(int argc, const char **argv)
118+
{
119+
const char *argv0 = argv[-1];
120+
121+
if (argc > 1 && !strcmp("lock", argv[1])) {
122+
if (argc > 2)
123+
child(argv[2]);
124+
else
125+
contention_orchestrator(argv0);
126+
} else
127+
die("Usage: %s %s lock [<child-name>]", argv0, argv[0]);
128+
129+
return 0;
130+
}

t/t0070-fundamental.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,10 @@ test_expect_success 'check for a bug in the regex routines' '
3434
test-tool regex --bug
3535
'
3636

37+
test_expect_success 'multiple processes can GIT_TRACE to the same file' '
38+
GIT_TRACE="$(pwd)/trace" test-tool trace lock &&
39+
sed -n "/while-locked/,\$s/.*delayed$/YES/p" <trace >actual &&
40+
test YES = "$(cat actual)"
41+
'
42+
3743
test_done

0 commit comments

Comments
 (0)