Skip to content

Commit

Permalink
maintenance: use random minute in systemd scheduler
Browse files Browse the repository at this point in the history
The get_random_minute() method was created to allow maintenance
schedules to be fixed to a random minute of the hour. This randomness is
only intended to spread out the load from a number of clients, but each
client should have an hour between each maintenance cycle.

Add this random minute to the systemd integration.

This integration is more complicated than similar changes for other
schedulers because of a neat trick that systemd allows: templating.

The previous implementation generated two template files with names
of the form 'git-maintenance@.(timer|service)'. The '.timer' or
'.service' indicates that this is a template that is picked up when we
later specify '...@<schedule>.timer' or '...@<schedule>.service'. The
'<schedule>' string is then used to insert into the template both the
'OnCalendar' schedule setting and the '--schedule' parameter of the
'git maintenance run' command.

In order to set these schedules to a given minute, we can no longer use
the 'hourly', 'daily', or 'weekly' strings for '<schedule>' and instead
need to abandon the template model.

Modify the template with a custom schedule in the 'OnCalendar' setting.
This schedule has some interesting differences from cron-like patterns,
but is relatively easy to figure out from context. The one that might be
confusing is that '*-*-*' is a date-based pattern, but this must be
omitted when using 'Mon' to signal that we care about the day of the
week. Monday is used since that matches the day used for the 'weekly'
schedule used previously.

The rest of the change involves making sure we are writing these .timer
and .service files before initializing the schedule with 'systemctl' and
deleting the files when we are done. Some changes are also made to share
the random minute along with a single computation of the execution path
of the current Git executable.

Signed-off-by: Derrick Stolee <derrickstolee@github.com>
  • Loading branch information
derrickstolee committed Aug 7, 2023
1 parent 37f96b6 commit 14e340b
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 20 deletions.
82 changes: 63 additions & 19 deletions builtin/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -2299,13 +2299,20 @@ static char *xdg_config_home_systemd(const char *filename)
return xdg_config_home_for("systemd/user", filename);
}

