Skip to content

Commit a782a7e

Browse files
Merge pull request #208 from jeffhostetler/users/jeffhost/gvfs-helper-robust-retry-take2
gvfs-helper: auto-retry after network errors, resource throttling, split GET and POST semantics
2 parents f0c4615 + 5c65e9a commit a782a7e

File tree

3 files changed

+1684
-580
lines changed

3 files changed

+1684
-580
lines changed

gvfs-helper-client.c

Lines changed: 149 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
static struct oidset gh_client__oidset_queued = OIDSET_INIT;
1515
static unsigned long gh_client__oidset_count;
16-
static int gh_client__includes_immediate;
1716

1817
struct gh_server__process {
1918
struct subprocess_entry subprocess; /* must be first */
@@ -24,13 +23,20 @@ static int gh_server__subprocess_map_initialized;
2423
static struct hashmap gh_server__subprocess_map;
2524
static struct object_directory *gh_client__chosen_odb;
2625

27-
#define CAP_GET (1u<<1)
26+
/*
27+
* The "objects" capability has 2 verbs: "get" and "post".
28+
*/
29+
#define CAP_OBJECTS (1u<<1)
30+
#define CAP_OBJECTS_NAME "objects"
31+
32+
#define CAP_OBJECTS__VERB_GET1_NAME "get"
33+
#define CAP_OBJECTS__VERB_POST_NAME "post"
2834

2935
static int gh_client__start_fn(struct subprocess_entry *subprocess)
3036
{
3137
static int versions[] = {1, 0};
3238
static struct subprocess_capability capabilities[] = {
33-
{ "get", CAP_GET },
39+
{ CAP_OBJECTS_NAME, CAP_OBJECTS },
3440
{ NULL, 0 }
3541
};
3642

@@ -42,14 +48,16 @@ static int gh_client__start_fn(struct subprocess_entry *subprocess)
4248
}
4349

4450
/*
45-
* Send:
51+
* Send the queued OIDs in the OIDSET to gvfs-helper for it to
52+
* fetch from the cache-server or main Git server using "/gvfs/objects"
53+
* POST semantics.
4654
*
47-
* get LF
55+
* objects.post LF
4856
* (<hex-oid> LF)*
4957
* <flush>
5058
*
5159
*/
52-
static int gh_client__get__send_command(struct child_process *process)
60+
static int gh_client__send__objects_post(struct child_process *process)
5361
{
5462
struct oidset_iter iter;
5563
struct object_id *oid;
@@ -60,7 +68,9 @@ static int gh_client__get__send_command(struct child_process *process)
6068
* so that we don't have to.
6169
*/
6270

63-
err = packet_write_fmt_gently(process->in, "get\n");
71+
err = packet_write_fmt_gently(
72+
process->in,
73+
(CAP_OBJECTS_NAME "." CAP_OBJECTS__VERB_POST_NAME "\n"));
6474
if (err)
6575
return err;
6676

@@ -79,6 +89,46 @@ static int gh_client__get__send_command(struct child_process *process)
7989
return 0;
8090
}
8191

92+
/*
93+
* Send the given OID to gvfs-helper for it to fetch from the
94+
* cache-server or main Git server using "/gvfs/objects" GET
95+
* semantics.
96+
*
97+
* This ignores any queued OIDs.
98+
*
99+
* objects.get LF
100+
* <hex-oid> LF
101+
* <flush>
102+
*
103+
*/
104+
static int gh_client__send__objects_get(struct child_process *process,
105+
const struct object_id *oid)
106+
{
107+
int err;
108+
109+
/*
110+
* We assume that all of the packet_ routines call error()
111+
* so that we don't have to.
112+
*/
113+
114+
err = packet_write_fmt_gently(
115+
process->in,
116+
(CAP_OBJECTS_NAME "." CAP_OBJECTS__VERB_GET1_NAME "\n"));
117+
if (err)
118+
return err;
119+
120+
err = packet_write_fmt_gently(process->in, "%s\n",
121+
oid_to_hex(oid));
122+
if (err)
123+
return err;
124+
125+
err = packet_flush_gently(process->in);
126+
if (err)
127+
return err;
128+
129+
return 0;
130+
}
131+
82132
/*
83133
* Verify that the pathname found in the "odb" response line matches
84134
* what we requested.
@@ -148,7 +198,7 @@ static void gh_client__update_packed_git(const char *line)
148198
}
149199

150200
/*
151-
* We expect:
201+
* Both CAP_OBJECTS verbs return the same format response:
152202
*
153203
* <odb>
154204
* <data>*
@@ -179,7 +229,7 @@ static void gh_client__update_packed_git(const char *line)
179229
* grouped with a queued request for a blob. The tree-walk *might* be
180230
* able to continue and let the 404 blob be handled later.
181231
*/
182-
static int gh_client__get__receive_response(
232+
static int gh_client__objects__receive_response(
183233
struct child_process *process,
184234
enum gh_client__created *p_ghc,
185235
int *p_nr_loose, int *p_nr_packfile)
@@ -259,17 +309,12 @@ static void gh_client__choose_odb(void)
259309
}
260310
}
261311

