Skip to content

Commit ae7785d

Browse files
committed
Merge branch 'bp/sub-process-convert-filter'
Code from "conversion using external process" codepath has been extracted to a separate sub-process.[ch] module. * bp/sub-process-convert-filter: convert: update subprocess_read_status() to not die on EOF sub-process: move sub-process functions into separate files convert: rename reusable sub-process functions convert: update generic functions to only use generic data structures convert: separate generic structures and variables from the filter specific ones convert: split start_multi_file_filter() into two separate functions pkt-line: annotate packet_writel with LAST_ARG_MUST_BE_NULL convert: move packet_write_line() into pkt-line as packet_writel() pkt-line: add packet_read_line_gently() pkt-line: fix packet_read_line() to handle len < 0 errors convert: remove erroneous tests for errno == EPIPE
2 parents 7d5e13f + 4f2a2e9 commit ae7785d

7 files changed

Lines changed: 293 additions & 129 deletions

File tree

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
sub-process API
2+
===============
3+
4+
The sub-process API makes it possible to run background sub-processes
5+
for the entire lifetime of a Git invocation. If Git needs to communicate
6+
with an external process multiple times, then this can reduces the process
7+
invocation overhead. Git and the sub-process communicate through stdin and
8+
stdout.
9+
10+
The sub-processes are kept in a hashmap by command name and looked up
11+
via the subprocess_find_entry function. If an existing instance can not
12+
be found then a new process should be created and started. When the
13+
parent git command terminates, all sub-processes are also terminated.
14+
15+
This API is based on the run-command API.
16+
17+
Data structures
18+
---------------
19+
20+
* `struct subprocess_entry`
21+
22+
The sub-process structure. Members should not be accessed directly.
23+
24+
Types
25+
-----
26+
27+
'int(*subprocess_start_fn)(struct subprocess_entry *entry)'::
28+
29+
User-supplied function to initialize the sub-process. This is
30+
typically used to negotiate the interface version and capabilities.
31+
32+
33+
Functions
34+
---------
35+
36+
`cmd2process_cmp`::
37+
38+
Function to test two subprocess hashmap entries for equality.
39+
40+
`subprocess_start`::
41+
42+
Start a subprocess and add it to the subprocess hashmap.
43+
44+
`subprocess_stop`::
45+
46+
Kill a subprocess and remove it from the subprocess hashmap.
47+
48+
`subprocess_find_entry`::
49+
50+
Find a subprocess in the subprocess hashmap.
51+
52+
`subprocess_get_child_process`::
53+
54+
Get the underlying `struct child_process` from a subprocess.
55+
56+
`subprocess_read_status`::
57+
58+
Helper function to read packets looking for the last "status=<foo>"
59+
key/value pair.

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -842,6 +842,7 @@ LIB_OBJS += streaming.o
842842
LIB_OBJS += string-list.o
843843
LIB_OBJS += submodule.o
844844
LIB_OBJS += submodule-config.o
845+
LIB_OBJS += sub-process.o
845846
LIB_OBJS += symlinks.o
846847
LIB_OBJS += tag.o
847848
LIB_OBJS += tempfile.o

convert.c

Lines changed: 33 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "quote.h"
55
#include "sigchain.h"
66
#include "pkt-line.h"
7+
#include "sub-process.h"
78

89
/*
910
* convert.c - convert a file when checking it out and checking it in.
@@ -497,126 +498,26 @@ static int apply_single_file_filter(const char *path, const char *src, size_t le
497498
#define CAP_SMUDGE (1u<<1)
498499

499500
struct cmd2process {
500-
struct hashmap_entry ent; /* must be the first member! */
501+
struct subprocess_entry subprocess; /* must be the first member! */
501502
unsigned int supported_capabilities;
502-
const char *cmd;
503-
struct child_process process;
504503
};
505504