static int systemd_timer_write_unit_templates(const char *exec_path)
static int systemd_timer_write_unit_template(enum schedule_priority schedule,
const char *exec_path,
int minute)
{
char *filename;
FILE *file;
const char *unit;
char *schedule_pattern = NULL;
const char *frequency = get_frequency(schedule);
char *local_timer_name = xstrfmt("git-maintenance@%s.timer", frequency);
char *local_service_name = xstrfmt("git-maintenance@%s.service", frequency);

filename = xdg_config_home_systemd(local_timer_name);

filename = xdg_config_home_systemd("git-maintenance@.timer");
if (safe_create_leading_directories(filename)) {
error(_("failed to create directories for '%s'"), filename);
goto error;
Expand All @@ -2314,6 +2321,23 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
if (!file)
goto error;

switch (schedule) {
case SCHEDULE_HOURLY:
schedule_pattern = xstrfmt("*-*-* *:%02d:00", minute);
break;

case SCHEDULE_DAILY:
schedule_pattern = xstrfmt("*-*-* 0:%02d:00", minute);
break;

case SCHEDULE_WEEKLY:
schedule_pattern = xstrfmt("Mon 0:%02d:00", minute);
break;

default:
BUG("Unhandled schedule_priority");
}

unit = "# This file was created and is maintained by Git.\n"
"# Any edits made in this file might be replaced in the future\n"
"# by a Git command.\n"
Expand All @@ -2322,12 +2346,12 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
"Description=Optimize Git repositories data\n"
"\n"
"[Timer]\n"
"OnCalendar=%i\n"
"OnCalendar=%s\n"
"Persistent=true\n"
"\n"
"[Install]\n"
"WantedBy=timers.target\n";
if (fputs(unit, file) == EOF) {
if (fprintf(file, unit, schedule_pattern) < 0) {
error(_("failed to write to '%s'"), filename);
fclose(file);
goto error;
Expand All @@ -2338,7 +2362,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
}
free(filename);

filename = xdg_config_home_systemd("git-maintenance@.service");
filename = xdg_config_home_systemd(local_service_name);
file = fopen_or_warn(filename, "w");
if (!file)
goto error;
Expand All @@ -2352,7 +2376,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
"\n"
"[Service]\n"
"Type=oneshot\n"
"ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n"
"ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s\n"
"LockPersonality=yes\n"
"MemoryDenyWriteExecute=yes\n"
"NoNewPrivileges=yes\n"
Expand All @@ -2362,7 +2386,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
"RestrictSUIDSGID=yes\n"
"SystemCallArchitectures=native\n"
"SystemCallFilter=@system-service\n";
if (fprintf(file, unit, exec_path, exec_path) < 0) {
if (fprintf(file, unit, exec_path, exec_path, frequency) < 0) {
error(_("failed to write to '%s'"), filename);
fclose(file);
goto error;
Expand All @@ -2375,13 +2399,16 @@ static int systemd_timer_write_unit_templates(const char *exec_path)
return 0;

error:
free(schedule_pattern);
free(local_timer_name);
free(filename);
systemd_timer_delete_unit_templates();
return -1;
}

static int systemd_timer_enable_unit(int enable,
enum schedule_priority schedule)
enum schedule_priority schedule,
const char *exec_path,
int minute)
{
const char *cmd = "systemctl";
struct child_process child = CHILD_PROCESS_INIT;
Expand All @@ -2398,6 +2425,8 @@ static int systemd_timer_enable_unit(int enable,
*/
if (!enable)
child.no_stderr = 1;
else if (systemd_timer_write_unit_template(schedule, exec_path, minute))
return -1;

get_schedule_cmd(&cmd, NULL);
strvec_split(&child.args, cmd);
Expand All @@ -2420,38 +2449,53 @@ static int systemd_timer_enable_unit(int enable,
return 0;
}

static int systemd_timer_delete_unit_templates(void)
static int systemd_timer_delete_unit_template(enum schedule_priority priority)
{
const char *frequency = get_frequency(priority);
char *local_timer_name = xstrfmt("git-maintenance@%s.timer", frequency);
char *local_service_name = xstrfmt("git-maintenance@%s.service", frequency);
int ret = 0;
char *filename = xdg_config_home_systemd("git-maintenance@.timer");
char *filename = xdg_config_home_systemd(local_timer_name);
if (unlink(filename) && !is_missing_file_error(errno))
ret = error_errno(_("failed to delete '%s'"), filename);
FREE_AND_NULL(filename);

filename = xdg_config_home_systemd("git-maintenance@.service");
filename = xdg_config_home_systemd(local_service_name);
if (unlink(filename) && !is_missing_file_error(errno))
ret = error_errno(_("failed to delete '%s'"), filename);

free(filename);
free(local_timer_name);
free(local_service_name);
return ret;
}

static int systemd_timer_delete_unit_templates(void)
{
/* Purposefully not short-circuited to make sure all are called. */
return systemd_timer_delete_unit_template(SCHEDULE_HOURLY) |
systemd_timer_delete_unit_template(SCHEDULE_DAILY) |
systemd_timer_delete_unit_template(SCHEDULE_WEEKLY);
}

static int systemd_timer_delete_units(void)
{
return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) ||
systemd_timer_enable_unit(0, SCHEDULE_DAILY) ||
systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) ||
int minute = get_random_minute();
const char *exec_path = git_exec_path();
return systemd_timer_enable_unit(0, SCHEDULE_HOURLY, exec_path, minute) ||
systemd_timer_enable_unit(0, SCHEDULE_DAILY, exec_path, minute) ||
systemd_timer_enable_unit(0, SCHEDULE_WEEKLY, exec_path, minute) ||
systemd_timer_delete_unit_templates();
}

static int systemd_timer_setup_units(void)
{
int minute = get_random_minute();
const char *exec_path = git_exec_path();

int ret = systemd_timer_write_unit_templates(exec_path) ||
systemd_timer_enable_unit(1, SCHEDULE_HOURLY) ||
systemd_timer_enable_unit(1, SCHEDULE_DAILY) ||
systemd_timer_enable_unit(1, SCHEDULE_WEEKLY);
int ret = systemd_timer_enable_unit(1, SCHEDULE_HOURLY, exec_path, minute) ||
systemd_timer_enable_unit(1, SCHEDULE_DAILY, exec_path, minute) ||
systemd_timer_enable_unit(1, SCHEDULE_WEEKLY, exec_path, minute);
if (ret)
systemd_timer_delete_units();
return ret;
Expand Down
4 changes: 3 additions & 1 deletion t/t7900-maintenance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,9 @@ test_expect_success 'start and stop Linux/systemd maintenance' '
# start registers the repo
git config --get --global --fixed-value maintenance.repo "$(pwd)" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@.service" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@hourly.service" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@daily.service" &&
test_systemd_analyze_verify "systemd/user/git-maintenance@weekly.service" &&
printf -- "--user enable --now git-maintenance@%s.timer\n" hourly daily weekly >expect &&
test_cmp expect args &&
Expand Down

0 comments on commit 14e340b

Please sign in to comment.