fsmonitor--daemon: implement handle_client callback
Teach fsmonitor--daemon to respond to IPC requests from client Git processes and respond with a list of modified pathnames relative to the provided token. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Jeff Hostetler committed
Mar 25, 2022 at 18:03 UTC
518a522f405ddc51d289192718864f1c784afc6f
1 file changed
+309
-2
builtin/fsmonitor--daemon.c
+309
-2
@@ -7,6 +7,7 @@
7
#include "fsmonitor--daemon.h"
8
#include "simple-ipc.h"
9
#include "khash.h"
10
+#include "pkt-line.h"
11
12
static const char * const builtin_fsmonitor__daemon_usage[] = {
13
N_("git fsmonitor--daemon start [<options>]"),
@@ -364,6 +365,310 @@ void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)
365
pthread_mutex_unlock(&state->main_lock);
366
}
367
368
+/*
369
+ * Format an opaque token string to send to the client.
370
+ */
371
+static void with_lock__format_response_token(
372
+ struct strbuf *response_token,
373
+ const struct strbuf *response_token_id,
374
+ const struct fsmonitor_batch *batch)
375
+{
376
+ /* assert current thread holding state->main_lock */
377
+
378
+ strbuf_reset(response_token);
379
+ strbuf_addf(response_token, "builtin:%s:%"PRIu64,
380
+ response_token_id->buf, batch->batch_seq_nr);
381
+}
382
+
383
+/*
384
+ * Parse an opaque token from the client.
385
+ * Returns -1 on error.
386
+ */
387
+static int fsmonitor_parse_client_token(const char *buf_token,
388
+ struct strbuf *requested_token_id,
389
+ uint64_t *seq_nr)
390
+{
391
+ const char *p;
392
+ char *p_end;
393
+
394
+ strbuf_reset(requested_token_id);
395
+ *seq_nr = 0;
396
+
397
+ if (!skip_prefix(buf_token, "builtin:", &p))
398
+ return -1;
399
+
400
+ while (*p && *p != ':')
401
+ strbuf_addch(requested_token_id, *p++);
402
+ if (!*p++)
403
+ return -1;
404
+
405
+ *seq_nr = (uint64_t)strtoumax(p, &p_end, 10);
406
+ if (*p_end)
407
+ return -1;
408
+
409
+ return 0;
410
+}
411
+
412
+KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal)
413
+
414
+static int do_handle_client(struct fsmonitor_daemon_state *state,
415
+ const char *command,
416
+ ipc_server_reply_cb *reply,
417
+ struct ipc_server_reply_data *reply_data)
418
+{
419
+ struct fsmonitor_token_data *token_data = NULL;
420
+ struct strbuf response_token = STRBUF_INIT;
421
+ struct strbuf requested_token_id = STRBUF_INIT;
422
+ struct strbuf payload = STRBUF_INIT;
423
+ uint64_t requested_oldest_seq_nr = 0;
424
+ uint64_t total_response_len = 0;
425
+ const char *p;
426
+ const struct fsmonitor_batch *batch_head;
427
+ const struct fsmonitor_batch *batch;
428
+ intmax_t count = 0, duplicates = 0;
429
+ kh_str_t *shown;
430
+ int hash_ret;
431
+ int do_trivial = 0;
432
+ int do_flush = 0;
433
+
434
+ /*
435
+ * We expect `command` to be of the form:
436
+ *
437
+ * <command> := quit NUL
438
+ * | flush NUL
439
+ * | <V1-time-since-epoch-ns> NUL
440
+ * | <V2-opaque-fsmonitor-token> NUL
441
+ */
442
+
443
+ if (!strcmp(command, "quit")) {
444
+ /*
445
+ * A client has requested over the socket/pipe that the
446
+ * daemon shutdown.
447
+ *
448
+ * Tell the IPC thread pool to shutdown (which completes
449
+ * the await in the main thread (which can stop the
450
+ * fsmonitor listener thread)).
451
+ *
452
+ * There is no reply to the client.
453
+ */
454
+ return SIMPLE_IPC_QUIT;
455
+
456
+ } else if (!strcmp(command, "flush")) {
457
+ /*
458
+ * Flush all of our cached data and generate a new token
459
+ * just like if we lost sync with the filesystem.
460
+ *
461
+ * Then send a trivial response using the new token.
462
+ */
463
+ do_flush = 1;
464
+ do_trivial = 1;
465
+
466
+ } else if (!skip_prefix(command, "builtin:", &p)) {
467
+ /* assume V1 timestamp or garbage */
468
+
469
+ char *p_end;
470
+
471
+ strtoumax(command, &p_end, 10);
472
+ trace_printf_key(&trace_fsmonitor,
473
+ ((*p_end) ?
474
+ "fsmonitor: invalid command line '%s'" :
475
+ "fsmonitor: unsupported V1 protocol '%s'"),
476
+ command);
477
+ do_trivial = 1;
478
+
479
+ } else {
480
+ /* We have "builtin:*" */
481
+ if (fsmonitor_parse_client_token(command, &requested_token_id,
482
+ &requested_oldest_seq_nr)) {
483
+ trace_printf_key(&trace_fsmonitor,
484
+ "fsmonitor: invalid V2 protocol token '%s'",
485
+ command);
486
+ do_trivial = 1;
487
+
488
+ } else {
489
+ /*
490
+ * We have a V2 valid token:
491
+ * "builtin:<token_id>:<seq_nr>"
492
+ */
493
+ }
494
+ }
495
+
496
+ pthread_mutex_lock(&state->main_lock);
497
+
498
+ if (!state->current_token_data)
499
+ BUG("fsmonitor state does not have a current token");
500
+
501
+ if (do_flush)
502
+ with_lock__do_force_resync(state);
503
+
504
+ /*
505
+ * We mark the current head of the batch list as "pinned" so
506
+ * that the listener thread will treat this item as read-only
507
+ * (and prevent any more paths from being added to it) from
508
+ * now on.
509
+ */
510
+ token_data = state->current_token_data;
511
+ batch_head = token_data->batch_head;
512
+ ((struct fsmonitor_batch *)batch_head)->pinned_time = time(NULL);
513
+
514
+ /*
515
+ * FSMonitor Protocol V2 requires that we send a response header
516
+ * with a "new current token" and then all of the paths that changed
517
+ * since the "requested token". We send the seq_nr of the just-pinned
518
+ * head batch so that future requests from a client will be relative
519
+ * to it.
520
+ */
521
+ with_lock__format_response_token(&response_token,
522
+ &token_data->token_id, batch_head);
523
+
524
+ reply(reply_data, response_token.buf, response_token.len + 1);
525
+ total_response_len += response_token.len + 1;
526
+
527
+ trace2_data_string("fsmonitor", the_repository, "response/token",
528
+ response_token.buf);
529
+ trace_printf_key(&trace_fsmonitor, "response token: %s",
530
+ response_token.buf);
531
+
532
+ if (!do_trivial) {
533
+ if (strcmp(requested_token_id.buf, token_data->token_id.buf)) {
534
+ /*
535
+ * The client last spoke to a different daemon
536
+ * instance -OR- the daemon had to resync with
537
+ * the filesystem (and lost events), so reject.
538
+ */
539
+ trace2_data_string("fsmonitor", the_repository,
540
+ "response/token", "different");
541
+ do_trivial = 1;
542
+
543
+ } else if (requested_oldest_seq_nr <
544
+ token_data->batch_tail->batch_seq_nr) {
545
+ /*
546
+ * The client wants older events than we have for
547
+ * this token_id. This means that the end of our
548
+ * batch list was truncated and we cannot give the
549
+ * client a complete snapshot relative to their
550
+ * request.
551
+ */
552
+ trace_printf_key(&trace_fsmonitor,
553
+ "client requested truncated data");
554
+ do_trivial = 1;
555
+ }
556
+ }
557
+
558
+ if (do_trivial) {
559
+ pthread_mutex_unlock(&state->main_lock);
560
+
561
+ reply(reply_data, "/", 2);
562
+
563
+ trace2_data_intmax("fsmonitor", the_repository,
564
+ "response/trivial", 1);
565
+
566
+ goto cleanup;
567
+ }
568
+
569
+ /*
570
+ * We're going to hold onto a pointer to the current
571
+ * token-data while we walk the list of batches of files.
572
+ * During this time, we will NOT be under the lock.
573
+ * So we ref-count it.
574
+ *
575
+ * This allows the listener thread to continue prepending
576
+ * new batches of items to the token-data (which we'll ignore).
577
+ *
578
+ * AND it allows the listener thread to do a token-reset
579
+ * (and install a new `current_token_data`).
580
+ */
581
+ token_data->client_ref_count++;
582
+
583
+ pthread_mutex_unlock(&state->main_lock);
584
+
585
+ /*
586
+ * The client request is relative to the token that they sent,
587
+ * so walk the batch list backwards from the current head back
588
+ * to the batch (sequence number) they named.
589
+ *
590
+ * We use khash to de-dup the list of pathnames.
591
+ *
592
+ * NEEDSWORK: each batch contains a list of interned strings,
593
+ * so we only need to do pointer comparisons here to build the
594
+ * hash table. Currently, we're still comparing the string
595
+ * values.
596
+ */
597
+ shown = kh_init_str();
598
+ for (batch = batch_head;
599
+ batch && batch->batch_seq_nr > requested_oldest_seq_nr;
600
+ batch = batch->next) {
601
+ size_t k;
602
+
603
+ for (k = 0; k < batch->nr; k++) {
604
+ const char *s = batch->interned_paths[k];
605
+ size_t s_len;
606
+
607
+ if (kh_get_str(shown, s) != kh_end(shown))
608
+ duplicates++;
609
+ else {
610
+ kh_put_str(shown, s, &hash_ret);
611
+
612
+ trace_printf_key(&trace_fsmonitor,
613
+ "send[%"PRIuMAX"]: %s",
614
+ count, s);
615
+
616
+ /* Each path gets written with a trailing NUL */
617
+ s_len = strlen(s) + 1;
618
+
619
+ if (payload.len + s_len >=
620
+ LARGE_PACKET_DATA_MAX) {
621
+ reply(reply_data, payload.buf,
622
+ payload.len);
623
+ total_response_len += payload.len;
624
+ strbuf_reset(&payload);
625
+ }
626
+
627
+ strbuf_add(&payload, s, s_len);
628
+ count++;
629
+ }
630
+ }
631
+ }
632
+
633
+ if (payload.len) {
634
+ reply(reply_data, payload.buf, payload.len);
635
+ total_response_len += payload.len;
636
+ }
637
+
638
+ kh_release_str(shown);
639
+
640
+ pthread_mutex_lock(&state->main_lock);
641
+
642
+ if (token_data->client_ref_count > 0)
643
+ token_data->client_ref_count--;
644
+
645
+ if (token_data->client_ref_count == 0) {
646
+ if (token_data != state->current_token_data) {
647
+ /*
648
+ * The listener thread did a token-reset while we were
649
+ * walking the batch list. Therefore, this token is
650
+ * stale and can be discarded completely. If we are
651
+ * the last reader thread using this token, we own
652
+ * that work.
653
+ */
654
+ fsmonitor_free_token_data(token_data);
655
+ }
656
+ }
657
+
658
+ pthread_mutex_unlock(&state->main_lock);
659
+
660
+ trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len);
661
+ trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count);
662
+ trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates);
663
+
664
+cleanup:
665
+ strbuf_release(&response_token);
666
+ strbuf_release(&requested_token_id);
667
+ strbuf_release(&payload);
668
+
669
+ return 0;
670
+}
671
+
672
static ipc_server_application_cb handle_client;
673
674
static int handle_client(void *data,
@@ -371,7 +676,7 @@ static int handle_client(void *data,
676
ipc_server_reply_cb *reply,
677
struct ipc_server_reply_data *reply_data)
678
{
374
- /* struct fsmonitor_daemon_state *state = data; */
679
+ struct fsmonitor_daemon_state *state = data;
680
int result;
681
682
/*
@@ -382,10 +687,12 @@ static int handle_client(void *data,
687
if (command_len != strlen(command))
688
BUG("FSMonitor assumes text messages");
689
690
+ trace_printf_key(&trace_fsmonitor, "requested token: %s", command);
691
+
692
trace2_region_enter("fsmonitor", "handle_client", the_repository);
693
trace2_data_string("fsmonitor", the_repository, "request", command);
694
388
- result = 0; /* TODO Do something here. */
695
+ result = do_handle_client(state, command, reply, reply_data);
696
697
trace2_region_leave("fsmonitor", "handle_client", the_repository);
698