262-
static int gh_client__get(enum gh_client__created *p_ghc)
312+
static struct gh_server__process *gh_client__find_long_running_process(
313+
unsigned int cap_needed)
263314
{
264315
struct gh_server__process *entry;
265-
struct child_process *process;
266316
struct argv_array argv = ARGV_ARRAY_INIT;
267317
struct strbuf quoted = STRBUF_INIT;
268-
int nr_loose = 0;
269-
int nr_packfile = 0;
270-
int err = 0;
271-
272-
trace2_region_enter("gh-client", "get", the_repository);
273318

274319
gh_client__choose_odb();
275320

@@ -285,6 +330,11 @@ static int gh_client__get(enum gh_client__created *p_ghc)
285330

286331
sq_quote_argv_pretty(&quoted, argv.argv);
287332

333+
/*
334+
* Find an existing long-running process with the above command
335+
* line -or- create a new long-running process for this and
336+
* subsequent 'get' requests.
337+
*/
288338
if (!gh_server__subprocess_map_initialized) {
289339
gh_server__subprocess_map_initialized = 1;
290340
hashmap_init(&gh_server__subprocess_map,
@@ -298,70 +348,24 @@ static int gh_client__get(enum gh_client__created *p_ghc)
298348
entry = xmalloc(sizeof(*entry));
299349
entry->supported_capabilities = 0;
300350

301-
err = subprocess_start_argv(
302-
&gh_server__subprocess_map, &entry->subprocess, 1,
303-
&argv, gh_client__start_fn);
304-
if (err) {
305-
free(entry);
306-
goto leave_region;
307-
}
351+
if (subprocess_start_argv(&gh_server__subprocess_map,
352+
&entry->subprocess, 1,
353+
&argv, gh_client__start_fn))
354+
FREE_AND_NULL(entry);
308355
}
309356

310-
process = &entry->subprocess.process;
311-
312-
if (!(CAP_GET & entry->supported_capabilities)) {
313-
error("gvfs-helper: does not support GET");
357+
if (entry &&
358+
(entry->supported_capabilities & cap_needed) != cap_needed) {
359+
error("gvfs-helper: does not support needed capabilities");
314360
subprocess_stop(&gh_server__subprocess_map,
315361
(struct subprocess_entry *)entry);
316-
free(entry);
317-
err = -1;
318-
goto leave_region;
362+
FREE_AND_NULL(entry);
319363
}
320364

321-
sigchain_push(SIGPIPE, SIG_IGN);
322-
323-
err = gh_client__get__send_command(process);
324-
if (!err)
325-
err = gh_client__get__receive_response(process, p_ghc,
326-
&nr_loose, &nr_packfile);
327-
328-
sigchain_pop(SIGPIPE);
329-
330-
if (err) {
331-
subprocess_stop(&gh_server__subprocess_map,
332-
(struct subprocess_entry *)entry);
333-
free(entry);
334-
}
335-
336-
leave_region:
337365
argv_array_clear(&argv);
338366
strbuf_release(&quoted);
339367

340-
trace2_data_intmax("gh-client", the_repository,
341-
"get/immediate", gh_client__includes_immediate);
342-
343-
trace2_data_intmax("gh-client", the_repository,
344-
"get/nr_objects", gh_client__oidset_count);
345-
346-
if (nr_loose)
347-
trace2_data_intmax("gh-client", the_repository,
348-
"get/nr_loose", nr_loose);
349-
350-
if (nr_packfile)
351-
trace2_data_intmax("gh-client", the_repository,
352-
"get/nr_packfile", nr_packfile);
353-
354-
if (err)
355-
trace2_data_intmax("gh-client", the_repository,
356-
"get/error", err);
357-
358-
trace2_region_leave("gh-client", "get", the_repository);
359-
360-
oidset_clear(&gh_client__oidset_queued);
361-
gh_client__oidset_count = 0;
362-
gh_client__includes_immediate = 0;
363-
364-
return err;
368+
return entry;
365369
}
366370

367371
void gh_client__queue_oid(const struct object_id *oid)
@@ -388,28 +392,97 @@ void gh_client__queue_oid_array(const struct object_id *oids, int oid_nr)
388392
gh_client__queue_oid(&oids[k]);
389393
}
390394

395+
/*
396+
* Bulk fetch all of the queued OIDs in the OIDSET.
397+
*/
391398
int gh_client__drain_queue(enum gh_client__created *p_ghc)
392399
{
400+
struct gh_server__process *entry;
401+
struct child_process *process;
402+
int nr_loose = 0;
403+
int nr_packfile = 0;
404+
int err = 0;
405+
393406
*p_ghc = GHC__CREATED__NOTHING;
394407

395408
if (!gh_client__oidset_count)
396409
return 0;
397410

398-
return gh_client__get(p_ghc);
411+
entry = gh_client__find_long_running_process(CAP_OBJECTS);
412+
if (!entry)
413+
return -1;
414+
415+
trace2_region_enter("gh-client", "objects/post", the_repository);
416+
417+
process = &entry->subprocess.process;
418+
419+
sigchain_push(SIGPIPE, SIG_IGN);
420+
421+
err = gh_client__send__objects_post(process);
422+
if (!err)
423+
err = gh_client__objects__receive_response(
424+
process, p_ghc, &nr_loose, &nr_packfile);
425+
426+
sigchain_pop(SIGPIPE);
427+
428+
if (err) {
429+
subprocess_stop(&gh_server__subprocess_map,
430+
(struct subprocess_entry *)entry);
431+
FREE_AND_NULL(entry);
432+
}
433+
434+
trace2_data_intmax("gh-client", the_repository,
435+
"objects/post/nr_objects", gh_client__oidset_count);
436+
trace2_region_leave("gh-client", "objects/post", the_repository);
437+
438+
oidset_clear(&gh_client__oidset_queued);
439+
gh_client__oidset_count = 0;
440+
441+
return err;
399442
}
400443

444+
/*
445+
* Get exactly 1 object immediately.
446+
* Ignore any queued objects.
447+
*/
401448
int gh_client__get_immediate(const struct object_id *oid,
402449
enum gh_client__created *p_ghc)
403450
{
404-
gh_client__includes_immediate = 1;
451+
struct gh_server__process *entry;
452+
struct child_process *process;
453+
int nr_loose = 0;
454+
int nr_packfile = 0;
455+
int err = 0;
405456

406457
// TODO consider removing this trace2. it is useful for interactive
407458
// TODO debugging, but may generate way too much noise for a data
408459
// TODO event.
409460
trace2_printf("gh_client__get_immediate: %s", oid_to_hex(oid));
410461

411-
if (!oidset_insert(&gh_client__oidset_queued, oid))
412-
gh_client__oidset_count++;
462+
entry = gh_client__find_long_running_process(CAP_OBJECTS);
463+
if (!entry)
464+
return -1;
465+
466+
trace2_region_enter("gh-client", "objects/get", the_repository);
413467

414-
return gh_client__drain_queue(p_ghc);
468+
process = &entry->subprocess.process;
469+
470+
sigchain_push(SIGPIPE, SIG_IGN);
471+
472+
err = gh_client__send__objects_get(process, oid);
473+
if (!err)
474+
err = gh_client__objects__receive_response(
475+
process, p_ghc, &nr_loose, &nr_packfile);
476+
477+
sigchain_pop(SIGPIPE);
478+
479+
if (err) {
480+
subprocess_stop(&gh_server__subprocess_map,
481+
(struct subprocess_entry *)entry);
482+
FREE_AND_NULL(entry);
483+
}
484+
485+
trace2_region_leave("gh-client", "objects/get", the_repository);
486+
487+
return err;
415488
}

gvfs-helper-client.h

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ enum gh_client__created {
3232
};
3333

3434
/*
35-
* Ask `gvfs-helper server` to immediately fetch an object.
36-
* Wait for the response.
35+
* Ask `gvfs-helper server` to immediately fetch a single object
36+
* using "/gvfs/objects" GET semantics.
3737
*
38-
* This may also fetch any queued (non-immediate) objects and
39-
* so may create one or more loose objects and/or packfiles.
40-
* It is undefined whether the requested OID will be loose or
41-
* in a packfile.
38+
* A long-running background process is used to make subsequent
39+
* requests more efficient.
40+
*
41+
* A loose object will be created in the shared-cache ODB and
42+
* in-memory cache updated.
4243
*/
4344
int gh_client__get_immediate(const struct object_id *oid,
4445
enum gh_client__created *p_ghc);
@@ -47,16 +48,21 @@ int gh_client__get_immediate(const struct object_id *oid,
4748
* Queue this OID for a future fetch using `gvfs-helper service`.
4849
* It does not wait.
4950
*
50-
* The GHC layer is free to process this queue in any way it wants,
51-
* including individual fetches, bulk fetches, and batching. And
52-
* it may add queued objects to immediate requests.
53-
*
5451
* Callers should not rely on the queued object being on disk until
5552
* the queue has been drained.
5653
*/
5754
void gh_client__queue_oid(const struct object_id *oid);
5855
void gh_client__queue_oid_array(const struct object_id *oids, int oid_nr);
5956

57+
/*
58+
* Ask `gvfs-helper server` to fetch the set of queued OIDs using
59+
* "/gvfs/objects" POST semantics.
60+
*
61+
* A long-running background process is used to subsequent requests
62+
* more efficient.
63+
*
64+
* One or more packfiles will be created in the shared-cache ODB.
65+
*/
6066
int gh_client__drain_queue(enum gh_client__created *p_ghc);
6167

6268
#endif /* GVFS_HELPER_CLIENT_H */

0 commit comments

Comments
 (0)