[IMP] runbot: avoid long idle transaction

Git gc can last a few minutes, it's not a big deal since it's executed
once a day but the transaction is kept idele during this time wich is
not useful. This commit should help to avoid this.
This commit is contained in:
Xavier-Do 2024-02-12 11:21:09 +01:00 committed by Christophe Monniez
parent 60dbbcb72e
commit f7a1a6a11d
5 changed files with 29 additions and 12 deletions

View File

@ -374,13 +374,17 @@ class Repo(models.Model):
def _source_path(self, *path_parts): def _source_path(self, *path_parts):
return self.env['runbot.runbot']._path('sources', sanitize(self.name), *path_parts) return self.env['runbot.runbot']._path('sources', sanitize(self.name), *path_parts)
def _git(self, cmd, errors='strict'): def _get_git_command(self, cmd, errors='strict'):
"""Execute a git command 'cmd'""" """Execute a git command 'cmd'"""
self.ensure_one() self.ensure_one()
config_args = [] config_args = []
if self.identity_file: if self.identity_file:
config_args = ['-c', 'core.sshCommand=ssh -i %s/.ssh/%s' % (str(Path.home()), self.identity_file)] config_args = ['-c', 'core.sshCommand=ssh -i %s/.ssh/%s' % (str(Path.home()), self.identity_file)]
cmd = ['git', '-C', self.path] + config_args + cmd cmd = ['git', '-C', self.path] + config_args + cmd
return cmd
def _git(self, cmd, errors='strict'):
cmd = self._get_git_command(cmd, errors)
_logger.info("git command: %s", ' '.join(cmd)) _logger.info("git command: %s", ' '.join(cmd))
return subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode(errors=errors) return subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode(errors=errors)

View File

@ -383,16 +383,6 @@ class Runbot(models.AbstractModel):
if ignored: if ignored:
_logger.info('docker (%s) not deleted because not dest format', list(ignored)) _logger.info('docker (%s) not deleted because not dest format', list(ignored))
def _git_gc(self, host):
"""
cleanup and optimize git repositories on the host
"""
for repo in self.env['runbot.repo'].search([]):
try:
repo._git(['gc', '--prune=all', '--quiet'])
except CalledProcessError as e:
message = f'git gc failed for {repo.name} on {host.name} with exit status {e.returncode} and message "{e.output[:60]} ..."'
self._warning(message)
def _warning(self, message, *args): def _warning(self, message, *args):
if args: if args:

View File

@ -28,7 +28,9 @@ class BuilderClient(RunbotClient):
self.host._set_psql_conn_count() self.host._set_psql_conn_count()
self.host._docker_build() self.host._docker_build()
self.env['runbot.repo']._update_git_config() self.env['runbot.repo']._update_git_config()
self.env.cr.commit()
self.git_gc() self.git_gc()
self.env.cr.commit()
return self.env['runbot.runbot']._scheduler_loop_turn(self.host) return self.env['runbot.runbot']._scheduler_loop_turn(self.host)

View File

@ -20,7 +20,9 @@ class LeaderClient(RunbotClient): # Conductor, Director, Main, Maestro, Lead
def loop_turn(self): def loop_turn(self):
if self.count == 0: if self.count == 0:
self.env['runbot.repo']._update_git_config() self.env['runbot.repo']._update_git_config()
self.env.cr.commit()
self.git_gc() self.git_gc()
self.env.cr.commit()
return self.env['runbot.runbot']._fetch_loop_turn(self.host, self.pull_info_failures) return self.env['runbot.runbot']._fetch_loop_turn(self.host, self.pull_info_failures)

View File

@ -9,6 +9,7 @@ import sys
import threading import threading
import time import time
import signal import signal
import subprocess
from contextlib import nullcontext from contextlib import nullcontext
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
@ -111,7 +112,25 @@ class RunbotClient():
""" git gc once a day """ """ git gc once a day """
if self.next_git_gc_date < datetime.now(): if self.next_git_gc_date < datetime.now():
_logger.info('Starting git gc on repositories') _logger.info('Starting git gc on repositories')
self.env['runbot.runbot']._git_gc(self.host) commands = []
host_name = self.host.name
for repo in self.env['runbot.repo'].search([]):
commands.append(repo.name, (repo._get_git_command(['gc', '--prune=all', '--quiet'])))
self.env.cr.rollback()
# gc commands can be slow, rollbacking to avoid to keep a transaction idle for multiple minutes.
messages = []
for repo_name, command in commands:
try:
start = time.time()
subprocess.check_output(command, stderr=subprocess.STDOUT).decode()
_logger.info('Git gc on %s took %ss', repo_name, time.time() - start)
except subprocess.CalledProcessError as e:
message = f'git gc failed for {repo_name} on {host_name} with exit status {e.returncode} and message "{e.output[:60]} ..."'
messages.append(message)
for message in messages:
self.env['runbot.runbot']._warning(message)
self.env.cr.commit()
self.update_next_git_gc_date() self.update_next_git_gc_date()
def run(client_class): def run(client_class):