maintenance: create `launchctl` configuration using a lock file

When two `git maintenance` processes try to write the `.plist` file, we need to help them with serializing their efforts. The 150ms time-out value was determined from thin air. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Aug 24, 2021 at 15:43 UTC bb01122a82002dc5948147800da40a149779a7b1
1 file changed +30 -17
builtin/gc.c
+30 -17
@@ -1602,16 +1602,14 @@ static int launchctl_remove_plists(const char *cmd)
1602
1603 static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd)
1604 {
1605 - FILE *plist;
1606 - int i;
1605 + int i, fd;
1606 const char *preamble, *repeat;
1607 const char *frequency = get_frequency(schedule);
1608 char *name = launchctl_service_name(frequency);
1609 char *filename = launchctl_service_filename(name);
1611 -
1612 - if (safe_create_leading_directories(filename))
1613 - die(_("failed to create directories for '%s'"), filename);
1614 - plist = xfopen(filename, "w");
1610 + struct lock_file lk = LOCK_INIT;
1611 + static unsigned long lock_file_timeout_ms = ULONG_MAX;
1612 + struct strbuf plist = STRBUF_INIT;
1613
1614 preamble = "<?xml version=\"1.0\"?>\n"
1615 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
@@ -1630,7 +1628,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
1628 "</array>\n"
1629 "<key>StartCalendarInterval</key>\n"
1630 "<array>\n";
1633 - fprintf(plist, preamble, name, exec_path, exec_path, frequency);
1631 + strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency);
1632
1633 switch (schedule) {
1634 case SCHEDULE_HOURLY:
@@ -1639,7 +1637,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
1637 "<key>Minute</key><integer>0</integer>\n"
1638 "</dict>\n";
1639 for (i = 1; i <= 23; i++)
1642 - fprintf(plist, repeat, i);
1640 + strbuf_addf(&plist, repeat, i);
1641 break;
1642
1643 case SCHEDULE_DAILY:
@@ -1649,24 +1647,38 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
1647 "<key>Minute</key><integer>0</integer>\n"
1648 "</dict>\n";
1649 for (i = 1; i <= 6; i++)
1652 - fprintf(plist, repeat, i);
1650 + strbuf_addf(&plist, repeat, i);
1651 break;
1652
1653 case SCHEDULE_WEEKLY:
1656 - fprintf(plist,
1657 - "<dict>\n"
1658 - "<key>Day</key><integer>0</integer>\n"
1659 - "<key>Hour</key><integer>0</integer>\n"
1660 - "<key>Minute</key><integer>0</integer>\n"
1661 - "</dict>\n");
1654 + strbuf_addstr(&plist,
1655 + "<dict>\n"
1656 + "<key>Day</key><integer>0</integer>\n"
1657 + "<key>Hour</key><integer>0</integer>\n"
1658 + "<key>Minute</key><integer>0</integer>\n"
1659 + "</dict>\n");
1660 break;
1661
1662 default:
1663 /* unreachable */
1664 break;
1665 }
1668 - fprintf(plist, "</array>\n</dict>\n</plist>\n");
1669 - fclose(plist);
1666 + strbuf_addstr(&plist, "</array>\n</dict>\n</plist>\n");
1667 +
1668 + if (safe_create_leading_directories(filename))
1669 + die(_("failed to create directories for '%s'"), filename);
1670 +
1671 + if ((long)lock_file_timeout_ms < 0 &&
1672 + git_config_get_ulong("gc.launchctlplistlocktimeoutms",
1673 + &lock_file_timeout_ms))
1674 + lock_file_timeout_ms = 150;
1675 +
1676 + fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR,
1677 + lock_file_timeout_ms);
1678 +
1679 + if (write_in_full(fd, plist.buf, plist.len) < 0 ||
1680 + commit_lock_file(&lk))
1681 + die_errno(_("could not write '%s'"), filename);
1682
1683 /* bootout might fail if not already running, so ignore */
1684 launchctl_boot_plist(0, filename, cmd);
@@ -1675,6 +1687,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
1687
1688 free(filename);
1689 free(name);
1690 + strbuf_release(&plist);
1691 return 0;
1692 }
1693