506-
static int cmd_process_map_initialized;
507-
static struct hashmap cmd_process_map;
508-
509-
static int cmd2process_cmp(const struct cmd2process *e1,
510-
const struct cmd2process *e2,
511-
const void *unused)
512-
{
513-
return strcmp(e1->cmd, e2->cmd);
514-
}
515-
516-
static struct cmd2process *find_multi_file_filter_entry(struct hashmap *hashmap, const char *cmd)
517-
{
518-
struct cmd2process key;
519-
hashmap_entry_init(&key, strhash(cmd));
520-
key.cmd = cmd;
521-
return hashmap_get(hashmap, &key, NULL);
522-
}
505+
static int subprocess_map_initialized;
506+
static struct hashmap subprocess_map;
523507

524-
static int packet_write_list(int fd, const char *line, ...)
508+
static int start_multi_file_filter_fn(struct subprocess_entry *subprocess)
525509
{
526-
va_list args;
527510
int err;
528-
va_start(args, line);
529-
for (;;) {
530-
if (!line)
531-
break;
532-
if (strlen(line) > LARGE_PACKET_DATA_MAX)
533-
return -1;
534-
err = packet_write_fmt_gently(fd, "%s\n", line);
535-
if (err)
536-
return err;
537-
line = va_arg(args, const char*);
538-
}
539-
va_end(args);
540-
return packet_flush_gently(fd);
541-
}
542-
543-
static void read_multi_file_filter_status(int fd, struct strbuf *status)
544-
{
545-
struct strbuf **pair;
546-
char *line;
547-
for (;;) {
548-
line = packet_read_line(fd, NULL);
549-
if (!line)
550-
break;
551-
pair = strbuf_split_str(line, '=', 2);
552-
if (pair[0] && pair[0]->len && pair[1]) {
553-
/* the last "status=<foo>" line wins */
554-
if (!strcmp(pair[0]->buf, "status=")) {
555-
strbuf_reset(status);
556-
strbuf_addbuf(status, pair[1]);
557-
}
558-
}
559-
strbuf_list_free(pair);
560-
}
561-
}
562-
563-
static void kill_multi_file_filter(struct hashmap *hashmap, struct cmd2process *entry)
564-
{
565-
if (!entry)
566-
return;
567-
568-
entry->process.clean_on_exit = 0;
569-
kill(entry->process.pid, SIGTERM);
570-
finish_command(&entry->process);
571-
572-
hashmap_remove(hashmap, entry, NULL);
573-
free(entry);
574-
}
575-
576-
static void stop_multi_file_filter(struct child_process *process)
577-
{
578-
sigchain_push(SIGPIPE, SIG_IGN);
579-
/* Closing the pipe signals the filter to initiate a shutdown. */
580-
close(process->in);
581-
close(process->out);
582-
sigchain_pop(SIGPIPE);
583-
/* Finish command will wait until the shutdown is complete. */
584-
finish_command(process);
585-
}
586-
587-
static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, const char *cmd)
588-
{
589-
int err;
590-
struct cmd2process *entry;
591-
struct child_process *process;
592-
const char *argv[] = { cmd, NULL };
511+
struct cmd2process *entry = (struct cmd2process *)subprocess;
593512
struct string_list cap_list = STRING_LIST_INIT_NODUP;
594513
char *cap_buf;
595514
const char *cap_name;
596-
597-
entry = xmalloc(sizeof(*entry));
598-
entry->cmd = cmd;
599-
entry->supported_capabilities = 0;
600-
process = &entry->process;
601-
602-
child_process_init(process);
603-
process->argv = argv;
604-
process->use_shell = 1;
605-
process->in = -1;
606-
process->out = -1;
607-
process->clean_on_exit = 1;
608-
process->clean_on_exit_handler = stop_multi_file_filter;
609-
610-
if (start_command(process)) {
611-
error("cannot fork to run external filter '%s'", cmd);
612-
return NULL;
613-
}
614-
615-
hashmap_entry_init(entry, strhash(cmd));
515+
struct child_process *process = &subprocess->process;
516+
const char *cmd = subprocess->cmd;
616517

617518
sigchain_push(SIGPIPE, SIG_IGN);
618519

619-
err = packet_write_list(process->in, "git-filter-client", "version=2", NULL);
520+
err = packet_writel(process->in, "git-filter-client", "version=2", NULL);
620521
if (err)
621522
goto done;
622523

@@ -632,7 +533,7 @@ static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, cons
632533
if (err)
633534
goto done;
634535

635-
err = packet_write_list(process->in, "capability=clean", "capability=smudge", NULL);
536+
err = packet_writel(process->in, "capability=clean", "capability=smudge", NULL);
636537

637538
for (;;) {
638539
cap_buf = packet_read_line(process->out, NULL);
@@ -661,14 +562,7 @@ static struct cmd2process *start_multi_file_filter(struct hashmap *hashmap, cons
661562
done:
662563
sigchain_pop(SIGPIPE);
663564

664-
if (err || errno == EPIPE) {
665-
error("initialization for external filter '%s' failed", cmd);
666-
kill_multi_file_filter(hashmap, entry);
667-
return NULL;
668-
}
669-
670-
hashmap_add(hashmap, entry);
671-
return entry;
565+
return err;
672566
}
673567

674568
static int apply_multi_file_filter(const char *path, const char *src, size_t len,
@@ -682,22 +576,26 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
682576
struct strbuf filter_status = STRBUF_INIT;
683577
const char *filter_type;
684578

685-
if (!cmd_process_map_initialized) {
686-
cmd_process_map_initialized = 1;
687-
hashmap_init(&cmd_process_map, (hashmap_cmp_fn) cmd2process_cmp, 0);
579+
if (!subprocess_map_initialized) {
580+
subprocess_map_initialized = 1;
581+
hashmap_init(&subprocess_map, (hashmap_cmp_fn) cmd2process_cmp, 0);
688582
entry = NULL;
689583
} else {
690-
entry = find_multi_file_filter_entry(&cmd_process_map, cmd);
584+
entry = (struct cmd2process *)subprocess_find_entry(&subprocess_map, cmd);
691585
}
692586

693587
fflush(NULL);
694588

695589
if (!entry) {
696-
entry = start_multi_file_filter(&cmd_process_map, cmd);
697-
if (!entry)
590+
entry = xmalloc(sizeof(*entry));
591+
entry->supported_capabilities = 0;
592+
593+
if (subprocess_start(&subprocess_map, &entry->subprocess, cmd, start_multi_file_filter_fn)) {
594+
free(entry);
698595
return 0;
596+
}
699597
}
700-
process = &entry->process;
598+
process = &entry->subprocess.process;
701599

702600
if (!(wanted_capability & entry->supported_capabilities))
703601
return 0;
@@ -737,7 +635,10 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
737635
if (err)
738636
goto done;
739637

740-
read_multi_file_filter_status(process->out, &filter_status);
638+
err = subprocess_read_status(process->out, &filter_status);
639+
if (err)
640+
goto done;
641+
741642
err = strcmp(filter_status.buf, "success");
742643
if (err)
743644
goto done;
@@ -746,13 +647,16 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
746647
if (err)
747648
goto done;
748649

749-
read_multi_file_filter_status(process->out, &filter_status);
650+
err = subprocess_read_status(process->out, &filter_status);
651+
if (err)
652+
goto done;
653+
750654
err = strcmp(filter_status.buf, "success");
751655

752656
done:
753657
sigchain_pop(SIGPIPE);
754658

755-
if (err || errno == EPIPE) {
659+
if (err) {
756660
if (!strcmp(filter_status.buf, "error")) {
757661
/* The filter signaled a problem with the file. */
758662
} else if (!strcmp(filter_status.buf, "abort")) {
@@ -768,7 +672,8 @@ static int apply_multi_file_filter(const char *path, const char *src, size_t len
768672
* Force shutdown and restart if another blob requires filtering.
769673
*/
770674
error("external filter '%s' failed", cmd);
771-
kill_multi_file_filter(&cmd_process_map, entry);
675+
subprocess_stop(&subprocess_map, &entry->subprocess);
676+
free(entry);
772677
}
773678
} else {
774679
strbuf_swap(dst, &nbuf);

pkt-line.c

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,25 @@ int packet_write_fmt_gently(int fd, const char *fmt, ...)
171171
return status;
172172
}
173173

174+
int packet_writel(int fd, const char *line, ...)
175+
{
176+
va_list args;
177+
int err;
178+
va_start(args, line);
179+
for (;;) {
180+
if (!line)
181+
break;
182+
if (strlen(line) > LARGE_PACKET_DATA_MAX)
183+
return -1;
184+
err = packet_write_fmt_gently(fd, "%s\n", line);
185+
if (err)
186+
return err;
187+
line = va_arg(args, const char*);
188+
}
189+
va_end(args);
190+
return packet_flush_gently(fd);
191+
}
192+
174193
static int packet_write_gently(const int fd_out, const char *buf, size_t size)
175194
{
176195
static char packet_write_buffer[LARGE_PACKET_MAX];
@@ -315,14 +334,26 @@ static char *packet_read_line_generic(int fd,
315334
PACKET_READ_CHOMP_NEWLINE);
316335
if (dst_len)
317336
*dst_len = len;
318-
return len ? packet_buffer : NULL;
337+
return (len > 0) ? packet_buffer : NULL;
319338
}
320339

321340
char *packet_read_line(int fd, int *len_p)
322341
{
323342
return packet_read_line_generic(fd, NULL, NULL, len_p);
324343
}
325344

345+
int packet_read_line_gently(int fd, int *dst_len, char **dst_line)
346+
{
347+
int len = packet_read(fd, NULL, NULL,
348+
packet_buffer, sizeof(packet_buffer),
349+
PACKET_READ_CHOMP_NEWLINE|PACKET_READ_GENTLE_ON_EOF);
350+
if (dst_len)
351+
*dst_len = len;
352+
if (dst_line)
353+
*dst_line = (len > 0) ? packet_buffer : NULL;
354+
return len;
355+
}
356+
326357
char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)
327358
{
328359
return packet_read_line_generic(-1, src, src_len, dst_len);

pkt-line.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ void packet_buf_flush(struct strbuf *buf);
2525
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
2626
int packet_flush_gently(int fd);
2727
int packet_write_fmt_gently(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
28+
LAST_ARG_MUST_BE_NULL
29+
int packet_writel(int fd, const char *line, ...);
2830
int write_packetized_from_fd(int fd_in, int fd_out);
2931
int write_packetized_from_buf(const char *src_in, size_t len, int fd_out);
3032

@@ -73,6 +75,17 @@ int packet_read(int fd, char **src_buffer, size_t *src_len, char
7375
*/
7476
char *packet_read_line(int fd, int *size);
7577

78+
/*
79+
* Convenience wrapper for packet_read that sets the PACKET_READ_GENTLE_ON_EOF
80+
* and CHOMP_NEWLINE options. The return value specifies the number of bytes
81+
* read into the buffer or -1 on truncated input. If the *dst_line parameter
82+
* is not NULL it will return NULL for a flush packet or when the number of
83+
* bytes copied is zero and otherwise points to a static buffer (that may be
84+
* overwritten by subsequent calls). If the size parameter is not NULL, the
85+
* length of the packet is written to it.
86+
*/
87+
int packet_read_line_gently(int fd, int *size, char **dst_line);
88+
7689
/*
7790
* Same as packet_read_line, but read from a buf rather than a descriptor;
7891
* see packet_read for details on how src_* is used.

0 commit comments

Comments
 (0)